-
-
Notifications
You must be signed in to change notification settings - Fork 0
/
github.ts
122 lines (108 loc) · 3.68 KB
/
github.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
import { create } from "https://deno.land/x/djwt/mod.ts";
const GH_API_URL = "https://api.github.com";
export class GitHub {
jwt?: string;
installationId?: number;
token?: string;
constructor() {
}
_request(method: string, url: string, body?: any, init?: RequestInit) {
return fetch(GH_API_URL + url, {
method,
body: JSON.stringify(body),
headers: {
'Authorization': `token ${this.token}`,
'Accept': "application/vnd.github+json",
...init?.headers
},
...init
});
}
request(method: string, url: string, body?: any, init?: RequestInit) {
return this._request(method, url, body, {
headers: { 'Authorization': `token ${this.token}` },
...init
});
}
bearerRequest(method: string, url: string, body?: any, init?: RequestInit) {
return this._request(method, url, body, {
headers: { 'Authorization': `Bearer ${this.jwt}` },
...init
});
}
issueUpdateAssignees(owner: string, repo: string, issueNumber: number, assignees: string[]) {
return this.request('POST', `/repos/${owner}/${repo}/issues/${issueNumber}/assignees`, {
assignees
});
}
async initialize(appId: string, repoOwnerId: string) {
await this.createJwt(appId);
const installationsRsp = await this.getInstallations();
if (installationsRsp.ok) {
const installations = await installationsRsp.json();
console.debug('<- github', installations);
const foundInstall = installations.find((i) => i.target_id === repoOwnerId);
if (foundInstall) {
await this.setInstallationId(foundInstall.id);
await this.getAccessToken();
} else {
throw new Error('App installation not found');
}
}
}
getInstallationRepositories() {
return this.request('GET', '/installation/repositories');
}
getInstallations() {
return this.bearerRequest('GET', '/app/installations');
}
setInstallationId(installationId: number) {
this.installationId = installationId;
}
async getAccessToken(): Promise<void> {
const rsp = await this.bearerRequest('POST', `/app/installations/${this.installationId}/access_tokens`);
const accessTokens = await rsp.json();
this.token = accessTokens.token;
}
// The private keys provided by GitHub are in PKCS#1 format, but the WebCrypto API only supports PKCS#8.
// You need to convert it first:
// $ openssl pkcs8 -topk8 -inform PEM -outform PEM -nocrypt -in private-key.pem -out private-key-pkcs8.key
async createJwt(appId: string) {
const str2ab = (str: string) => {
const buf = new ArrayBuffer(str.length);
const bufView = new Uint8Array(buf);
for (let i = 0, strLen = str.length; i < strLen; i++) {
bufView[i] = str.charCodeAt(i);
}
return buf;
}
const pem = await Deno.env.get('GITHUB_APP_PRIVATE_KEY');
if (!pem) throw new Error('GITHUB_APP_PRIVATE_KEY not set');
const pemHeader = "-----BEGIN PRIVATE KEY-----";
const pemFooter = "-----END PRIVATE KEY-----";
const pemContents = pem.substring(
pemHeader.length,
pem.length - pemFooter.length,
);
// base64 decode the string to get the binary data
const binaryDerString = atob(pemContents);
// convert from a binary string to an ArrayBuffer
const binaryDer = str2ab(binaryDerString);
const key = await crypto.subtle.importKey(
"pkcs8",
binaryDer,
{
name: "RSASSA-PKCS1-v1_5",
hash: "SHA-256"
},
true,
["sign"]
);
const nowSeconds = Math.round(Date.now() / 1000);
this.jwt = await create({ alg: "RS256", typ: "JWT" }, {
iat: nowSeconds - 60,
exp: nowSeconds + (10 * 60),
iss: appId
}, key);
}
}