-
Notifications
You must be signed in to change notification settings - Fork 0
/
index.ts
214 lines (181 loc) · 6.31 KB
/
index.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
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
import { Router } from 'itty-router'
//import '@cloudflare/workers-types'
// REQUIRED ENVIRONMENT VARIABLES
// const SLACK_SIGNING_SECRET_RRC
// const SLACK_SIGNING_SECRET_HAB
// const SLACK_SIGNING_SECRET_GTXR
// const GITHUB_USERNAME
// const GITHUB_TOKEN
const SIGN_VERSION = 'v0' // per documentation, this is always "v0"
/**
* Verify that a request actually came from Slack using our Signing Secret
* and HMAC-SHA256.
*
* Based on code examples found in Cloudflare's documentation:
* https://developers.cloudflare.com/workers/examples/signing-requests
*
* @param {Request} request incoming request purportedly from Slack
* @returns {Promise<boolean>} true if the signature verification was valid
*/
async function verifySlackSignature(request: Request, secret: string) {
const timestamp = request.headers.get('x-slack-request-timestamp')
console.log('verifySlackSignature')
console.log(timestamp)
// remove starting 'v0=' from the signature header
const header = request.headers.get('x-slack-signature')
if (!header) {
return false
}
const signatureStr = header.substring(3)
// convert the hex string of x-slack-signature header to binary
const signature = hexToBytes(signatureStr)
const content = await request.clone().text()
const authString = `${SIGN_VERSION}:${timestamp}:${content}`
let encoder = new TextEncoder()
const key = await crypto.subtle.importKey(
'raw',
encoder.encode(secret),
{ name: 'HMAC', hash: 'SHA-256' },
false,
['verify']
)
const verified = await crypto.subtle.verify(
'HMAC',
key,
signature,
encoder.encode(authString)
)
console.log(verified)
return verified
}
interface GithubResponse {
id: string
}
/**
* Modified version of hex to bytes function posted here:
* https://stackoverflow.com/a/34356351/489667
*
* @param {string} hex a string of hexadecimal characters
* @returns {ArrayBuffer} binary form of the hexadecimal string
*/
function hexToBytes(hex: string) {
const bytes = new Uint8Array(hex.length / 2)
for (let c = 0; c < hex.length; c += 2) {
bytes[c / 2] = parseInt(hex.substr(c, 2), 16)
}
return bytes.buffer
}
const verifySlackSignatureRRC = async (request: Request) => {
const verified = await verifySlackSignature(
request,
SLACK_SIGNING_SECRET_RRC
)
if (!verified) {
console.log('not authenticated')
return new Response('Not Authenticated', { status: 401 })
}
}
const verifySlackSignatureHAB = async (request: Request) => {
const verified = await verifySlackSignature(
request,
SLACK_SIGNING_SECRET_HAB
)
if (!verified) {
return new Response('Not Authenticated', { status: 401 })
}
}
const verifySlackSignatureGTXR = async (request: Request) => {
const verified = await verifySlackSignature(
request,
SLACK_SIGNING_SECRET_GTXR
)
if (!verified) {
return new Response('Not Authenticated', { status: 401 })
}
}
async function handleInviteRequest(request: Request) {
const body = await request.text()
const formData = new URLSearchParams(body)
const username = formData.get('text')
const usernameUrl = 'https://api.github.com/users/' + username
const orgUrl = 'https://api.github.com/orgs/ramblinrocketclub/invitations'
console.log('handleInviteRequest')
console.log('username ' + username)
const githubUsername = await fetch(usernameUrl, {
method: 'GET',
headers: {
'User-Agent': 'aditsachde',
Accept: 'application/vnd.github.v3+json',
Authorization: 'token ' + GITHUB_TOKEN,
},
})
const status = githubUsername.status
console.log(status)
console.log(githubUsername)
if (status !== 200) {
return new Response('Username not found!', { status: 200 })
}
const githubUsernameJson = await githubUsername.json()
if (!(githubUsernameJson as GithubResponse).hasOwnProperty('id')) {
return new Response('Bad response from GitHub!!', { status: 200 })
}
let id = (githubUsernameJson as GithubResponse).id
//console.log('github username headers: ' + JSON.stringify(githubUsername.headers))
//console.log("github username json response: " + JSON.stringify(githubUsernameJson))
console.log('inviting user: ' + id)
const stringifiedBody = JSON.stringify({
invitee_id: id,
})
console.log(stringifiedBody)
const githubInvite = await fetch(orgUrl, {
method: 'POST',
headers: {
'User-Agent': 'aditsachde',
Accept: 'application/vnd.github.v3+json',
Authorization: 'token ' + GITHUB_TOKEN,
},
body: stringifiedBody,
})
const inviteStatus = githubInvite.status
console.log(inviteStatus)
console.log(githubInvite)
if (inviteStatus !== 201) {
return new Response(
'Failed to create invite! You may already be in the organization!',
{ status: 200 }
)
}
return new Response(
'User ' +
username +
'invited to the RRC organization. Please check your email!',
{ status: 200 }
)
}
// Create a new router
const router = Router()
router.post('/rrc', verifySlackSignatureRRC, async request => {
console.log('handling invite request @ rrc')
return await handleInviteRequest(request)
})
router.post('/hab', verifySlackSignatureHAB, async request => {
console.log('handling invite request @ hab')
return await handleInviteRequest(request)
})
router.post('/gtxr', verifySlackSignatureGTXR, async request => {
console.log('handling invite request @ gtxr')
return await handleInviteRequest(request)
})
/*
This is the last route we define, it will match anything that hasn't hit a route we've defined
above, therefore it's useful as a 404 (and avoids us hitting worker exceptions, so make sure to include it!).
Visit any page that doesn't exist (e.g. /foobar) to see it in action.
*/
router.all('*', () => new Response('404, not found!', { status: 404 }))
/*
This snippet ties our worker to the router we deifned above, all incoming requests
are passed to the router where your routes are called and the response is sent.
*/
addEventListener('fetch', e => {
e.respondWith(router.handle(e.request))
})