strapi / strapi

🚀 Strapi is the leading open-source headless CMS. It’s 100% JavaScript/TypeScript, fully customizable, and developer-first.
https://strapi.io
Other
63.86k stars 8.13k forks source link

Allow change user email as non required field #5061

Closed abdonrd closed 4 years ago

abdonrd commented 4 years ago

Please describe your feature request:

Allow change user email as non required field.

lauriejim commented 4 years ago

Hello @abdonrd , can you please share a use case.

abdonrd commented 4 years ago

Right now, we have an application where the users do not have email. They only have name and username.

lauriejim commented 4 years ago

Okay and you want this feature available from the admin panel.

The workaround is to customize the extensions/users-permissions/models/User.settings.json and to set the required value of email to false. Then to customize the register function and remove the email validation.

derrickmehaffy commented 4 years ago

Marking as closed as this is a niche use-case that will always require customization as we need to have one main required field for the user. Most of the code-base uses email (including the admin panel, where the username is not required).

In the case of 3rd party auth, email is almost exclusively unique and usernames are not.

jnuno98 commented 3 years ago

Why don't you just make the User table fully customizable? I really don't understand why it is completely locked I am doing an application where i really don't even want an User table with this format and it is a bit annoying that it can't be easily changed

shestakov-vladyslav commented 2 years ago

I do support last comment

SzczurekYT commented 2 years ago

I also. I'm making an app, and I don't need an email. It would be a lot easier for me If I could just remove all the email stuff.

asadul44 commented 2 years ago

After change as you mention, in strapi version 4.1.12 when create user show again error the email required

ventsislavnikolov commented 1 year ago

Did anyone find a solution in Strapi 4 to set username not required?

MrTheFirst commented 1 year ago

In my application, I create users via telegram by phone number, the user's email also interferes.

creazy231 commented 1 year ago

I have some applications using MetaMask authentication and therefore there is no need for an email. Also have some applications where I just don't need a username - only email and password.

ArthurPedroti commented 1 year ago

Does anyone have a solution for this problem in 2023?

Only changing the schema.json on "src/extensions/users-permissions/content-types/user/schema.json" doesn't work anymore

I tried to change to "required": false or "unique": false,, but the admin interface seems to change but the validation still occours

creazy231 commented 1 year ago

Does anyone have a solution for this problem in 2023?

Only changing the schema.json on "src/extensions/users-permissions/content-types/user/schema.json" doesn't work anymore

I tried to change to "required": false or "unique": false,, but the admin interface seems to change but the validation still occours

have you tried running yarn build / npm run build after you've changed the schema.json?

derrickmehaffy commented 1 year ago

Build doesn't do anything for the backend, all it does is build the admin panel.

derrickmehaffy commented 1 year ago

Currently it isn't really possible to completely disable it as it's basically hard coded into the plugin to require it but we are working on plans to build an entirely new plugin that will replace the current one.

creazy231 commented 1 year ago

When you build an entirely new plugin, please also add support for adding or saving metadata from OAuth's to the user. For example: When a user logging in using discord I'd like to get and safe his discord_id in some way. Currently I have to patch the user-permissions plugin to be able to which isn't that cool tbh 💀

SzczurekYT commented 1 year ago

Yeah. The issue with is that so need a required, unique filed, and currently it is the email field. I think that in the new version strapi should allow editing the User table, and just required the admin to set a field which will be used to identify a user. As you saw in the comments it doesn't need to be an email and possibilities are endless, it could ba a phone number, some id, unique tag etc.

ArthurPedroti commented 1 year ago

If I can set the email to not be unique, I can make a workaround and put some admin email in always account, but even that I can't do, and it's complicated if you are doing some other use cases and your users don't have emails.

I think if we can choose to email not be unique, we can make a workaround and don't need to do a big modification on the plugin, because email will continue to be required.

infomus commented 10 months ago

It's 2024 and this is still a very much needed customization. I don't agree that it's niche - there are many applications I can think of that do not and should not require an email to use

infomus commented 10 months ago

I found a workaround if anyone is interested:

Change configurable to false and required to true in server/src/extensions/users-permissions/content-types/user/schema.json and then go into the admin portal and delete the email field. Allow the server to restart, then re-add the email field but keep "Unique field" unchecked.

imHowie commented 9 months ago

I found a workaround if anyone is interested:如果有人感兴趣,我找到了一个解决方法:

Change configurable to false and required to true in server/src/extensions/users-permissions/content-types/user/schema.json and then go into the admin portal and delete the email field. Allow the server to restart, then re-add the email field but keep "Unique field" unchecked.更改 configurable 为 false 和 required true in server/src/extensions/users-permissions/content-types/user/schema.json ,然后进入管理门户并删除电子邮件字段。允许服务器重新启动,然后重新添加电子邮件字段,但保持“唯一字段”未选中。

此方法不管用了,我的Strapi版本是4.14.4

HarishGarg2002 commented 2 months ago

Hey I was able to solve this using these workaround. My answer may not be much well written because this is my first time I am answering somewhere but yeah it will surely help you.

So firstly change the node_modules\@strapi\plugin-users-permissions\server\content-types\user\index.js file

     username: {
      type: 'string',
      minLength: 3,
      unique: true,
      configurable: false,
      required: false,
    },
    email: {
      type: 'email',
      minLength: 6,
      configurable: false,
      required: false,
    },

Next in file node_modules\@strapi\plugin-users-permissions\server\controllers\content-manager-user.js

Update this file as (Just commenting out some code related to email and username validation)

'use strict';

const _ = require('lodash');
const { contentTypes: contentTypesUtils } = require('@strapi/utils');
const { ApplicationError, ValidationError, NotFoundError, ForbiddenError } =
  require('@strapi/utils').errors;
const { validateCreateUserBody, validateUpdateUserBody } = require('./validation/user');

const { UPDATED_BY_ATTRIBUTE, CREATED_BY_ATTRIBUTE } = contentTypesUtils.constants;

const userModel = 'plugin::users-permissions.user';
const ACTIONS = {
  read: 'plugin::content-manager.explorer.read',
  create: 'plugin::content-manager.explorer.create',
  edit: 'plugin::content-manager.explorer.update',
  delete: 'plugin::content-manager.explorer.delete',
};

const findEntityAndCheckPermissions = async (ability, action, model, id) => {
  const entity = await strapi.query(userModel).findOne({
    where: { id },
    populate: [`${CREATED_BY_ATTRIBUTE}.roles`],
  });

  if (_.isNil(entity)) {
    throw new NotFoundError();
  }

  const pm = strapi.admin.services.permission.createPermissionsManager({ ability, action, model });

  if (pm.ability.cannot(pm.action, pm.toSubject(entity))) {
    throw new ForbiddenError();
  }

  const entityWithoutCreatorRoles = _.omit(entity, `${CREATED_BY_ATTRIBUTE}.roles`);

  return { pm, entity: entityWithoutCreatorRoles };
};

module.exports = {
  /**
   * Create a/an user record.
   * @return {Object}
   */
  async create(ctx) {
    const { body } = ctx.request;
    const { user: admin, userAbility } = ctx.state;

    const pm = strapi.admin.services.permission.createPermissionsManager({
      ability: userAbility,
      action: ACTIONS.create,
      model: userModel,
    });

    if (!pm.isAllowed) {
      return ctx.forbidden();
    }

    const sanitizedBody = await pm.pickPermittedFieldsOf(body, { subject: userModel });

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

    // await validateCreateUserBody(ctx.request.body);

    // const userWithSameUsername = await strapi
    //   .query('plugin::users-permissions.user')
    //   .findOne({ where: { username } });

    // if (userWithSameUsername) {
    //   throw new ApplicationError('Username already taken');
    // }

    // if (advanced.unique_email) {
    //   const userWithSameEmail = await strapi
    //     .query('plugin::users-permissions.user')
    //     .findOne({ where: { email: email.toLowerCase() } });

    //   if (userWithSameEmail) {
    //     throw new ApplicationError('Email already taken');
    //   }
    // }

    const user = {
      ...sanitizedBody,
      provider: 'local',
      [CREATED_BY_ATTRIBUTE]: admin.id,
      [UPDATED_BY_ATTRIBUTE]: admin.id,
    };

    // user.email = _.toLower(user.email);

    if (!user.role) {
      const defaultRole = await strapi
        .query('plugin::users-permissions.role')
        .findOne({ where: { type: advanced.default_role } });

      user.role = defaultRole.id;
    }

    try {
      const data = await strapi
        .service('plugin::content-manager.entity-manager')
        .create(user, userModel);
      const sanitizedData = await pm.sanitizeOutput(data, { action: ACTIONS.read });

      ctx.created(sanitizedData);
    } catch (error) {
      throw new ApplicationError(error.message);
    }
  },
  /**
   * Update a/an user record.
   * @return {Object}
   */

  async update(ctx) {
    const { id } = ctx.params;
    const { body } = ctx.request;
    const { user: admin, userAbility } = ctx.state;

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

    // const { email, username, password } = body;

    const { pm, entity } = await findEntityAndCheckPermissions(
      userAbility,
      ACTIONS.edit,
      userModel,
      id
    );
    const user = entity;

    // await validateUpdateUserBody(ctx.request.body);

    // if (_.has(body, 'password') && !password && user.provider === 'local') {
    //   throw new ValidationError('password.notNull');
    // }

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

    //   if (userWithSameUsername && _.toString(userWithSameUsername.id) !== _.toString(id)) {
    //     throw new ApplicationError('Username already taken');
    //   }
    // }

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

    //   if (userWithSameEmail && _.toString(userWithSameEmail.id) !== _.toString(id)) {
    //     throw new ApplicationError('Email already taken');
    //   }
    //   body.email = _.toLower(body.email);
    // }

    const sanitizedData = await pm.pickPermittedFieldsOf(body, { subject: pm.toSubject(user) });
    const updateData = _.omit({ ...sanitizedData, updatedBy: admin.id }, 'createdBy');

    const data = await strapi
      .service('plugin::content-manager.entity-manager')
      .update({ id }, updateData, userModel);

    ctx.body = await pm.sanitizeOutput(data, { action: ACTIONS.read });
  },
};

Now we can update the code in node_modules\@strapi\plugin-users-permissions\server\content-types\user\index.js file I dont think this step is necessary as we have already commented out the code using functions from this file but still there may be any other file using this functions, so I still changed it

'use strict';

const { yup, validateYupSchema } = require('@strapi/utils');

const deleteRoleSchema = yup.object().shape({
  role: yup.strapiID().required(),
});

const createUserBodySchema = yup.object().shape({
  email: yup.string().email().optional(),
  username: yup.string().optional(),
  password: yup.string().optional(),
  role: yup.lazy((value) =>
    typeof value === 'object'
      ? yup
          .object()
          .shape({
            connect: yup
              .array()
              .of(yup.object().shape({ id: yup.strapiID().required() }))
              .min(1, 'Users must have a role')
              .required(),
          })
          .required()
      : yup.strapiID().required()
  ),
});

const updateUserBodySchema = yup.object().shape({
  email: yup.string().email().optional(),
  username: yup.string().optional(),
  password: yup.string().optional(),
  role: yup.lazy((value) =>
    typeof value === 'object'
      ? yup.object().shape({
          connect: yup
            .array()
            .of(yup.object().shape({ id: yup.strapiID().required() }))
            .required(),
          disconnect: yup
            .array()
            .test('CheckDisconnect', 'Cannot remove role', function test(disconnectValue) {
              if (value.connect.length === 0 && disconnectValue.length > 0) {
                return false;
              }

              return true;
            })
            .required(),
        })
      : yup.strapiID()
  ),
});

module.exports = {
  validateCreateUserBody: validateYupSchema(createUserBodySchema),
  validateUpdateUserBody: validateYupSchema(updateUserBodySchema),
  validateDeleteRoleBody: validateYupSchema(deleteRoleSchema),
};