PageRenderTime 26ms CodeModel.GetById 34ms RepoModel.GetById 1ms app.codeStats 0ms

/packages/accounts-password/password_tests.js

https://github.com/zszbys/meteor
JavaScript | 934 lines | 774 code | 71 blank | 89 comment | 7 complexity | 63a0c7ea1131b29c69cfb90d7769d33d MD5 | raw file
  1. Accounts._noConnectionCloseDelayForTest = true;
  2. if (Meteor.isServer) {
  3. Meteor.methods({
  4. getUserId: function () {
  5. return this.userId;
  6. }
  7. });
  8. }
  9. if (Meteor.isClient) (function () {
  10. // XXX note, only one test can do login/logout things at once! for
  11. // now, that is this test.
  12. Accounts._isolateLoginTokenForTest();
  13. var logoutStep = function (test, expect) {
  14. Meteor.logout(expect(function (error) {
  15. test.equal(error, undefined);
  16. test.equal(Meteor.user(), null);
  17. }));
  18. };
  19. var loggedInAs = function (someUsername, test, expect) {
  20. return expect(function (error) {
  21. test.equal(error, undefined);
  22. test.equal(Meteor.user().username, someUsername);
  23. });
  24. };
  25. var waitForLoggedOutStep = function (test, expect) {
  26. pollUntil(expect, function () {
  27. return Meteor.userId() === null;
  28. }, 10 * 1000, 100);
  29. };
  30. var invalidateLoginsStep = function (test, expect) {
  31. Meteor.call("testInvalidateLogins", 'fail', expect(function (error) {
  32. test.isFalse(error);
  33. }));
  34. };
  35. var hideActualLoginErrorStep = function (test, expect) {
  36. Meteor.call("testInvalidateLogins", 'hide', expect(function (error) {
  37. test.isFalse(error);
  38. }));
  39. };
  40. var validateLoginsStep = function (test, expect) {
  41. Meteor.call("testInvalidateLogins", false, expect(function (error) {
  42. test.isFalse(error);
  43. }));
  44. };
  45. testAsyncMulti("passwords - basic login with password", [
  46. function (test, expect) {
  47. // setup
  48. this.username = Random.id();
  49. this.email = Random.id() + '-intercept@example.com';
  50. this.password = 'password';
  51. Accounts.createUser(
  52. {username: this.username, email: this.email, password: this.password},
  53. loggedInAs(this.username, test, expect));
  54. },
  55. function (test, expect) {
  56. test.notEqual(Meteor.userId(), null);
  57. },
  58. logoutStep,
  59. function (test, expect) {
  60. Meteor.loginWithPassword(this.username, this.password,
  61. loggedInAs(this.username, test, expect));
  62. },
  63. logoutStep,
  64. // This next step tests reactive contexts which are reactive on
  65. // Meteor.user().
  66. function (test, expect) {
  67. // Set up a reactive context that only refreshes when Meteor.user() is
  68. // invalidated.
  69. var loaded = false;
  70. var handle = Deps.autorun(function () {
  71. if (Meteor.user() && Meteor.user().emails)
  72. loaded = true;
  73. });
  74. // At the beginning, we're not logged in.
  75. test.isFalse(loaded);
  76. Meteor.loginWithPassword(this.username, this.password, expect(function (error) {
  77. test.equal(error, undefined);
  78. test.notEqual(Meteor.userId(), null);
  79. // By the time of the login callback, the user should be loaded.
  80. test.isTrue(Meteor.user().emails);
  81. // Flushing should get us the rerun as well.
  82. Deps.flush();
  83. test.isTrue(loaded);
  84. handle.stop();
  85. }));
  86. },
  87. logoutStep,
  88. function (test, expect) {
  89. Meteor.loginWithPassword({username: this.username}, this.password,
  90. loggedInAs(this.username, test, expect));
  91. },
  92. logoutStep,
  93. function (test, expect) {
  94. Meteor.loginWithPassword(this.email, this.password,
  95. loggedInAs(this.username, test, expect));
  96. },
  97. logoutStep,
  98. function (test, expect) {
  99. Meteor.loginWithPassword({email: this.email}, this.password,
  100. loggedInAs(this.username, test, expect));
  101. },
  102. logoutStep
  103. ]);
  104. testAsyncMulti("passwords - plain text passwords", [
  105. function (test, expect) {
  106. // setup
  107. this.username = Random.id();
  108. this.email = Random.id() + '-intercept@example.com';
  109. this.password = 'password';
  110. // create user with raw password (no API, need to invoke callLoginMethod
  111. // directly)
  112. Accounts.callLoginMethod({
  113. methodName: 'createUser',
  114. methodArguments: [{username: this.username, password: this.password}],
  115. userCallback: loggedInAs(this.username, test, expect)
  116. });
  117. },
  118. logoutStep,
  119. // check can login normally with this password.
  120. function(test, expect) {
  121. Meteor.loginWithPassword({username: this.username}, this.password,
  122. loggedInAs(this.username, test, expect));
  123. },
  124. logoutStep,
  125. // plain text password. no API for this, have to invoke callLoginMethod
  126. // directly.
  127. function (test, expect) {
  128. Accounts.callLoginMethod({
  129. // wrong password
  130. methodArguments: [{user: {username: this.username}, password: 'wrong'}],
  131. userCallback: expect(function (error) {
  132. test.isTrue(error);
  133. test.isFalse(Meteor.user());
  134. })});
  135. },
  136. function (test, expect) {
  137. Accounts.callLoginMethod({
  138. // right password
  139. methodArguments: [{user: {username: this.username},
  140. password: this.password}],
  141. userCallback: loggedInAs(this.username, test, expect)
  142. });
  143. },
  144. logoutStep
  145. ]);
  146. testAsyncMulti("passwords - changing passwords", [
  147. function (test, expect) {
  148. // setup
  149. this.username = Random.id();
  150. this.email = Random.id() + '-intercept@example.com';
  151. this.password = 'password';
  152. this.password2 = 'password2';
  153. Accounts.createUser(
  154. {username: this.username, email: this.email, password: this.password},
  155. loggedInAs(this.username, test, expect));
  156. },
  157. // change password with bad old password. we stay logged in.
  158. function (test, expect) {
  159. var self = this;
  160. Accounts.changePassword('wrong', 'doesntmatter', expect(function (error) {
  161. test.isTrue(error);
  162. test.equal(Meteor.user().username, self.username);
  163. }));
  164. },
  165. // change password with good old password.
  166. function (test, expect) {
  167. Accounts.changePassword(this.password, this.password2,
  168. loggedInAs(this.username, test, expect));
  169. },
  170. logoutStep,
  171. // old password, failed login
  172. function (test, expect) {
  173. Meteor.loginWithPassword(this.email, this.password, expect(function (error) {
  174. test.isTrue(error);
  175. test.isFalse(Meteor.user());
  176. }));
  177. },
  178. // new password, success
  179. function (test, expect) {
  180. Meteor.loginWithPassword(this.email, this.password2,
  181. loggedInAs(this.username, test, expect));
  182. },
  183. logoutStep
  184. ]);
  185. testAsyncMulti("passwords - changing password logs out other clients", [
  186. function (test, expect) {
  187. this.username = Random.id();
  188. this.email = Random.id() + '-intercept@example.com';
  189. this.password = 'password';
  190. this.password2 = 'password2';
  191. Accounts.createUser(
  192. { username: this.username, email: this.email, password: this.password },
  193. loggedInAs(this.username, test, expect));
  194. },
  195. // Log in a second connection as this user.
  196. function (test, expect) {
  197. var self = this;
  198. self.secondConn = DDP.connect(Meteor.absoluteUrl());
  199. self.secondConn.call('login',
  200. { user: { username: self.username }, password: self.password },
  201. expect(function (err, result) {
  202. test.isFalse(err);
  203. self.secondConn.setUserId(result.id);
  204. test.isTrue(self.secondConn.userId());
  205. self.secondConn.onReconnect = function () {
  206. self.secondConn.apply(
  207. 'login',
  208. [{ resume: result.token }],
  209. { wait: true },
  210. function (err, result) {
  211. self.secondConn.setUserId(result && result.id || null);
  212. }
  213. );
  214. };
  215. }));
  216. },
  217. function (test, expect) {
  218. var self = this;
  219. Accounts.changePassword(self.password, self.password2, expect(function (err) {
  220. test.isFalse(err);
  221. }));
  222. },
  223. // Now that we've changed the password, wait until the second
  224. // connection gets logged out.
  225. function (test, expect) {
  226. var self = this;
  227. pollUntil(expect, function () {
  228. return self.secondConn.userId() === null;
  229. }, 10 * 1000, 100);
  230. }
  231. ]);
  232. testAsyncMulti("passwords - new user hooks", [
  233. function (test, expect) {
  234. // setup
  235. this.username = Random.id();
  236. this.email = Random.id() + '-intercept@example.com';
  237. this.password = 'password';
  238. },
  239. // test Accounts.validateNewUser
  240. function(test, expect) {
  241. Accounts.createUser(
  242. {username: this.username, password: this.password,
  243. // should fail the new user validators
  244. profile: {invalid: true}},
  245. expect(function (error) {
  246. test.equal(error.error, 403);
  247. test.equal(error.reason, "User validation failed");
  248. }));
  249. },
  250. logoutStep,
  251. function(test, expect) {
  252. Accounts.createUser(
  253. {username: this.username, password: this.password,
  254. // should fail the new user validator with a special
  255. // exception
  256. profile: {invalidAndThrowException: true}},
  257. expect(function (error) {
  258. test.equal(
  259. error.reason,
  260. "An exception thrown within Accounts.validateNewUser");
  261. }));
  262. },
  263. // test Accounts.onCreateUser
  264. function(test, expect) {
  265. Accounts.createUser(
  266. {username: this.username, password: this.password,
  267. testOnCreateUserHook: true},
  268. loggedInAs(this.username, test, expect));
  269. },
  270. function(test, expect) {
  271. test.equal(Meteor.user().profile.touchedByOnCreateUser, true);
  272. },
  273. logoutStep
  274. ]);
  275. testAsyncMulti("passwords - Meteor.user()", [
  276. function (test, expect) {
  277. // setup
  278. this.username = Random.id();
  279. this.password = 'password';
  280. Accounts.createUser(
  281. {username: this.username, password: this.password,
  282. testOnCreateUserHook: true},
  283. loggedInAs(this.username, test, expect));
  284. },
  285. // test Meteor.user(). This test properly belongs in
  286. // accounts-base/accounts_tests.js, but this is where the tests that
  287. // actually log in are.
  288. function(test, expect) {
  289. var self = this;
  290. var clientUser = Meteor.user();
  291. Accounts.connection.call('testMeteorUser', expect(function (err, result) {
  292. test.equal(result._id, clientUser._id);
  293. test.equal(result.username, clientUser.username);
  294. test.equal(result.username, self.username);
  295. test.equal(result.profile.touchedByOnCreateUser, true);
  296. test.equal(err, undefined);
  297. }));
  298. },
  299. function(test, expect) {
  300. // Test that even with no published fields, we still have a document.
  301. Accounts.connection.call('clearUsernameAndProfile', expect(function() {
  302. test.isTrue(Meteor.userId());
  303. var user = Meteor.user();
  304. test.equal(user, {_id: Meteor.userId()});
  305. }));
  306. },
  307. logoutStep,
  308. function(test, expect) {
  309. var clientUser = Meteor.user();
  310. test.equal(clientUser, null);
  311. test.equal(Meteor.userId(), null);
  312. Accounts.connection.call('testMeteorUser', expect(function (err, result) {
  313. test.equal(err, undefined);
  314. test.equal(result, null);
  315. }));
  316. }
  317. ]);
  318. testAsyncMulti("passwords - allow rules", [
  319. // create a second user to have an id for in a later test
  320. function (test, expect) {
  321. this.otherUsername = Random.id();
  322. Accounts.createUser(
  323. {username: this.otherUsername, password: 'dontcare',
  324. testOnCreateUserHook: true},
  325. loggedInAs(this.otherUsername, test, expect));
  326. },
  327. function (test, expect) {
  328. this.otherUserId = Meteor.userId();
  329. },
  330. function (test, expect) {
  331. // real setup
  332. this.username = Random.id();
  333. this.password = 'password';
  334. Accounts.createUser(
  335. {username: this.username, password: this.password,
  336. testOnCreateUserHook: true},
  337. loggedInAs(this.username, test, expect));
  338. },
  339. // test the default Meteor.users allow rule. This test properly belongs in
  340. // accounts-base/accounts_tests.js, but this is where the tests that
  341. // actually log in are.
  342. function(test, expect) {
  343. this.userId = Meteor.userId();
  344. test.notEqual(this.userId, null);
  345. test.notEqual(this.userId, this.otherUserId);
  346. // Can't update fields other than profile.
  347. Meteor.users.update(
  348. this.userId, {$set: {disallowed: true, 'profile.updated': 42}},
  349. expect(function (err) {
  350. test.isTrue(err);
  351. test.equal(err.error, 403);
  352. test.isFalse(_.has(Meteor.user(), 'disallowed'));
  353. test.isFalse(_.has(Meteor.user().profile, 'updated'));
  354. }));
  355. },
  356. function(test, expect) {
  357. // Can't update another user.
  358. Meteor.users.update(
  359. this.otherUserId, {$set: {'profile.updated': 42}},
  360. expect(function (err) {
  361. test.isTrue(err);
  362. test.equal(err.error, 403);
  363. }));
  364. },
  365. function(test, expect) {
  366. // Can't update using a non-ID selector. (This one is thrown client-side.)
  367. test.throws(function () {
  368. Meteor.users.update(
  369. {username: this.username}, {$set: {'profile.updated': 42}});
  370. });
  371. test.isFalse(_.has(Meteor.user().profile, 'updated'));
  372. },
  373. function(test, expect) {
  374. // Can update own profile using ID.
  375. Meteor.users.update(
  376. this.userId, {$set: {'profile.updated': 42}},
  377. expect(function (err) {
  378. test.isFalse(err);
  379. test.equal(42, Meteor.user().profile.updated);
  380. }));
  381. },
  382. logoutStep
  383. ]);
  384. testAsyncMulti("passwords - tokens", [
  385. function (test, expect) {
  386. // setup
  387. this.username = Random.id();
  388. this.password = 'password';
  389. Accounts.createUser(
  390. {username: this.username, password: this.password},
  391. loggedInAs(this.username, test, expect));
  392. },
  393. function (test, expect) {
  394. // we can't login with an invalid token
  395. var expectLoginError = expect(function (err) {
  396. test.isTrue(err);
  397. });
  398. Meteor.loginWithToken('invalid', expectLoginError);
  399. },
  400. function (test, expect) {
  401. // we can login with a valid token
  402. var expectLoginOK = expect(function (err) {
  403. test.isFalse(err);
  404. });
  405. Meteor.loginWithToken(Accounts._storedLoginToken(), expectLoginOK);
  406. },
  407. function (test, expect) {
  408. // test logging out invalidates our token
  409. var expectLoginError = expect(function (err) {
  410. test.isTrue(err);
  411. });
  412. var token = Accounts._storedLoginToken();
  413. test.isTrue(token);
  414. Meteor.logout(function () {
  415. Meteor.loginWithToken(token, expectLoginError);
  416. });
  417. },
  418. function (test, expect) {
  419. var self = this;
  420. // Test that login tokens get expired. We should get logged out when a
  421. // token expires, and not be able to log in again with the same token.
  422. var expectNoError = expect(function (err) {
  423. test.isFalse(err);
  424. });
  425. Meteor.loginWithPassword(this.username, this.password, function (error) {
  426. self.token = Accounts._storedLoginToken();
  427. test.isTrue(self.token);
  428. expectNoError(error);
  429. Accounts.connection.call("expireTokens");
  430. });
  431. },
  432. waitForLoggedOutStep,
  433. function (test, expect) {
  434. var token = Accounts._storedLoginToken();
  435. test.isFalse(token);
  436. },
  437. function (test, expect) {
  438. // Test that once expireTokens is finished, we can't login again with our
  439. // previous token.
  440. Meteor.loginWithToken(this.token, expect(function (err, result) {
  441. test.isTrue(err);
  442. test.equal(Meteor.userId(), null);
  443. }));
  444. },
  445. logoutStep,
  446. function (test, expect) {
  447. var self = this;
  448. // Test that Meteor.logoutOtherClients logs out a second
  449. // authentcated connection while leaving Accounts.connection
  450. // logged in.
  451. var secondConn = DDP.connect(Meteor.absoluteUrl());
  452. var token;
  453. var expectSecondConnLoggedOut = expect(function (err, result) {
  454. test.isTrue(err);
  455. });
  456. var expectAccountsConnLoggedIn = expect(function (err, result) {
  457. test.isFalse(err);
  458. });
  459. var expectSecondConnLoggedIn = expect(function (err, result) {
  460. test.equal(result.token, token);
  461. test.isFalse(err);
  462. Meteor.logoutOtherClients(function (err) {
  463. test.isFalse(err);
  464. secondConn.call('login', { resume: token },
  465. expectSecondConnLoggedOut);
  466. Accounts.connection.call('login', {
  467. resume: Accounts._storedLoginToken()
  468. }, expectAccountsConnLoggedIn);
  469. });
  470. });
  471. Meteor.loginWithPassword(
  472. self.username,
  473. self.password,
  474. expect(function (err) {
  475. test.isFalse(err);
  476. token = Accounts._storedLoginToken();
  477. test.isTrue(token);
  478. secondConn.call('login', { resume: token },
  479. expectSecondConnLoggedIn);
  480. })
  481. );
  482. },
  483. logoutStep,
  484. // The tests below this point are for the deprecated
  485. // `logoutOtherClients` method.
  486. function (test, expect) {
  487. var self = this;
  488. // Test that Meteor.logoutOtherClients logs out a second authenticated
  489. // connection while leaving Accounts.connection logged in.
  490. var token;
  491. self.secondConn = DDP.connect(Meteor.absoluteUrl());
  492. var expectLoginError = expect(function (err) {
  493. test.isTrue(err);
  494. });
  495. var expectValidToken = expect(function (err, result) {
  496. test.isFalse(err);
  497. test.isTrue(result);
  498. self.tokenFromLogoutOthers = result.token;
  499. });
  500. var expectSecondConnLoggedIn = expect(function (err, result) {
  501. test.equal(result.token, token);
  502. test.isFalse(err);
  503. // This test will fail if an unrelated reconnect triggers before the
  504. // connection is logged out. In general our tests aren't resilient to
  505. // mid-test reconnects.
  506. self.secondConn.onReconnect = function () {
  507. self.secondConn.call("login", { resume: token }, expectLoginError);
  508. };
  509. Accounts.connection.call("logoutOtherClients", expectValidToken);
  510. });
  511. Meteor.loginWithPassword(this.username, this.password, expect(function (err) {
  512. test.isFalse(err);
  513. token = Accounts._storedLoginToken();
  514. self.beforeLogoutOthersToken = token;
  515. test.isTrue(token);
  516. self.secondConn.call("login", { resume: token },
  517. expectSecondConnLoggedIn);
  518. }));
  519. },
  520. // Test that logoutOtherClients logged out Accounts.connection and that the
  521. // previous token is no longer valid.
  522. waitForLoggedOutStep,
  523. function (test, expect) {
  524. var self = this;
  525. var token = Accounts._storedLoginToken();
  526. test.isFalse(token);
  527. this.secondConn.close();
  528. Meteor.loginWithToken(
  529. self.beforeLogoutOthersToken,
  530. expect(function (err) {
  531. test.isTrue(err);
  532. test.isFalse(Meteor.userId());
  533. })
  534. );
  535. },
  536. // Test that logoutOtherClients returned a new token that we can use to
  537. // log in.
  538. function (test, expect) {
  539. var self = this;
  540. Meteor.loginWithToken(
  541. self.tokenFromLogoutOthers,
  542. expect(function (err) {
  543. test.isFalse(err);
  544. test.isTrue(Meteor.userId());
  545. })
  546. );
  547. },
  548. logoutStep,
  549. function (test, expect) {
  550. var self = this;
  551. // Test that deleting a user logs out that user's connections.
  552. Meteor.loginWithPassword(this.username, this.password, expect(function (err) {
  553. test.isFalse(err);
  554. Accounts.connection.call("removeUser", self.username);
  555. }));
  556. },
  557. waitForLoggedOutStep
  558. ]);
  559. testAsyncMulti("passwords - validateLoginAttempt", [
  560. function (test, expect) {
  561. this.username = Random.id();
  562. this.password = "password";
  563. Accounts.createUser(
  564. {username: this.username, password: this.password},
  565. loggedInAs(this.username, test, expect));
  566. },
  567. logoutStep,
  568. invalidateLoginsStep,
  569. function (test, expect) {
  570. Meteor.loginWithPassword(
  571. this.username,
  572. this.password,
  573. expect(function (error) {
  574. test.isTrue(error);
  575. test.equal(error.reason, "Login forbidden");
  576. })
  577. );
  578. },
  579. validateLoginsStep,
  580. function (test, expect) {
  581. Meteor.loginWithPassword(
  582. "no such user",
  583. "some password",
  584. expect(function (error) {
  585. test.isTrue(error);
  586. test.equal(error.reason, 'User not found');
  587. })
  588. );
  589. },
  590. hideActualLoginErrorStep,
  591. function (test, expect) {
  592. Meteor.loginWithPassword(
  593. "no such user",
  594. "some password",
  595. expect(function (error) {
  596. test.isTrue(error);
  597. test.equal(error.reason, 'hide actual error');
  598. })
  599. );
  600. },
  601. validateLoginsStep
  602. ]);
  603. testAsyncMulti("passwords - onLogin hook", [
  604. function (test, expect) {
  605. Meteor.call("testCaptureLogins", expect(function (error) {
  606. test.isFalse(error);
  607. }));
  608. },
  609. function (test, expect) {
  610. this.username = Random.id();
  611. this.password = "password";
  612. Accounts.createUser(
  613. {username: this.username, password: this.password},
  614. loggedInAs(this.username, test, expect));
  615. },
  616. function (test, expect) {
  617. var self = this;
  618. Meteor.call("testFetchCapturedLogins", expect(function (error, logins) {
  619. test.isFalse(error);
  620. test.equal(logins.length, 1);
  621. var login = logins[0];
  622. test.isTrue(login.successful);
  623. var attempt = login.attempt;
  624. test.equal(attempt.type, "password");
  625. test.isTrue(attempt.allowed);
  626. test.equal(attempt.methodName, "createUser");
  627. test.equal(attempt.methodArguments[0].username, self.username);
  628. }));
  629. }
  630. ]);
  631. testAsyncMulti("passwords - onLoginFailed hook", [
  632. function (test, expect) {
  633. this.username = Random.id();
  634. this.password = "password";
  635. Accounts.createUser(
  636. {username: this.username, password: this.password},
  637. loggedInAs(this.username, test, expect));
  638. },
  639. logoutStep,
  640. function (test, expect) {
  641. Meteor.call("testCaptureLogins", expect(function (error) {
  642. test.isFalse(error);
  643. }));
  644. },
  645. function (test, expect) {
  646. Meteor.loginWithPassword(this.username, "incorrect", expect(function (error) {
  647. test.isTrue(error);
  648. }));
  649. },
  650. function (test, expect) {
  651. Meteor.call("testFetchCapturedLogins", expect(function (error, logins) {
  652. test.isFalse(error);
  653. test.equal(logins.length, 1);
  654. var login = logins[0];
  655. test.isFalse(login.successful);
  656. var attempt = login.attempt;
  657. test.equal(attempt.type, "password");
  658. test.isFalse(attempt.allowed);
  659. test.equal(attempt.error.reason, "Incorrect password");
  660. }));
  661. },
  662. function (test, expect) {
  663. Meteor.call("testCaptureLogins", expect(function (error) {
  664. test.isFalse(error);
  665. }));
  666. },
  667. function (test, expect) {
  668. Meteor.loginWithPassword("no such user", "incorrect", expect(function (error) {
  669. test.isTrue(error);
  670. }));
  671. },
  672. function (test, expect) {
  673. Meteor.call("testFetchCapturedLogins", expect(function (error, logins) {
  674. test.isFalse(error);
  675. test.equal(logins.length, 1);
  676. var login = logins[0];
  677. test.isFalse(login.successful);
  678. var attempt = login.attempt;
  679. test.equal(attempt.type, "password");
  680. test.isFalse(attempt.allowed);
  681. test.equal(attempt.error.reason, "User not found");
  682. }));
  683. }
  684. ]);
  685. testAsyncMulti("passwords - srp to bcrypt upgrade", [
  686. logoutStep,
  687. // Create user with old SRP credentials in the database.
  688. function (test, expect) {
  689. var self = this;
  690. Meteor.call("testCreateSRPUser", expect(function (error, result) {
  691. test.isFalse(error);
  692. self.username = result;
  693. }));
  694. },
  695. // We are able to login with the old style credentials in the database.
  696. function (test, expect) {
  697. Meteor.loginWithPassword(this.username, 'abcdef', expect(function (error) {
  698. test.isFalse(error);
  699. }));
  700. },
  701. function (test, expect) {
  702. Meteor.call("testSRPUpgrade", this.username, expect(function (error) {
  703. test.isFalse(error);
  704. }));
  705. },
  706. logoutStep,
  707. // After the upgrade to bcrypt we're still able to login.
  708. function (test, expect) {
  709. Meteor.loginWithPassword(this.username, 'abcdef', expect(function (error) {
  710. test.isFalse(error);
  711. }));
  712. },
  713. logoutStep,
  714. function (test, expect) {
  715. Meteor.call("removeUser", this.username, expect(function (error) {
  716. test.isFalse(error);
  717. }));
  718. }
  719. ]);
  720. testAsyncMulti("passwords - srp to bcrypt upgrade via password change", [
  721. logoutStep,
  722. // Create user with old SRP credentials in the database.
  723. function (test, expect) {
  724. var self = this;
  725. Meteor.call("testCreateSRPUser", expect(function (error, result) {
  726. test.isFalse(error);
  727. self.username = result;
  728. }));
  729. },
  730. // Log in with the plaintext password handler, which should NOT upgrade us to bcrypt.
  731. function (test, expect) {
  732. Accounts.callLoginMethod({
  733. methodName: "login",
  734. methodArguments: [ { user: { username: this.username }, password: "abcdef" } ],
  735. userCallback: expect(function (err) {
  736. test.isFalse(err);
  737. })
  738. });
  739. },
  740. function (test, expect) {
  741. Meteor.call("testNoSRPUpgrade", this.username, expect(function (error) {
  742. test.isFalse(error);
  743. }));
  744. },
  745. // Changing our password should upgrade us to bcrypt.
  746. function (test, expect) {
  747. Accounts.changePassword("abcdef", "abcdefg", expect(function (error) {
  748. test.isFalse(error);
  749. }));
  750. },
  751. function (test, expect) {
  752. Meteor.call("testSRPUpgrade", this.username, expect(function (error) {
  753. test.isFalse(error);
  754. }));
  755. },
  756. // And after the upgrade we should be able to change our password again.
  757. function (test, expect) {
  758. Accounts.changePassword("abcdefg", "abcdef", expect(function (error) {
  759. test.isFalse(error);
  760. }));
  761. },
  762. logoutStep
  763. ]);
  764. }) ();
  765. if (Meteor.isServer) (function () {
  766. Tinytest.add(
  767. 'passwords - setup more than one onCreateUserHook',
  768. function (test) {
  769. test.throws(function() {
  770. Accounts.onCreateUser(function () {});
  771. });
  772. });
  773. Tinytest.add(
  774. 'passwords - createUser hooks',
  775. function (test) {
  776. var username = Random.id();
  777. test.throws(function () {
  778. // should fail the new user validators
  779. Accounts.createUser({username: username, profile: {invalid: true}});
  780. });
  781. var userId = Accounts.createUser({username: username,
  782. testOnCreateUserHook: true});
  783. test.isTrue(userId);
  784. var user = Meteor.users.findOne(userId);
  785. test.equal(user.profile.touchedByOnCreateUser, true);
  786. });
  787. Tinytest.add(
  788. 'passwords - setPassword',
  789. function (test) {
  790. var username = Random.id();
  791. var userId = Accounts.createUser({username: username});
  792. var user = Meteor.users.findOne(userId);
  793. // no services yet.
  794. test.equal(user.services.password, undefined);
  795. // set a new password.
  796. Accounts.setPassword(userId, 'new password');
  797. user = Meteor.users.findOne(userId);
  798. var oldSaltedHash = user.services.password.bcrypt;
  799. test.isTrue(oldSaltedHash);
  800. // reset with the same password, see we get a different salted hash
  801. Accounts.setPassword(userId, 'new password');
  802. user = Meteor.users.findOne(userId);
  803. var newSaltedHash = user.services.password.bcrypt;
  804. test.isTrue(newSaltedHash);
  805. test.notEqual(oldSaltedHash, newSaltedHash);
  806. // cleanup
  807. Meteor.users.remove(userId);
  808. });
  809. // This test properly belongs in accounts-base/accounts_tests.js, but
  810. // this is where the tests that actually log in are.
  811. Tinytest.add('accounts - user() out of context', function (test) {
  812. // basic server context, no method.
  813. test.throws(function () {
  814. Meteor.user();
  815. });
  816. });
  817. // XXX would be nice to test Accounts.config({forbidClientAccountCreation: true})
  818. Tinytest.addAsync(
  819. 'passwords - login token observes get cleaned up',
  820. function (test, onComplete) {
  821. var username = Random.id();
  822. Accounts.createUser({
  823. username: username,
  824. password: 'password'
  825. });
  826. makeTestConnection(
  827. test,
  828. function (clientConn, serverConn) {
  829. serverConn.onClose(function () {
  830. test.isFalse(Accounts._getUserObserve(serverConn.id));
  831. onComplete();
  832. });
  833. var result = clientConn.call('login', {
  834. user: {username: username},
  835. password: 'password'
  836. });
  837. test.isTrue(result);
  838. var token = Accounts._getAccountData(serverConn.id, 'loginToken');
  839. test.isTrue(token);
  840. // We poll here, instead of just checking `_getUserObserve`
  841. // once, because the login method defers the creation of the
  842. // observe, and setting up the observe yields, so we could end
  843. // up here before the observe has been set up.
  844. simplePoll(
  845. function () {
  846. return !! Accounts._getUserObserve(serverConn.id);
  847. },
  848. function () {
  849. test.isTrue(Accounts._getUserObserve(serverConn.id));
  850. clientConn.disconnect();
  851. },
  852. function () {
  853. test.fail("timed out waiting for user observe for connection " +
  854. serverConn.id);
  855. onComplete();
  856. }
  857. );
  858. },
  859. onComplete
  860. );
  861. }
  862. );
  863. }) ();