services/fxaccounts/tests/xpcshell/test_client.js

Thu, 22 Jan 2015 13:21:57 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 22 Jan 2015 13:21:57 +0100
branch
TOR_BUG_9701
changeset 15
b8a032363ba2
permissions
-rw-r--r--

Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6

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

mercurial