mirror of
https://github.com/strapi/strapi.git
synced 2025-09-02 21:32:43 +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",
|
||||
"handler": "authentication.registrationInfo"
|
||||
},
|
||||
{
|
||||
"method": "POST",
|
||||
"path": "/register",
|
||||
"handler": "authentication.register"
|
||||
},
|
||||
{
|
||||
"method": "POST",
|
||||
"path": "/auth/local/register",
|
||||
|
@ -3,6 +3,8 @@
|
||||
const passport = require('koa-passport');
|
||||
const compose = require('koa-compose');
|
||||
|
||||
const { validateRegistrationInput } = require('../validation/authentication');
|
||||
|
||||
module.exports = {
|
||||
login: compose([
|
||||
(ctx, next) => {
|
||||
@ -68,4 +70,23 @@ module.exports = {
|
||||
|
||||
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', () => {
|
||||
test('Return true if the user already exists', async () => {
|
||||
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);
|
||||
};
|
||||
|
||||
/**
|
||||
* 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
|
||||
* @param attributes A partial user object
|
||||
@ -49,9 +59,38 @@ const findRegistrationInfo = async registrationToken => {
|
||||
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 = {
|
||||
sanitizeUser,
|
||||
create,
|
||||
update,
|
||||
exists,
|
||||
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 userCreationSchema = yup.object().shape({
|
||||
email: yup
|
||||
.string()
|
||||
.email()
|
||||
.required(),
|
||||
firstname: yup
|
||||
.string()
|
||||
.min(1)
|
||||
.required(),
|
||||
lastname: yup
|
||||
.string()
|
||||
.min(1)
|
||||
.required(),
|
||||
roles: yup
|
||||
.array()
|
||||
.min(1)
|
||||
.required(),
|
||||
});
|
||||
const userCreationSchema = yup
|
||||
.object()
|
||||
.shape({
|
||||
email: yup
|
||||
.string()
|
||||
.email()
|
||||
.required(),
|
||||
firstname: yup
|
||||
.string()
|
||||
.min(1)
|
||||
.required(),
|
||||
lastname: yup
|
||||
.string()
|
||||
.min(1)
|
||||
.required(),
|
||||
roles: yup
|
||||
.array()
|
||||
.min(1)
|
||||
.required(),
|
||||
})
|
||||
.noUnknown();
|
||||
|
||||
const validateUserCreationInput = data => {
|
||||
return userCreationSchema.validate(data, { strict: true, abortEarly: false }).catch(handleReject);
|
||||
|
Loading…
x
Reference in New Issue
Block a user