kucherenko / strapi-plugin-passwordless

A plugin for Strapi Headless CMS that provides ability to sign-in/sign-up to an application by link had sent to email.
MIT License
77 stars 27 forks source link

Extending / customizing a plugin #18

Open Chmarusso opened 1 year ago

Chmarusso commented 1 year ago

Did anyone try to extend strapi-plugin-passwordless?

I tried methods suggested in official docs and I'm not able to customize createToken service function.

I've tried the extensions folder technique and override it through server-strapi.js. Any advice on what should I try next?

kucherenko commented 1 year ago

Hi, thank you for the issue.

To be honest I didn't try to rewrite services, could you please give me the link to the official docs that are you trying to follow?

kucherenko commented 1 year ago

@Chmarusso Also, I can try to implement the logic in the public version of the plugin. I had planned to add token patterns - numbers, symbols, etc.

Chmarusso commented 1 year ago

@kucherenko

That's a tutorial that I tried to follow: https://docs.strapi.io/dev-docs/plugins-extension#extending-a-plugin-s-interface

I had planned to add token patterns - numbers, symbols, etc.

That is exactly the reason why I'm trying to customize your extension 😃 I would like to use customAlphabet from nanoid and just generate tokens that are only containing 0-9

Chmarusso commented 1 year ago

In the meantime, I managed to override plugin in the following way:

Not ideal, but works.

Regarding how your plugin may support custom alphabets in future. Perhaps something like this:

    async createToken(email, context) {
      const settings = await this.settings();
      const {token_length = 20, alphabet = "-_1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"} = settings;
      await strapi.query('plugin::passwordless.token').update({where: {email}, data: {is_active: false}});
      const body = customAlphabet(alphabet, token_length);
      const tokenInfo = {
        email,
        body: body(),
        context: JSON.stringify(context)
      };
      return strapi.query('plugin::passwordless.token').create({data: tokenInfo});
    },
striveShop commented 6 months ago

@kucherenko

Hi, is there any progress there.

I plan to generate a code that only contains numbers.

Are there already settings for this or do you have to overwrite the code yourself?

marcosnc08 commented 5 months ago

Hi! I could do this, it may help:

  1. Create a folder named passwordless in src/extensions
  2. Create a strapi-server.ts file with the following:
import passwordlessService from "./services/passwordless";

export default (plugin) => {
  plugin.services.passwordless = passwordlessService;

  return plugin;
};

Use this function to replace a controller or service as you wish. In my case replaces the passwordless service with a custom service.

striveShop commented 5 months ago

Hi! I could do this, it may help:

  1. Create a folder named passwordless in src/extensions
  2. Create a strapi-server.ts file with the following:
import passwordlessService from "./services/passwordless";

export default (plugin) => {
  plugin.services.passwordless = passwordlessService;

  return plugin;
};

Use this function to replace a controller or service as you wish. In my case replaces the passwordless service with a custom service.

Okay thanks i will try it

cvolant commented 5 months ago

Thanks @marcosnc08

Here is a complete working implementation.

// src/extenstions/passwordless/strapi-server.ts
import { Strapi } from '@strapi/strapi'
import passwordlessService from 'strapi-plugin-passwordless/server/services/passwordless'

const extendedPasswordlessService = ({ strapi }: { strapi: Strapi }) => {
  const { sendLoginLink: _extracted, ...otherMethods } = passwordlessService({ strapi })

  return {
    ...otherMethods,
    async sendLoginLink(token) {
      const settings = await this.settings()
      const user = await this.fetchUser({ email: token.email })
      let context = undefined

      try {
        context = JSON.parse(JSON.parse(token.context))
      } catch (_) {
        console.error('passwordless send-link failed to parse context: context should be in JSON format.')
      }

      const params = {
        URL: settings.confirmationUrl,
        CODE: token.body,
        USER: user,
        CONTEXT: context,
      }

      console.log('sendLoginLink params:', params)

      const text = await this.template(settings.message_text, params)
      const html = await this.template(settings.message_html, params)
      const subject = await this.template(settings.object, params)

      const sendData = {
        to: token.email,
        from: settings.from_email && settings.from_name ? `${settings.from_name} <${settings.from_email}>` : undefined,
        replyTo: settings.response_email,
        subject,
        text,
        html,
      }
      // Send an email to the user.
      return await strapi.plugin('email').service('email').send(sendData)
    },
  }
}

export default (plugin) => {
  plugin.services.passwordless = extendedPasswordlessService

  return plugin
}

And here is the log I get:

sendLoginLink params: {
  URL: 'http://localhost:3000/fr/mon-compte',
  CODE: 'k3D2mMGKZqkqv4gCkUmq',
  USER: {
    id: 18,
    username: 'Kim',
    email: 'kim@test.com',
    provider: null,
    confirmed: false,
    blocked: false,
    createdAt: '2024-02-01T11:20:58.478Z',
    updatedAt: '2024-02-01T11:20:58.478Z',
    role: {
      id: 6,
      name: 'Authenticated',
      description: 'Default role given to authenticated user.',
      type: 'authenticated',
      createdAt: '2023-03-02T18:53:41.863Z',
      updatedAt: '2023-03-02T18:53:41.863Z'
    }
  },
  CONTEXT: { to: 'http://localhost:3000/fr/ici' }
}

And here is how it can be used in the admin "Subject", "Message (text)" and "Message (html)" fields:

Context.to: <%= CONTEXT.to %>
USER.username: <%= USER.username %>

:warning: With this implementation, the context param of the request should be formatted in json.

Ex: {"to":"http://localhost:3000/fr/ici"}.

cvolant commented 5 months ago

I saw in the code that the templates use lodash template, where we can find more details about syntax.

SetiZ commented 2 days ago

thank @cvolant for the code exemple, I was able to change some parts and use an email template from Brevo directly, instead of the standard template of passwordless plugin