strapi-server.js 8.18 KB
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.startOtpLogin = 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({ 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.finishOtpLogin = 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",
  });
  plugin.routes["content-api"].routes.push({
    method: "POST",
    path: "/users/start-otp-login",
    handler: "user.startOtpLogin",
  });
  plugin.routes["content-api"].routes.push({
    method: "POST",
    path: "/users/finish-otp-login",
    handler: "user.finishOtpLogin",
  });

  return plugin;
};

module.exports = userPermissionExtension;