Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 6 additions & 8 deletions src/actions/challenge.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,16 @@ const {
} = require('../constants');

/**
* @api {amqp} <prefix>.challenge Creates user challenges
* @api {amqp} <prefix>.challenge Request account activation
* @apiVersion 1.0.0
* @apiName ChallengeUser
* @apiGroup Users
*
* @apiDescription Must be used internally to create user challenges. Currently only email challenge is supported. Contains
* password reset challenge & account activation challenge. The latter is called from the `registration` action automatically,
* when the account must complete the challenge
* @apiDescription Requests account activation. Phone and email challenges are supported.
* This challenge also is called from the `registration` action automatically, when
* the account must complete the challenge
*
* @apiParam (Payload) {String="email"} type - type of challenge, only "email" is supported now
* @apiParam (Payload) {String} type - type of challenge, `phone` and `email` challenge are supported
* @apiParam (Payload) {String} username - user's username
* @apiParam (Payload) {String} [remoteip] - used for security log
* @apiParam (Payload) {String} [metadata] - not used, but in the future this would be associated with user when challenge is required
Expand All @@ -34,7 +34,6 @@ module.exports = async function sendChallenge({ params }) {
const service = this;
const { config } = service;
const { defaultAudience } = config.jwt;
const { throttle, ttl } = config.token[params.type];
const { username, type } = params;

const internalData = await getInternalData.call(service, username);
Expand All @@ -47,8 +46,7 @@ module.exports = async function sendChallenge({ params }) {
const { [defaultAudience]: metadata } = await getMetadata(service, userId, defaultAudience);

const challengeOpts = {
ttl,
throttle,
...config.token[params.type],
action: USERS_ACTION_ACTIVATE,
id: resolvedUsername,
};
Expand Down
3 changes: 2 additions & 1 deletion src/utils/challenges/challenge.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,8 @@ async function generateChallenge(type, opts, ctx = {}, wait = false) {
if (error.message === '429') {
const { mailto: email } = this.config.validation;
const duration = moment().add(opts.throttle, 'seconds').toNow(true);
const msg = `We've already sent you an email, if it doesn't come - please try again in ${duration} or send us an email`;
const method = type === CHALLENGE_TYPE_EMAIL ? CHALLENGE_TYPE_EMAIL : 'code';
const msg = `We've already sent you an ${method}, if it doesn't come - please try again in ${duration} or send us an email`;
const reason = { ...error.reason, duration: opts.throttle, email };

throw DetailedHttpStatusError(429, msg, reason);
Expand Down
54 changes: 53 additions & 1 deletion test/suites/actions/register.stubbed.js
Original file line number Diff line number Diff line change
Expand Up @@ -109,25 +109,77 @@ describe('#register stubbed', function suite() {
assert.equal(is.undefined(lastResponse.password), true);
};

const shouldBeAbleToRequestActivationCode = async () => {
const amqpStub = sinon.stub(this.users.amqp, 'publishAndWait');
const opts = {
activate: false,
audience: '*.localhost',
challengeType: 'phone',
skipPassword: true,
username: '79215555555',
};

amqpStub
.withArgs('phone.message.predefined')
.resolves({ queued: true });

await this.users.dispatch('register', { params: opts });
await Promise.delay(1000);
const { context } = await this.users.dispatch('challenge', {
params: {
username: '79215555555',
type: 'phone',
},
});

assert.equal(context.username, '79215555555');

const args0 = amqpStub.args[0][1];
const args1 = amqpStub.args[1][1];

assert.ok(args0.message.match(/^[0-9]{4}/));
assert.equal(args0.to, '+79215555555');

assert.ok(args1.message.match(/^[0-9]{4}/));
assert.equal(args1.to, '+79215555555');

amqpStub.restore();
};

describe('#password validator disabled', () => {
beforeEach(startService.bind(this));
beforeEach(async () => {
await startService.call(this, {
token: {
phone: {
throttle: 1, // seconds
},
},
});
});
afterEach(clearRedis.bind(this));

it('must be able to send activation code by sms', mustBeAbleToSendActivationCodeBySms);
it('must be able to send password by sms', mustBeAbleToSendPasswordBySms);
it('should be able to register without password', shouldBeAbleToRegisterWithoutPassword);
it('should be able to request activation code', shouldBeAbleToRequestActivationCode);
});

describe('#password validator enabled', () => {
beforeEach(async () => {
await startService.call(this, {
passwordValidator: { enabled: true },
token: {
phone: {
throttle: 1, // seconds
},
},
});
});
afterEach(clearRedis.bind(this));

it('must be able to send activation code by sms', mustBeAbleToSendActivationCodeBySms);
it('must be able to send password by sms', mustBeAbleToSendPasswordBySms);
it('should be able to register without password', shouldBeAbleToRegisterWithoutPassword);
it('should be able to request activation code', shouldBeAbleToRequestActivationCode);
});
});