michael@0: /* Any copyright is dedicated to the Public Domain. michael@0: * http://creativecommons.org/publicdomain/zero/1.0/ */ michael@0: michael@0: "use strict"; michael@0: michael@0: Cu.import("resource://gre/modules/FxAccountsClient.jsm"); michael@0: Cu.import("resource://gre/modules/Promise.jsm"); michael@0: Cu.import("resource://services-common/utils.js"); michael@0: Cu.import("resource://services-crypto/utils.js"); michael@0: michael@0: const FAKE_SESSION_TOKEN = "a0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebf"; michael@0: michael@0: function run_test() { michael@0: run_next_test(); michael@0: } michael@0: michael@0: // https://wiki.mozilla.org/Identity/AttachedServices/KeyServerProtocol#.2Faccount.2Fkeys michael@0: let ACCOUNT_KEYS = { michael@0: keyFetch: h("8081828384858687 88898a8b8c8d8e8f"+ michael@0: "9091929394959697 98999a9b9c9d9e9f"), michael@0: michael@0: response: h("ee5c58845c7c9412 b11bbd20920c2fdd"+ michael@0: "d83c33c9cd2c2de2 d66b222613364636"+ michael@0: "c2c0f8cfbb7c6304 72c0bd88451342c6"+ michael@0: "c05b14ce342c5ad4 6ad89e84464c993c"+ michael@0: "3927d30230157d08 17a077eef4b20d97"+ michael@0: "6f7a97363faf3f06 4c003ada7d01aa70"), michael@0: michael@0: kA: h("2021222324252627 28292a2b2c2d2e2f"+ michael@0: "3031323334353637 38393a3b3c3d3e3f"), michael@0: michael@0: wrapKB: h("4041424344454647 48494a4b4c4d4e4f"+ michael@0: "5051525354555657 58595a5b5c5d5e5f"), michael@0: }; michael@0: michael@0: // https://github.com/mozilla/fxa-auth-server/wiki/onepw-protocol#wiki-use-session-certificatesign-etc michael@0: let SESSION_KEYS = { michael@0: sessionToken: h("a0a1a2a3a4a5a6a7 a8a9aaabacadaeaf"+ michael@0: "b0b1b2b3b4b5b6b7 b8b9babbbcbdbebf"), michael@0: michael@0: tokenID: h("c0a29dcf46174973 da1378696e4c82ae"+ michael@0: "10f723cf4f4d9f75 e39f4ae3851595ab"), michael@0: michael@0: reqHMACkey: h("9d8f22998ee7f579 8b887042466b72d5"+ michael@0: "3e56ab0c094388bf 65831f702d2febc0"), michael@0: }; michael@0: michael@0: function deferredStop(server) { michael@0: let deferred = Promise.defer(); michael@0: server.stop(deferred.resolve); michael@0: return deferred.promise; michael@0: } michael@0: michael@0: add_task(function test_authenticated_get_request() { michael@0: let message = "{\"msg\": \"Great Success!\"}"; michael@0: let credentials = { michael@0: id: "eyJleHBpcmVzIjogMTM2NTAxMDg5OC4x", michael@0: key: "qTZf4ZFpAMpMoeSsX3zVRjiqmNs=", michael@0: algorithm: "sha256" michael@0: }; michael@0: let method = "GET"; michael@0: michael@0: let server = httpd_setup({"/foo": function(request, response) { michael@0: do_check_true(request.hasHeader("Authorization")); michael@0: michael@0: response.setStatusLine(request.httpVersion, 200, "OK"); michael@0: response.bodyOutputStream.write(message, message.length); michael@0: } michael@0: }); michael@0: michael@0: let client = new FxAccountsClient(server.baseURI); michael@0: michael@0: let result = yield client._request("/foo", method, credentials); michael@0: do_check_eq("Great Success!", result.msg); michael@0: michael@0: yield deferredStop(server); michael@0: }); michael@0: michael@0: add_task(function test_authenticated_post_request() { michael@0: let credentials = { michael@0: id: "eyJleHBpcmVzIjogMTM2NTAxMDg5OC4x", michael@0: key: "qTZf4ZFpAMpMoeSsX3zVRjiqmNs=", michael@0: algorithm: "sha256" michael@0: }; michael@0: let method = "POST"; michael@0: michael@0: let server = httpd_setup({"/foo": function(request, response) { michael@0: do_check_true(request.hasHeader("Authorization")); michael@0: michael@0: response.setStatusLine(request.httpVersion, 200, "OK"); michael@0: response.setHeader("Content-Type", "application/json"); michael@0: response.bodyOutputStream.writeFrom(request.bodyInputStream, request.bodyInputStream.available()); michael@0: } michael@0: }); michael@0: michael@0: let client = new FxAccountsClient(server.baseURI); michael@0: michael@0: let result = yield client._request("/foo", method, credentials, {foo: "bar"}); michael@0: do_check_eq("bar", result.foo); michael@0: michael@0: yield deferredStop(server); michael@0: }); michael@0: michael@0: add_task(function test_500_error() { michael@0: let message = "

Ooops!

"; michael@0: let method = "GET"; michael@0: michael@0: let server = httpd_setup({"/foo": function(request, response) { michael@0: response.setStatusLine(request.httpVersion, 500, "Internal Server Error"); michael@0: response.bodyOutputStream.write(message, message.length); michael@0: } michael@0: }); michael@0: michael@0: let client = new FxAccountsClient(server.baseURI); michael@0: michael@0: try { michael@0: yield client._request("/foo", method); michael@0: do_throw("Expected to catch an exception"); michael@0: } catch (e) { michael@0: do_check_eq(500, e.code); michael@0: do_check_eq("Internal Server Error", e.message); michael@0: } michael@0: michael@0: yield deferredStop(server); michael@0: }); michael@0: michael@0: add_task(function test_backoffError() { michael@0: let method = "GET"; michael@0: let server = httpd_setup({ michael@0: "/retryDelay": function(request, response) { michael@0: response.setHeader("Retry-After", "30"); michael@0: response.setStatusLine(request.httpVersion, 429, "Client has sent too many requests"); michael@0: let message = "

Ooops!

"; michael@0: response.bodyOutputStream.write(message, message.length); michael@0: }, michael@0: "/duringDelayIShouldNotBeCalled": function(request, response) { michael@0: response.setStatusLine(request.httpVersion, 200, "OK"); michael@0: let jsonMessage = "{\"working\": \"yes\"}"; michael@0: response.bodyOutputStream.write(jsonMessage, jsonMessage.length); michael@0: }, michael@0: }); michael@0: michael@0: let client = new FxAccountsClient(server.baseURI); michael@0: michael@0: // Retry-After header sets client.backoffError michael@0: do_check_eq(client.backoffError, null); michael@0: try { michael@0: yield client._request("/retryDelay", method); michael@0: } catch (e) { michael@0: do_check_eq(429, e.code); michael@0: do_check_eq(30, e.retryAfter); michael@0: do_check_neq(typeof(client.fxaBackoffTimer), "undefined"); michael@0: do_check_neq(client.backoffError, null); michael@0: } michael@0: // While delay is in effect, client short-circuits any requests michael@0: // and re-rejects with previous error. michael@0: try { michael@0: yield client._request("/duringDelayIShouldNotBeCalled", method); michael@0: throw new Error("I should not be reached"); michael@0: } catch (e) { michael@0: do_check_eq(e.retryAfter, 30); michael@0: do_check_eq(e.message, "Client has sent too many requests"); michael@0: do_check_neq(client.backoffError, null); michael@0: } michael@0: // Once timer fires, client nulls error out and HTTP calls work again. michael@0: client._clearBackoff(); michael@0: let result = yield client._request("/duringDelayIShouldNotBeCalled", method); michael@0: do_check_eq(client.backoffError, null); michael@0: do_check_eq(result.working, "yes"); michael@0: michael@0: yield deferredStop(server); michael@0: }); michael@0: michael@0: add_task(function test_signUp() { michael@0: let creationMessage = JSON.stringify({ michael@0: uid: "uid", michael@0: sessionToken: "sessionToken", michael@0: keyFetchToken: "keyFetchToken" michael@0: }); michael@0: let errorMessage = JSON.stringify({code: 400, errno: 101, error: "account exists"}); michael@0: let created = false; michael@0: michael@0: let server = httpd_setup({ michael@0: "/account/create": function(request, response) { michael@0: let body = CommonUtils.readBytesFromInputStream(request.bodyInputStream); michael@0: let jsonBody = JSON.parse(body); michael@0: michael@0: // https://github.com/mozilla/fxa-auth-server/wiki/onepw-protocol#wiki-test-vectors michael@0: do_check_eq(jsonBody.email, "andré@example.org"); michael@0: michael@0: if (!created) { michael@0: do_check_eq(jsonBody.authPW, "247b675ffb4c46310bc87e26d712153abe5e1c90ef00a4784594f97ef54f2375"); michael@0: created = true; michael@0: michael@0: response.setStatusLine(request.httpVersion, 200, "OK"); michael@0: response.bodyOutputStream.write(creationMessage, creationMessage.length); michael@0: return; michael@0: } michael@0: michael@0: // Error trying to create same account a second time michael@0: response.setStatusLine(request.httpVersion, 400, "Bad request"); michael@0: response.bodyOutputStream.write(errorMessage, errorMessage.length); michael@0: return; michael@0: }, michael@0: }); michael@0: michael@0: let client = new FxAccountsClient(server.baseURI); michael@0: let result = yield client.signUp('andré@example.org', 'pässwörd'); michael@0: do_check_eq("uid", result.uid); michael@0: do_check_eq("sessionToken", result.sessionToken); michael@0: do_check_eq("keyFetchToken", result.keyFetchToken); michael@0: michael@0: // Try to create account again. Triggers error path. michael@0: try { michael@0: result = yield client.signUp('andré@example.org', 'pässwörd'); michael@0: do_throw("Expected to catch an exception"); michael@0: } catch(expectedError) { michael@0: do_check_eq(101, expectedError.errno); michael@0: } michael@0: michael@0: yield deferredStop(server); michael@0: }); michael@0: michael@0: add_task(function test_signIn() { michael@0: let sessionMessage_noKey = JSON.stringify({ michael@0: sessionToken: FAKE_SESSION_TOKEN michael@0: }); michael@0: let sessionMessage_withKey = JSON.stringify({ michael@0: sessionToken: FAKE_SESSION_TOKEN, michael@0: keyFetchToken: "keyFetchToken" michael@0: }); michael@0: let errorMessage_notExistent = JSON.stringify({ michael@0: code: 400, michael@0: errno: 102, michael@0: error: "doesn't exist" michael@0: }); michael@0: let errorMessage_wrongCap = JSON.stringify({ michael@0: code: 400, michael@0: errno: 120, michael@0: error: "Incorrect email case", michael@0: email: "you@example.com" michael@0: }); michael@0: michael@0: let server = httpd_setup({ michael@0: "/account/login": function(request, response) { michael@0: let body = CommonUtils.readBytesFromInputStream(request.bodyInputStream); michael@0: let jsonBody = JSON.parse(body); michael@0: michael@0: if (jsonBody.email == "mé@example.com") { michael@0: do_check_eq("", request._queryString); michael@0: do_check_eq(jsonBody.authPW, "08b9d111196b8408e8ed92439da49206c8ecfbf343df0ae1ecefcd1e0174a8b6"); michael@0: response.setStatusLine(request.httpVersion, 200, "OK"); michael@0: response.bodyOutputStream.write(sessionMessage_noKey, michael@0: sessionMessage_noKey.length); michael@0: return; michael@0: } michael@0: else if (jsonBody.email == "you@example.com") { michael@0: do_check_eq("keys=true", request._queryString); michael@0: do_check_eq(jsonBody.authPW, "93d20ec50304d496d0707ec20d7e8c89459b6396ec5dd5b9e92809c5e42856c7"); michael@0: response.setStatusLine(request.httpVersion, 200, "OK"); michael@0: response.bodyOutputStream.write(sessionMessage_withKey, michael@0: sessionMessage_withKey.length); michael@0: return; michael@0: } michael@0: else if (jsonBody.email == "You@example.com") { michael@0: // Error trying to sign in with a wrong capitalization michael@0: response.setStatusLine(request.httpVersion, 400, "Bad request"); michael@0: response.bodyOutputStream.write(errorMessage_wrongCap, michael@0: errorMessage_wrongCap.length); michael@0: return; michael@0: } michael@0: else { michael@0: // Error trying to sign in to nonexistent account michael@0: response.setStatusLine(request.httpVersion, 400, "Bad request"); michael@0: response.bodyOutputStream.write(errorMessage_notExistent, michael@0: errorMessage_notExistent.length); michael@0: return; michael@0: } michael@0: }, michael@0: }); michael@0: michael@0: // Login without retrieving optional keys michael@0: let client = new FxAccountsClient(server.baseURI); michael@0: let result = yield client.signIn('mé@example.com', 'bigsecret'); michael@0: do_check_eq(FAKE_SESSION_TOKEN, result.sessionToken); michael@0: do_check_eq(result.unwrapBKey, michael@0: "c076ec3f4af123a615157154c6e1d0d6293e514fd7b0221e32d50517ecf002b8"); michael@0: do_check_eq(undefined, result.keyFetchToken); michael@0: michael@0: // Login with retrieving optional keys michael@0: let result = yield client.signIn('you@example.com', 'bigsecret', true); michael@0: do_check_eq(FAKE_SESSION_TOKEN, result.sessionToken); michael@0: do_check_eq(result.unwrapBKey, michael@0: "65970516211062112e955d6420bebe020269d6b6a91ebd288319fc8d0cb49624"); michael@0: do_check_eq("keyFetchToken", result.keyFetchToken); michael@0: michael@0: // Retry due to wrong email capitalization michael@0: let result = yield client.signIn('You@example.com', 'bigsecret', true); michael@0: do_check_eq(FAKE_SESSION_TOKEN, result.sessionToken); michael@0: do_check_eq(result.unwrapBKey, michael@0: "65970516211062112e955d6420bebe020269d6b6a91ebd288319fc8d0cb49624"); michael@0: do_check_eq("keyFetchToken", result.keyFetchToken); michael@0: michael@0: // Don't retry due to wrong email capitalization michael@0: try { michael@0: let result = yield client.signIn('You@example.com', 'bigsecret', true, false); michael@0: do_throw("Expected to catch an exception"); michael@0: } catch (expectedError) { michael@0: do_check_eq(120, expectedError.errno); michael@0: do_check_eq("you@example.com", expectedError.email); michael@0: } michael@0: michael@0: // Trigger error path michael@0: try { michael@0: result = yield client.signIn("yøü@bad.example.org", "nofear"); michael@0: do_throw("Expected to catch an exception"); michael@0: } catch (expectedError) { michael@0: do_check_eq(102, expectedError.errno); michael@0: } michael@0: michael@0: yield deferredStop(server); michael@0: }); michael@0: michael@0: add_task(function test_signOut() { michael@0: let signoutMessage = JSON.stringify({}); michael@0: let errorMessage = JSON.stringify({code: 400, errno: 102, error: "doesn't exist"}); michael@0: let signedOut = false; michael@0: michael@0: let server = httpd_setup({ michael@0: "/session/destroy": function(request, response) { michael@0: if (!signedOut) { michael@0: signedOut = true; michael@0: do_check_true(request.hasHeader("Authorization")); michael@0: response.setStatusLine(request.httpVersion, 200, "OK"); michael@0: response.bodyOutputStream.write(signoutMessage, signoutMessage.length); michael@0: return; michael@0: } michael@0: michael@0: // Error trying to sign out of nonexistent account michael@0: response.setStatusLine(request.httpVersion, 400, "Bad request"); michael@0: response.bodyOutputStream.write(errorMessage, errorMessage.length); michael@0: return; michael@0: }, michael@0: }); michael@0: michael@0: let client = new FxAccountsClient(server.baseURI); michael@0: let result = yield client.signOut("FakeSession"); michael@0: do_check_eq(typeof result, "object"); michael@0: michael@0: // Trigger error path michael@0: try { michael@0: result = yield client.signOut("FakeSession"); michael@0: do_throw("Expected to catch an exception"); michael@0: } catch(expectedError) { michael@0: do_check_eq(102, expectedError.errno); michael@0: } michael@0: michael@0: yield deferredStop(server); michael@0: }); michael@0: michael@0: add_task(function test_recoveryEmailStatus() { michael@0: let emailStatus = JSON.stringify({verified: true}); michael@0: let errorMessage = JSON.stringify({code: 400, errno: 102, error: "doesn't exist"}); michael@0: let tries = 0; michael@0: michael@0: let server = httpd_setup({ michael@0: "/recovery_email/status": function(request, response) { michael@0: do_check_true(request.hasHeader("Authorization")); michael@0: michael@0: if (tries === 0) { michael@0: tries += 1; michael@0: response.setStatusLine(request.httpVersion, 200, "OK"); michael@0: response.bodyOutputStream.write(emailStatus, emailStatus.length); michael@0: return; michael@0: } michael@0: michael@0: // Second call gets an error trying to query a nonexistent account michael@0: response.setStatusLine(request.httpVersion, 400, "Bad request"); michael@0: response.bodyOutputStream.write(errorMessage, errorMessage.length); michael@0: return; michael@0: }, michael@0: }); michael@0: michael@0: let client = new FxAccountsClient(server.baseURI); michael@0: let result = yield client.recoveryEmailStatus(FAKE_SESSION_TOKEN); michael@0: do_check_eq(result.verified, true); michael@0: michael@0: // Trigger error path michael@0: try { michael@0: result = yield client.recoveryEmailStatus("some bogus session"); michael@0: do_throw("Expected to catch an exception"); michael@0: } catch(expectedError) { michael@0: do_check_eq(102, expectedError.errno); michael@0: } michael@0: michael@0: yield deferredStop(server); michael@0: }); michael@0: michael@0: add_task(function test_resendVerificationEmail() { michael@0: let emptyMessage = "{}"; michael@0: let errorMessage = JSON.stringify({code: 400, errno: 102, error: "doesn't exist"}); michael@0: let tries = 0; michael@0: michael@0: let server = httpd_setup({ michael@0: "/recovery_email/resend_code": function(request, response) { michael@0: do_check_true(request.hasHeader("Authorization")); michael@0: if (tries === 0) { michael@0: tries += 1; michael@0: response.setStatusLine(request.httpVersion, 200, "OK"); michael@0: response.bodyOutputStream.write(emptyMessage, emptyMessage.length); michael@0: return; michael@0: } michael@0: michael@0: // Second call gets an error trying to query a nonexistent account michael@0: response.setStatusLine(request.httpVersion, 400, "Bad request"); michael@0: response.bodyOutputStream.write(errorMessage, errorMessage.length); michael@0: return; michael@0: }, michael@0: }); michael@0: michael@0: let client = new FxAccountsClient(server.baseURI); michael@0: let result = yield client.resendVerificationEmail(FAKE_SESSION_TOKEN); michael@0: do_check_eq(JSON.stringify(result), emptyMessage); michael@0: michael@0: // Trigger error path michael@0: try { michael@0: result = yield client.resendVerificationEmail("some bogus session"); michael@0: do_throw("Expected to catch an exception"); michael@0: } catch(expectedError) { michael@0: do_check_eq(102, expectedError.errno); michael@0: } michael@0: michael@0: yield deferredStop(server); michael@0: }); michael@0: michael@0: add_task(function test_accountKeys() { michael@0: // Four calls to accountKeys(). The first one should work correctly, and we michael@0: // should get a valid bundle back, in exchange for our keyFetch token, from michael@0: // which we correctly derive kA and wrapKB. The subsequent three calls michael@0: // should all trigger separate error paths. michael@0: let responseMessage = JSON.stringify({bundle: ACCOUNT_KEYS.response}); michael@0: let errorMessage = JSON.stringify({code: 400, errno: 102, error: "doesn't exist"}); michael@0: let emptyMessage = "{}"; michael@0: let attempt = 0; michael@0: michael@0: let server = httpd_setup({ michael@0: "/account/keys": function(request, response) { michael@0: do_check_true(request.hasHeader("Authorization")); michael@0: attempt += 1; michael@0: michael@0: switch(attempt) { michael@0: case 1: michael@0: // First time succeeds michael@0: response.setStatusLine(request.httpVersion, 200, "OK"); michael@0: response.bodyOutputStream.write(responseMessage, responseMessage.length); michael@0: break; michael@0: michael@0: case 2: michael@0: // Second time, return no bundle to trigger client error michael@0: response.setStatusLine(request.httpVersion, 200, "OK"); michael@0: response.bodyOutputStream.write(emptyMessage, emptyMessage.length); michael@0: break; michael@0: michael@0: case 3: michael@0: // Return gibberish to trigger client MAC error michael@0: // Tweak a byte michael@0: let garbageResponse = JSON.stringify({ michael@0: bundle: ACCOUNT_KEYS.response.slice(0, -1) + "1" michael@0: }); michael@0: response.setStatusLine(request.httpVersion, 200, "OK"); michael@0: response.bodyOutputStream.write(garbageResponse, garbageResponse.length); michael@0: break; michael@0: michael@0: case 4: michael@0: // Trigger error for nonexistent account michael@0: response.setStatusLine(request.httpVersion, 400, "Bad request"); michael@0: response.bodyOutputStream.write(errorMessage, errorMessage.length); michael@0: break; michael@0: } michael@0: }, michael@0: }); michael@0: michael@0: let client = new FxAccountsClient(server.baseURI); michael@0: michael@0: // First try, all should be good michael@0: let result = yield client.accountKeys(ACCOUNT_KEYS.keyFetch); michael@0: do_check_eq(CommonUtils.hexToBytes(ACCOUNT_KEYS.kA), result.kA); michael@0: do_check_eq(CommonUtils.hexToBytes(ACCOUNT_KEYS.wrapKB), result.wrapKB); michael@0: michael@0: // Second try, empty bundle should trigger error michael@0: try { michael@0: result = yield client.accountKeys(ACCOUNT_KEYS.keyFetch); michael@0: do_throw("Expected to catch an exception"); michael@0: } catch(expectedError) { michael@0: do_check_eq(expectedError.message, "failed to retrieve keys"); michael@0: } michael@0: michael@0: // Third try, bad bundle results in MAC error michael@0: try { michael@0: result = yield client.accountKeys(ACCOUNT_KEYS.keyFetch); michael@0: do_throw("Expected to catch an exception"); michael@0: } catch(expectedError) { michael@0: do_check_eq(expectedError.message, "error unbundling encryption keys"); michael@0: } michael@0: michael@0: // Fourth try, pretend account doesn't exist michael@0: try { michael@0: result = yield client.accountKeys(ACCOUNT_KEYS.keyFetch); michael@0: do_throw("Expected to catch an exception"); michael@0: } catch(expectedError) { michael@0: do_check_eq(102, expectedError.errno); michael@0: } michael@0: michael@0: yield deferredStop(server); michael@0: }); michael@0: michael@0: add_task(function test_signCertificate() { michael@0: let certSignMessage = JSON.stringify({cert: {bar: "baz"}}); michael@0: let errorMessage = JSON.stringify({code: 400, errno: 102, error: "doesn't exist"}); michael@0: let tries = 0; michael@0: michael@0: let server = httpd_setup({ michael@0: "/certificate/sign": function(request, response) { michael@0: do_check_true(request.hasHeader("Authorization")); michael@0: michael@0: if (tries === 0) { michael@0: tries += 1; michael@0: let body = CommonUtils.readBytesFromInputStream(request.bodyInputStream); michael@0: let jsonBody = JSON.parse(body); michael@0: do_check_eq(JSON.parse(jsonBody.publicKey).foo, "bar"); michael@0: do_check_eq(jsonBody.duration, 600); michael@0: response.setStatusLine(request.httpVersion, 200, "OK"); michael@0: response.bodyOutputStream.write(certSignMessage, certSignMessage.length); michael@0: return; michael@0: } michael@0: michael@0: // Second attempt, trigger error michael@0: response.setStatusLine(request.httpVersion, 400, "Bad request"); michael@0: response.bodyOutputStream.write(errorMessage, errorMessage.length); michael@0: return; michael@0: }, michael@0: }); michael@0: michael@0: let client = new FxAccountsClient(server.baseURI); michael@0: let result = yield client.signCertificate(FAKE_SESSION_TOKEN, JSON.stringify({foo: "bar"}), 600); michael@0: do_check_eq("baz", result.bar); michael@0: michael@0: // Account doesn't exist michael@0: try { michael@0: result = yield client.signCertificate("bogus", JSON.stringify({foo: "bar"}), 600); michael@0: do_throw("Expected to catch an exception"); michael@0: } catch(expectedError) { michael@0: do_check_eq(102, expectedError.errno); michael@0: } michael@0: michael@0: yield deferredStop(server); michael@0: }); michael@0: michael@0: add_task(function test_accountExists() { michael@0: let sessionMessage = JSON.stringify({sessionToken: FAKE_SESSION_TOKEN}); michael@0: let existsMessage = JSON.stringify({error: "wrong password", code: 400, errno: 103}); michael@0: let doesntExistMessage = JSON.stringify({error: "no such account", code: 400, errno: 102}); michael@0: let emptyMessage = "{}"; michael@0: michael@0: let server = httpd_setup({ michael@0: "/account/login": function(request, response) { michael@0: let body = CommonUtils.readBytesFromInputStream(request.bodyInputStream); michael@0: let jsonBody = JSON.parse(body); michael@0: michael@0: switch (jsonBody.email) { michael@0: // We'll test that these users' accounts exist michael@0: case "i.exist@example.com": michael@0: case "i.also.exist@example.com": michael@0: response.setStatusLine(request.httpVersion, 400, "Bad request"); michael@0: response.bodyOutputStream.write(existsMessage, existsMessage.length); michael@0: break; michael@0: michael@0: // This user's account doesn't exist michael@0: case "i.dont.exist@example.com": michael@0: response.setStatusLine(request.httpVersion, 400, "Bad request"); michael@0: response.bodyOutputStream.write(doesntExistMessage, doesntExistMessage.length); michael@0: break; michael@0: michael@0: // This user throws an unexpected response michael@0: // This will reject the client signIn promise michael@0: case "i.break.things@example.com": michael@0: response.setStatusLine(request.httpVersion, 500, "Alas"); michael@0: response.bodyOutputStream.write(emptyMessage, emptyMessage.length); michael@0: break; michael@0: michael@0: default: michael@0: throw new Error("Unexpected login from " + jsonBody.email); michael@0: break; michael@0: } michael@0: }, michael@0: }); michael@0: michael@0: let client = new FxAccountsClient(server.baseURI); michael@0: let result; michael@0: michael@0: result = yield client.accountExists("i.exist@example.com"); michael@0: do_check_true(result); michael@0: michael@0: result = yield client.accountExists("i.also.exist@example.com"); michael@0: do_check_true(result); michael@0: michael@0: result = yield client.accountExists("i.dont.exist@example.com"); michael@0: do_check_false(result); michael@0: michael@0: try { michael@0: result = yield client.accountExists("i.break.things@example.com"); michael@0: do_throw("Expected to catch an exception"); michael@0: } catch(unexpectedError) { michael@0: do_check_eq(unexpectedError.code, 500); michael@0: } michael@0: michael@0: yield deferredStop(server); michael@0: }); michael@0: michael@0: add_task(function test_email_case() { michael@0: let canonicalEmail = "greta.garbo@gmail.com"; michael@0: let clientEmail = "Greta.Garbo@gmail.COM"; michael@0: let attempts = 0; michael@0: michael@0: function writeResp(response, msg) { michael@0: if (typeof msg === "object") { michael@0: msg = JSON.stringify(msg); michael@0: } michael@0: response.bodyOutputStream.write(msg, msg.length); michael@0: } michael@0: michael@0: let server = httpd_setup( michael@0: { michael@0: "/account/login": function(request, response) { michael@0: response.setHeader("Content-Type", "application/json; charset=utf-8"); michael@0: attempts += 1; michael@0: if (attempts > 2) { michael@0: response.setStatusLine(request.httpVersion, 429, "Sorry, you had your chance"); michael@0: return writeResp(response, ""); michael@0: } michael@0: michael@0: let body = CommonUtils.readBytesFromInputStream(request.bodyInputStream); michael@0: let jsonBody = JSON.parse(body); michael@0: let email = jsonBody.email; michael@0: michael@0: // If the client has the wrong case on the email, we return a 400, with michael@0: // the capitalization of the email as saved in the accounts database. michael@0: if (email == canonicalEmail) { michael@0: response.setStatusLine(request.httpVersion, 200, "Yay"); michael@0: return writeResp(response, {areWeHappy: "yes"}); michael@0: } michael@0: michael@0: response.setStatusLine(request.httpVersion, 400, "Incorrect email case"); michael@0: return writeResp(response, { michael@0: code: 400, michael@0: errno: 120, michael@0: error: "Incorrect email case", michael@0: email: canonicalEmail michael@0: }); michael@0: }, michael@0: } michael@0: ); michael@0: michael@0: let client = new FxAccountsClient(server.baseURI); michael@0: michael@0: let result = yield client.signIn(clientEmail, "123456"); michael@0: do_check_eq(result.areWeHappy, "yes"); michael@0: do_check_eq(attempts, 2); michael@0: michael@0: yield deferredStop(server); michael@0: }); michael@0: michael@0: add_task(function test__deriveHawkCredentials() { michael@0: let client = new FxAccountsClient("https://example.org"); michael@0: michael@0: let credentials = client._deriveHawkCredentials( michael@0: SESSION_KEYS.sessionToken, "sessionToken"); michael@0: michael@0: do_check_eq(credentials.algorithm, "sha256"); michael@0: do_check_eq(credentials.id, SESSION_KEYS.tokenID); michael@0: do_check_eq(CommonUtils.bytesAsHex(credentials.key), SESSION_KEYS.reqHMACkey); michael@0: }); michael@0: michael@0: // turn formatted test vectors into normal hex strings michael@0: function h(hexStr) { michael@0: return hexStr.replace(/\s+/g, ""); michael@0: }