const utils = require("@strapi/utils"); const _ = require("lodash"); const { sanitize } = utils; const { ApplicationError, ValidationError } = utils.errors; const { validateRegisterBody, } = require("@strapi/plugin-users-permissions/server/controllers/validation/auth"); const { getService } = require("@strapi/plugin-users-permissions/server/utils"); const sanitizeUser = (user, ctx) => { const { auth } = ctx.state; const userSchema = strapi.getModel("plugin::users-permissions.user"); return sanitize.contentAPI.output(user, userSchema, { auth }); }; const userPermissionExtension = (plugin) => { /** Example of overriding and adding a new endpoint, check the section where we have registered this as a route below. */ plugin.controllers.user.updateMe = (ctx) => { ctx.params.id = ctx.state.user.id; return plugin.controllers.user.update(ctx); }; plugin.controllers.user.startEndUserOtpLogin = async (ctx) => { const { emailAddress, mobileNumber } = ctx.request.body; if (!emailAddress || !mobileNumber) { throw new ValidationError( "Please specify both the email address & mobile numbers." ); } const pluginStore = await strapi.store({ type: "plugin", name: "users-permissions", }); const emailSettings = await pluginStore.get({ key: "email" }); // Find the channel partner first. // const endUser = await strapi.query("api::end-user.end-user").findOne({ // populate: ["user"], // where: { // $and: [ // { publishedAt: { $notNull: true } }, // { mobileNo: mobileNumber }, // ], // }, // }); const endUser = await strapi .query("api::end-user.end-user") .findOne({ populate: ["user"], where: { $and: [ { publishedAt: { $notNull: true } }, { mobileNo: mobileNumber }, ], }, }); if (!endUser) { throw new ValidationError( "No end user registered with specified email address, mobile number combination." ); } // Find the linked user next. const user = await strapi .query("plugin::users-permissions.user") .findOne({where:{ id: endUser.user.id }}); if (!user || user.blocked) { throw new ValidationError("Unable to resolve user linked to end user."); } const resetPasswordSettings = _.get( emailSettings, "reset_password.options", {} ); const oneTimePassword = Math.floor(100000 + Math.random() * 900000); const emailToSend = { to: user.email, from: resetPasswordSettings.from.email || resetPasswordSettings.from.name ? `${resetPasswordSettings.from.name} <${resetPasswordSettings.from.email}>` : undefined, replyTo: resetPasswordSettings.response_email, subject: `Your one time password is: ${oneTimePassword}`, text: `Hello ${endUser.fullName}, Your one time password to login to your partner portal is ${oneTimePassword}`, html: `<p>Hello ${endUser.fullName}, <br></br>Your one time password to login to the hiranandani offers portal is ${oneTimePassword}</p><br /> Best Regards, <br /> Team Hiranandani.`, }; // NOTE: Update the user before sending the email so an Admin can generate the link if the email fails const updateUser=await getService("user").edit(user.id, { oneTimePassword: `${oneTimePassword}`, }); // Send an email to the user. await strapi.plugin("email").service("email").send(emailToSend); // await getService("user").sendOTPOnEmail(emailToSend); // TODO: Send SMS. ctx.send({ ok: true, message: "otp sent" }); }; plugin.controllers.user.finishEndUserOtpLogin = async (ctx) => { const { oneTimePassword, emailAddress, mobileNumber } = ctx.request.body; if (!oneTimePassword || !mobileNumber || !emailAddress) { throw new ValidationError( "Please specify the oneTimePassword, email address and mobile numbers." ); } // Find the channel partner first. const endUser = await strapi.query("api::end-user.end-user").findOne({ populate: ["user"], where: { $and: [ { publishedAt: { $notNull: true } }, // { user: { email: emailAddress } }, { mobileNo: mobileNumber }, ], }, }); if (!endUser) { throw new ValidationError( "No end user registered with specified email address, mobile number combination." ); } // Find the linked user next. const user = await strapi.query("plugin::users-permissions.user").findOne({ where: { $and: [{ id: endUser.user.id }, { oneTimePassword: oneTimePassword }], }, }); if (!user || user.blocked) { throw new ValidationError("Code provided is not valid."); } await getService("user").edit(user.id, { oneTimePassword: null, password: oneTimePassword, }); ctx.send({ ok: true, message: "otp updated" }); }; plugin.controllers.user.startChannelPartnerOtpLogin = async (ctx) => { const { mahareraNumber, mobileNumber } = ctx.request.body; if (!mahareraNumber || !mobileNumber) { throw new ValidationError( "Please specify both the maharera & mobile numbers." ); } const pluginStore = await strapi.store({ type: "plugin", name: "users-permissions", }); const emailSettings = await pluginStore.get({ key: "email" }); // Find the channel partner first. const channelPartner = await strapi .query("api::channel-partner.channel-partner") .findOne({ populate: ["user"], where: { $and: [ { publishedAt: { $notNull: true } }, { reraNumber: mahareraNumber }, { mobileNo: mobileNumber }, ], }, }); if (!channelPartner) { throw new ValidationError( "No channel partner registered with specified maharera number, mobile number combination." ); } // Find the linked user next. const user = await strapi .query("plugin::users-permissions.user") .findOne({where:{ id: channelPartner.user.id }}); if (!user || user.blocked) { throw new ValidationError( "Unable to resolve user linked to channel partner." ); } const resetPasswordSettings = _.get( emailSettings, "reset_password.options", {} ); const oneTimePassword = Math.floor(100000 + Math.random() * 900000); const emailToSend = { to: user.email, from: resetPasswordSettings.from.email || resetPasswordSettings.from.name ? `${resetPasswordSettings.from.name} <${resetPasswordSettings.from.email}>` : undefined, replyTo: resetPasswordSettings.response_email, subject: `Your one time password is: ${oneTimePassword}`, text: `Hello ${channelPartner.contactPersonName}, Your one time password to login to your partner portal is ${oneTimePassword}`, html: `<p>Hello ${channelPartner.contactPersonName}, <br></br>Your one time password to login to your partner portal is ${oneTimePassword}</p><br /> Best Regards, <br /> Team Hiranandani.`, }; // NOTE: Update the user before sending the email so an Admin can generate the link if the email fails await getService("user").edit(user.id, { oneTimePassword: `${oneTimePassword}`, }); // Send an email to the user. await strapi.plugin("email").service("email").send(emailToSend); // TODO: Send SMS. ctx.send({ ok: true, message: "otp sent" }); }; plugin.controllers.user.finishChannelPartnerOtpLogin = async (ctx) => { const { oneTimePassword, mahareraNumber, mobileNumber } = ctx.request.body; if (!oneTimePassword || !mobileNumber || !mahareraNumber) { throw new ValidationError( "Please specify the oneTimePassword, maharera number and mobile numbers." ); } // Find the channel partner first. const channelPartner = await strapi .query("api::channel-partner.channel-partner") .findOne({ populate: ["user"], where: { $and: [ { publishedAt: { $notNull: true } }, { reraNumber: mahareraNumber }, { mobileNo: mobileNumber }, ], }, }); if (!channelPartner) { throw new ValidationError( "No channel partner registered with specified maharera number, mobile number combination." ); } // Find the linked user next. const user = await strapi.query("plugin::users-permissions.user").findOne({ where: { $and: [ { id: channelPartner.user.id }, { oneTimePassword: oneTimePassword }, ], }, }); if (!user || user.blocked) { throw new ValidationError("Code provided is not valid."); } await getService("user").edit(user.id, { oneTimePassword: null, password: oneTimePassword, }); ctx.send({ ok: true, message: "otp updated" }); }; /** Example of overriding an existing route. */ plugin.controllers.auth.register = async (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", "blocked", "confirmationToken", "resetPasswordToken", "provider", ]), provider: "local", }; await validateRegisterBody(params); // We have added the ability to choose the role. // This is the customisation that we wanted to do to make this possible const newUserRole = params?.role ? params?.role : settings.default_role; // the query was also changed to apply a query on "name" rather than the default "type". const role = await strapi .query("plugin::users-permissions.role") .findOne({ where: { name: newUserRole } }); if (!role) { throw new ApplicationError("Impossible to find the default role"); } // @ts-ignore const { email, username, provider } = params; const identifierFilter = { $or: [ { email: email.toLowerCase() }, { username: email.toLowerCase() }, { username }, { email: username }, ], }; const conflictingUserCount = await strapi .query("plugin::users-permissions.user") .count({ where: { ...identifierFilter, provider }, }); if (conflictingUserCount > 0) { throw new ApplicationError("Email or Username are already taken"); } if (settings.unique_email) { const conflictingUserCount = await strapi .query("plugin::users-permissions.user") .count({ where: { ...identifierFilter }, }); if (conflictingUserCount > 0) { throw new ApplicationError("Email or Username are already taken"); } } let newUser = { ...params, role: role.id, email: email.toLowerCase(), username, confirmed: !settings.email_confirmation, }; const user = await strapi .plugin("users-permissions") .service("user") .add(newUser); const sanitizedUser = await sanitizeUser(user, ctx); if (settings.email_confirmation) { try { await strapi .plugin("users-permissions") .service("user") .sendConfirmationEmail(sanitizedUser); } catch (err) { throw new ApplicationError(err.message); } return ctx.send({ user: sanitizedUser }); } const jwt = strapi .plugin("users-permissions") .service("jwt") .issue(_.pick(user, ["id"])); return ctx.send({ jwt, user: sanitizedUser, }); }; /** Endpoint used to allow edits on a user done by currently logged in user only their own record. */ plugin.routes["content-api"].routes.push({ method: "PUT", path: "/users/me", handler: "user.updateMe", }); /** Endpoints used to facilitate channel partner login with otp */ plugin.routes["content-api"].routes.push({ method: "POST", path: "/users/channel-partner/start-otp-login", handler: "user.startChannelPartnerOtpLogin", }); plugin.routes["content-api"].routes.push({ method: "POST", path: "/users/channel-partner/finish-otp-login", handler: "user.finishChannelPartnerOtpLogin", }); /** Endpoints used to facilitate end user login with otp */ plugin.routes["content-api"].routes.push({ method: "POST", path: "/users/end-user/start-otp-login", handler: "user.startEndUserOtpLogin", }); plugin.routes["content-api"].routes.push({ method: "POST", path: "/users/end-user/finish-otp-login", handler: "user.finishEndUserOtpLogin", }); return plugin; }; module.exports = userPermissionExtension;