1
|
|
|
import test from 'ava' |
2
|
|
|
import ApiBuilder from 'claudia-api-builder' |
3
|
|
|
import * as fs from 'fs' |
4
|
|
|
import authenticator, { |
5
|
|
|
APIGatewayProxyEventJwt, |
6
|
|
|
authenticator as authenticator2, |
7
|
|
|
JwtHeader, |
8
|
|
|
Secret, |
9
|
|
|
SigningKeyCallback |
10
|
|
|
} from '../../package/' |
11
|
|
|
|
12
|
|
|
test('authenticator is exported', (t) => { |
13
|
|
|
t.is(typeof authenticator, 'function') |
14
|
|
|
t.is(authenticator, authenticator2) |
15
|
|
|
}) |
16
|
|
|
|
17
|
|
|
/** |
18
|
|
|
* ## Using a Public Key |
19
|
|
|
* |
20
|
|
|
* Using a public key with an algorithm such as RS256 is recommended |
21
|
|
|
* for security and convenience. The public key can be kept inline with |
22
|
|
|
* your code without any security concerns. |
23
|
|
|
*/ |
24
|
|
|
|
25
|
|
|
// tslint:disable-next-line:max-line-length |
26
|
|
|
const TOKEN_RS512 = 'eyJhbGciOiJSUzUxMiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.iNa6ZAKSn3J8GvG6hgfydtbRsKekSp1RunRceNhrRtUSZVvlSz4X1g1OXSJwCG2OyYXhaN0pyxuR7JlI1n663DnkEvop3T7Whxoy5uMxji9vSZ5MrvtLXY75On0ALqZuPuyuH4x6o1xI0huKHJGmRM2OVqD9W80AkpYtszwRwXkjiXdfJHMry9czXm5JrYNp9VowA9jATpkH2IatfSIVAK0c6hJg6Gz05PdtMjFwHpJJFn0qzfexf97pZgqITOX4f-pHZQ6i_jnBciocjMrn62tz8XpGrgjSGNqeUxROPjKCTnmwi0kpMAuBp2rUgj7Ns5wHaKE1riz_qrQqCgxrww' |
27
|
|
|
|
28
|
|
|
const PUBLIC_KEY = `-----BEGIN PUBLIC KEY----- |
29
|
|
|
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAry0bg77WbExsds8R4eJo |
30
|
|
|
fNqbeWnu1QqRqG0wOk35JenMXDU6mCfUFas0ANgS/2PhxOoem5dtxKpJEzXF8eQh |
31
|
|
|
xrO3J9zD9HMbLVMfodpG9Up9u+AUICGvMCAbAuCHcp7vTZtc+OmmSyk5qF1ApGnU |
32
|
|
|
rWromBB8TDFVx0UdOR6I+1F3DvIk7mgjLAhwzycgsLRZFwXxS2mwHVAafD6QYbxZ |
33
|
|
|
I655+ltaf3Gb3CBJSz888i3DfaKT30cCC/7r3rnOqbKjUcG8qxrsp+yOo8l6BeeJ |
34
|
|
|
g57ITeuaRrSza7zdvS0Vydp9RS7VS9JdHQv9b48b7rsx+WLghI/AQ3kK0Xg85C9R |
35
|
|
|
TQIDAQAB |
36
|
|
|
-----END PUBLIC KEY-----` |
37
|
|
|
|
38
|
|
|
test.cb('Authenticate with public key', (t) => { |
39
|
|
|
// Begin by creating your Api Builder as normal |
40
|
|
|
const api = new ApiBuilder() |
41
|
|
|
// Next pass in the authenticator along with your key and any config |
42
|
|
|
api.intercept(authenticator(PUBLIC_KEY)) |
43
|
|
|
// Register your routes as normal |
44
|
|
|
api.get('/greeting', (event: APIGatewayProxyEventJwt) => `Hello ${event.jwt.payload.name}!`) |
45
|
|
|
// Export the proxyRouter normally in your code |
46
|
|
|
// export handler = api.proxyRouter |
47
|
|
|
|
48
|
|
|
// Here we call it instead to test it |
49
|
|
|
testApi(t, api, { |
50
|
|
|
context: { method: 'GET', path: '/greeting' }, |
51
|
|
|
headers: { Authorization: 'bearer ' + TOKEN_RS512 } |
52
|
|
|
}, { |
53
|
|
|
body: '"Hello John Doe!"', |
54
|
|
|
statusCode: 200 |
55
|
|
|
}) |
56
|
|
|
}) |
57
|
|
|
|
58
|
|
|
/** |
59
|
|
|
* ## Using a Secret Key |
60
|
|
|
* |
61
|
|
|
* When using a secret key you cannot safely include it in your code. |
62
|
|
|
* You must fetch the secret key securely at run time, either from a |
63
|
|
|
* file on you server or using a service like AWS Secrets Manager. |
64
|
|
|
* |
65
|
|
|
* Create a function that fetches the secret key like below, |
66
|
|
|
* and returns it via the callback or as a promise. |
67
|
|
|
* Be sure to decode from base64 if needed. |
68
|
|
|
*/ |
69
|
|
|
|
70
|
|
|
// tslint:disable-next-line:max-line-length |
71
|
|
|
const TOKEN_HS512 = 'eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.Hk1Qgr18H-VwmDnMcljqEFy8_F1zeIVS-FY-3Xl2pKsMEeFii5-WEVDyBNRPredB9JjoNAkR23iOkTDN4Mu-Xg' |
72
|
|
|
|
73
|
|
|
const getSecretKeyCb = (header: JwtHeader, callback: SigningKeyCallback) => { |
74
|
|
|
const filename = `test/test.secret.${header.alg.toLowerCase()}.b64.txt` |
75
|
|
|
fs.readFile(filename, (err, b64) => { |
76
|
|
|
if (err) { |
77
|
|
|
return callback(err) |
78
|
|
|
} |
79
|
|
|
// toString is important, if the first arg is a buffer the second is ignored |
80
|
|
|
const secret = Buffer.from(b64.toString(), 'base64') |
81
|
|
|
return callback(null, secret) |
82
|
|
|
}) |
83
|
|
|
} |
84
|
|
|
|
85
|
|
|
const getSecretKeyP = (header: JwtHeader) => |
86
|
|
|
new Promise((res, rej) => { |
87
|
|
|
getSecretKeyCb(header, (err, secret) => { |
88
|
|
|
if (err) { |
89
|
|
|
rej(err) |
90
|
|
|
} else { |
91
|
|
|
res(secret) |
92
|
|
|
} |
93
|
|
|
}) |
94
|
|
|
}) |
95
|
|
|
|
96
|
|
|
test.cb('Authenticate with secret key, via promise', (t) => { |
97
|
|
|
// Begin by creating your Api Builder as normal |
98
|
|
|
const api = new ApiBuilder() |
99
|
|
|
// Next pass in the authenticator along with your key and any config |
100
|
|
|
api.intercept(authenticator(getSecretKeyP)) |
101
|
|
|
// Register your routes as normal |
102
|
|
|
api.get('/greeting', (event: APIGatewayProxyEventJwt) => `Hello ${event.jwt.payload.name}!`) |
103
|
|
|
// Export the proxyRouter normally in your code |
104
|
|
|
// export handler = api.proxyRouter |
105
|
|
|
|
106
|
|
|
// Here we call it instead to test it |
107
|
|
|
testApi(t, api, { |
108
|
|
|
context: { method: 'GET', path: '/greeting' }, |
109
|
|
|
headers: { Authorization: 'bearer ' + TOKEN_HS512 } |
110
|
|
|
}, { |
111
|
|
|
body: '"Hello John Doe!"', |
112
|
|
|
statusCode: 200 |
113
|
|
|
}) |
114
|
|
|
}) |
115
|
|
|
|
116
|
|
|
test.cb('Authenticate with secret key, via callback', (t) => { |
117
|
|
|
// Begin by creating your Api Builder as normal |
118
|
|
|
const api = new ApiBuilder() |
119
|
|
|
// Next pass in the authenticator along with your key and any config |
120
|
|
|
api.intercept(authenticator(getSecretKeyCb)) |
121
|
|
|
// Register your routes as normal |
122
|
|
|
api.get('/greeting', (event: APIGatewayProxyEventJwt) => `Hello ${event.jwt.payload.name}!`) |
123
|
|
|
// Export the proxyRouter normally in your code |
124
|
|
|
// export handler = api.proxyRouter |
125
|
|
|
|
126
|
|
|
// Here we call it instead to test it |
127
|
|
|
testApi(t, api, { |
128
|
|
|
context: { method: 'GET', path: '/greeting' }, |
129
|
|
|
headers: { Authorization: 'bearer ' + TOKEN_HS512 } |
130
|
|
|
}, { |
131
|
|
|
body: '"Hello John Doe!"', |
132
|
|
|
statusCode: 200 |
133
|
|
|
}) |
134
|
|
|
}) |
135
|
|
|
|
136
|
|
|
/** |
137
|
|
|
* ## JWT Headers & Signature |
138
|
|
|
* |
139
|
|
|
* You can access the headers, payload and signature of the JWT |
140
|
|
|
*/ |
141
|
|
|
|
142
|
|
|
test.cb('Headers & Signature access', (t) => { |
143
|
|
|
const api = new ApiBuilder() |
144
|
|
|
api.intercept(authenticator(PUBLIC_KEY)) |
145
|
|
|
api.get('/token', (event: APIGatewayProxyEventJwt) => ({ |
146
|
|
|
algorithm: event.jwt.header.alg, |
147
|
|
|
signature: event.jwt.signature.substr(0, 12), |
148
|
|
|
subscriber: event.jwt.payload.sub |
149
|
|
|
})) |
150
|
|
|
|
151
|
|
|
testApi(t, api, { |
152
|
|
|
context: { method: 'GET', path: '/token' }, |
153
|
|
|
headers: { Authorization: 'bearer ' + TOKEN_RS512 } |
154
|
|
|
}, { |
155
|
|
|
body: JSON.stringify({ |
156
|
|
|
algorithm: 'RS512', |
157
|
|
|
signature: 'iNa6ZAKSn3J8', |
158
|
|
|
subscriber: '1234567890' |
159
|
|
|
}), |
160
|
|
|
statusCode: 200 |
161
|
|
|
}) |
162
|
|
|
}) |
163
|
|
|
|
164
|
|
|
/** |
165
|
|
|
* ## Extra Config |
166
|
|
|
* |
167
|
|
|
* You can specify more arguments to increase security and help catch bugs |
168
|
|
|
* |
169
|
|
|
* For a full list see https://www.npmjs.com/package/jsonwebtoken#jwtverifytoken-secretorpublickey-options-callback |
170
|
|
|
*/ |
171
|
|
|
|
172
|
|
|
test.cb('Specify algorithm - success', (t) => { |
173
|
|
|
const api = new ApiBuilder() |
174
|
|
|
api.intercept(authenticator(PUBLIC_KEY, { algorithms: ['RS512'] })) |
175
|
|
|
api.get('/greeting', (event: APIGatewayProxyEventJwt) => `Hello ${event.jwt.payload.name}!`) |
176
|
|
|
testApi(t, api, { |
177
|
|
|
context: { method: 'GET', path: '/greeting' }, |
178
|
|
|
headers: { Authorization: 'bearer ' + TOKEN_RS512 } |
179
|
|
|
}, { |
180
|
|
|
body: '"Hello John Doe!"', |
181
|
|
|
statusCode: 200 |
182
|
|
|
}) |
183
|
|
|
}) |
184
|
|
|
|
185
|
|
|
test.cb('Specify algorithm - failure', (t) => { |
186
|
|
|
const api = new ApiBuilder() |
187
|
|
|
api.intercept(authenticator(PUBLIC_KEY, { algorithms: ['RS256'] })) |
188
|
|
|
api.get('/greeting', (event: APIGatewayProxyEventJwt) => `Hello ${event.jwt.payload.name}!`) |
189
|
|
|
testApi(t, api, { |
190
|
|
|
context: { method: 'GET', path: '/greeting' }, |
191
|
|
|
headers: { Authorization: 'bearer ' + TOKEN_RS512 } |
192
|
|
|
}, { |
193
|
|
|
body: '"Unauthorised: JsonWebTokenError invalid algorithm"', |
194
|
|
|
statusCode: 401 |
195
|
|
|
}) |
196
|
|
|
}) |
197
|
|
|
|
198
|
|
|
test.cb('Specify audience - success', (t) => { |
199
|
|
|
// tslint:disable-next-line:max-line-length |
200
|
|
|
const TOKEN_RS512_AUD = 'eyJhbGciOiJSUzUxMiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyLCJhdWQiOiJGNDJFRDA4Mi03OTlGLTQ3NkItQjc3RS0xMjAxM0Y1Mzc5QTUifQ.VAnH9ozEAcL3foiSgqJspqS05AdYchn57uKrbCUEwX9uXbsg8nct9bL7y8Omw6qg5ZdTcNsnor8tysGW460yOmg06Pbx0SRHJifJGLpy1bOCWRPG_5NB5aM6uKf78T2QCJXm9f73nKfZ9QJUlfzW41bT2khnsO8gTVYo9yd3yesrKegMlSomxd4VrZFYz4jbNh2f9FUe8MNkubfOxVbM5U7sh5aZMs_uoef08Gxp3Aqx7fPpzj16uW2JTNlhoIYUF4J33T0SufgiR1Xw3R3Jn2BnwdlfgqjLrv0lxzDzHoPyPP8i6TSl3notTcTmLc_GItdcnLNPn8wtjxKNW81tMQ' |
201
|
|
|
|
202
|
|
|
const api = new ApiBuilder() |
203
|
|
|
api.intercept(authenticator(PUBLIC_KEY, { audience: 'F42ED082-799F-476B-B77E-12013F5379A5' })) |
204
|
|
|
api.get('/greeting', (event: APIGatewayProxyEventJwt) => `Hello ${event.jwt.payload.name}!`) |
205
|
|
|
testApi(t, api, { |
206
|
|
|
context: { method: 'GET', path: '/greeting' }, |
207
|
|
|
headers: { Authorization: 'bearer ' + TOKEN_RS512_AUD } |
208
|
|
|
}, { |
209
|
|
|
body: '"Hello John Doe!"', |
210
|
|
|
statusCode: 200 |
211
|
|
|
}) |
212
|
|
|
}) |
213
|
|
|
|
214
|
|
|
test.cb('Specify audience - failure', (t) => { |
215
|
|
|
const api = new ApiBuilder() |
216
|
|
|
api.intercept(authenticator(PUBLIC_KEY, { audience: 'F42ED082-799F-476B-B77E-12013F5379A5' })) |
217
|
|
|
api.get('/greeting', (event: APIGatewayProxyEventJwt) => `Hello ${event.jwt.payload.name}!`) |
218
|
|
|
testApi(t, api, { |
219
|
|
|
context: { method: 'GET', path: '/greeting' }, |
220
|
|
|
headers: { Authorization: 'bearer ' + TOKEN_RS512 } |
221
|
|
|
}, { |
222
|
|
|
body: '"Unauthorised: JsonWebTokenError jwt audience invalid. expected: F42ED082-799F-476B-B77E-12013F5379A5"', |
223
|
|
|
statusCode: 401 |
224
|
|
|
}) |
225
|
|
|
}) |
226
|
|
|
|
227
|
|
|
/** |
228
|
|
|
* ## When is a JWT not a JWT? |
229
|
|
|
*/ |
230
|
|
|
|
231
|
|
|
test.cb('XWT', (t) => { |
232
|
|
|
// tslint:disable-next-line:max-line-length |
233
|
|
|
const TOKEN_RS512_XWT = 'eyJhbGciOiJSUzUxMiIsInR5cCI6IlhXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.liqYAp9dbby9IzokHKbLkQb5mUAHoO0Ef7hn4Wz-Oh6kVcdqGQjtUFZPbONe9NPLLCuX_82_TkWXdKja5ISHLw3m028d2NGEQ2cbBxOCjHSqeuztUavwzQPeJUNUREh07IQK_MTw-BCskKLoJToIx2NZ3AfttCu4QWXBaJTZkIds0sQIQR11Z3w48QQS7Bjbtmrhzufpw_yfk8Fh0a0PjAlYmTgkE7JAUBT0NwqVPuqrTNUKxHe5DrqeuSAr0VeHSM05HvJFqrncF0KuBfTj0HUzwi6JpZxzlwxx_gzfaHEw2lFtRLJIonVLMAKM1aFj5m67FyfxqUQx0JnqRkEWrQ' |
234
|
|
|
|
235
|
|
|
const api = new ApiBuilder() |
236
|
|
|
api.intercept(authenticator(PUBLIC_KEY)) |
237
|
|
|
api.get('/greeting', (event: APIGatewayProxyEventJwt) => `Hello ${event.jwt.payload.name}!`) |
238
|
|
|
testApi(t, api, { |
239
|
|
|
context: { method: 'GET', path: '/greeting' }, |
240
|
|
|
headers: { Authorization: 'bearer ' + TOKEN_RS512_XWT } |
241
|
|
|
}, { |
242
|
|
|
body: '"Hello John Doe!"', |
243
|
|
|
statusCode: 200 |
244
|
|
|
}) |
245
|
|
|
}) |
246
|
|
|
|
247
|
|
|
/** |
248
|
|
|
* ## Failures and Errors |
249
|
|
|
* |
250
|
|
|
* Here follow a number of error cases |
251
|
|
|
*/ |
252
|
|
|
|
253
|
|
|
test.cb('Expired', (t) => { |
254
|
|
|
// tslint:disable-next-line:max-line-length |
255
|
|
|
const TOKEN_RS512_EXP = 'eyJhbGciOiJSUzUxMiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiZXhwIjoxNTE2MjM5MDIyfQ.ed_PPzBaYmalmwTp1OxF9--QlPATgTWYVuc2Qg-tofDe4Mhn98B0aEZ-3wN9h2loQG05xhhUy_ZOyLYPYhZrKavU7UiVEIRmDUj2VYzmX575_GdGmxsaoluNP3xYqGjxs4U1-uQN1YIEQRvGx2pn-QeK9crawvzLVdZgyBr69-xVUbsDNIR5msx2Qg2uZLrPWe4ZGoYlpecUDfSoktHAkxsTfcjtE2niS_-Y8yoRqGemu8MWNwMca7edg2xJn-J0z5DDMYgzdVyI9oHkf-vu_lb535ekuYAigXBKLRBbPO9zzXv3LmJFlDJKJzkKGU8CSkUTR11ftsEc7BbUvsQ6Zg' |
256
|
|
|
|
257
|
|
|
const api = new ApiBuilder() |
258
|
|
|
api.intercept(authenticator(PUBLIC_KEY)) |
259
|
|
|
api.get('/greeting', (event: APIGatewayProxyEventJwt) => `Hello ${event.jwt.payload.name}!`) |
260
|
|
|
testApi(t, api, { |
261
|
|
|
context: { method: 'GET', path: '/greeting' }, |
262
|
|
|
headers: { Authorization: 'bearer ' + TOKEN_RS512_EXP } |
263
|
|
|
}, { |
264
|
|
|
body: '"Unauthorised: TokenExpiredError jwt expired"', |
265
|
|
|
statusCode: 401 |
266
|
|
|
}) |
267
|
|
|
}) |
268
|
|
|
|
269
|
|
|
test.cb('Invalid/Forged token signature', (t) => { |
270
|
|
|
// tslint:disable-next-line:max-line-length |
271
|
|
|
const TOKEN_RS512_INV = 'eyJhbGciOiJSUzUxMiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiZXhwIjoxNTE2MjM5MDIyfQ.ed_PPzBaYmalmwTp1OxF9--QlPATgTWYVuc2Qg-tofDe4Mhn98B0aEZ-3wN9h2loQG05xhhUy_ZOyLYPYhZrKavU7UiVEIRmDUj2VYzmX575_GdGmxsaoluNP3xYqGjxs4U1-uQN1YIEQRvGx2pn-QeK9crawvzLVdZgyBr69-xVUbsDNIR5msx2Qg2uZLrPWe4ZGoYlpecUDfSoktHAkxsTfcjtE2niS_-Y8yoRqGemu8MWNwMca7edg2xJn-J0z5DDMYgzdVyI9oHkf-vu_lb535ekuYAigXBKLRBbPO9zzXv3LmJFlDJKJzkKGU7CSkUTR11ftsEc7BbUvsQ6Zg' |
272
|
|
|
|
273
|
|
|
const api = new ApiBuilder() |
274
|
|
|
api.intercept(authenticator(PUBLIC_KEY)) |
275
|
|
|
api.get('/greeting', (event: APIGatewayProxyEventJwt) => `Hello ${event.jwt.payload.name}!`) |
276
|
|
|
testApi(t, api, { |
277
|
|
|
context: { method: 'GET', path: '/greeting' }, |
278
|
|
|
headers: { Authorization: 'bearer ' + TOKEN_RS512_INV } |
279
|
|
|
}, { |
280
|
|
|
body: '"Unauthorised: JsonWebTokenError invalid signature"', |
281
|
|
|
statusCode: 401 |
282
|
|
|
}) |
283
|
|
|
}) |
284
|
|
|
|
285
|
|
|
test.cb('Forgot to provide secret/key', (t) => { |
286
|
|
|
const api = new ApiBuilder() |
287
|
|
|
api.intercept(authenticator) // sic |
288
|
|
|
api.get('/greeting', (event: APIGatewayProxyEventJwt) => `Hello ${event.jwt.payload.name}!`) |
289
|
|
|
testApi(t, api, { |
290
|
|
|
context: { method: 'GET', path: '/greeting' }, |
291
|
|
|
headers: { Authorization: 'bearer ' + TOKEN_RS512 } |
292
|
|
|
}, |
293
|
|
|
undefined, |
294
|
|
|
'event does not contain routing information' |
295
|
|
|
) |
296
|
|
|
}) |
297
|
|
|
|
298
|
|
|
const getSecretKeyPFail = () => |
299
|
|
|
new Promise((res, rej) => { |
300
|
|
|
getSecretKeyCb({ alg: 'fail' }, (err, secret) => { |
301
|
|
|
if (err) { |
302
|
|
|
rej(err) |
303
|
|
|
} else { |
304
|
|
|
res(secret) |
305
|
|
|
} |
306
|
|
|
}) |
307
|
|
|
}) |
308
|
|
|
|
309
|
|
|
test.cb('Failure fetching key', (t) => { |
310
|
|
|
const api = new ApiBuilder() |
311
|
|
|
api.intercept(authenticator(getSecretKeyPFail)) |
312
|
|
|
api.get('/greeting', (event: APIGatewayProxyEventJwt) => `Hello ${event.jwt.payload.name}!`) |
313
|
|
|
testApi(t, api, { |
314
|
|
|
context: { method: 'GET', path: '/greeting' }, |
315
|
|
|
headers: { Authorization: 'bearer ' + TOKEN_HS512 } |
316
|
|
|
}, { |
317
|
|
|
// tslint:disable-next-line:max-line-length |
318
|
|
|
body: '"Unauthorised: JsonWebTokenError error in secret or public key callback: ENOENT: no such file or directory, open \'test/test.secret.fail.b64.txt\'"', |
319
|
|
|
statusCode: 401 |
320
|
|
|
}) |
321
|
|
|
}) |
322
|
|
|
|
323
|
|
|
test.cb('No headers in event', (t) => { |
324
|
|
|
const api = new ApiBuilder() |
325
|
|
|
api.intercept(authenticator(PUBLIC_KEY)) |
326
|
|
|
api.get('/greeting', (event: APIGatewayProxyEventJwt) => `Hello ${event.jwt.payload.name}!`) |
327
|
|
|
testApi(t, api, { |
328
|
|
|
context: { method: 'GET', path: '/greeting' } |
329
|
|
|
}, { |
330
|
|
|
body: '"Unauthorised: no headers"', |
331
|
|
|
statusCode: 401 |
332
|
|
|
}) |
333
|
|
|
}) |
334
|
|
|
|
335
|
|
|
test.cb('No authorization in headers', (t) => { |
336
|
|
|
const api = new ApiBuilder() |
337
|
|
|
api.intercept(authenticator(PUBLIC_KEY)) |
338
|
|
|
api.get('/greeting', (event: APIGatewayProxyEventJwt) => `Hello ${event.jwt.payload.name}!`) |
339
|
|
|
testApi(t, api, { |
340
|
|
|
context: { method: 'GET', path: '/greeting' }, |
341
|
|
|
headers: {} |
342
|
|
|
}, { |
343
|
|
|
body: '"Unauthorised: no authorization header"', |
344
|
|
|
statusCode: 401 |
345
|
|
|
}) |
346
|
|
|
}) |
347
|
|
|
|
348
|
|
|
test.cb('No bearer in authorization header', (t) => { |
349
|
|
|
const api = new ApiBuilder() |
350
|
|
|
api.intercept(authenticator(PUBLIC_KEY)) |
351
|
|
|
api.get('/greeting', (event: APIGatewayProxyEventJwt) => `Hello ${event.jwt.payload.name}!`) |
352
|
|
|
testApi(t, api, { |
353
|
|
|
context: { method: 'GET', path: '/greeting' }, |
354
|
|
|
headers: { Authorization: 'Basic QWxhZGRpbjpPcGVuU2VzYW1l' } |
355
|
|
|
}, { |
356
|
|
|
body: '"Unauthorised: authorization scheme must be bearer"', |
357
|
|
|
statusCode: 401 |
358
|
|
|
}) |
359
|
|
|
}) |
360
|
|
|
|
361
|
|
|
test.cb('No token in authorization header', (t) => { |
362
|
|
|
const api = new ApiBuilder() |
363
|
|
|
api.intercept(authenticator(PUBLIC_KEY)) |
364
|
|
|
api.get('/greeting', (event: APIGatewayProxyEventJwt) => `Hello ${event.jwt.payload.name}!`) |
365
|
|
|
testApi(t, api, { |
366
|
|
|
context: { method: 'GET', path: '/greeting' }, |
367
|
|
|
headers: { Authorization: 'bearer ' } |
368
|
|
|
}, { |
369
|
|
|
body: '"Unauthorised: no authorization token"', |
370
|
|
|
statusCode: 401 |
371
|
|
|
}) |
372
|
|
|
}) |
373
|
|
|
|
374
|
|
|
const testApi = (t: any, api: any, event: any, expectedResponse: any, expectedError: any = null) => { |
375
|
|
|
t.plan(1) |
376
|
|
|
const done = async (error: any, response: any) => { |
377
|
|
|
// await error |
378
|
|
|
// await response |
379
|
|
|
if (response) delete response.headers |
380
|
|
|
t.deepEqual( |
381
|
|
|
{ error, response }, |
382
|
|
|
{ error: expectedError, response: expectedResponse } |
383
|
|
|
) |
384
|
|
|
t.end() |
385
|
|
|
} |
386
|
|
|
api.proxyRouter(event, { done }, done) |
387
|
|
|
} |
388
|
|
|
|