mirror of
https://github.com/strapi/strapi.git
synced 2025-09-05 06:43:07 +00:00
Add register route
Signed-off-by: Alexandre Bodin <bodin.alex@gmail.com>
This commit is contained in:
parent
01854f431f
commit
a841400f85
@ -69,6 +69,11 @@
|
|||||||
"path": "/registration-info",
|
"path": "/registration-info",
|
||||||
"handler": "authentication.registrationInfo"
|
"handler": "authentication.registrationInfo"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"method": "POST",
|
||||||
|
"path": "/register",
|
||||||
|
"handler": "authentication.register"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"method": "POST",
|
"method": "POST",
|
||||||
"path": "/auth/local/register",
|
"path": "/auth/local/register",
|
||||||
|
@ -3,6 +3,8 @@
|
|||||||
const passport = require('koa-passport');
|
const passport = require('koa-passport');
|
||||||
const compose = require('koa-compose');
|
const compose = require('koa-compose');
|
||||||
|
|
||||||
|
const { validateRegistrationInput } = require('../validation/authentication');
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
login: compose([
|
login: compose([
|
||||||
(ctx, next) => {
|
(ctx, next) => {
|
||||||
@ -68,4 +70,23 @@ module.exports = {
|
|||||||
|
|
||||||
ctx.body = { data: registrationInfo };
|
ctx.body = { data: registrationInfo };
|
||||||
},
|
},
|
||||||
|
|
||||||
|
async register(ctx) {
|
||||||
|
const input = ctx.request.body;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await validateRegistrationInput(input);
|
||||||
|
} catch (err) {
|
||||||
|
return ctx.badRequest('ValidationError', err);
|
||||||
|
}
|
||||||
|
|
||||||
|
const user = await strapi.admin.services.user.register(input);
|
||||||
|
|
||||||
|
ctx.body = {
|
||||||
|
data: {
|
||||||
|
token: strapi.admin.services.token.createJwtToken(user),
|
||||||
|
user: strapi.admin.services.user.sanitizeUser(user),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
@ -78,6 +78,27 @@ describe('User', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('update', () => {
|
||||||
|
test('Forwards call to the query layer', async () => {
|
||||||
|
const user = {
|
||||||
|
email: 'test@strapi.io',
|
||||||
|
};
|
||||||
|
const update = jest.fn(() => Promise.resolve(user));
|
||||||
|
|
||||||
|
global.strapi = {
|
||||||
|
query() {
|
||||||
|
return { update };
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const params = { id: 1 };
|
||||||
|
const input = { email: 'test@strapi.io' };
|
||||||
|
const result = await userService.update(params, input);
|
||||||
|
|
||||||
|
expect(update).toHaveBeenCalledWith(params, input);
|
||||||
|
expect(result).toBe(user);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('exists', () => {
|
describe('exists', () => {
|
||||||
test('Return true if the user already exists', async () => {
|
test('Return true if the user already exists', async () => {
|
||||||
const count = jest.fn(() => Promise.resolve(1));
|
const count = jest.fn(() => Promise.resolve(1));
|
||||||
@ -148,4 +169,142 @@ describe('User', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('register', () => {
|
||||||
|
test('Fails if no matching user is found', async () => {
|
||||||
|
const findOne = jest.fn(() => Promise.resolve(undefined));
|
||||||
|
|
||||||
|
global.strapi = {
|
||||||
|
query() {
|
||||||
|
return {
|
||||||
|
findOne,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
errors: {
|
||||||
|
badRequest(msg) {
|
||||||
|
throw new Error(msg);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const input = {
|
||||||
|
registrationToken: '123',
|
||||||
|
userInfo: {
|
||||||
|
firstname: 'test',
|
||||||
|
lastname: 'Strapi',
|
||||||
|
password: 'Test1234',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(userService.register(input)).rejects.toThrowError('Invalid registration info');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Create a password hash', async () => {
|
||||||
|
const findOne = jest.fn(() => Promise.resolve({ id: 1 }));
|
||||||
|
const update = jest.fn(user => Promise.resolve(user));
|
||||||
|
const hashPassword = jest.fn(() => Promise.resolve('123456789'));
|
||||||
|
|
||||||
|
global.strapi = {
|
||||||
|
query() {
|
||||||
|
return {
|
||||||
|
findOne,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
admin: {
|
||||||
|
services: {
|
||||||
|
user: { update },
|
||||||
|
auth: { hashPassword },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const input = {
|
||||||
|
registrationToken: '123',
|
||||||
|
userInfo: {
|
||||||
|
firstname: 'test',
|
||||||
|
lastname: 'Strapi',
|
||||||
|
password: 'Test1234',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
await userService.register(input);
|
||||||
|
|
||||||
|
expect(hashPassword).toHaveBeenCalledWith('Test1234');
|
||||||
|
expect(update).toHaveBeenCalledWith(
|
||||||
|
{ id: 1 },
|
||||||
|
expect.objectContaining({ password: '123456789' })
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Set user firstname and lastname', async () => {
|
||||||
|
const findOne = jest.fn(() => Promise.resolve({ id: 1 }));
|
||||||
|
const update = jest.fn(user => Promise.resolve(user));
|
||||||
|
const hashPassword = jest.fn(() => Promise.resolve('123456789'));
|
||||||
|
|
||||||
|
global.strapi = {
|
||||||
|
query() {
|
||||||
|
return {
|
||||||
|
findOne,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
admin: {
|
||||||
|
services: {
|
||||||
|
user: { update },
|
||||||
|
auth: { hashPassword },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const input = {
|
||||||
|
registrationToken: '123',
|
||||||
|
userInfo: {
|
||||||
|
firstname: 'test',
|
||||||
|
lastname: 'Strapi',
|
||||||
|
password: 'Test1234',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
await userService.register(input);
|
||||||
|
|
||||||
|
expect(hashPassword).toHaveBeenCalledWith('Test1234');
|
||||||
|
expect(update).toHaveBeenCalledWith(
|
||||||
|
{ id: 1 },
|
||||||
|
expect.objectContaining({ firstname: 'test', lastname: 'Strapi' })
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Set user to active', async () => {
|
||||||
|
const findOne = jest.fn(() => Promise.resolve({ id: 1 }));
|
||||||
|
const update = jest.fn(user => Promise.resolve(user));
|
||||||
|
const hashPassword = jest.fn(() => Promise.resolve('123456789'));
|
||||||
|
|
||||||
|
global.strapi = {
|
||||||
|
query() {
|
||||||
|
return {
|
||||||
|
findOne,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
admin: {
|
||||||
|
services: {
|
||||||
|
user: { update },
|
||||||
|
auth: { hashPassword },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const input = {
|
||||||
|
registrationToken: '123',
|
||||||
|
userInfo: {
|
||||||
|
firstname: 'test',
|
||||||
|
lastname: 'Strapi',
|
||||||
|
password: 'Test1234',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
await userService.register(input);
|
||||||
|
|
||||||
|
expect(hashPassword).toHaveBeenCalledWith('Test1234');
|
||||||
|
expect(update).toHaveBeenCalledWith({ id: 1 }, expect.objectContaining({ isActive: true }));
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@ -25,6 +25,16 @@ const create = async attributes => {
|
|||||||
return strapi.query('user', 'admin').create(user);
|
return strapi.query('user', 'admin').create(user);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update a user in database
|
||||||
|
* @param params query params to find the user to update
|
||||||
|
* @param attributes A partial user object
|
||||||
|
* @returns {Promise<user>}
|
||||||
|
*/
|
||||||
|
const update = async (params, attributes) => {
|
||||||
|
return strapi.query('user', 'admin').update(params, attributes);
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if a user with specific attributes exists in the database
|
* Check if a user with specific attributes exists in the database
|
||||||
* @param attributes A partial user object
|
* @param attributes A partial user object
|
||||||
@ -49,9 +59,38 @@ const findRegistrationInfo = async registrationToken => {
|
|||||||
return _.pick(user, ['email', 'firstname', 'lastname']);
|
return _.pick(user, ['email', 'firstname', 'lastname']);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers a user based on a registrationToken and some informations to update
|
||||||
|
* @param {Object} params
|
||||||
|
* @param {Object} params.registrationInfo registration token
|
||||||
|
* @param {Object} params.userInfo user info
|
||||||
|
*/
|
||||||
|
const register = async ({ registrationToken, userInfo }) => {
|
||||||
|
const matchingUser = await strapi.query('user', 'admin').findOne({ registrationToken });
|
||||||
|
|
||||||
|
if (!matchingUser) {
|
||||||
|
throw strapi.errors.badRequest('Invalid registration info');
|
||||||
|
}
|
||||||
|
|
||||||
|
const hashedPassword = await strapi.admin.services.auth.hashPassword(userInfo.password);
|
||||||
|
|
||||||
|
return strapi.admin.services.user.update(
|
||||||
|
{ id: matchingUser.id },
|
||||||
|
{
|
||||||
|
password: hashedPassword,
|
||||||
|
firstname: userInfo.firstname,
|
||||||
|
lastname: userInfo.lastname,
|
||||||
|
registrationToken: null,
|
||||||
|
isActive: true,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
sanitizeUser,
|
sanitizeUser,
|
||||||
create,
|
create,
|
||||||
|
update,
|
||||||
exists,
|
exists,
|
||||||
findRegistrationInfo,
|
findRegistrationInfo,
|
||||||
|
register,
|
||||||
};
|
};
|
||||||
|
@ -234,4 +234,105 @@ describe('Admin Auth End to End', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('GET /register', () => {
|
||||||
|
test('Fails on missing payload', async () => {
|
||||||
|
const res = await rq({
|
||||||
|
url: '/admin/register',
|
||||||
|
method: 'POST',
|
||||||
|
body: {
|
||||||
|
userInfo: {},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(res.statusCode).toBe(400);
|
||||||
|
expect(res.body).toEqual({
|
||||||
|
statusCode: 400,
|
||||||
|
error: 'Bad Request',
|
||||||
|
message: 'ValidationError',
|
||||||
|
data: {
|
||||||
|
registrationToken: ['registrationToken is a required field'],
|
||||||
|
|
||||||
|
'userInfo.firstname': ['userInfo.firstname is a required field'],
|
||||||
|
'userInfo.lastname': ['userInfo.lastname is a required field'],
|
||||||
|
'userInfo.password': ['userInfo.password is a required field'],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Fails on invalid password', async () => {
|
||||||
|
const user = {
|
||||||
|
email: 'test1@strapi.io', // FIXME: Have to increment emails until we can delete the users after each test
|
||||||
|
firstname: 'test',
|
||||||
|
lastname: 'strapi',
|
||||||
|
};
|
||||||
|
const createRes = await createUser(user);
|
||||||
|
|
||||||
|
const registrationToken = createRes.body.data.registrationToken;
|
||||||
|
|
||||||
|
const res = await rq({
|
||||||
|
url: '/admin/register',
|
||||||
|
method: 'POST',
|
||||||
|
body: {
|
||||||
|
registrationToken,
|
||||||
|
userInfo: {
|
||||||
|
firstname: 'test',
|
||||||
|
lastname: 'Strapi',
|
||||||
|
password: '123',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(res.body.data);
|
||||||
|
|
||||||
|
expect(res.statusCode).toBe(400);
|
||||||
|
expect(res.body).toEqual({
|
||||||
|
statusCode: 400,
|
||||||
|
error: 'Bad Request',
|
||||||
|
message: 'ValidationError',
|
||||||
|
data: {
|
||||||
|
'userInfo.password': ['userInfo.password must contain at least one uppercase character'],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Registers user correctly', async () => {
|
||||||
|
const user = {
|
||||||
|
email: 'test2@strapi.io', // FIXME: Have to increment emails until we can delete the users after each test
|
||||||
|
firstname: 'test',
|
||||||
|
lastname: 'strapi',
|
||||||
|
};
|
||||||
|
const createRes = await createUser(user);
|
||||||
|
|
||||||
|
const registrationToken = createRes.body.data.registrationToken;
|
||||||
|
|
||||||
|
const userInfo = {
|
||||||
|
firstname: 'test',
|
||||||
|
lastname: 'Strapi',
|
||||||
|
password: '1Test2azda3',
|
||||||
|
};
|
||||||
|
|
||||||
|
const res = await rq({
|
||||||
|
url: '/admin/register',
|
||||||
|
method: 'POST',
|
||||||
|
body: {
|
||||||
|
registrationToken,
|
||||||
|
userInfo,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(res.statusCode).toBe(200);
|
||||||
|
expect(res.body.data).toEqual({
|
||||||
|
token: expect.any(String),
|
||||||
|
user: {
|
||||||
|
email: user.email,
|
||||||
|
firstname: expect.any(String),
|
||||||
|
lastname: expect.any(String),
|
||||||
|
password: expect.any(String),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(res.body.data.user.password === userInfo.password).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
41
packages/strapi-admin/validation/authentication.js
Normal file
41
packages/strapi-admin/validation/authentication.js
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
const { yup, formatYupErrors } = require('strapi-utils');
|
||||||
|
|
||||||
|
const registrationSchema = yup
|
||||||
|
.object()
|
||||||
|
.shape({
|
||||||
|
registrationToken: yup.string().required(),
|
||||||
|
userInfo: yup
|
||||||
|
.object()
|
||||||
|
.shape({
|
||||||
|
firstname: yup
|
||||||
|
.string()
|
||||||
|
.min(1)
|
||||||
|
.required(),
|
||||||
|
lastname: yup
|
||||||
|
.string()
|
||||||
|
.min(1)
|
||||||
|
.required(),
|
||||||
|
password: yup
|
||||||
|
.string()
|
||||||
|
.min(8)
|
||||||
|
.matches(/[a-z]/, '${path} must contain at least one lowercase character')
|
||||||
|
.matches(/[A-Z]/, '${path} must contain at least one uppercase character')
|
||||||
|
.matches(/\d/, '${path} must contain at least one number')
|
||||||
|
.required(),
|
||||||
|
})
|
||||||
|
.required()
|
||||||
|
.noUnknown(),
|
||||||
|
})
|
||||||
|
.noUnknown();
|
||||||
|
|
||||||
|
const validateRegistrationInput = data => {
|
||||||
|
return registrationSchema
|
||||||
|
.validate(data, { strict: true, abortEarly: false })
|
||||||
|
.catch(error => Promise.reject(formatYupErrors(error)));
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
validateRegistrationInput,
|
||||||
|
};
|
@ -4,24 +4,27 @@ const { yup, formatYupErrors } = require('strapi-utils');
|
|||||||
|
|
||||||
const handleReject = error => Promise.reject(formatYupErrors(error));
|
const handleReject = error => Promise.reject(formatYupErrors(error));
|
||||||
|
|
||||||
const userCreationSchema = yup.object().shape({
|
const userCreationSchema = yup
|
||||||
email: yup
|
.object()
|
||||||
.string()
|
.shape({
|
||||||
.email()
|
email: yup
|
||||||
.required(),
|
.string()
|
||||||
firstname: yup
|
.email()
|
||||||
.string()
|
.required(),
|
||||||
.min(1)
|
firstname: yup
|
||||||
.required(),
|
.string()
|
||||||
lastname: yup
|
.min(1)
|
||||||
.string()
|
.required(),
|
||||||
.min(1)
|
lastname: yup
|
||||||
.required(),
|
.string()
|
||||||
roles: yup
|
.min(1)
|
||||||
.array()
|
.required(),
|
||||||
.min(1)
|
roles: yup
|
||||||
.required(),
|
.array()
|
||||||
});
|
.min(1)
|
||||||
|
.required(),
|
||||||
|
})
|
||||||
|
.noUnknown();
|
||||||
|
|
||||||
const validateUserCreationInput = data => {
|
const validateUserCreationInput = data => {
|
||||||
return userCreationSchema.validate(data, { strict: true, abortEarly: false }).catch(handleReject);
|
return userCreationSchema.validate(data, { strict: true, abortEarly: false }).catch(handleReject);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user