'use strict'; /** * Auth.js controller * * @description: A set of functions called "actions" for managing `Auth`. */ /* eslint-disable no-useless-escape */ const crypto = require('crypto'); const _ = require('lodash'); const utils = require('@strapi/utils'); const { getService } = require('../utils'); const { validateCallbackBody, validateRegisterBody, validateSendEmailConfirmationBody, } = require('./validation/auth'); const { sanitize } = utils; const { ApplicationError, ValidationError } = utils.errors; 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 sanitizeUser = (user, ctx) => { const { auth } = ctx.state; const userSchema = strapi.getModel('plugin::users-permissions.user'); return sanitize.contentAPI.output(user, userSchema, { auth }); }; module.exports = { async callback(ctx) { const provider = ctx.params.provider || 'local'; const params = ctx.request.body; const store = await strapi.store({ type: 'plugin', name: 'users-permissions' }); if (provider === 'local') { if (!_.get(await store.get({ key: 'grant' }), 'email.enabled')) { throw new ApplicationError('This provider is disabled'); } await validateCallbackBody(params); const query = { provider }; // Check if the provided identifier is an email or not. const isEmail = emailRegExp.test(params.identifier); // Set the identifier to the appropriate query field. if (isEmail) { query.email = params.identifier.toLowerCase(); } else { query.username = params.identifier; } // Check if the user exists. const user = await strapi.query('plugin::users-permissions.user').findOne({ where: query }); if (!user) { throw new ValidationError('Invalid identifier or password'); } if ( _.get(await store.get({ key: 'advanced' }), 'email_confirmation') && user.confirmed !== true ) { throw new ApplicationError('Your account email is not confirmed'); } if (user.blocked === true) { throw new ApplicationError('Your account has been blocked by an administrator'); } // The user never authenticated with the `local` provider. if (!user.password) { throw new ApplicationError( 'This user never set a local password, please login with the provider used during account creation' ); } const validPassword = await getService('user').validatePassword( params.password, user.password ); if (!validPassword) { throw new ValidationError('Invalid identifier or password'); } else { ctx.send({ jwt: getService('jwt').issue({ id: user.id, }), user: await sanitizeUser(user, ctx), }); } } else { if (!_.get(await store.get({ key: 'grant' }), [provider, 'enabled'])) { throw new ApplicationError('This provider is disabled'); } // Connect the user with the third-party provider. let user; let error; try { [user, error] = await getService('providers').connect(provider, ctx.query); } catch ([user, error]) { throw new ApplicationError(error.message); } if (!user) { throw new ApplicationError(error.message); } ctx.send({ jwt: getService('jwt').issue({ id: user.id }), user: await sanitizeUser(user, ctx), }); } }, async resetPassword(ctx) { const params = _.assign({}, ctx.request.body, ctx.params); if ( params.password && params.passwordConfirmation && params.password === params.passwordConfirmation && params.code ) { const user = await strapi .query('plugin::users-permissions.user') .findOne({ where: { resetPasswordToken: `${params.code}` } }); if (!user) { throw new ValidationError('Incorrect code provided'); } const password = await getService('user').hashPassword({ password: params.password }); // Update the user. await strapi .query('plugin::users-permissions.user') .update({ where: { id: user.id }, data: { resetPasswordToken: null, password } }); ctx.send({ jwt: getService('jwt').issue({ id: user.id }), user: await sanitizeUser(user, ctx), }); } else if ( params.password && params.passwordConfirmation && params.password !== params.passwordConfirmation ) { throw new ValidationError('Passwords do not match'); } else { throw new ValidationError('Incorrect params provided'); } }, async connect(ctx, next) { const grant = require('grant-koa'); const providers = await strapi .store({ type: 'plugin', name: 'users-permissions', key: 'grant' }) .get(); const apiPrefix = strapi.config.get('api.rest.prefix'); const grantConfig = { defaults: { prefix: `${apiPrefix}/connect`, }, ...providers, }; const [requestPath] = ctx.request.url.split('?'); const provider = requestPath.split('/connect/')[1].split('/')[0]; if (!_.get(grantConfig[provider], 'enabled')) { throw new ApplicationError('This provider is disabled'); } if (!strapi.config.server.url.startsWith('http')) { strapi.log.warn( 'You are using a third party provider for login. Make sure to set an absolute url in config/server.js. More info here: https://strapi.io/documentation/developer-docs/latest/development/plugins/users-permissions.html#setting-up-the-server-url' ); } // Ability to pass OAuth callback dynamically grantConfig[provider].callback = _.get(ctx, 'query.callback') || grantConfig[provider].callback; grantConfig[provider].redirect_uri = getService('providers').buildRedirectUri(provider); return grant(grantConfig)(ctx, next); }, async forgotPassword(ctx) { let { email } = ctx.request.body; // Check if the provided email is valid or not. const isEmail = emailRegExp.test(email); if (isEmail) { email = email.toLowerCase(); } else { throw new ValidationError('Please provide a valid email address'); } const pluginStore = await strapi.store({ type: 'plugin', name: 'users-permissions' }); // Find the user by email. const user = await strapi .query('plugin::users-permissions.user') .findOne({ where: { email: email.toLowerCase() } }); // User not found. if (!user) { throw new ApplicationError('This email does not exist'); } // User blocked if (user.blocked) { throw new ApplicationError('This user is disabled'); } // Generate random token. const resetPasswordToken = crypto.randomBytes(64).toString('hex'); const settings = await pluginStore.get({ key: 'email' }).then(storeEmail => { try { return storeEmail['reset_password'].options; } catch (error) { return {}; } }); const advanced = await pluginStore.get({ key: 'advanced', }); const userInfo = await sanitizeUser(user, ctx); settings.message = await getService('users-permissions').template(settings.message, { URL: advanced.email_reset_password, USER: userInfo, TOKEN: resetPasswordToken, }); settings.object = await getService('users-permissions').template(settings.object, { USER: userInfo, }); try { // Send an email to the user. await strapi .plugin('email') .service('email') .send({ to: user.email, from: settings.from.email || settings.from.name ? `${settings.from.name} <${settings.from.email}>` : undefined, replyTo: settings.response_email, subject: settings.object, text: settings.message, html: settings.message, }); } catch (err) { throw new ApplicationError(err.message); } // Update the user. await strapi .query('plugin::users-permissions.user') .update({ where: { id: user.id }, data: { resetPasswordToken } }); ctx.send({ ok: true }); }, async register(ctx) { const pluginStore = await strapi.store({ type: 'plugin', name: 'users-permissions' }); const settings = await pluginStore.get({ key: 'advanced', }); if (!settings.allow_register) { throw new ApplicationError('Register action is currently disabled'); } const params = { ..._.omit(ctx.request.body, ['confirmed', 'confirmationToken', 'resetPasswordToken']), provider: 'local', }; await validateRegisterBody(params); // Throw an error if the password selected by the user // contains more than three times the symbol '$'. if (getService('user').isHashed(params.password)) { throw new ValidationError( 'Your password cannot contain more than three times the symbol `$`' ); } const role = await strapi .query('plugin::users-permissions.role') .findOne({ where: { type: settings.default_role } }); if (!role) { throw new ApplicationError('Impossible to find the default role'); } // Check if the provided email is valid or not. const isEmail = emailRegExp.test(params.email); if (isEmail) { params.email = params.email.toLowerCase(); } else { throw new ValidationError('Please provide a valid email address'); } params.role = role.id; params.password = await getService('user').hashPassword(params); const user = await strapi.query('plugin::users-permissions.user').findOne({ where: { email: params.email }, }); if (user && user.provider === params.provider) { throw new ApplicationError('Email is already taken'); } if (user && user.provider !== params.provider && settings.unique_email) { throw new ApplicationError('Email is already taken'); } try { if (!settings.email_confirmation) { params.confirmed = true; } const user = await strapi.query('plugin::users-permissions.user').create({ data: params }); const sanitizedUser = await sanitizeUser(user, ctx); if (settings.email_confirmation) { try { await getService('user').sendConfirmationEmail(sanitizedUser); } catch (err) { throw new ApplicationError(err.message); } return ctx.send({ user: sanitizedUser }); } const jwt = getService('jwt').issue(_.pick(user, ['id'])); return ctx.send({ jwt, user: sanitizedUser, }); } catch (err) { if (_.includes(err.message, 'username')) { throw new ApplicationError('Username already taken'); } else { throw new ApplicationError('Email already taken'); } } }, async emailConfirmation(ctx, next, returnUser) { const { confirmation: confirmationToken } = ctx.query; const userService = getService('user'); const jwtService = getService('jwt'); if (_.isEmpty(confirmationToken)) { throw new ValidationError('token.invalid'); } const user = await userService.fetch({ confirmationToken }, []); if (!user) { throw new ValidationError('token.invalid'); } await userService.edit({ id: user.id }, { confirmed: true, confirmationToken: null }); if (returnUser) { ctx.send({ jwt: jwtService.issue({ id: user.id }), user: await sanitizeUser(user, ctx), }); } else { const settings = await strapi .store({ type: 'plugin', name: 'users-permissions', key: 'advanced' }) .get(); ctx.redirect(settings.email_confirmation_redirection || '/'); } }, async sendEmailConfirmation(ctx) { const params = _.assign(ctx.request.body); await validateSendEmailConfirmationBody(params); const isEmail = emailRegExp.test(params.email); if (isEmail) { params.email = params.email.toLowerCase(); } else { throw new ValidationError('wrong.email'); } const user = await strapi.query('plugin::users-permissions.user').findOne({ where: { email: params.email }, }); if (user.confirmed) { throw new ApplicationError('already.confirmed'); } if (user.blocked) { throw new ApplicationError('blocked.user'); } try { await getService('user').sendConfirmationEmail(user); ctx.send({ email: user.email, sent: true, }); } catch (err) { throw new ApplicationError(err.message); } }, };