test_client.js (29959B)
1 /* Any copyright is dedicated to the Public Domain. 2 * http://creativecommons.org/publicdomain/zero/1.0/ */ 3 4 "use strict"; 5 6 const { FxAccountsClient } = ChromeUtils.importESModule( 7 "resource://gre/modules/FxAccountsClient.sys.mjs" 8 ); 9 10 const FAKE_SESSION_TOKEN = 11 "a0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebf"; 12 13 // https://wiki.mozilla.org/Identity/AttachedServices/KeyServerProtocol#.2Faccount.2Fkeys 14 var ACCOUNT_KEYS = { 15 keyFetch: h( 16 // eslint-disable-next-line no-useless-concat 17 "8081828384858687 88898a8b8c8d8e8f" + "9091929394959697 98999a9b9c9d9e9f" 18 ), 19 20 response: h( 21 "ee5c58845c7c9412 b11bbd20920c2fdd" + 22 "d83c33c9cd2c2de2 d66b222613364636" + 23 "c2c0f8cfbb7c6304 72c0bd88451342c6" + 24 "c05b14ce342c5ad4 6ad89e84464c993c" + 25 "3927d30230157d08 17a077eef4b20d97" + 26 "6f7a97363faf3f06 4c003ada7d01aa70" 27 ), 28 29 kA: h( 30 // eslint-disable-next-line no-useless-concat 31 "2021222324252627 28292a2b2c2d2e2f" + "3031323334353637 38393a3b3c3d3e3f" 32 ), 33 34 wrapKB: h( 35 // eslint-disable-next-line no-useless-concat 36 "4041424344454647 48494a4b4c4d4e4f" + "5051525354555657 58595a5b5c5d5e5f" 37 ), 38 }; 39 40 add_task(async function test_authenticated_get_request() { 41 let message = '{"msg": "Great Success!"}'; 42 let credentials = { 43 id: "eyJleHBpcmVzIjogMTM2NTAxMDg5OC4x", 44 key: "qTZf4ZFpAMpMoeSsX3zVRjiqmNs=", 45 algorithm: "sha256", 46 }; 47 let method = "GET"; 48 49 let server = httpd_setup({ 50 "/foo": function (request, response) { 51 Assert.ok(request.hasHeader("Authorization")); 52 53 response.setStatusLine(request.httpVersion, 200, "OK"); 54 response.bodyOutputStream.write(message, message.length); 55 }, 56 }); 57 58 let client = new FxAccountsClient(server.baseURI); 59 60 let result = await client._request("/foo", method, credentials); 61 Assert.equal("Great Success!", result.msg); 62 63 await promiseStopServer(server); 64 }); 65 66 add_task(async function test_authenticated_post_request() { 67 let credentials = { 68 id: "eyJleHBpcmVzIjogMTM2NTAxMDg5OC4x", 69 key: "qTZf4ZFpAMpMoeSsX3zVRjiqmNs=", 70 algorithm: "sha256", 71 }; 72 let method = "POST"; 73 74 let server = httpd_setup({ 75 "/foo": function (request, response) { 76 Assert.ok(request.hasHeader("Authorization")); 77 78 response.setStatusLine(request.httpVersion, 200, "OK"); 79 response.setHeader("Content-Type", "application/json"); 80 response.bodyOutputStream.writeFrom( 81 request.bodyInputStream, 82 request.bodyInputStream.available() 83 ); 84 }, 85 }); 86 87 let client = new FxAccountsClient(server.baseURI); 88 89 let result = await client._request("/foo", method, credentials, { 90 foo: "bar", 91 }); 92 Assert.equal("bar", result.foo); 93 94 await promiseStopServer(server); 95 }); 96 97 add_task(async function test_500_error() { 98 let message = "<h1>Ooops!</h1>"; 99 let method = "GET"; 100 101 let server = httpd_setup({ 102 "/foo": function (request, response) { 103 response.setStatusLine(request.httpVersion, 500, "Internal Server Error"); 104 response.bodyOutputStream.write(message, message.length); 105 }, 106 }); 107 108 let client = new FxAccountsClient(server.baseURI); 109 110 try { 111 await client._request("/foo", method); 112 do_throw("Expected to catch an exception"); 113 } catch (e) { 114 Assert.equal(500, e.code); 115 Assert.equal("Internal Server Error", e.message); 116 } 117 118 await promiseStopServer(server); 119 }); 120 121 add_task(async function test_backoffError() { 122 let method = "GET"; 123 let server = httpd_setup({ 124 "/retryDelay": function (request, response) { 125 response.setHeader("Retry-After", "30"); 126 response.setStatusLine( 127 request.httpVersion, 128 429, 129 "Client has sent too many requests" 130 ); 131 let message = "<h1>Ooops!</h1>"; 132 response.bodyOutputStream.write(message, message.length); 133 }, 134 "/duringDelayIShouldNotBeCalled": function (request, response) { 135 response.setStatusLine(request.httpVersion, 200, "OK"); 136 let jsonMessage = '{"working": "yes"}'; 137 response.bodyOutputStream.write(jsonMessage, jsonMessage.length); 138 }, 139 }); 140 141 let client = new FxAccountsClient(server.baseURI); 142 143 // Retry-After header sets client.backoffError 144 Assert.equal(client.backoffError, null); 145 try { 146 await client._request("/retryDelay", method); 147 } catch (e) { 148 Assert.equal(429, e.code); 149 Assert.equal(30, e.retryAfter); 150 Assert.notEqual(typeof client.fxaBackoffTimer, "undefined"); 151 Assert.notEqual(client.backoffError, null); 152 } 153 // While delay is in effect, client short-circuits any requests 154 // and re-rejects with previous error. 155 try { 156 await client._request("/duringDelayIShouldNotBeCalled", method); 157 throw new Error("I should not be reached"); 158 } catch (e) { 159 Assert.equal(e.retryAfter, 30); 160 Assert.equal(e.message, "Client has sent too many requests"); 161 Assert.notEqual(client.backoffError, null); 162 } 163 // Once timer fires, client nulls error out and HTTP calls work again. 164 client._clearBackoff(); 165 let result = await client._request("/duringDelayIShouldNotBeCalled", method); 166 Assert.equal(client.backoffError, null); 167 Assert.equal(result.working, "yes"); 168 169 await promiseStopServer(server); 170 }); 171 172 add_task(async function test_signUp() { 173 let creationMessage_noKey = JSON.stringify({ 174 uid: "uid", 175 sessionToken: "sessionToken", 176 }); 177 let creationMessage_withKey = JSON.stringify({ 178 uid: "uid", 179 sessionToken: "sessionToken", 180 keyFetchToken: "keyFetchToken", 181 }); 182 let errorMessage = JSON.stringify({ 183 code: 400, 184 errno: 101, 185 error: "account exists", 186 }); 187 let created = false; 188 189 // Note these strings must be unicode and not already utf-8 encoded. 190 let unicodeUsername = "andr\xe9@example.org"; // 'andré@example.org' 191 let unicodePassword = "p\xe4ssw\xf6rd"; // 'pässwörd' 192 let server = httpd_setup({ 193 "/account/create": function (request, response) { 194 let body = CommonUtils.readBytesFromInputStream(request.bodyInputStream); 195 body = CommonUtils.decodeUTF8(body); 196 let jsonBody = JSON.parse(body); 197 198 // https://github.com/mozilla/fxa-auth-server/wiki/onepw-protocol#wiki-test-vectors 199 200 if (created) { 201 // Error trying to create same account a second time 202 response.setStatusLine(request.httpVersion, 400, "Bad request"); 203 response.bodyOutputStream.write(errorMessage, errorMessage.length); 204 return; 205 } 206 207 if (jsonBody.email == unicodeUsername) { 208 Assert.equal("", request._queryString); 209 Assert.equal( 210 jsonBody.authPW, 211 "247b675ffb4c46310bc87e26d712153abe5e1c90ef00a4784594f97ef54f2375" 212 ); 213 214 response.setStatusLine(request.httpVersion, 200, "OK"); 215 response.bodyOutputStream.write( 216 creationMessage_noKey, 217 creationMessage_noKey.length 218 ); 219 return; 220 } 221 222 if (jsonBody.email == "you@example.org") { 223 Assert.equal("keys=true", request._queryString); 224 Assert.equal( 225 jsonBody.authPW, 226 "e5c1cdfdaa5fcee06142db865b212cc8ba8abee2a27d639d42c139f006cdb930" 227 ); 228 created = true; 229 230 response.setStatusLine(request.httpVersion, 200, "OK"); 231 response.bodyOutputStream.write( 232 creationMessage_withKey, 233 creationMessage_withKey.length 234 ); 235 return; 236 } 237 // just throwing here doesn't make any log noise, so have an assertion 238 // fail instead. 239 Assert.ok(false, "unexpected email: " + jsonBody.email); 240 }, 241 }); 242 243 // Try to create an account without retrieving optional keys. 244 let client = new FxAccountsClient(server.baseURI); 245 let result = await client.signUp(unicodeUsername, unicodePassword); 246 Assert.equal("uid", result.uid); 247 Assert.equal("sessionToken", result.sessionToken); 248 Assert.equal(undefined, result.keyFetchToken); 249 Assert.equal( 250 result.unwrapBKey, 251 "de6a2648b78284fcb9ffa81ba95803309cfba7af583c01a8a1a63e567234dd28" 252 ); 253 254 // Try to create an account retrieving optional keys. 255 result = await client.signUp("you@example.org", "pässwörd", true); 256 Assert.equal("uid", result.uid); 257 Assert.equal("sessionToken", result.sessionToken); 258 Assert.equal("keyFetchToken", result.keyFetchToken); 259 Assert.equal( 260 result.unwrapBKey, 261 "f589225b609e56075d76eb74f771ff9ab18a4dc0e901e131ba8f984c7fb0ca8c" 262 ); 263 264 // Try to create an existing account. Triggers error path. 265 try { 266 result = await client.signUp(unicodeUsername, unicodePassword); 267 do_throw("Expected to catch an exception"); 268 } catch (expectedError) { 269 Assert.equal(101, expectedError.errno); 270 } 271 272 await promiseStopServer(server); 273 }); 274 275 add_task(async function test_signIn() { 276 let sessionMessage_noKey = JSON.stringify({ 277 sessionToken: FAKE_SESSION_TOKEN, 278 }); 279 let sessionMessage_withKey = JSON.stringify({ 280 sessionToken: FAKE_SESSION_TOKEN, 281 keyFetchToken: "keyFetchToken", 282 }); 283 let errorMessage_notExistent = JSON.stringify({ 284 code: 400, 285 errno: 102, 286 error: "doesn't exist", 287 }); 288 let errorMessage_wrongCap = JSON.stringify({ 289 code: 400, 290 errno: 120, 291 error: "Incorrect email case", 292 email: "you@example.com", 293 }); 294 295 // Note this strings must be unicode and not already utf-8 encoded. 296 let unicodeUsername = "m\xe9@example.com"; // 'mé@example.com' 297 let server = httpd_setup({ 298 "/account/login": function (request, response) { 299 let body = CommonUtils.readBytesFromInputStream(request.bodyInputStream); 300 body = CommonUtils.decodeUTF8(body); 301 let jsonBody = JSON.parse(body); 302 303 if (jsonBody.email == unicodeUsername) { 304 Assert.equal("", request._queryString); 305 Assert.equal( 306 jsonBody.authPW, 307 "08b9d111196b8408e8ed92439da49206c8ecfbf343df0ae1ecefcd1e0174a8b6" 308 ); 309 response.setStatusLine(request.httpVersion, 200, "OK"); 310 response.bodyOutputStream.write( 311 sessionMessage_noKey, 312 sessionMessage_noKey.length 313 ); 314 } else if (jsonBody.email == "you@example.com") { 315 Assert.equal("keys=true", request._queryString); 316 Assert.equal( 317 jsonBody.authPW, 318 "93d20ec50304d496d0707ec20d7e8c89459b6396ec5dd5b9e92809c5e42856c7" 319 ); 320 response.setStatusLine(request.httpVersion, 200, "OK"); 321 response.bodyOutputStream.write( 322 sessionMessage_withKey, 323 sessionMessage_withKey.length 324 ); 325 } else if (jsonBody.email == "You@example.com") { 326 // Error trying to sign in with a wrong capitalization 327 response.setStatusLine(request.httpVersion, 400, "Bad request"); 328 response.bodyOutputStream.write( 329 errorMessage_wrongCap, 330 errorMessage_wrongCap.length 331 ); 332 } else { 333 // Error trying to sign in to nonexistent account 334 response.setStatusLine(request.httpVersion, 400, "Bad request"); 335 response.bodyOutputStream.write( 336 errorMessage_notExistent, 337 errorMessage_notExistent.length 338 ); 339 } 340 }, 341 }); 342 343 // Login without retrieving optional keys 344 let client = new FxAccountsClient(server.baseURI); 345 let result = await client.signIn(unicodeUsername, "bigsecret"); 346 Assert.equal(FAKE_SESSION_TOKEN, result.sessionToken); 347 Assert.equal( 348 result.unwrapBKey, 349 "c076ec3f4af123a615157154c6e1d0d6293e514fd7b0221e32d50517ecf002b8" 350 ); 351 Assert.equal(undefined, result.keyFetchToken); 352 353 // Login with retrieving optional keys 354 result = await client.signIn("you@example.com", "bigsecret", true); 355 Assert.equal(FAKE_SESSION_TOKEN, result.sessionToken); 356 Assert.equal( 357 result.unwrapBKey, 358 "65970516211062112e955d6420bebe020269d6b6a91ebd288319fc8d0cb49624" 359 ); 360 Assert.equal("keyFetchToken", result.keyFetchToken); 361 362 // Retry due to wrong email capitalization 363 result = await client.signIn("You@example.com", "bigsecret", true); 364 Assert.equal(FAKE_SESSION_TOKEN, result.sessionToken); 365 Assert.equal( 366 result.unwrapBKey, 367 "65970516211062112e955d6420bebe020269d6b6a91ebd288319fc8d0cb49624" 368 ); 369 Assert.equal("keyFetchToken", result.keyFetchToken); 370 371 // Trigger error path 372 try { 373 result = await client.signIn("yøü@bad.example.org", "nofear"); 374 do_throw("Expected to catch an exception"); 375 } catch (expectedError) { 376 Assert.equal(102, expectedError.errno); 377 } 378 379 await promiseStopServer(server); 380 }); 381 382 add_task(async function test_signOut() { 383 let signoutMessage = JSON.stringify({}); 384 let errorMessage = JSON.stringify({ 385 code: 400, 386 errno: 102, 387 error: "doesn't exist", 388 }); 389 let signedOut = false; 390 391 let server = httpd_setup({ 392 "/session/destroy": function (request, response) { 393 if (!signedOut) { 394 signedOut = true; 395 Assert.ok(request.hasHeader("Authorization")); 396 response.setStatusLine(request.httpVersion, 200, "OK"); 397 response.bodyOutputStream.write(signoutMessage, signoutMessage.length); 398 return; 399 } 400 401 // Error trying to sign out of nonexistent account 402 response.setStatusLine(request.httpVersion, 400, "Bad request"); 403 response.bodyOutputStream.write(errorMessage, errorMessage.length); 404 }, 405 }); 406 407 let client = new FxAccountsClient(server.baseURI); 408 let result = await client.signOut("FakeSession"); 409 Assert.equal(typeof result, "object"); 410 411 // Trigger error path 412 try { 413 result = await client.signOut("FakeSession"); 414 do_throw("Expected to catch an exception"); 415 } catch (expectedError) { 416 Assert.equal(102, expectedError.errno); 417 } 418 419 await promiseStopServer(server); 420 }); 421 422 add_task(async function test_recoveryEmailStatus() { 423 let emailStatus = JSON.stringify({ verified: true }); 424 let errorMessage = JSON.stringify({ 425 code: 400, 426 errno: 102, 427 error: "doesn't exist", 428 }); 429 let tries = 0; 430 431 let server = httpd_setup({ 432 "/recovery_email/status": function (request, response) { 433 Assert.ok(request.hasHeader("Authorization")); 434 Assert.equal("", request._queryString); 435 436 if (tries === 0) { 437 tries += 1; 438 response.setStatusLine(request.httpVersion, 200, "OK"); 439 response.bodyOutputStream.write(emailStatus, emailStatus.length); 440 return; 441 } 442 443 // Second call gets an error trying to query a nonexistent account 444 response.setStatusLine(request.httpVersion, 400, "Bad request"); 445 response.bodyOutputStream.write(errorMessage, errorMessage.length); 446 }, 447 }); 448 449 let client = new FxAccountsClient(server.baseURI); 450 let result = await client.recoveryEmailStatus(FAKE_SESSION_TOKEN); 451 Assert.equal(result.verified, true); 452 453 // Trigger error path 454 try { 455 result = await client.recoveryEmailStatus("some bogus session"); 456 do_throw("Expected to catch an exception"); 457 } catch (expectedError) { 458 Assert.equal(102, expectedError.errno); 459 } 460 461 await promiseStopServer(server); 462 }); 463 464 add_task(async function test_recoveryEmailStatusWithReason() { 465 let emailStatus = JSON.stringify({ verified: true }); 466 let server = httpd_setup({ 467 "/recovery_email/status": function (request, response) { 468 Assert.ok(request.hasHeader("Authorization")); 469 // if there is a query string then it will have a reason 470 Assert.equal("reason=push", request._queryString); 471 472 response.setStatusLine(request.httpVersion, 200, "OK"); 473 response.bodyOutputStream.write(emailStatus, emailStatus.length); 474 }, 475 }); 476 477 let client = new FxAccountsClient(server.baseURI); 478 let result = await client.recoveryEmailStatus(FAKE_SESSION_TOKEN, { 479 reason: "push", 480 }); 481 Assert.equal(result.verified, true); 482 await promiseStopServer(server); 483 }); 484 485 add_task(async function test_resendVerificationEmail() { 486 let emptyMessage = "{}"; 487 let errorMessage = JSON.stringify({ 488 code: 400, 489 errno: 102, 490 error: "doesn't exist", 491 }); 492 let tries = 0; 493 494 let server = httpd_setup({ 495 "/recovery_email/resend_code": function (request, response) { 496 Assert.ok(request.hasHeader("Authorization")); 497 if (tries === 0) { 498 tries += 1; 499 response.setStatusLine(request.httpVersion, 200, "OK"); 500 response.bodyOutputStream.write(emptyMessage, emptyMessage.length); 501 return; 502 } 503 504 // Second call gets an error trying to query a nonexistent account 505 response.setStatusLine(request.httpVersion, 400, "Bad request"); 506 response.bodyOutputStream.write(errorMessage, errorMessage.length); 507 }, 508 }); 509 510 let client = new FxAccountsClient(server.baseURI); 511 let result = await client.resendVerificationEmail(FAKE_SESSION_TOKEN); 512 Assert.equal(JSON.stringify(result), emptyMessage); 513 514 // Trigger error path 515 try { 516 result = await client.resendVerificationEmail("some bogus session"); 517 do_throw("Expected to catch an exception"); 518 } catch (expectedError) { 519 Assert.equal(102, expectedError.errno); 520 } 521 522 await promiseStopServer(server); 523 }); 524 525 add_task(async function test_accountKeys() { 526 // Four calls to accountKeys(). The first one should work correctly, and we 527 // should get a valid bundle back, in exchange for our keyFetch token, from 528 // which we correctly derive kA and wrapKB. The subsequent three calls 529 // should all trigger separate error paths. 530 let responseMessage = JSON.stringify({ bundle: ACCOUNT_KEYS.response }); 531 let errorMessage = JSON.stringify({ 532 code: 400, 533 errno: 102, 534 error: "doesn't exist", 535 }); 536 let emptyMessage = "{}"; 537 let attempt = 0; 538 539 let server = httpd_setup({ 540 "/account/keys": function (request, response) { 541 Assert.ok(request.hasHeader("Authorization")); 542 attempt += 1; 543 544 switch (attempt) { 545 case 1: 546 // First time succeeds 547 response.setStatusLine(request.httpVersion, 200, "OK"); 548 response.bodyOutputStream.write( 549 responseMessage, 550 responseMessage.length 551 ); 552 break; 553 554 case 2: 555 // Second time, return no bundle to trigger client error 556 response.setStatusLine(request.httpVersion, 200, "OK"); 557 response.bodyOutputStream.write(emptyMessage, emptyMessage.length); 558 break; 559 560 case 3: { 561 // Return gibberish to trigger client MAC error 562 // Tweak a byte 563 let garbageResponse = JSON.stringify({ 564 bundle: ACCOUNT_KEYS.response.slice(0, -1) + "1", 565 }); 566 response.setStatusLine(request.httpVersion, 200, "OK"); 567 response.bodyOutputStream.write( 568 garbageResponse, 569 garbageResponse.length 570 ); 571 break; 572 } 573 574 case 4: 575 // Trigger error for nonexistent account 576 response.setStatusLine(request.httpVersion, 400, "Bad request"); 577 response.bodyOutputStream.write(errorMessage, errorMessage.length); 578 break; 579 } 580 }, 581 }); 582 583 let client = new FxAccountsClient(server.baseURI); 584 585 // First try, all should be good 586 let result = await client.accountKeys(ACCOUNT_KEYS.keyFetch); 587 Assert.equal(CommonUtils.hexToBytes(ACCOUNT_KEYS.kA), result.kA); 588 Assert.equal(CommonUtils.hexToBytes(ACCOUNT_KEYS.wrapKB), result.wrapKB); 589 590 // Second try, empty bundle should trigger error 591 try { 592 result = await client.accountKeys(ACCOUNT_KEYS.keyFetch); 593 do_throw("Expected to catch an exception"); 594 } catch (expectedError) { 595 Assert.equal(expectedError.message, "failed to retrieve keys"); 596 } 597 598 // Third try, bad bundle results in MAC error 599 try { 600 result = await client.accountKeys(ACCOUNT_KEYS.keyFetch); 601 do_throw("Expected to catch an exception"); 602 } catch (expectedError) { 603 Assert.equal(expectedError.message, "error unbundling encryption keys"); 604 } 605 606 // Fourth try, pretend account doesn't exist 607 try { 608 result = await client.accountKeys(ACCOUNT_KEYS.keyFetch); 609 do_throw("Expected to catch an exception"); 610 } catch (expectedError) { 611 Assert.equal(102, expectedError.errno); 612 } 613 614 await promiseStopServer(server); 615 }); 616 617 add_task(async function test_accessTokenWithSessionToken() { 618 let server = httpd_setup({ 619 "/oauth/token": function (request, response) { 620 const responseMessage = JSON.stringify({ 621 access_token: 622 "43793fdfffec22eb39fc3c44ed09193a6fde4c24e5d6a73f73178597b268af69", 623 token_type: "bearer", 624 scope: SCOPE_APP_SYNC, 625 expires_in: 21600, 626 auth_at: 1589579900, 627 }); 628 629 response.setStatusLine(request.httpVersion, 200, "OK"); 630 response.bodyOutputStream.write(responseMessage, responseMessage.length); 631 }, 632 }); 633 634 let client = new FxAccountsClient(server.baseURI); 635 let sessionTokenHex = 636 "0599c36ebb5cad6feb9285b9547b65342b5434d55c07b33bffd4307ab8f82dc4"; 637 let clientId = "5882386c6d801776"; 638 let scope = SCOPE_APP_SYNC; 639 let ttl = 100; 640 let result = await client.accessTokenWithSessionToken( 641 sessionTokenHex, 642 clientId, 643 scope, 644 ttl 645 ); 646 Assert.ok(result); 647 648 await promiseStopServer(server); 649 }); 650 651 add_task(async function test_accountExists() { 652 let existsMessage = JSON.stringify({ 653 error: "wrong password", 654 code: 400, 655 errno: 103, 656 }); 657 let doesntExistMessage = JSON.stringify({ 658 error: "no such account", 659 code: 400, 660 errno: 102, 661 }); 662 let emptyMessage = "{}"; 663 664 let server = httpd_setup({ 665 "/account/login": function (request, response) { 666 let body = CommonUtils.readBytesFromInputStream(request.bodyInputStream); 667 let jsonBody = JSON.parse(body); 668 669 switch (jsonBody.email) { 670 // We'll test that these users' accounts exist 671 case "i.exist@example.com": 672 case "i.also.exist@example.com": 673 response.setStatusLine(request.httpVersion, 400, "Bad request"); 674 response.bodyOutputStream.write(existsMessage, existsMessage.length); 675 break; 676 677 // This user's account doesn't exist 678 case "i.dont.exist@example.com": 679 response.setStatusLine(request.httpVersion, 400, "Bad request"); 680 response.bodyOutputStream.write( 681 doesntExistMessage, 682 doesntExistMessage.length 683 ); 684 break; 685 686 // This user throws an unexpected response 687 // This will reject the client signIn promise 688 case "i.break.things@example.com": 689 response.setStatusLine(request.httpVersion, 500, "Alas"); 690 response.bodyOutputStream.write(emptyMessage, emptyMessage.length); 691 break; 692 693 default: 694 throw new Error("Unexpected login from " + jsonBody.email); 695 } 696 }, 697 }); 698 699 let client = new FxAccountsClient(server.baseURI); 700 let result; 701 702 result = await client.accountExists("i.exist@example.com"); 703 Assert.ok(result); 704 705 result = await client.accountExists("i.also.exist@example.com"); 706 Assert.ok(result); 707 708 result = await client.accountExists("i.dont.exist@example.com"); 709 Assert.ok(!result); 710 711 try { 712 result = await client.accountExists("i.break.things@example.com"); 713 do_throw("Expected to catch an exception"); 714 } catch (unexpectedError) { 715 Assert.equal(unexpectedError.code, 500); 716 } 717 718 await promiseStopServer(server); 719 }); 720 721 add_task(async function test_registerDevice() { 722 const DEVICE_ID = "device id"; 723 const DEVICE_NAME = "device name"; 724 const DEVICE_TYPE = "device type"; 725 const ERROR_NAME = "test that the client promise rejects"; 726 727 const server = httpd_setup({ 728 "/account/device": function (request, response) { 729 const body = JSON.parse( 730 CommonUtils.readBytesFromInputStream(request.bodyInputStream) 731 ); 732 733 if ( 734 body.id || 735 !body.name || 736 !body.type || 737 Object.keys(body).length !== 2 738 ) { 739 response.setStatusLine(request.httpVersion, 400, "Invalid request"); 740 response.bodyOutputStream.write("{}", 2); 741 return; 742 } 743 744 if (body.name === ERROR_NAME) { 745 response.setStatusLine(request.httpVersion, 500, "Alas"); 746 response.bodyOutputStream.write("{}", 2); 747 return; 748 } 749 750 body.id = DEVICE_ID; 751 body.createdAt = Date.now(); 752 753 const responseMessage = JSON.stringify(body); 754 755 response.setStatusLine(request.httpVersion, 200, "OK"); 756 response.bodyOutputStream.write(responseMessage, responseMessage.length); 757 }, 758 }); 759 760 const client = new FxAccountsClient(server.baseURI); 761 const result = await client.registerDevice( 762 FAKE_SESSION_TOKEN, 763 DEVICE_NAME, 764 DEVICE_TYPE 765 ); 766 767 Assert.ok(result); 768 Assert.equal(Object.keys(result).length, 4); 769 Assert.equal(result.id, DEVICE_ID); 770 Assert.equal(typeof result.createdAt, "number"); 771 Assert.greater(result.createdAt, 0); 772 Assert.equal(result.name, DEVICE_NAME); 773 Assert.equal(result.type, DEVICE_TYPE); 774 775 try { 776 await client.registerDevice(FAKE_SESSION_TOKEN, ERROR_NAME, DEVICE_TYPE); 777 do_throw("Expected to catch an exception"); 778 } catch (unexpectedError) { 779 Assert.equal(unexpectedError.code, 500); 780 } 781 782 await promiseStopServer(server); 783 }); 784 785 add_task(async function test_updateDevice() { 786 const DEVICE_ID = "some other id"; 787 const DEVICE_NAME = "some other name"; 788 const ERROR_ID = "test that the client promise rejects"; 789 790 const server = httpd_setup({ 791 "/account/device": function (request, response) { 792 const body = JSON.parse( 793 CommonUtils.readBytesFromInputStream(request.bodyInputStream) 794 ); 795 796 if ( 797 !body.id || 798 !body.name || 799 body.type || 800 Object.keys(body).length !== 2 801 ) { 802 response.setStatusLine(request.httpVersion, 400, "Invalid request"); 803 response.bodyOutputStream.write("{}", 2); 804 return; 805 } 806 807 if (body.id === ERROR_ID) { 808 response.setStatusLine(request.httpVersion, 500, "Alas"); 809 response.bodyOutputStream.write("{}", 2); 810 return; 811 } 812 813 const responseMessage = JSON.stringify(body); 814 815 response.setStatusLine(request.httpVersion, 200, "OK"); 816 response.bodyOutputStream.write(responseMessage, responseMessage.length); 817 }, 818 }); 819 820 const client = new FxAccountsClient(server.baseURI); 821 const result = await client.updateDevice( 822 FAKE_SESSION_TOKEN, 823 DEVICE_ID, 824 DEVICE_NAME 825 ); 826 827 Assert.ok(result); 828 Assert.equal(Object.keys(result).length, 2); 829 Assert.equal(result.id, DEVICE_ID); 830 Assert.equal(result.name, DEVICE_NAME); 831 832 try { 833 await client.updateDevice(FAKE_SESSION_TOKEN, ERROR_ID, DEVICE_NAME); 834 do_throw("Expected to catch an exception"); 835 } catch (unexpectedError) { 836 Assert.equal(unexpectedError.code, 500); 837 } 838 839 await promiseStopServer(server); 840 }); 841 842 add_task(async function test_getDeviceList() { 843 let canReturnDevices; 844 845 const server = httpd_setup({ 846 "/account/devices": function (request, response) { 847 if (canReturnDevices) { 848 response.setStatusLine(request.httpVersion, 200, "OK"); 849 response.bodyOutputStream.write("[]", 2); 850 } else { 851 response.setStatusLine(request.httpVersion, 500, "Alas"); 852 response.bodyOutputStream.write("{}", 2); 853 } 854 }, 855 }); 856 857 const client = new FxAccountsClient(server.baseURI); 858 859 canReturnDevices = true; 860 const result = await client.getDeviceList(FAKE_SESSION_TOKEN); 861 Assert.ok(Array.isArray(result)); 862 Assert.equal(result.length, 0); 863 864 try { 865 canReturnDevices = false; 866 await client.getDeviceList(FAKE_SESSION_TOKEN); 867 do_throw("Expected to catch an exception"); 868 } catch (unexpectedError) { 869 Assert.equal(unexpectedError.code, 500); 870 } 871 872 await promiseStopServer(server); 873 }); 874 875 add_task(async function test_client_metrics() { 876 function writeResp(response, msg) { 877 if (typeof msg === "object") { 878 msg = JSON.stringify(msg); 879 } 880 response.bodyOutputStream.write(msg, msg.length); 881 } 882 883 let server = httpd_setup({ 884 "/session/destroy": function (request, response) { 885 response.setHeader("Content-Type", "application/json; charset=utf-8"); 886 response.setStatusLine(request.httpVersion, 401, "Unauthorized"); 887 writeResp(response, { 888 error: "invalid authentication timestamp", 889 code: 401, 890 errno: 111, 891 }); 892 }, 893 }); 894 895 let client = new FxAccountsClient(server.baseURI); 896 897 await Assert.rejects( 898 client.signOut(FAKE_SESSION_TOKEN, { 899 service: "sync", 900 }), 901 function (err) { 902 return err.errno == 111; 903 } 904 ); 905 906 await promiseStopServer(server); 907 }); 908 909 add_task(async function test_email_case() { 910 let canonicalEmail = "greta.garbo@gmail.com"; 911 let clientEmail = "Greta.Garbo@gmail.COM"; 912 let attempts = 0; 913 914 function writeResp(response, msg) { 915 if (typeof msg === "object") { 916 msg = JSON.stringify(msg); 917 } 918 response.bodyOutputStream.write(msg, msg.length); 919 } 920 921 let server = httpd_setup({ 922 "/account/login": function (request, response) { 923 response.setHeader("Content-Type", "application/json; charset=utf-8"); 924 attempts += 1; 925 if (attempts > 2) { 926 response.setStatusLine( 927 request.httpVersion, 928 429, 929 "Sorry, you had your chance" 930 ); 931 return writeResp(response, ""); 932 } 933 934 let body = CommonUtils.readBytesFromInputStream(request.bodyInputStream); 935 let jsonBody = JSON.parse(body); 936 let email = jsonBody.email; 937 938 // If the client has the wrong case on the email, we return a 400, with 939 // the capitalization of the email as saved in the accounts database. 940 if (email == canonicalEmail) { 941 response.setStatusLine(request.httpVersion, 200, "Yay"); 942 return writeResp(response, { areWeHappy: "yes" }); 943 } 944 945 response.setStatusLine(request.httpVersion, 400, "Incorrect email case"); 946 return writeResp(response, { 947 code: 400, 948 errno: 120, 949 error: "Incorrect email case", 950 email: canonicalEmail, 951 }); 952 }, 953 }); 954 955 let client = new FxAccountsClient(server.baseURI); 956 957 let result = await client.signIn(clientEmail, "123456"); 958 Assert.equal(result.areWeHappy, "yes"); 959 Assert.equal(attempts, 2); 960 961 await promiseStopServer(server); 962 }); 963 964 // turn formatted test vectors into normal hex strings 965 function h(hexStr) { 966 return hexStr.replace(/\s+/g, ""); 967 }