|
1 /* Any copyright is dedicated to the Public Domain. |
|
2 * http://creativecommons.org/publicdomain/zero/1.0/ */ |
|
3 |
|
4 "use strict"; |
|
5 |
|
6 Cu.import("resource://gre/modules/FxAccountsClient.jsm"); |
|
7 Cu.import("resource://gre/modules/Promise.jsm"); |
|
8 Cu.import("resource://services-common/utils.js"); |
|
9 Cu.import("resource://services-crypto/utils.js"); |
|
10 |
|
11 const FAKE_SESSION_TOKEN = "a0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebf"; |
|
12 |
|
13 function run_test() { |
|
14 run_next_test(); |
|
15 } |
|
16 |
|
17 // https://wiki.mozilla.org/Identity/AttachedServices/KeyServerProtocol#.2Faccount.2Fkeys |
|
18 let ACCOUNT_KEYS = { |
|
19 keyFetch: h("8081828384858687 88898a8b8c8d8e8f"+ |
|
20 "9091929394959697 98999a9b9c9d9e9f"), |
|
21 |
|
22 response: h("ee5c58845c7c9412 b11bbd20920c2fdd"+ |
|
23 "d83c33c9cd2c2de2 d66b222613364636"+ |
|
24 "c2c0f8cfbb7c6304 72c0bd88451342c6"+ |
|
25 "c05b14ce342c5ad4 6ad89e84464c993c"+ |
|
26 "3927d30230157d08 17a077eef4b20d97"+ |
|
27 "6f7a97363faf3f06 4c003ada7d01aa70"), |
|
28 |
|
29 kA: h("2021222324252627 28292a2b2c2d2e2f"+ |
|
30 "3031323334353637 38393a3b3c3d3e3f"), |
|
31 |
|
32 wrapKB: h("4041424344454647 48494a4b4c4d4e4f"+ |
|
33 "5051525354555657 58595a5b5c5d5e5f"), |
|
34 }; |
|
35 |
|
36 // https://github.com/mozilla/fxa-auth-server/wiki/onepw-protocol#wiki-use-session-certificatesign-etc |
|
37 let SESSION_KEYS = { |
|
38 sessionToken: h("a0a1a2a3a4a5a6a7 a8a9aaabacadaeaf"+ |
|
39 "b0b1b2b3b4b5b6b7 b8b9babbbcbdbebf"), |
|
40 |
|
41 tokenID: h("c0a29dcf46174973 da1378696e4c82ae"+ |
|
42 "10f723cf4f4d9f75 e39f4ae3851595ab"), |
|
43 |
|
44 reqHMACkey: h("9d8f22998ee7f579 8b887042466b72d5"+ |
|
45 "3e56ab0c094388bf 65831f702d2febc0"), |
|
46 }; |
|
47 |
|
48 function deferredStop(server) { |
|
49 let deferred = Promise.defer(); |
|
50 server.stop(deferred.resolve); |
|
51 return deferred.promise; |
|
52 } |
|
53 |
|
54 add_task(function test_authenticated_get_request() { |
|
55 let message = "{\"msg\": \"Great Success!\"}"; |
|
56 let credentials = { |
|
57 id: "eyJleHBpcmVzIjogMTM2NTAxMDg5OC4x", |
|
58 key: "qTZf4ZFpAMpMoeSsX3zVRjiqmNs=", |
|
59 algorithm: "sha256" |
|
60 }; |
|
61 let method = "GET"; |
|
62 |
|
63 let server = httpd_setup({"/foo": function(request, response) { |
|
64 do_check_true(request.hasHeader("Authorization")); |
|
65 |
|
66 response.setStatusLine(request.httpVersion, 200, "OK"); |
|
67 response.bodyOutputStream.write(message, message.length); |
|
68 } |
|
69 }); |
|
70 |
|
71 let client = new FxAccountsClient(server.baseURI); |
|
72 |
|
73 let result = yield client._request("/foo", method, credentials); |
|
74 do_check_eq("Great Success!", result.msg); |
|
75 |
|
76 yield deferredStop(server); |
|
77 }); |
|
78 |
|
79 add_task(function test_authenticated_post_request() { |
|
80 let credentials = { |
|
81 id: "eyJleHBpcmVzIjogMTM2NTAxMDg5OC4x", |
|
82 key: "qTZf4ZFpAMpMoeSsX3zVRjiqmNs=", |
|
83 algorithm: "sha256" |
|
84 }; |
|
85 let method = "POST"; |
|
86 |
|
87 let server = httpd_setup({"/foo": function(request, response) { |
|
88 do_check_true(request.hasHeader("Authorization")); |
|
89 |
|
90 response.setStatusLine(request.httpVersion, 200, "OK"); |
|
91 response.setHeader("Content-Type", "application/json"); |
|
92 response.bodyOutputStream.writeFrom(request.bodyInputStream, request.bodyInputStream.available()); |
|
93 } |
|
94 }); |
|
95 |
|
96 let client = new FxAccountsClient(server.baseURI); |
|
97 |
|
98 let result = yield client._request("/foo", method, credentials, {foo: "bar"}); |
|
99 do_check_eq("bar", result.foo); |
|
100 |
|
101 yield deferredStop(server); |
|
102 }); |
|
103 |
|
104 add_task(function test_500_error() { |
|
105 let message = "<h1>Ooops!</h1>"; |
|
106 let method = "GET"; |
|
107 |
|
108 let server = httpd_setup({"/foo": function(request, response) { |
|
109 response.setStatusLine(request.httpVersion, 500, "Internal Server Error"); |
|
110 response.bodyOutputStream.write(message, message.length); |
|
111 } |
|
112 }); |
|
113 |
|
114 let client = new FxAccountsClient(server.baseURI); |
|
115 |
|
116 try { |
|
117 yield client._request("/foo", method); |
|
118 do_throw("Expected to catch an exception"); |
|
119 } catch (e) { |
|
120 do_check_eq(500, e.code); |
|
121 do_check_eq("Internal Server Error", e.message); |
|
122 } |
|
123 |
|
124 yield deferredStop(server); |
|
125 }); |
|
126 |
|
127 add_task(function test_backoffError() { |
|
128 let method = "GET"; |
|
129 let server = httpd_setup({ |
|
130 "/retryDelay": function(request, response) { |
|
131 response.setHeader("Retry-After", "30"); |
|
132 response.setStatusLine(request.httpVersion, 429, "Client has sent too many requests"); |
|
133 let message = "<h1>Ooops!</h1>"; |
|
134 response.bodyOutputStream.write(message, message.length); |
|
135 }, |
|
136 "/duringDelayIShouldNotBeCalled": function(request, response) { |
|
137 response.setStatusLine(request.httpVersion, 200, "OK"); |
|
138 let jsonMessage = "{\"working\": \"yes\"}"; |
|
139 response.bodyOutputStream.write(jsonMessage, jsonMessage.length); |
|
140 }, |
|
141 }); |
|
142 |
|
143 let client = new FxAccountsClient(server.baseURI); |
|
144 |
|
145 // Retry-After header sets client.backoffError |
|
146 do_check_eq(client.backoffError, null); |
|
147 try { |
|
148 yield client._request("/retryDelay", method); |
|
149 } catch (e) { |
|
150 do_check_eq(429, e.code); |
|
151 do_check_eq(30, e.retryAfter); |
|
152 do_check_neq(typeof(client.fxaBackoffTimer), "undefined"); |
|
153 do_check_neq(client.backoffError, null); |
|
154 } |
|
155 // While delay is in effect, client short-circuits any requests |
|
156 // and re-rejects with previous error. |
|
157 try { |
|
158 yield client._request("/duringDelayIShouldNotBeCalled", method); |
|
159 throw new Error("I should not be reached"); |
|
160 } catch (e) { |
|
161 do_check_eq(e.retryAfter, 30); |
|
162 do_check_eq(e.message, "Client has sent too many requests"); |
|
163 do_check_neq(client.backoffError, null); |
|
164 } |
|
165 // Once timer fires, client nulls error out and HTTP calls work again. |
|
166 client._clearBackoff(); |
|
167 let result = yield client._request("/duringDelayIShouldNotBeCalled", method); |
|
168 do_check_eq(client.backoffError, null); |
|
169 do_check_eq(result.working, "yes"); |
|
170 |
|
171 yield deferredStop(server); |
|
172 }); |
|
173 |
|
174 add_task(function test_signUp() { |
|
175 let creationMessage = JSON.stringify({ |
|
176 uid: "uid", |
|
177 sessionToken: "sessionToken", |
|
178 keyFetchToken: "keyFetchToken" |
|
179 }); |
|
180 let errorMessage = JSON.stringify({code: 400, errno: 101, error: "account exists"}); |
|
181 let created = false; |
|
182 |
|
183 let server = httpd_setup({ |
|
184 "/account/create": function(request, response) { |
|
185 let body = CommonUtils.readBytesFromInputStream(request.bodyInputStream); |
|
186 let jsonBody = JSON.parse(body); |
|
187 |
|
188 // https://github.com/mozilla/fxa-auth-server/wiki/onepw-protocol#wiki-test-vectors |
|
189 do_check_eq(jsonBody.email, "andré@example.org"); |
|
190 |
|
191 if (!created) { |
|
192 do_check_eq(jsonBody.authPW, "247b675ffb4c46310bc87e26d712153abe5e1c90ef00a4784594f97ef54f2375"); |
|
193 created = true; |
|
194 |
|
195 response.setStatusLine(request.httpVersion, 200, "OK"); |
|
196 response.bodyOutputStream.write(creationMessage, creationMessage.length); |
|
197 return; |
|
198 } |
|
199 |
|
200 // Error trying to create same account a second time |
|
201 response.setStatusLine(request.httpVersion, 400, "Bad request"); |
|
202 response.bodyOutputStream.write(errorMessage, errorMessage.length); |
|
203 return; |
|
204 }, |
|
205 }); |
|
206 |
|
207 let client = new FxAccountsClient(server.baseURI); |
|
208 let result = yield client.signUp('andré@example.org', 'pässwörd'); |
|
209 do_check_eq("uid", result.uid); |
|
210 do_check_eq("sessionToken", result.sessionToken); |
|
211 do_check_eq("keyFetchToken", result.keyFetchToken); |
|
212 |
|
213 // Try to create account again. Triggers error path. |
|
214 try { |
|
215 result = yield client.signUp('andré@example.org', 'pässwörd'); |
|
216 do_throw("Expected to catch an exception"); |
|
217 } catch(expectedError) { |
|
218 do_check_eq(101, expectedError.errno); |
|
219 } |
|
220 |
|
221 yield deferredStop(server); |
|
222 }); |
|
223 |
|
224 add_task(function test_signIn() { |
|
225 let sessionMessage_noKey = JSON.stringify({ |
|
226 sessionToken: FAKE_SESSION_TOKEN |
|
227 }); |
|
228 let sessionMessage_withKey = JSON.stringify({ |
|
229 sessionToken: FAKE_SESSION_TOKEN, |
|
230 keyFetchToken: "keyFetchToken" |
|
231 }); |
|
232 let errorMessage_notExistent = JSON.stringify({ |
|
233 code: 400, |
|
234 errno: 102, |
|
235 error: "doesn't exist" |
|
236 }); |
|
237 let errorMessage_wrongCap = JSON.stringify({ |
|
238 code: 400, |
|
239 errno: 120, |
|
240 error: "Incorrect email case", |
|
241 email: "you@example.com" |
|
242 }); |
|
243 |
|
244 let server = httpd_setup({ |
|
245 "/account/login": function(request, response) { |
|
246 let body = CommonUtils.readBytesFromInputStream(request.bodyInputStream); |
|
247 let jsonBody = JSON.parse(body); |
|
248 |
|
249 if (jsonBody.email == "mé@example.com") { |
|
250 do_check_eq("", request._queryString); |
|
251 do_check_eq(jsonBody.authPW, "08b9d111196b8408e8ed92439da49206c8ecfbf343df0ae1ecefcd1e0174a8b6"); |
|
252 response.setStatusLine(request.httpVersion, 200, "OK"); |
|
253 response.bodyOutputStream.write(sessionMessage_noKey, |
|
254 sessionMessage_noKey.length); |
|
255 return; |
|
256 } |
|
257 else if (jsonBody.email == "you@example.com") { |
|
258 do_check_eq("keys=true", request._queryString); |
|
259 do_check_eq(jsonBody.authPW, "93d20ec50304d496d0707ec20d7e8c89459b6396ec5dd5b9e92809c5e42856c7"); |
|
260 response.setStatusLine(request.httpVersion, 200, "OK"); |
|
261 response.bodyOutputStream.write(sessionMessage_withKey, |
|
262 sessionMessage_withKey.length); |
|
263 return; |
|
264 } |
|
265 else if (jsonBody.email == "You@example.com") { |
|
266 // Error trying to sign in with a wrong capitalization |
|
267 response.setStatusLine(request.httpVersion, 400, "Bad request"); |
|
268 response.bodyOutputStream.write(errorMessage_wrongCap, |
|
269 errorMessage_wrongCap.length); |
|
270 return; |
|
271 } |
|
272 else { |
|
273 // Error trying to sign in to nonexistent account |
|
274 response.setStatusLine(request.httpVersion, 400, "Bad request"); |
|
275 response.bodyOutputStream.write(errorMessage_notExistent, |
|
276 errorMessage_notExistent.length); |
|
277 return; |
|
278 } |
|
279 }, |
|
280 }); |
|
281 |
|
282 // Login without retrieving optional keys |
|
283 let client = new FxAccountsClient(server.baseURI); |
|
284 let result = yield client.signIn('mé@example.com', 'bigsecret'); |
|
285 do_check_eq(FAKE_SESSION_TOKEN, result.sessionToken); |
|
286 do_check_eq(result.unwrapBKey, |
|
287 "c076ec3f4af123a615157154c6e1d0d6293e514fd7b0221e32d50517ecf002b8"); |
|
288 do_check_eq(undefined, result.keyFetchToken); |
|
289 |
|
290 // Login with retrieving optional keys |
|
291 let result = yield client.signIn('you@example.com', 'bigsecret', true); |
|
292 do_check_eq(FAKE_SESSION_TOKEN, result.sessionToken); |
|
293 do_check_eq(result.unwrapBKey, |
|
294 "65970516211062112e955d6420bebe020269d6b6a91ebd288319fc8d0cb49624"); |
|
295 do_check_eq("keyFetchToken", result.keyFetchToken); |
|
296 |
|
297 // Retry due to wrong email capitalization |
|
298 let result = yield client.signIn('You@example.com', 'bigsecret', true); |
|
299 do_check_eq(FAKE_SESSION_TOKEN, result.sessionToken); |
|
300 do_check_eq(result.unwrapBKey, |
|
301 "65970516211062112e955d6420bebe020269d6b6a91ebd288319fc8d0cb49624"); |
|
302 do_check_eq("keyFetchToken", result.keyFetchToken); |
|
303 |
|
304 // Don't retry due to wrong email capitalization |
|
305 try { |
|
306 let result = yield client.signIn('You@example.com', 'bigsecret', true, false); |
|
307 do_throw("Expected to catch an exception"); |
|
308 } catch (expectedError) { |
|
309 do_check_eq(120, expectedError.errno); |
|
310 do_check_eq("you@example.com", expectedError.email); |
|
311 } |
|
312 |
|
313 // Trigger error path |
|
314 try { |
|
315 result = yield client.signIn("yøü@bad.example.org", "nofear"); |
|
316 do_throw("Expected to catch an exception"); |
|
317 } catch (expectedError) { |
|
318 do_check_eq(102, expectedError.errno); |
|
319 } |
|
320 |
|
321 yield deferredStop(server); |
|
322 }); |
|
323 |
|
324 add_task(function test_signOut() { |
|
325 let signoutMessage = JSON.stringify({}); |
|
326 let errorMessage = JSON.stringify({code: 400, errno: 102, error: "doesn't exist"}); |
|
327 let signedOut = false; |
|
328 |
|
329 let server = httpd_setup({ |
|
330 "/session/destroy": function(request, response) { |
|
331 if (!signedOut) { |
|
332 signedOut = true; |
|
333 do_check_true(request.hasHeader("Authorization")); |
|
334 response.setStatusLine(request.httpVersion, 200, "OK"); |
|
335 response.bodyOutputStream.write(signoutMessage, signoutMessage.length); |
|
336 return; |
|
337 } |
|
338 |
|
339 // Error trying to sign out of nonexistent account |
|
340 response.setStatusLine(request.httpVersion, 400, "Bad request"); |
|
341 response.bodyOutputStream.write(errorMessage, errorMessage.length); |
|
342 return; |
|
343 }, |
|
344 }); |
|
345 |
|
346 let client = new FxAccountsClient(server.baseURI); |
|
347 let result = yield client.signOut("FakeSession"); |
|
348 do_check_eq(typeof result, "object"); |
|
349 |
|
350 // Trigger error path |
|
351 try { |
|
352 result = yield client.signOut("FakeSession"); |
|
353 do_throw("Expected to catch an exception"); |
|
354 } catch(expectedError) { |
|
355 do_check_eq(102, expectedError.errno); |
|
356 } |
|
357 |
|
358 yield deferredStop(server); |
|
359 }); |
|
360 |
|
361 add_task(function test_recoveryEmailStatus() { |
|
362 let emailStatus = JSON.stringify({verified: true}); |
|
363 let errorMessage = JSON.stringify({code: 400, errno: 102, error: "doesn't exist"}); |
|
364 let tries = 0; |
|
365 |
|
366 let server = httpd_setup({ |
|
367 "/recovery_email/status": function(request, response) { |
|
368 do_check_true(request.hasHeader("Authorization")); |
|
369 |
|
370 if (tries === 0) { |
|
371 tries += 1; |
|
372 response.setStatusLine(request.httpVersion, 200, "OK"); |
|
373 response.bodyOutputStream.write(emailStatus, emailStatus.length); |
|
374 return; |
|
375 } |
|
376 |
|
377 // Second call gets an error trying to query a nonexistent account |
|
378 response.setStatusLine(request.httpVersion, 400, "Bad request"); |
|
379 response.bodyOutputStream.write(errorMessage, errorMessage.length); |
|
380 return; |
|
381 }, |
|
382 }); |
|
383 |
|
384 let client = new FxAccountsClient(server.baseURI); |
|
385 let result = yield client.recoveryEmailStatus(FAKE_SESSION_TOKEN); |
|
386 do_check_eq(result.verified, true); |
|
387 |
|
388 // Trigger error path |
|
389 try { |
|
390 result = yield client.recoveryEmailStatus("some bogus session"); |
|
391 do_throw("Expected to catch an exception"); |
|
392 } catch(expectedError) { |
|
393 do_check_eq(102, expectedError.errno); |
|
394 } |
|
395 |
|
396 yield deferredStop(server); |
|
397 }); |
|
398 |
|
399 add_task(function test_resendVerificationEmail() { |
|
400 let emptyMessage = "{}"; |
|
401 let errorMessage = JSON.stringify({code: 400, errno: 102, error: "doesn't exist"}); |
|
402 let tries = 0; |
|
403 |
|
404 let server = httpd_setup({ |
|
405 "/recovery_email/resend_code": function(request, response) { |
|
406 do_check_true(request.hasHeader("Authorization")); |
|
407 if (tries === 0) { |
|
408 tries += 1; |
|
409 response.setStatusLine(request.httpVersion, 200, "OK"); |
|
410 response.bodyOutputStream.write(emptyMessage, emptyMessage.length); |
|
411 return; |
|
412 } |
|
413 |
|
414 // Second call gets an error trying to query a nonexistent account |
|
415 response.setStatusLine(request.httpVersion, 400, "Bad request"); |
|
416 response.bodyOutputStream.write(errorMessage, errorMessage.length); |
|
417 return; |
|
418 }, |
|
419 }); |
|
420 |
|
421 let client = new FxAccountsClient(server.baseURI); |
|
422 let result = yield client.resendVerificationEmail(FAKE_SESSION_TOKEN); |
|
423 do_check_eq(JSON.stringify(result), emptyMessage); |
|
424 |
|
425 // Trigger error path |
|
426 try { |
|
427 result = yield client.resendVerificationEmail("some bogus session"); |
|
428 do_throw("Expected to catch an exception"); |
|
429 } catch(expectedError) { |
|
430 do_check_eq(102, expectedError.errno); |
|
431 } |
|
432 |
|
433 yield deferredStop(server); |
|
434 }); |
|
435 |
|
436 add_task(function test_accountKeys() { |
|
437 // Four calls to accountKeys(). The first one should work correctly, and we |
|
438 // should get a valid bundle back, in exchange for our keyFetch token, from |
|
439 // which we correctly derive kA and wrapKB. The subsequent three calls |
|
440 // should all trigger separate error paths. |
|
441 let responseMessage = JSON.stringify({bundle: ACCOUNT_KEYS.response}); |
|
442 let errorMessage = JSON.stringify({code: 400, errno: 102, error: "doesn't exist"}); |
|
443 let emptyMessage = "{}"; |
|
444 let attempt = 0; |
|
445 |
|
446 let server = httpd_setup({ |
|
447 "/account/keys": function(request, response) { |
|
448 do_check_true(request.hasHeader("Authorization")); |
|
449 attempt += 1; |
|
450 |
|
451 switch(attempt) { |
|
452 case 1: |
|
453 // First time succeeds |
|
454 response.setStatusLine(request.httpVersion, 200, "OK"); |
|
455 response.bodyOutputStream.write(responseMessage, responseMessage.length); |
|
456 break; |
|
457 |
|
458 case 2: |
|
459 // Second time, return no bundle to trigger client error |
|
460 response.setStatusLine(request.httpVersion, 200, "OK"); |
|
461 response.bodyOutputStream.write(emptyMessage, emptyMessage.length); |
|
462 break; |
|
463 |
|
464 case 3: |
|
465 // Return gibberish to trigger client MAC error |
|
466 // Tweak a byte |
|
467 let garbageResponse = JSON.stringify({ |
|
468 bundle: ACCOUNT_KEYS.response.slice(0, -1) + "1" |
|
469 }); |
|
470 response.setStatusLine(request.httpVersion, 200, "OK"); |
|
471 response.bodyOutputStream.write(garbageResponse, garbageResponse.length); |
|
472 break; |
|
473 |
|
474 case 4: |
|
475 // Trigger error for nonexistent account |
|
476 response.setStatusLine(request.httpVersion, 400, "Bad request"); |
|
477 response.bodyOutputStream.write(errorMessage, errorMessage.length); |
|
478 break; |
|
479 } |
|
480 }, |
|
481 }); |
|
482 |
|
483 let client = new FxAccountsClient(server.baseURI); |
|
484 |
|
485 // First try, all should be good |
|
486 let result = yield client.accountKeys(ACCOUNT_KEYS.keyFetch); |
|
487 do_check_eq(CommonUtils.hexToBytes(ACCOUNT_KEYS.kA), result.kA); |
|
488 do_check_eq(CommonUtils.hexToBytes(ACCOUNT_KEYS.wrapKB), result.wrapKB); |
|
489 |
|
490 // Second try, empty bundle should trigger error |
|
491 try { |
|
492 result = yield client.accountKeys(ACCOUNT_KEYS.keyFetch); |
|
493 do_throw("Expected to catch an exception"); |
|
494 } catch(expectedError) { |
|
495 do_check_eq(expectedError.message, "failed to retrieve keys"); |
|
496 } |
|
497 |
|
498 // Third try, bad bundle results in MAC error |
|
499 try { |
|
500 result = yield client.accountKeys(ACCOUNT_KEYS.keyFetch); |
|
501 do_throw("Expected to catch an exception"); |
|
502 } catch(expectedError) { |
|
503 do_check_eq(expectedError.message, "error unbundling encryption keys"); |
|
504 } |
|
505 |
|
506 // Fourth try, pretend account doesn't exist |
|
507 try { |
|
508 result = yield client.accountKeys(ACCOUNT_KEYS.keyFetch); |
|
509 do_throw("Expected to catch an exception"); |
|
510 } catch(expectedError) { |
|
511 do_check_eq(102, expectedError.errno); |
|
512 } |
|
513 |
|
514 yield deferredStop(server); |
|
515 }); |
|
516 |
|
517 add_task(function test_signCertificate() { |
|
518 let certSignMessage = JSON.stringify({cert: {bar: "baz"}}); |
|
519 let errorMessage = JSON.stringify({code: 400, errno: 102, error: "doesn't exist"}); |
|
520 let tries = 0; |
|
521 |
|
522 let server = httpd_setup({ |
|
523 "/certificate/sign": function(request, response) { |
|
524 do_check_true(request.hasHeader("Authorization")); |
|
525 |
|
526 if (tries === 0) { |
|
527 tries += 1; |
|
528 let body = CommonUtils.readBytesFromInputStream(request.bodyInputStream); |
|
529 let jsonBody = JSON.parse(body); |
|
530 do_check_eq(JSON.parse(jsonBody.publicKey).foo, "bar"); |
|
531 do_check_eq(jsonBody.duration, 600); |
|
532 response.setStatusLine(request.httpVersion, 200, "OK"); |
|
533 response.bodyOutputStream.write(certSignMessage, certSignMessage.length); |
|
534 return; |
|
535 } |
|
536 |
|
537 // Second attempt, trigger error |
|
538 response.setStatusLine(request.httpVersion, 400, "Bad request"); |
|
539 response.bodyOutputStream.write(errorMessage, errorMessage.length); |
|
540 return; |
|
541 }, |
|
542 }); |
|
543 |
|
544 let client = new FxAccountsClient(server.baseURI); |
|
545 let result = yield client.signCertificate(FAKE_SESSION_TOKEN, JSON.stringify({foo: "bar"}), 600); |
|
546 do_check_eq("baz", result.bar); |
|
547 |
|
548 // Account doesn't exist |
|
549 try { |
|
550 result = yield client.signCertificate("bogus", JSON.stringify({foo: "bar"}), 600); |
|
551 do_throw("Expected to catch an exception"); |
|
552 } catch(expectedError) { |
|
553 do_check_eq(102, expectedError.errno); |
|
554 } |
|
555 |
|
556 yield deferredStop(server); |
|
557 }); |
|
558 |
|
559 add_task(function test_accountExists() { |
|
560 let sessionMessage = JSON.stringify({sessionToken: FAKE_SESSION_TOKEN}); |
|
561 let existsMessage = JSON.stringify({error: "wrong password", code: 400, errno: 103}); |
|
562 let doesntExistMessage = JSON.stringify({error: "no such account", code: 400, errno: 102}); |
|
563 let emptyMessage = "{}"; |
|
564 |
|
565 let server = httpd_setup({ |
|
566 "/account/login": function(request, response) { |
|
567 let body = CommonUtils.readBytesFromInputStream(request.bodyInputStream); |
|
568 let jsonBody = JSON.parse(body); |
|
569 |
|
570 switch (jsonBody.email) { |
|
571 // We'll test that these users' accounts exist |
|
572 case "i.exist@example.com": |
|
573 case "i.also.exist@example.com": |
|
574 response.setStatusLine(request.httpVersion, 400, "Bad request"); |
|
575 response.bodyOutputStream.write(existsMessage, existsMessage.length); |
|
576 break; |
|
577 |
|
578 // This user's account doesn't exist |
|
579 case "i.dont.exist@example.com": |
|
580 response.setStatusLine(request.httpVersion, 400, "Bad request"); |
|
581 response.bodyOutputStream.write(doesntExistMessage, doesntExistMessage.length); |
|
582 break; |
|
583 |
|
584 // This user throws an unexpected response |
|
585 // This will reject the client signIn promise |
|
586 case "i.break.things@example.com": |
|
587 response.setStatusLine(request.httpVersion, 500, "Alas"); |
|
588 response.bodyOutputStream.write(emptyMessage, emptyMessage.length); |
|
589 break; |
|
590 |
|
591 default: |
|
592 throw new Error("Unexpected login from " + jsonBody.email); |
|
593 break; |
|
594 } |
|
595 }, |
|
596 }); |
|
597 |
|
598 let client = new FxAccountsClient(server.baseURI); |
|
599 let result; |
|
600 |
|
601 result = yield client.accountExists("i.exist@example.com"); |
|
602 do_check_true(result); |
|
603 |
|
604 result = yield client.accountExists("i.also.exist@example.com"); |
|
605 do_check_true(result); |
|
606 |
|
607 result = yield client.accountExists("i.dont.exist@example.com"); |
|
608 do_check_false(result); |
|
609 |
|
610 try { |
|
611 result = yield client.accountExists("i.break.things@example.com"); |
|
612 do_throw("Expected to catch an exception"); |
|
613 } catch(unexpectedError) { |
|
614 do_check_eq(unexpectedError.code, 500); |
|
615 } |
|
616 |
|
617 yield deferredStop(server); |
|
618 }); |
|
619 |
|
620 add_task(function test_email_case() { |
|
621 let canonicalEmail = "greta.garbo@gmail.com"; |
|
622 let clientEmail = "Greta.Garbo@gmail.COM"; |
|
623 let attempts = 0; |
|
624 |
|
625 function writeResp(response, msg) { |
|
626 if (typeof msg === "object") { |
|
627 msg = JSON.stringify(msg); |
|
628 } |
|
629 response.bodyOutputStream.write(msg, msg.length); |
|
630 } |
|
631 |
|
632 let server = httpd_setup( |
|
633 { |
|
634 "/account/login": function(request, response) { |
|
635 response.setHeader("Content-Type", "application/json; charset=utf-8"); |
|
636 attempts += 1; |
|
637 if (attempts > 2) { |
|
638 response.setStatusLine(request.httpVersion, 429, "Sorry, you had your chance"); |
|
639 return writeResp(response, ""); |
|
640 } |
|
641 |
|
642 let body = CommonUtils.readBytesFromInputStream(request.bodyInputStream); |
|
643 let jsonBody = JSON.parse(body); |
|
644 let email = jsonBody.email; |
|
645 |
|
646 // If the client has the wrong case on the email, we return a 400, with |
|
647 // the capitalization of the email as saved in the accounts database. |
|
648 if (email == canonicalEmail) { |
|
649 response.setStatusLine(request.httpVersion, 200, "Yay"); |
|
650 return writeResp(response, {areWeHappy: "yes"}); |
|
651 } |
|
652 |
|
653 response.setStatusLine(request.httpVersion, 400, "Incorrect email case"); |
|
654 return writeResp(response, { |
|
655 code: 400, |
|
656 errno: 120, |
|
657 error: "Incorrect email case", |
|
658 email: canonicalEmail |
|
659 }); |
|
660 }, |
|
661 } |
|
662 ); |
|
663 |
|
664 let client = new FxAccountsClient(server.baseURI); |
|
665 |
|
666 let result = yield client.signIn(clientEmail, "123456"); |
|
667 do_check_eq(result.areWeHappy, "yes"); |
|
668 do_check_eq(attempts, 2); |
|
669 |
|
670 yield deferredStop(server); |
|
671 }); |
|
672 |
|
673 add_task(function test__deriveHawkCredentials() { |
|
674 let client = new FxAccountsClient("https://example.org"); |
|
675 |
|
676 let credentials = client._deriveHawkCredentials( |
|
677 SESSION_KEYS.sessionToken, "sessionToken"); |
|
678 |
|
679 do_check_eq(credentials.algorithm, "sha256"); |
|
680 do_check_eq(credentials.id, SESSION_KEYS.tokenID); |
|
681 do_check_eq(CommonUtils.bytesAsHex(credentials.key), SESSION_KEYS.reqHMACkey); |
|
682 }); |
|
683 |
|
684 // turn formatted test vectors into normal hex strings |
|
685 function h(hexStr) { |
|
686 return hexStr.replace(/\s+/g, ""); |
|
687 } |