Add admin registeration API

Signed-off-by: Alexandre Bodin <bodin.alex@gmail.com>
This commit is contained in:
Alexandre Bodin 2020-05-22 11:15:06 +02:00
parent 34de63766f
commit 7a056c2f45
11 changed files with 189 additions and 104 deletions

View File

@ -8,35 +8,10 @@
"policies": []
}
},
{
"method": "GET",
"path": "/currentEnvironment",
"handler": "Admin.getCurrentEnvironment",
"policies": []
},
{
"method": "GET",
"path": "/gaConfig",
"handler": "Admin.getGaConfig",
"policies": []
},
{
"method": "GET",
"path": "/strapiVersion",
"handler": "Admin.getStrapiVersion",
"policies": []
},
{
"method": "GET",
"path": "/layout",
"handler": "Admin.getLayout",
"policies": []
},
{
"method": "GET",
"path": "/init",
"handler": "Admin.init",
"policies": []
"handler": "Admin.init"
},
{
"method": "POST",
@ -64,6 +39,11 @@
"path": "/renew-token",
"handler": "authentication.renewToken"
},
{
"method": "POST",
"path": "/register-admin",
"handler": "authentication.registerAdmin"
},
{
"method": "GET",
"path": "/registration-info",
@ -74,11 +54,6 @@
"path": "/register",
"handler": "authentication.register"
},
{
"method": "POST",
"path": "/auth/local/register",
"handler": "Auth.register"
},
{
"method": "POST",
"path": "/auth/forgot-password",

View File

@ -27,47 +27,13 @@ module.exports = {
const autoReload = strapi.config.get('autoReload', false);
const strapiVersion = strapi.config.get('info.strapi', null);
const hasAdmin = await strapi.admin.services.user.exists({});
return ctx.send({
data: { uuid, currentEnvironment, autoReload, strapiVersion },
data: { uuid, currentEnvironment, autoReload, strapiVersion, hasAdmin },
});
},
async getCurrentEnvironment(ctx) {
try {
const autoReload = strapi.config.autoReload;
return ctx.send({ autoReload, currentEnvironment: strapi.app.env });
} catch (err) {
ctx.badRequest(null, [{ messages: [{ id: 'An error occurred' }] }]);
}
},
async getStrapiVersion(ctx) {
try {
const strapiVersion = strapi.config.get('info.strapi', null);
return ctx.send({ strapiVersion });
} catch (err) {
return ctx.badRequest(null, [{ messages: [{ id: 'The version is not available' }] }]);
}
},
async getGaConfig(ctx) {
try {
ctx.send({ uuid: strapi.config.get('uuid', false) });
} catch (err) {
ctx.badRequest(null, [{ messages: [{ id: 'An error occurred' }] }]);
}
},
async getLayout(ctx) {
try {
const layout = require('../config/layout.js');
return ctx.send({ layout });
} catch (err) {
return ctx.badRequest(null, [{ messages: [{ id: 'An error occurred' }] }]);
}
},
async installPlugin(ctx) {
try {
const { plugin } = ctx.request.body;

View File

@ -8,21 +8,11 @@
/* eslint-disable no-useless-escape */
const crypto = require('crypto');
const _ = require('lodash');
const emailRegExp = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
const formatError = error => [
{ messages: [{ id: error.id, message: error.message, field: error.field }] },
];
// this is a temp patch for the next dev
const createUser = params => ({
...params,
isActive: true,
firstname: null,
lastname: null,
});
module.exports = {
async register(ctx) {
const params = ctx.request.body;

View File

@ -5,6 +5,7 @@ const compose = require('koa-compose');
const {
validateRegistrationInput,
validateAdminRegistrationInput,
validateRegistrationInfoQuery,
} = require('../validation/authentication');
@ -94,4 +95,34 @@ module.exports = {
},
};
},
async registerAdmin(ctx) {
const input = ctx.request.body;
try {
await validateAdminRegistrationInput(input);
} catch (err) {
return ctx.badRequest('ValidationError', err);
}
const hasAdmin = await strapi.admin.services.user.exists({});
if (hasAdmin) {
return ctx.badRequest('You cannot register a new super admin');
}
// TODO: assign super admin role
const user = await strapi.admin.services.user.create({
...input,
registrationToken: null,
isActive: true,
});
ctx.body = {
data: {
token: strapi.admin.services.token.createJwtToken(user),
user: strapi.admin.services.user.sanitizeUser(user),
},
};
},
};

View File

@ -26,11 +26,13 @@ describe('User', () => {
test('Creates a user by merging given and default attributes', async () => {
const create = jest.fn(user => Promise.resolve(user));
const createToken = jest.fn(() => 'token');
const hashPassword = jest.fn(() => Promise.resolve('123456789'));
global.strapi = {
admin: {
services: {
token: { createToken },
auth: { hashPassword },
},
},
query() {
@ -48,14 +50,56 @@ describe('User', () => {
expect(result).toStrictEqual(expected);
});
test('Creates a user by using given attributes', async () => {
test('Creates a user and hash password if provided', async () => {
const create = jest.fn(user => Promise.resolve(user));
const createToken = jest.fn(() => 'token');
const hashPassword = jest.fn(() => Promise.resolve('123456789'));
global.strapi = {
admin: {
services: {
token: { createToken },
auth: { hashPassword },
},
},
query() {
return { create };
},
};
const input = {
firstname: 'John',
lastname: 'Doe',
email: 'johndoe@email.com',
password: 'Pcw123',
};
const expected = {
...input,
password: expect.any(String),
isActive: false,
roles: [],
registrationToken: 'token',
};
const result = await userService.create(input);
expect(create).toHaveBeenCalled();
expect(hashPassword).toHaveBeenCalledWith(input.password);
expect(createToken).toHaveBeenCalled();
expect(result).toStrictEqual(expected);
expect(result.password !== input.password).toBe(true);
});
test('Creates a user by using given attributes', async () => {
const create = jest.fn(user => Promise.resolve(user));
const createToken = jest.fn(() => 'token');
const hashPassword = jest.fn(() => Promise.resolve('123456789'));
global.strapi = {
admin: {
services: {
token: { createToken },
auth: { hashPassword },
},
},
query() {

View File

@ -22,6 +22,16 @@ const create = async attributes => {
...attributes,
});
// hash password if a new one is sent
if (_.has(user, 'password')) {
const hashedPassword = await strapi.admin.services.auth.hashPassword(user.password);
return strapi.query('user', 'admin').create({
...user,
password: hashedPassword,
});
}
return strapi.query('user', 'admin').create(user);
};

View File

@ -28,7 +28,7 @@ describe('Admin Auth End to End', () => {
method: 'POST',
body: {
email: 'admin@strapi.io',
password: 'pcw123',
password: 'Password123',
},
});
@ -106,7 +106,7 @@ describe('Admin Auth End to End', () => {
method: 'POST',
body: {
email: 'admin@strapi.io',
password: 'pcw123',
password: 'Password123',
},
});
@ -318,4 +318,72 @@ describe('Admin Auth End to End', () => {
expect(res.body.data.user.password === userInfo.password).toBe(false);
});
});
describe('GET /register-admin', () => {
test('Fails on missing payload', async () => {
const res = await rq({
url: '/admin/register-admin',
method: 'POST',
body: {},
});
expect(res.statusCode).toBe(400);
expect(res.body).toEqual({
statusCode: 400,
error: 'Bad Request',
message: 'ValidationError',
data: {
email: ['email is a required field'],
firstname: ['firstname is a required field'],
lastname: ['lastname is a required field'],
password: ['password is a required field'],
},
});
});
test('Fails on invalid password', async () => {
const res = await rq({
url: '/admin/register-admin',
method: 'POST',
body: {
email: 'test@strapi.io',
firstname: 'test',
lastname: 'Strapi',
password: '123',
},
});
expect(res.statusCode).toBe(400);
expect(res.body).toEqual({
statusCode: 400,
error: 'Bad Request',
message: 'ValidationError',
data: {
password: ['password must contain at least one uppercase character'],
},
});
});
test('Fails if already a user', async () => {
const userInfo = {
email: 'test-admin@strapi.io',
firstname: 'test',
lastname: 'Strapi',
password: '1Test2azda3',
};
const res = await rq({
url: '/admin/register-admin',
method: 'POST',
body: userInfo,
});
expect(res.statusCode).toBe(400);
expect(res.body).toEqual({
statusCode: 400,
error: 'Bad Request',
message: 'You cannot register a new super admin',
});
});
});
});

View File

@ -39,7 +39,25 @@ const validateRegistrationInfoQuery = query => {
.catch(error => Promise.reject(formatYupErrors(error)));
};
const adminRegistrationSchema = yup
.object()
.shape({
email: validators.email.required(),
firstname: validators.firstname.required(),
lastname: validators.lastname.required(),
password: validators.password.required(),
})
.required()
.noUnknown();
const validateAdminRegistrationInput = data => {
return adminRegistrationSchema
.validate(data, { strict: true, abortEarly: false })
.catch(error => Promise.reject(formatYupErrors(error)));
};
module.exports = {
validateRegistrationInput,
validateAdminRegistrationInput,
validateRegistrationInfoQuery,
};

View File

@ -8,19 +8,6 @@
"policies": []
}
},
{
"method": "GET",
"path": "/init",
"handler": "UsersPermissions.init",
"config": {
"policies": [],
"description": "Check if the first admin user has already been registered",
"tag": {
"plugin": "users-permissions",
"name": "Role"
}
}
},
{
"method": "GET",
"path": "/search/:id",

View File

@ -143,12 +143,6 @@ module.exports = {
ctx.send({ message: 'ok' });
},
async init(ctx) {
const admins = await strapi.query('user', 'admin').find({ _limit: 1 });
ctx.send({ hasAdmin: admins.length > 0 });
},
async searchUsers(ctx) {
const { id } = ctx.params;

View File

@ -1,20 +1,22 @@
const { createRequest } = require('./request');
const auth = {
username: 'admin',
email: 'admin@strapi.io',
password: 'pcw123',
firstname: 'admin',
lastname: 'admin',
password: 'Password123',
};
const rq = createRequest();
const register = async () => {
await rq({
url: '/admin/auth/local/register',
url: '/admin/register-admin',
method: 'POST',
body: auth,
}).catch(err => {
if (err.error.message.includes("You can't register a new admin")) return;
console.log(err);
if (err.message === 'You cannot register a new super admin') return;
throw err;
});
};