resetPassword work like change password and recover password 🤷‍♂️

I have been watching the mutation for resetPassword and have some concerns.

In a normal flow, the user has two use cases:

  1. I forgot your password
    This case occurs when the user does not remember their password and needs to recover it. The process is the standard, request via email, the email arrives with the tocken, etc. All this perfect.

  2. Change your password
    In this case, the user currently knows his password and simply wants to update it, the flow would be: current password, new password and confirmation.

Mutation.resetPassword performs the function of changing the password when it has been requested, that is, when the user has indicated that it must be recovered.

Now, how is the user supposed to be able to change her password without having to give her the forget my password? This is not a natural flow.

I expected something like:

Mutation.resetPassword fields:
currentPassword
newPassword
passwordConfirmation

Mutation.recoverPassword fields:
password
passwordConfirmation
code

Please if anyone has any ideas I will appreciate it very much.

CC: @DMehaffy @MattieBelt

1 Like

@DMehaffy @MattieBelt Something they can contribute?

I personally do not like directly overriding the user-plugins extension so instead what I did was create a custom controller (based on the original update() method from the plugin). This one handles e-mail and password changes but you can easily extend it to update other fields.

/api/account/config/routes.json

   {
  "routes": [
	{
	  "method": "PUT",
	  "path": "/account/updateUser",
	  "handler": "account.updateUser",
	  "config": {
		"policies": []
	  }
	}
  ]
}

/api/account/controllers/account.js

'use strict';
const _ = require('lodash');
const { sanitizeEntity } = require('strapi-utils');

const sanitizeUser = user =>
  sanitizeEntity(user, {
	model: strapi.query('user', 'users-permissions').model,
  });


module.exports = {

  async updateUser(ctx) {
	const advancedConfigs = await strapi
	  .store({
		environment: '',
		type: 'plugin',
		name: 'users-permissions',
		key: 'advanced',
	  })
	  .get();

	  console.log(ctx);

	const { id } = ctx.state.user;
	const { email, username, password, currentPassword, passwordConfirmation } = ctx.request.body;

	const user = await strapi.plugins['users-permissions'].services.user.fetch({
	  id,
	});

	if(!email && !password){
	  return ctx.badRequest('noUpdateData');
	}

	if(password && !currentPassword){
	  return ctx.badRequest('currentPassword.isNull');
	}

	if(password && !passwordConfirmation){
	  return ctx.badRequest('passwordConfirmation.isNull');
	}

	if(password && password != passwordConfirmation){
	  return ctx.badRequest('passwordConfirmation.noMatch');
	}

	const validPassword = await strapi.plugins['users-permissions'].services.user.validatePassword(currentPassword, user.password);

	if(password && !validPassword){
	  return ctx.badRequest('currentPassword.notValid')
	}

	if (_.has(ctx.request.body, 'email') && !email) {
	  return ctx.badRequest('email.notNull');
	}

	if (_.has(ctx.request.body, 'username') && !username) {
	  return ctx.badRequest('username.notNull');
	}

	if (_.has(ctx.request.body, 'password') && !password && user.provider === 'local') {
	  return ctx.badRequest('password.notNull');
	}

	if (_.has(ctx.request.body, 'username')) {
	  const userWithSameUsername = await strapi
		.query('user', 'users-permissions')
		.findOne({ username });

	  if (userWithSameUsername && userWithSameUsername.id != id) {
		return ctx.badRequest(
		  null,
		  formatError({
			id: 'Auth.form.error.username.taken',
			message: 'username.alreadyTaken.',
			field: ['username'],
		  })
		);
	  }
	}

	if (_.has(ctx.request.body, 'email') && advancedConfigs.unique_email) {
	  const userWithSameEmail = await strapi
		.query('user', 'users-permissions')
		.findOne({ email: email.toLowerCase() });

	  if (userWithSameEmail && userWithSameEmail.id != id) {
		return ctx.badRequest(
		  null,
		  formatError({
			id: 'Auth.form.error.email.taken',
			message: 'Email already taken',
			field: ['email'],
		  })
		);
	  }
	  ctx.request.body.email = ctx.request.body.email.toLowerCase();
	}

	let updateData = {
	  ...ctx.request.body,
	};

	if (_.has(ctx.request.body, 'password') && password === user.password) {
	  delete updateData.password;
	}

	const data = await strapi.plugins['users-permissions'].services.user.edit({ id }, updateData);

	ctx.send(sanitizeUser(data));
 },

}

I am trying to do it in strapi V4 but unable to achieve result.

errors
error: Undefined attribute level operator id
Error: Undefined attribute level operator id

please let me know what I am doing wrong

"use strict";
const _ = require("lodash");
const { sanitizeEntity } = require("strapi-utils");

const sanitizeUser = (user) =>
  sanitizeEntity(user, {
    model: strapi.query("user", "users-permissions").model,
  });

module.exports = {
  async updateUser(ctx) {
    const advancedConfigs = await strapi
      .store({
        environment: "",
        type: "plugin",
        name: "users-permissions",
        key: "advanced",
      })
      .get();

    console.log(ctx);

    const { id } = ctx.state.user;
    const { email, username, password, currentPassword, passwordConfirmation } =
      JSON.parse(ctx.request.body);

    const user = await strapi.plugins["users-permissions"].services.user.fetch({
      id,
    });

    if (!email && !password) {
      return ctx.badRequest("noUpdateData");
    }

    if (password && !currentPassword) {
      return ctx.badRequest("currentPassword.isNull");
    }

    if (password && !passwordConfirmation) {
      return ctx.badRequest("passwordConfirmation.isNull");
    }

    if (password && password != passwordConfirmation) {
      return ctx.badRequest("passwordConfirmation.noMatch");
    }

    const validPassword = await strapi.plugins[
      "users-permissions"
    ].services.user.validatePassword(currentPassword, user.password);

    if (password && !validPassword) {
      return ctx.badRequest("currentPassword.notValid");
    }

    if (_.has(ctx.request.body, "email") && !email) {
      return ctx.badRequest("email.notNull");
    }

    if (_.has(ctx.request.body, "username") && !username) {
      return ctx.badRequest("username.notNull");
    }

    if (
      _.has(ctx.request.body, "password") &&
      !password &&
      user.provider === "local"
    ) {
      return ctx.badRequest("password.notNull");
    }

    if (_.has(ctx.request.body, "username")) {
      const userWithSameUsername = await strapi
        .query("user", "users-permissions")
        .findOne({ username });

      if (userWithSameUsername && userWithSameUsername.id != id) {
        return ctx.badRequest(
          null,
          formatError({
            id: "Auth.form.error.username.taken",
            message: "username.alreadyTaken.",
            field: ["username"],
          })
        );
      }
    }

    if (_.has(ctx.request.body, "email") && advancedConfigs.unique_email) {
      const userWithSameEmail = await strapi
        .query("user", "users-permissions")
        .findOne({ email: email.toLowerCase() });

      if (userWithSameEmail && userWithSameEmail.id != id) {
        return ctx.badRequest(
          null,
          formatError({
            id: "Auth.form.error.email.taken",
            message: "Email already taken",
            field: ["email"],
          })
        );
      }
      ctx.request.body.email = ctx.request.body.email.toLowerCase();
    }

    let updateData = {
      ...JSON.parse(ctx.request.body),
    };

    if (_.has(ctx.request.body, "password") && password === user.password) {
      delete updateData.password;
    }

    const data = await strapi.plugins["users-permissions"].services.user.edit(
      { id },
      updateData
    );

    ctx.send(sanitizeUser(data));
  },
};