Skip to content

Commit

Permalink
feat(password): password confirm
Browse files Browse the repository at this point in the history
  • Loading branch information
serge1peshcoff committed Mar 26, 2020
1 parent 9ec7971 commit 14f55e5
Show file tree
Hide file tree
Showing 3 changed files with 170 additions and 0 deletions.
1 change: 1 addition & 0 deletions lib/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ GeneralRouter.post('/signup/:campaign_id', campaigns.registerUser);
GeneralRouter.post('/confirm-email', register.confirmEmail);
GeneralRouter.post('/login', login.login);
GeneralRouter.post('/password_reset', login.passwordReset);
GeneralRouter.post('/password_confirm', login.passwordConfirm);
GeneralRouter.post('/renew', login.renew);

// Endpoints allowing unauthorized and authorized access.
Expand Down
29 changes: 29 additions & 0 deletions middlewares/login.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
const moment = require('moment');

const { User, AccessToken, RefreshToken, PasswordReset } = require('../models');
const { Sequelize } = require('../lib/sequelize');
const errors = require('../lib/errors');
Expand Down Expand Up @@ -77,3 +79,30 @@ module.exports.passwordReset = async (req, res) => {
message: 'The password reset was triggered.'
});
};

module.exports.passwordConfirm = async (req, res) => {
const token = await PasswordReset.findOne({
where: { value: (req.body.token || '').trim() },
include: [User]
});

if (!token) {
return errors.makeNotFoundError(res, 'Token is invalid.');
}

if (moment(token.expires_at).isBefore(moment())) {
return errors.makeNotFoundError(res, 'Token is expired.');
}

await token.user.update({ password: req.body.password || '' });
await AccessToken.destroy({ where: { user_id: token.user.id } });
await RefreshToken.destroy({ where: { user_id: token.user.id } });
await token.destroy();

// TODO: notify user on changed password.

return res.json({
success: true,
message: 'Password was changed successfully.'
});
};
140 changes: 140 additions & 0 deletions test/api/password-confirm.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
const { startServer, stopServer } = require('../../lib/server.js');
const { PasswordReset, AccessToken, RefreshToken, User } = require('../../models');

const { request } = require('../scripts/helpers');
const generator = require('../scripts/generator');

describe('Password confirm', () => {
beforeAll(async () => {
await startServer();
});

afterAll(async () => {
await stopServer();
});

afterEach(async () => {
await generator.clearAll();
});

test('should return 404 if the password reset is not found', async () => {
const res = await request({
uri: '/password_confirm',
method: 'POST',
body: { token: '1', password: 'testtest' }
});

expect(res.statusCode).toEqual(404);
expect(res.body.success).toEqual(false);
expect(res.body).not.toHaveProperty('data');
expect(res.body).toHaveProperty('message');
});

test('should return 404 if no token is provided', async () => {
const res = await request({
uri: '/password_confirm',
method: 'POST',
body: {}
});

expect(res.statusCode).toEqual(404);
expect(res.body.success).toEqual(false);
expect(res.body).not.toHaveProperty('data');
expect(res.body).toHaveProperty('message');
});

test('should fail if password is not provided', async () => {
const user = await generator.createUser();
const reset = await generator.createPasswordReset({}, user);

const res = await request({
uri: '/password_confirm',
method: 'POST',
body: { token: reset.value }
});

expect(res.statusCode).toEqual(422);
expect(res.body.success).toEqual(false);
expect(res.body).toHaveProperty('errors');
expect(res.body).not.toHaveProperty('data');
expect(res.body.errors).toHaveProperty('password');
});

test('should fail if password is invalid', async () => {
const user = await generator.createUser();
const reset = await generator.createPasswordReset({}, user);

const res = await request({
uri: '/password_confirm',
method: 'POST',
body: { token: reset.value, password: 'short' }
});

expect(res.statusCode).toEqual(422);
expect(res.body.success).toEqual(false);
expect(res.body).toHaveProperty('errors');
expect(res.body).not.toHaveProperty('data');
expect(res.body.errors).toHaveProperty('password');
});

test('should fail if token is expired', async () => {
const user = await generator.createUser();
const reset = await generator.createPasswordReset({ expires_at: new Date() }, user);

const res = await request({
uri: '/password_confirm',
method: 'POST',
body: { token: reset.value, password: 'short' }
});

expect(res.statusCode).toEqual(404);
expect(res.body.success).toEqual(false);
expect(res.body).toHaveProperty('message');
expect(res.body).not.toHaveProperty('data');
});


test('should work if the token is found', async () => {
const user = await generator.createUser();
const reset = await generator.createPasswordReset({}, user);

const res = await request({
uri: '/password_confirm',
method: 'POST',
body: { token: reset.value, password: 'testtest' }
});

expect(res.statusCode).toEqual(200);
expect(res.body.success).toEqual(true);
expect(res.body).not.toHaveProperty('errors');
expect(res.body).toHaveProperty('message');

const resets = await PasswordReset.count({ where: { user_id: user.id } });
expect(resets).toEqual(0);

const accessTokens = await AccessToken.count({ where: { user_id: user.id } });
expect(accessTokens).toEqual(0);

const refreshTokens = await RefreshToken.count({ where: { user_id: user.id } });
expect(refreshTokens).toEqual(0);

const userFromDb = await User.scope('withPassword').findByPk(user.id);
expect(await userFromDb.checkPassword('testtest')).toEqual(true);
});

test('should work if the user is found with email with spaces/tabs', async () => {
const user = await generator.createUser();
const reset = await generator.createPasswordReset({}, user);

const res = await request({
uri: '/password_confirm',
method: 'POST',
body: { token: '\t\t\t \t' + reset.value + '\t \t', password: 'testtest' }
});

expect(res.statusCode).toEqual(200);
expect(res.body.success).toEqual(true);
expect(res.body).not.toHaveProperty('errors');
expect(res.body).toHaveProperty('message');
});
});

0 comments on commit 14f55e5

Please sign in to comment.