1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/services/fxaccounts/tests/xpcshell/test_client.js Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,687 @@ 1.4 +/* Any copyright is dedicated to the Public Domain. 1.5 + * http://creativecommons.org/publicdomain/zero/1.0/ */ 1.6 + 1.7 +"use strict"; 1.8 + 1.9 +Cu.import("resource://gre/modules/FxAccountsClient.jsm"); 1.10 +Cu.import("resource://gre/modules/Promise.jsm"); 1.11 +Cu.import("resource://services-common/utils.js"); 1.12 +Cu.import("resource://services-crypto/utils.js"); 1.13 + 1.14 +const FAKE_SESSION_TOKEN = "a0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebf"; 1.15 + 1.16 +function run_test() { 1.17 + run_next_test(); 1.18 +} 1.19 + 1.20 +// https://wiki.mozilla.org/Identity/AttachedServices/KeyServerProtocol#.2Faccount.2Fkeys 1.21 +let ACCOUNT_KEYS = { 1.22 + keyFetch: h("8081828384858687 88898a8b8c8d8e8f"+ 1.23 + "9091929394959697 98999a9b9c9d9e9f"), 1.24 + 1.25 + response: h("ee5c58845c7c9412 b11bbd20920c2fdd"+ 1.26 + "d83c33c9cd2c2de2 d66b222613364636"+ 1.27 + "c2c0f8cfbb7c6304 72c0bd88451342c6"+ 1.28 + "c05b14ce342c5ad4 6ad89e84464c993c"+ 1.29 + "3927d30230157d08 17a077eef4b20d97"+ 1.30 + "6f7a97363faf3f06 4c003ada7d01aa70"), 1.31 + 1.32 + kA: h("2021222324252627 28292a2b2c2d2e2f"+ 1.33 + "3031323334353637 38393a3b3c3d3e3f"), 1.34 + 1.35 + wrapKB: h("4041424344454647 48494a4b4c4d4e4f"+ 1.36 + "5051525354555657 58595a5b5c5d5e5f"), 1.37 +}; 1.38 + 1.39 +// https://github.com/mozilla/fxa-auth-server/wiki/onepw-protocol#wiki-use-session-certificatesign-etc 1.40 +let SESSION_KEYS = { 1.41 + sessionToken: h("a0a1a2a3a4a5a6a7 a8a9aaabacadaeaf"+ 1.42 + "b0b1b2b3b4b5b6b7 b8b9babbbcbdbebf"), 1.43 + 1.44 + tokenID: h("c0a29dcf46174973 da1378696e4c82ae"+ 1.45 + "10f723cf4f4d9f75 e39f4ae3851595ab"), 1.46 + 1.47 + reqHMACkey: h("9d8f22998ee7f579 8b887042466b72d5"+ 1.48 + "3e56ab0c094388bf 65831f702d2febc0"), 1.49 +}; 1.50 + 1.51 +function deferredStop(server) { 1.52 + let deferred = Promise.defer(); 1.53 + server.stop(deferred.resolve); 1.54 + return deferred.promise; 1.55 +} 1.56 + 1.57 +add_task(function test_authenticated_get_request() { 1.58 + let message = "{\"msg\": \"Great Success!\"}"; 1.59 + let credentials = { 1.60 + id: "eyJleHBpcmVzIjogMTM2NTAxMDg5OC4x", 1.61 + key: "qTZf4ZFpAMpMoeSsX3zVRjiqmNs=", 1.62 + algorithm: "sha256" 1.63 + }; 1.64 + let method = "GET"; 1.65 + 1.66 + let server = httpd_setup({"/foo": function(request, response) { 1.67 + do_check_true(request.hasHeader("Authorization")); 1.68 + 1.69 + response.setStatusLine(request.httpVersion, 200, "OK"); 1.70 + response.bodyOutputStream.write(message, message.length); 1.71 + } 1.72 + }); 1.73 + 1.74 + let client = new FxAccountsClient(server.baseURI); 1.75 + 1.76 + let result = yield client._request("/foo", method, credentials); 1.77 + do_check_eq("Great Success!", result.msg); 1.78 + 1.79 + yield deferredStop(server); 1.80 +}); 1.81 + 1.82 +add_task(function test_authenticated_post_request() { 1.83 + let credentials = { 1.84 + id: "eyJleHBpcmVzIjogMTM2NTAxMDg5OC4x", 1.85 + key: "qTZf4ZFpAMpMoeSsX3zVRjiqmNs=", 1.86 + algorithm: "sha256" 1.87 + }; 1.88 + let method = "POST"; 1.89 + 1.90 + let server = httpd_setup({"/foo": function(request, response) { 1.91 + do_check_true(request.hasHeader("Authorization")); 1.92 + 1.93 + response.setStatusLine(request.httpVersion, 200, "OK"); 1.94 + response.setHeader("Content-Type", "application/json"); 1.95 + response.bodyOutputStream.writeFrom(request.bodyInputStream, request.bodyInputStream.available()); 1.96 + } 1.97 + }); 1.98 + 1.99 + let client = new FxAccountsClient(server.baseURI); 1.100 + 1.101 + let result = yield client._request("/foo", method, credentials, {foo: "bar"}); 1.102 + do_check_eq("bar", result.foo); 1.103 + 1.104 + yield deferredStop(server); 1.105 +}); 1.106 + 1.107 +add_task(function test_500_error() { 1.108 + let message = "<h1>Ooops!</h1>"; 1.109 + let method = "GET"; 1.110 + 1.111 + let server = httpd_setup({"/foo": function(request, response) { 1.112 + response.setStatusLine(request.httpVersion, 500, "Internal Server Error"); 1.113 + response.bodyOutputStream.write(message, message.length); 1.114 + } 1.115 + }); 1.116 + 1.117 + let client = new FxAccountsClient(server.baseURI); 1.118 + 1.119 + try { 1.120 + yield client._request("/foo", method); 1.121 + do_throw("Expected to catch an exception"); 1.122 + } catch (e) { 1.123 + do_check_eq(500, e.code); 1.124 + do_check_eq("Internal Server Error", e.message); 1.125 + } 1.126 + 1.127 + yield deferredStop(server); 1.128 +}); 1.129 + 1.130 +add_task(function test_backoffError() { 1.131 + let method = "GET"; 1.132 + let server = httpd_setup({ 1.133 + "/retryDelay": function(request, response) { 1.134 + response.setHeader("Retry-After", "30"); 1.135 + response.setStatusLine(request.httpVersion, 429, "Client has sent too many requests"); 1.136 + let message = "<h1>Ooops!</h1>"; 1.137 + response.bodyOutputStream.write(message, message.length); 1.138 + }, 1.139 + "/duringDelayIShouldNotBeCalled": function(request, response) { 1.140 + response.setStatusLine(request.httpVersion, 200, "OK"); 1.141 + let jsonMessage = "{\"working\": \"yes\"}"; 1.142 + response.bodyOutputStream.write(jsonMessage, jsonMessage.length); 1.143 + }, 1.144 + }); 1.145 + 1.146 + let client = new FxAccountsClient(server.baseURI); 1.147 + 1.148 + // Retry-After header sets client.backoffError 1.149 + do_check_eq(client.backoffError, null); 1.150 + try { 1.151 + yield client._request("/retryDelay", method); 1.152 + } catch (e) { 1.153 + do_check_eq(429, e.code); 1.154 + do_check_eq(30, e.retryAfter); 1.155 + do_check_neq(typeof(client.fxaBackoffTimer), "undefined"); 1.156 + do_check_neq(client.backoffError, null); 1.157 + } 1.158 + // While delay is in effect, client short-circuits any requests 1.159 + // and re-rejects with previous error. 1.160 + try { 1.161 + yield client._request("/duringDelayIShouldNotBeCalled", method); 1.162 + throw new Error("I should not be reached"); 1.163 + } catch (e) { 1.164 + do_check_eq(e.retryAfter, 30); 1.165 + do_check_eq(e.message, "Client has sent too many requests"); 1.166 + do_check_neq(client.backoffError, null); 1.167 + } 1.168 + // Once timer fires, client nulls error out and HTTP calls work again. 1.169 + client._clearBackoff(); 1.170 + let result = yield client._request("/duringDelayIShouldNotBeCalled", method); 1.171 + do_check_eq(client.backoffError, null); 1.172 + do_check_eq(result.working, "yes"); 1.173 + 1.174 + yield deferredStop(server); 1.175 +}); 1.176 + 1.177 +add_task(function test_signUp() { 1.178 + let creationMessage = JSON.stringify({ 1.179 + uid: "uid", 1.180 + sessionToken: "sessionToken", 1.181 + keyFetchToken: "keyFetchToken" 1.182 + }); 1.183 + let errorMessage = JSON.stringify({code: 400, errno: 101, error: "account exists"}); 1.184 + let created = false; 1.185 + 1.186 + let server = httpd_setup({ 1.187 + "/account/create": function(request, response) { 1.188 + let body = CommonUtils.readBytesFromInputStream(request.bodyInputStream); 1.189 + let jsonBody = JSON.parse(body); 1.190 + 1.191 + // https://github.com/mozilla/fxa-auth-server/wiki/onepw-protocol#wiki-test-vectors 1.192 + do_check_eq(jsonBody.email, "andré@example.org"); 1.193 + 1.194 + if (!created) { 1.195 + do_check_eq(jsonBody.authPW, "247b675ffb4c46310bc87e26d712153abe5e1c90ef00a4784594f97ef54f2375"); 1.196 + created = true; 1.197 + 1.198 + response.setStatusLine(request.httpVersion, 200, "OK"); 1.199 + response.bodyOutputStream.write(creationMessage, creationMessage.length); 1.200 + return; 1.201 + } 1.202 + 1.203 + // Error trying to create same account a second time 1.204 + response.setStatusLine(request.httpVersion, 400, "Bad request"); 1.205 + response.bodyOutputStream.write(errorMessage, errorMessage.length); 1.206 + return; 1.207 + }, 1.208 + }); 1.209 + 1.210 + let client = new FxAccountsClient(server.baseURI); 1.211 + let result = yield client.signUp('andré@example.org', 'pässwörd'); 1.212 + do_check_eq("uid", result.uid); 1.213 + do_check_eq("sessionToken", result.sessionToken); 1.214 + do_check_eq("keyFetchToken", result.keyFetchToken); 1.215 + 1.216 + // Try to create account again. Triggers error path. 1.217 + try { 1.218 + result = yield client.signUp('andré@example.org', 'pässwörd'); 1.219 + do_throw("Expected to catch an exception"); 1.220 + } catch(expectedError) { 1.221 + do_check_eq(101, expectedError.errno); 1.222 + } 1.223 + 1.224 + yield deferredStop(server); 1.225 +}); 1.226 + 1.227 +add_task(function test_signIn() { 1.228 + let sessionMessage_noKey = JSON.stringify({ 1.229 + sessionToken: FAKE_SESSION_TOKEN 1.230 + }); 1.231 + let sessionMessage_withKey = JSON.stringify({ 1.232 + sessionToken: FAKE_SESSION_TOKEN, 1.233 + keyFetchToken: "keyFetchToken" 1.234 + }); 1.235 + let errorMessage_notExistent = JSON.stringify({ 1.236 + code: 400, 1.237 + errno: 102, 1.238 + error: "doesn't exist" 1.239 + }); 1.240 + let errorMessage_wrongCap = JSON.stringify({ 1.241 + code: 400, 1.242 + errno: 120, 1.243 + error: "Incorrect email case", 1.244 + email: "you@example.com" 1.245 + }); 1.246 + 1.247 + let server = httpd_setup({ 1.248 + "/account/login": function(request, response) { 1.249 + let body = CommonUtils.readBytesFromInputStream(request.bodyInputStream); 1.250 + let jsonBody = JSON.parse(body); 1.251 + 1.252 + if (jsonBody.email == "mé@example.com") { 1.253 + do_check_eq("", request._queryString); 1.254 + do_check_eq(jsonBody.authPW, "08b9d111196b8408e8ed92439da49206c8ecfbf343df0ae1ecefcd1e0174a8b6"); 1.255 + response.setStatusLine(request.httpVersion, 200, "OK"); 1.256 + response.bodyOutputStream.write(sessionMessage_noKey, 1.257 + sessionMessage_noKey.length); 1.258 + return; 1.259 + } 1.260 + else if (jsonBody.email == "you@example.com") { 1.261 + do_check_eq("keys=true", request._queryString); 1.262 + do_check_eq(jsonBody.authPW, "93d20ec50304d496d0707ec20d7e8c89459b6396ec5dd5b9e92809c5e42856c7"); 1.263 + response.setStatusLine(request.httpVersion, 200, "OK"); 1.264 + response.bodyOutputStream.write(sessionMessage_withKey, 1.265 + sessionMessage_withKey.length); 1.266 + return; 1.267 + } 1.268 + else if (jsonBody.email == "You@example.com") { 1.269 + // Error trying to sign in with a wrong capitalization 1.270 + response.setStatusLine(request.httpVersion, 400, "Bad request"); 1.271 + response.bodyOutputStream.write(errorMessage_wrongCap, 1.272 + errorMessage_wrongCap.length); 1.273 + return; 1.274 + } 1.275 + else { 1.276 + // Error trying to sign in to nonexistent account 1.277 + response.setStatusLine(request.httpVersion, 400, "Bad request"); 1.278 + response.bodyOutputStream.write(errorMessage_notExistent, 1.279 + errorMessage_notExistent.length); 1.280 + return; 1.281 + } 1.282 + }, 1.283 + }); 1.284 + 1.285 + // Login without retrieving optional keys 1.286 + let client = new FxAccountsClient(server.baseURI); 1.287 + let result = yield client.signIn('mé@example.com', 'bigsecret'); 1.288 + do_check_eq(FAKE_SESSION_TOKEN, result.sessionToken); 1.289 + do_check_eq(result.unwrapBKey, 1.290 + "c076ec3f4af123a615157154c6e1d0d6293e514fd7b0221e32d50517ecf002b8"); 1.291 + do_check_eq(undefined, result.keyFetchToken); 1.292 + 1.293 + // Login with retrieving optional keys 1.294 + let result = yield client.signIn('you@example.com', 'bigsecret', true); 1.295 + do_check_eq(FAKE_SESSION_TOKEN, result.sessionToken); 1.296 + do_check_eq(result.unwrapBKey, 1.297 + "65970516211062112e955d6420bebe020269d6b6a91ebd288319fc8d0cb49624"); 1.298 + do_check_eq("keyFetchToken", result.keyFetchToken); 1.299 + 1.300 + // Retry due to wrong email capitalization 1.301 + let result = yield client.signIn('You@example.com', 'bigsecret', true); 1.302 + do_check_eq(FAKE_SESSION_TOKEN, result.sessionToken); 1.303 + do_check_eq(result.unwrapBKey, 1.304 + "65970516211062112e955d6420bebe020269d6b6a91ebd288319fc8d0cb49624"); 1.305 + do_check_eq("keyFetchToken", result.keyFetchToken); 1.306 + 1.307 + // Don't retry due to wrong email capitalization 1.308 + try { 1.309 + let result = yield client.signIn('You@example.com', 'bigsecret', true, false); 1.310 + do_throw("Expected to catch an exception"); 1.311 + } catch (expectedError) { 1.312 + do_check_eq(120, expectedError.errno); 1.313 + do_check_eq("you@example.com", expectedError.email); 1.314 + } 1.315 + 1.316 + // Trigger error path 1.317 + try { 1.318 + result = yield client.signIn("yøü@bad.example.org", "nofear"); 1.319 + do_throw("Expected to catch an exception"); 1.320 + } catch (expectedError) { 1.321 + do_check_eq(102, expectedError.errno); 1.322 + } 1.323 + 1.324 + yield deferredStop(server); 1.325 +}); 1.326 + 1.327 +add_task(function test_signOut() { 1.328 + let signoutMessage = JSON.stringify({}); 1.329 + let errorMessage = JSON.stringify({code: 400, errno: 102, error: "doesn't exist"}); 1.330 + let signedOut = false; 1.331 + 1.332 + let server = httpd_setup({ 1.333 + "/session/destroy": function(request, response) { 1.334 + if (!signedOut) { 1.335 + signedOut = true; 1.336 + do_check_true(request.hasHeader("Authorization")); 1.337 + response.setStatusLine(request.httpVersion, 200, "OK"); 1.338 + response.bodyOutputStream.write(signoutMessage, signoutMessage.length); 1.339 + return; 1.340 + } 1.341 + 1.342 + // Error trying to sign out of nonexistent account 1.343 + response.setStatusLine(request.httpVersion, 400, "Bad request"); 1.344 + response.bodyOutputStream.write(errorMessage, errorMessage.length); 1.345 + return; 1.346 + }, 1.347 + }); 1.348 + 1.349 + let client = new FxAccountsClient(server.baseURI); 1.350 + let result = yield client.signOut("FakeSession"); 1.351 + do_check_eq(typeof result, "object"); 1.352 + 1.353 + // Trigger error path 1.354 + try { 1.355 + result = yield client.signOut("FakeSession"); 1.356 + do_throw("Expected to catch an exception"); 1.357 + } catch(expectedError) { 1.358 + do_check_eq(102, expectedError.errno); 1.359 + } 1.360 + 1.361 + yield deferredStop(server); 1.362 +}); 1.363 + 1.364 +add_task(function test_recoveryEmailStatus() { 1.365 + let emailStatus = JSON.stringify({verified: true}); 1.366 + let errorMessage = JSON.stringify({code: 400, errno: 102, error: "doesn't exist"}); 1.367 + let tries = 0; 1.368 + 1.369 + let server = httpd_setup({ 1.370 + "/recovery_email/status": function(request, response) { 1.371 + do_check_true(request.hasHeader("Authorization")); 1.372 + 1.373 + if (tries === 0) { 1.374 + tries += 1; 1.375 + response.setStatusLine(request.httpVersion, 200, "OK"); 1.376 + response.bodyOutputStream.write(emailStatus, emailStatus.length); 1.377 + return; 1.378 + } 1.379 + 1.380 + // Second call gets an error trying to query a nonexistent account 1.381 + response.setStatusLine(request.httpVersion, 400, "Bad request"); 1.382 + response.bodyOutputStream.write(errorMessage, errorMessage.length); 1.383 + return; 1.384 + }, 1.385 + }); 1.386 + 1.387 + let client = new FxAccountsClient(server.baseURI); 1.388 + let result = yield client.recoveryEmailStatus(FAKE_SESSION_TOKEN); 1.389 + do_check_eq(result.verified, true); 1.390 + 1.391 + // Trigger error path 1.392 + try { 1.393 + result = yield client.recoveryEmailStatus("some bogus session"); 1.394 + do_throw("Expected to catch an exception"); 1.395 + } catch(expectedError) { 1.396 + do_check_eq(102, expectedError.errno); 1.397 + } 1.398 + 1.399 + yield deferredStop(server); 1.400 +}); 1.401 + 1.402 +add_task(function test_resendVerificationEmail() { 1.403 + let emptyMessage = "{}"; 1.404 + let errorMessage = JSON.stringify({code: 400, errno: 102, error: "doesn't exist"}); 1.405 + let tries = 0; 1.406 + 1.407 + let server = httpd_setup({ 1.408 + "/recovery_email/resend_code": function(request, response) { 1.409 + do_check_true(request.hasHeader("Authorization")); 1.410 + if (tries === 0) { 1.411 + tries += 1; 1.412 + response.setStatusLine(request.httpVersion, 200, "OK"); 1.413 + response.bodyOutputStream.write(emptyMessage, emptyMessage.length); 1.414 + return; 1.415 + } 1.416 + 1.417 + // Second call gets an error trying to query a nonexistent account 1.418 + response.setStatusLine(request.httpVersion, 400, "Bad request"); 1.419 + response.bodyOutputStream.write(errorMessage, errorMessage.length); 1.420 + return; 1.421 + }, 1.422 + }); 1.423 + 1.424 + let client = new FxAccountsClient(server.baseURI); 1.425 + let result = yield client.resendVerificationEmail(FAKE_SESSION_TOKEN); 1.426 + do_check_eq(JSON.stringify(result), emptyMessage); 1.427 + 1.428 + // Trigger error path 1.429 + try { 1.430 + result = yield client.resendVerificationEmail("some bogus session"); 1.431 + do_throw("Expected to catch an exception"); 1.432 + } catch(expectedError) { 1.433 + do_check_eq(102, expectedError.errno); 1.434 + } 1.435 + 1.436 + yield deferredStop(server); 1.437 +}); 1.438 + 1.439 +add_task(function test_accountKeys() { 1.440 + // Four calls to accountKeys(). The first one should work correctly, and we 1.441 + // should get a valid bundle back, in exchange for our keyFetch token, from 1.442 + // which we correctly derive kA and wrapKB. The subsequent three calls 1.443 + // should all trigger separate error paths. 1.444 + let responseMessage = JSON.stringify({bundle: ACCOUNT_KEYS.response}); 1.445 + let errorMessage = JSON.stringify({code: 400, errno: 102, error: "doesn't exist"}); 1.446 + let emptyMessage = "{}"; 1.447 + let attempt = 0; 1.448 + 1.449 + let server = httpd_setup({ 1.450 + "/account/keys": function(request, response) { 1.451 + do_check_true(request.hasHeader("Authorization")); 1.452 + attempt += 1; 1.453 + 1.454 + switch(attempt) { 1.455 + case 1: 1.456 + // First time succeeds 1.457 + response.setStatusLine(request.httpVersion, 200, "OK"); 1.458 + response.bodyOutputStream.write(responseMessage, responseMessage.length); 1.459 + break; 1.460 + 1.461 + case 2: 1.462 + // Second time, return no bundle to trigger client error 1.463 + response.setStatusLine(request.httpVersion, 200, "OK"); 1.464 + response.bodyOutputStream.write(emptyMessage, emptyMessage.length); 1.465 + break; 1.466 + 1.467 + case 3: 1.468 + // Return gibberish to trigger client MAC error 1.469 + // Tweak a byte 1.470 + let garbageResponse = JSON.stringify({ 1.471 + bundle: ACCOUNT_KEYS.response.slice(0, -1) + "1" 1.472 + }); 1.473 + response.setStatusLine(request.httpVersion, 200, "OK"); 1.474 + response.bodyOutputStream.write(garbageResponse, garbageResponse.length); 1.475 + break; 1.476 + 1.477 + case 4: 1.478 + // Trigger error for nonexistent account 1.479 + response.setStatusLine(request.httpVersion, 400, "Bad request"); 1.480 + response.bodyOutputStream.write(errorMessage, errorMessage.length); 1.481 + break; 1.482 + } 1.483 + }, 1.484 + }); 1.485 + 1.486 + let client = new FxAccountsClient(server.baseURI); 1.487 + 1.488 + // First try, all should be good 1.489 + let result = yield client.accountKeys(ACCOUNT_KEYS.keyFetch); 1.490 + do_check_eq(CommonUtils.hexToBytes(ACCOUNT_KEYS.kA), result.kA); 1.491 + do_check_eq(CommonUtils.hexToBytes(ACCOUNT_KEYS.wrapKB), result.wrapKB); 1.492 + 1.493 + // Second try, empty bundle should trigger error 1.494 + try { 1.495 + result = yield client.accountKeys(ACCOUNT_KEYS.keyFetch); 1.496 + do_throw("Expected to catch an exception"); 1.497 + } catch(expectedError) { 1.498 + do_check_eq(expectedError.message, "failed to retrieve keys"); 1.499 + } 1.500 + 1.501 + // Third try, bad bundle results in MAC error 1.502 + try { 1.503 + result = yield client.accountKeys(ACCOUNT_KEYS.keyFetch); 1.504 + do_throw("Expected to catch an exception"); 1.505 + } catch(expectedError) { 1.506 + do_check_eq(expectedError.message, "error unbundling encryption keys"); 1.507 + } 1.508 + 1.509 + // Fourth try, pretend account doesn't exist 1.510 + try { 1.511 + result = yield client.accountKeys(ACCOUNT_KEYS.keyFetch); 1.512 + do_throw("Expected to catch an exception"); 1.513 + } catch(expectedError) { 1.514 + do_check_eq(102, expectedError.errno); 1.515 + } 1.516 + 1.517 + yield deferredStop(server); 1.518 +}); 1.519 + 1.520 +add_task(function test_signCertificate() { 1.521 + let certSignMessage = JSON.stringify({cert: {bar: "baz"}}); 1.522 + let errorMessage = JSON.stringify({code: 400, errno: 102, error: "doesn't exist"}); 1.523 + let tries = 0; 1.524 + 1.525 + let server = httpd_setup({ 1.526 + "/certificate/sign": function(request, response) { 1.527 + do_check_true(request.hasHeader("Authorization")); 1.528 + 1.529 + if (tries === 0) { 1.530 + tries += 1; 1.531 + let body = CommonUtils.readBytesFromInputStream(request.bodyInputStream); 1.532 + let jsonBody = JSON.parse(body); 1.533 + do_check_eq(JSON.parse(jsonBody.publicKey).foo, "bar"); 1.534 + do_check_eq(jsonBody.duration, 600); 1.535 + response.setStatusLine(request.httpVersion, 200, "OK"); 1.536 + response.bodyOutputStream.write(certSignMessage, certSignMessage.length); 1.537 + return; 1.538 + } 1.539 + 1.540 + // Second attempt, trigger error 1.541 + response.setStatusLine(request.httpVersion, 400, "Bad request"); 1.542 + response.bodyOutputStream.write(errorMessage, errorMessage.length); 1.543 + return; 1.544 + }, 1.545 + }); 1.546 + 1.547 + let client = new FxAccountsClient(server.baseURI); 1.548 + let result = yield client.signCertificate(FAKE_SESSION_TOKEN, JSON.stringify({foo: "bar"}), 600); 1.549 + do_check_eq("baz", result.bar); 1.550 + 1.551 + // Account doesn't exist 1.552 + try { 1.553 + result = yield client.signCertificate("bogus", JSON.stringify({foo: "bar"}), 600); 1.554 + do_throw("Expected to catch an exception"); 1.555 + } catch(expectedError) { 1.556 + do_check_eq(102, expectedError.errno); 1.557 + } 1.558 + 1.559 + yield deferredStop(server); 1.560 +}); 1.561 + 1.562 +add_task(function test_accountExists() { 1.563 + let sessionMessage = JSON.stringify({sessionToken: FAKE_SESSION_TOKEN}); 1.564 + let existsMessage = JSON.stringify({error: "wrong password", code: 400, errno: 103}); 1.565 + let doesntExistMessage = JSON.stringify({error: "no such account", code: 400, errno: 102}); 1.566 + let emptyMessage = "{}"; 1.567 + 1.568 + let server = httpd_setup({ 1.569 + "/account/login": function(request, response) { 1.570 + let body = CommonUtils.readBytesFromInputStream(request.bodyInputStream); 1.571 + let jsonBody = JSON.parse(body); 1.572 + 1.573 + switch (jsonBody.email) { 1.574 + // We'll test that these users' accounts exist 1.575 + case "i.exist@example.com": 1.576 + case "i.also.exist@example.com": 1.577 + response.setStatusLine(request.httpVersion, 400, "Bad request"); 1.578 + response.bodyOutputStream.write(existsMessage, existsMessage.length); 1.579 + break; 1.580 + 1.581 + // This user's account doesn't exist 1.582 + case "i.dont.exist@example.com": 1.583 + response.setStatusLine(request.httpVersion, 400, "Bad request"); 1.584 + response.bodyOutputStream.write(doesntExistMessage, doesntExistMessage.length); 1.585 + break; 1.586 + 1.587 + // This user throws an unexpected response 1.588 + // This will reject the client signIn promise 1.589 + case "i.break.things@example.com": 1.590 + response.setStatusLine(request.httpVersion, 500, "Alas"); 1.591 + response.bodyOutputStream.write(emptyMessage, emptyMessage.length); 1.592 + break; 1.593 + 1.594 + default: 1.595 + throw new Error("Unexpected login from " + jsonBody.email); 1.596 + break; 1.597 + } 1.598 + }, 1.599 + }); 1.600 + 1.601 + let client = new FxAccountsClient(server.baseURI); 1.602 + let result; 1.603 + 1.604 + result = yield client.accountExists("i.exist@example.com"); 1.605 + do_check_true(result); 1.606 + 1.607 + result = yield client.accountExists("i.also.exist@example.com"); 1.608 + do_check_true(result); 1.609 + 1.610 + result = yield client.accountExists("i.dont.exist@example.com"); 1.611 + do_check_false(result); 1.612 + 1.613 + try { 1.614 + result = yield client.accountExists("i.break.things@example.com"); 1.615 + do_throw("Expected to catch an exception"); 1.616 + } catch(unexpectedError) { 1.617 + do_check_eq(unexpectedError.code, 500); 1.618 + } 1.619 + 1.620 + yield deferredStop(server); 1.621 +}); 1.622 + 1.623 +add_task(function test_email_case() { 1.624 + let canonicalEmail = "greta.garbo@gmail.com"; 1.625 + let clientEmail = "Greta.Garbo@gmail.COM"; 1.626 + let attempts = 0; 1.627 + 1.628 + function writeResp(response, msg) { 1.629 + if (typeof msg === "object") { 1.630 + msg = JSON.stringify(msg); 1.631 + } 1.632 + response.bodyOutputStream.write(msg, msg.length); 1.633 + } 1.634 + 1.635 + let server = httpd_setup( 1.636 + { 1.637 + "/account/login": function(request, response) { 1.638 + response.setHeader("Content-Type", "application/json; charset=utf-8"); 1.639 + attempts += 1; 1.640 + if (attempts > 2) { 1.641 + response.setStatusLine(request.httpVersion, 429, "Sorry, you had your chance"); 1.642 + return writeResp(response, ""); 1.643 + } 1.644 + 1.645 + let body = CommonUtils.readBytesFromInputStream(request.bodyInputStream); 1.646 + let jsonBody = JSON.parse(body); 1.647 + let email = jsonBody.email; 1.648 + 1.649 + // If the client has the wrong case on the email, we return a 400, with 1.650 + // the capitalization of the email as saved in the accounts database. 1.651 + if (email == canonicalEmail) { 1.652 + response.setStatusLine(request.httpVersion, 200, "Yay"); 1.653 + return writeResp(response, {areWeHappy: "yes"}); 1.654 + } 1.655 + 1.656 + response.setStatusLine(request.httpVersion, 400, "Incorrect email case"); 1.657 + return writeResp(response, { 1.658 + code: 400, 1.659 + errno: 120, 1.660 + error: "Incorrect email case", 1.661 + email: canonicalEmail 1.662 + }); 1.663 + }, 1.664 + } 1.665 + ); 1.666 + 1.667 + let client = new FxAccountsClient(server.baseURI); 1.668 + 1.669 + let result = yield client.signIn(clientEmail, "123456"); 1.670 + do_check_eq(result.areWeHappy, "yes"); 1.671 + do_check_eq(attempts, 2); 1.672 + 1.673 + yield deferredStop(server); 1.674 +}); 1.675 + 1.676 +add_task(function test__deriveHawkCredentials() { 1.677 + let client = new FxAccountsClient("https://example.org"); 1.678 + 1.679 + let credentials = client._deriveHawkCredentials( 1.680 + SESSION_KEYS.sessionToken, "sessionToken"); 1.681 + 1.682 + do_check_eq(credentials.algorithm, "sha256"); 1.683 + do_check_eq(credentials.id, SESSION_KEYS.tokenID); 1.684 + do_check_eq(CommonUtils.bytesAsHex(credentials.key), SESSION_KEYS.reqHMACkey); 1.685 +}); 1.686 + 1.687 +// turn formatted test vectors into normal hex strings 1.688 +function h(hexStr) { 1.689 + return hexStr.replace(/\s+/g, ""); 1.690 +}