Init admin reset

Signed-off-by: Alexandre Bodin <bodin.alex@gmail.com>
This commit is contained in:
Alexandre Bodin 2020-10-12 22:46:56 +02:00
parent 5cc40a350f
commit c81af9a1d0
7 changed files with 189 additions and 43 deletions

View File

@ -20,7 +20,7 @@
"index.js"
],
"dependencies": {
"commander": "^2.20.0",
"commander": "6.1.0",
"strapi-generate-new": "3.2.3"
},
"scripts": {

View File

@ -744,4 +744,90 @@ describe('User', () => {
);
});
});
describe('resetPasswordByEmail', () => {
test('Throws on missing user', async () => {
const email = 'email@email.fr';
const password = 'invalidpass';
const findOne = jest.fn(() => {
return null;
});
global.strapi = {
query() {
return {
findOne,
};
},
};
expect.hasAssertions();
await userService.resetPasswordByEmail(email, password).catch(error => {
expect(findOne).toHaveBeenCalledWith({ email }, undefined);
expect(error).toEqual(new Error(`User not found for email: ${email}`));
});
});
test.each(['abc', 'Abcd', 'Abcdefgh', 'Abcd123'])(
'Throws on invalid password',
async password => {
const email = 'email@email.fr';
const findOne = jest.fn(() => ({ id: 1 }));
global.strapi = {
query() {
return {
findOne,
};
},
};
expect.hasAssertions();
await userService.resetPasswordByEmail(email, password).catch(error => {
expect(findOne).toHaveBeenCalledWith({ email }, undefined);
expect(error).toEqual(
new Error(
'Invalid password. Expected a minimum of 8 characters with at least one number and one uppercase letter'
)
);
});
}
);
});
test('Call the update function with the expected params', async () => {
const email = 'email@email.fr';
const password = 'Testing1234';
const hash = 'hash';
const userId = 1;
const findOne = jest.fn(() => ({ id: userId }));
const update = jest.fn();
const hashPassword = jest.fn(() => hash);
global.strapi = {
query() {
return {
findOne,
update,
};
},
admin: {
services: {
auth: {
hashPassword,
},
},
},
};
await userService.resetPasswordByEmail(email, password);
expect(findOne).toHaveBeenCalledWith({ email }, undefined);
expect(update).toHaveBeenCalledWith({ id: userId }, { password: hash });
expect(hashPassword).toHaveBeenCalledWith(password);
});
});

View File

@ -4,6 +4,7 @@ const _ = require('lodash');
const { stringIncludes } = require('strapi-utils');
const { createUser, hasSuperAdminRole } = require('../domain/user');
const { SUPER_ADMIN_CODE } = require('./constants');
const { password: passwordValidator } = require('../validation/common-validators');
const sanitizeUserRoles = role => _.pick(role, ['id', 'name', 'description', 'code']);
@ -43,7 +44,7 @@ const create = async attributes => {
/**
* Update a user in database
* @param params query params to find the user to update
* @param id query params to find the user to update
* @param attributes A partial user object
* @returns {Promise<user>}
*/
@ -89,6 +90,31 @@ const updateById = async (id, attributes) => {
return strapi.query('user', 'admin').update({ id }, attributes);
};
/**
* Reset a user password by email. (Used in admin:reset CLI)
* @param {string} email - user email
* @param {string} password - new password
*/
const resetPasswordByEmail = async (email, password) => {
const user = await findOne({ email });
if (!user) {
throw new Error(`User not found for email: ${email}`);
}
try {
await passwordValidator.validate(password);
} catch (error) {
throw new Error(
'Invalid password. Expected a minimum of 8 characters with at least one number and one uppercase letter'
);
}
const { id: userId } = user;
await updateById(userId, { password });
};
/**
* Check if a user is the last super admin
* @param {int|string} userId user's id to look for
@ -320,4 +346,5 @@ module.exports = {
assignARoleToAll,
displayWarningIfUsersDontHaveRole,
migrateUsers,
resetPasswordByEmail,
};

View File

@ -4,17 +4,12 @@
const _ = require('lodash');
const resolveCwd = require('resolve-cwd');
const { yellow } = require('chalk');
const program = require('commander');
const { Command } = require('commander');
const program = new Command();
const packageJSON = require('../package.json');
// Allow us to display `help()`, but omit the wildcard (`*`) command.
program.Command.prototype.usageMinusWildcard = program.usageMinusWildcard = () => {
program.commands = _.reject(program.commands, {
_name: '*',
});
program.help();
};
program.storeOptionsAsProperties(false).passCommandToAction(false);
const checkCwdIsStrapiApp = name => {
let logErrorAndExit = () => {
@ -72,20 +67,15 @@ const getLocalScript = name => (...args) => {
program.allowUnknownOption(true);
// Expose version.
program.version(packageJSON.version, '-v, --version');
// Make `-v` option case-insensitive.
process.argv = _.map(process.argv, arg => {
return arg === '-V' ? '-v' : arg;
});
program.option('-v, --version', 'output the version number');
// `$ strapi version` (--version synonym)
program
.command('version')
.description('output your version of Strapi')
.action(() => {
console.log(packageJSON.version);
process.stdout.write(packageJSON.version + '\n');
process.exit(0);
});
// `$ strapi console`
@ -126,7 +116,7 @@ program
.command('develop')
.alias('dev')
.option('--no-build', 'Disable build', false)
.option('--watch-admin', 'Enable watch', true)
.option('--watch-admin', 'Enable watch', false)
.option('--browser <name>', 'Open the browser', true)
.description('Start your Strapi application in development mode')
.action(getLocalScript('develop'));
@ -221,38 +211,25 @@ program
program
.command('configuration:dump')
.alias('config:dump')
.description('Dump configurations of your application')
.option('-f, --file <file>', 'Output file, default output is stdout')
.action(getLocalScript('configurationDump'));
program
.command('configuration:restore')
.alias('config:restore')
.description('Restore configurations of your application')
.option('-f, --file <file>', 'Input file, default input is stdin')
.option('-s, --strategy <strategy>', 'Strategy name, one of: "replace", "merge", "keep"')
.action(getLocalScript('configurationRestore'));
/**
* Normalize help argument
*/
// `$ strapi help` (--help synonym)
// Admin
program
.command('help')
.description('output the help')
.action(program.usageMinusWildcard);
.command('admin:reset-password')
.alias('admin:reset')
.description('Reset an admin user password')
.option('-e, --email <email>', 'The user email')
.option('-p, --password <password>', 'New password for the user')
.action(getLocalScript('admin-reset'));
// `$ strapi <unrecognized_cmd>`
// Mask the '*' in `help`.
program.command('*').action(program.usageMinusWildcard);
// Don't balk at unknown options.
/**
* `$ strapi`
*/
program.parse(process.argv);
const NO_COMMAND_SPECIFIED = program.args.length === 0;
if (NO_COMMAND_SPECIFIED) {
program.usageMinusWildcard();
}
program.parseAsync(process.argv);

View File

@ -0,0 +1,51 @@
'use strict';
const _ = require('lodash');
const inquirer = require('inquirer');
const strapi = require('../index');
const promptQuestions = [
{ type: 'input', name: 'email', message: 'User email?' },
{ type: 'password', name: 'password', message: 'New password?' },
{
type: 'confirm',
name: 'confirm',
message: "Do you really want to reset this user's password?",
},
];
/**
* Reset user's password
* @param {Object} cmdOptions - command options
* @param {string} cmdOptions.email - user's email
* @param {string} cmdOptions.password - user's new password
*/
module.exports = async function(cmdOptions) {
const { email, password } = cmdOptions;
if (_.isEmpty(email) && _.isEmpty(password) && process.stdin.isTTY) {
const inquiry = await inquirer.prompt(promptQuestions);
if (!inquiry.confirm) {
process.exit(0);
}
return changePassword(inquiry);
}
if (_.isEmpty(email) || _.isEmpty(password)) {
console.error('Missing required options `email` or `password`');
process.exit(1);
}
return changePassword({ email, password });
};
async function changePassword({ email, password }) {
const app = await strapi().load();
await app.admin.services.user.resetPasswordByEmail(email, password);
console.log(`Successfully reset user's password`);
process.exit(0);
}

View File

@ -19,7 +19,7 @@
"chokidar": "3.3.1",
"ci-info": "2.0.0",
"cli-table3": "^0.6.0",
"commander": "^2.20.0",
"commander": "6.1.0",
"cross-spawn": "^6.0.5",
"debug": "^4.1.1",
"delegates": "^1.0.0",

View File

@ -5651,6 +5651,11 @@ commander@2.3.0:
resolved "https://registry.yarnpkg.com/commander/-/commander-2.3.0.tgz#fd430e889832ec353b9acd1de217c11cb3eef873"
integrity sha1-/UMOiJgy7DU7ms0d4hfBHLPu+HM=
commander@6.1.0:
version "6.1.0"
resolved "https://registry.yarnpkg.com/commander/-/commander-6.1.0.tgz#f8d722b78103141006b66f4c7ba1e97315ba75bc"
integrity sha512-wl7PNrYWd2y5mp1OK/LhTlv8Ff4kQJQRXXAvF+uU/TPNiVJUxZLRYGj/B0y/lPGAVcSbJqH2Za/cvHmrPMC8mA==
commander@^2.19.0, commander@^2.20.0, commander@^2.20.3:
version "2.20.3"
resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33"