CCCC
CCCC

Reputation: 6461

feathers-authentication-management - User is already verified & not awaiting changes

Background

I am following these guides for creating email verification in Feathers.

This guide seems to be depreciated
https://blog.feathersjs.com/how-to-setup-email-verification-in-feathersjs-72ce9882e744#.4ebe36eqp

This guide seems to be newer but the steps are not complete.
https://hackernoon.com/setting-up-email-verification-in-feathersjs-ce764907e4f2

Problem

I have created an user by POST /users

Body

{
    "email": "[email protected]",
    "password": "Test@1234"
}

I have successfully received the verification email at [email protected].

When I send GET /users, the user object likes below:

        {
            "id": "76a47d8f-0e8d-4844-a8db-eb77e45ac947",
            "email": "[email protected]",
            "isVerified": "0",
            "verifyToken": "3555cad67ff8e83d24b4beadcb881e",
            "verifyExpires": "2021-04-14T09:00:07.000Z",
            "verifyChanges": {},
            "resetToken": null,
            "resetExpires": null
        },

After that, I want to verify the user by verifyToken using POST /authmanagement

Body

{
      "action": "verifySignupLong",
      "value": "3555cad67ff8e83d24b4beadcb881e"
}

Then an error occurs:

{
    "name": "BadRequest",
    "message": "User is already verified & not awaiting changes.",
    "code": 400,
    "className": "bad-request",
    "data": {},
    "errors": {
        "$className": "nothingToVerify"
    }
}

But I haven't done anything to verify user before, and user.isVerified is still "0"

Source Code
The code is basically following above guides to make.

users.model

const Sequelize = require('sequelize');
const DataTypes = Sequelize.DataTypes;

module.exports = function (app) {
  const sequelizeClient = app.get('sequelizeClient');
  const users = sequelizeClient.define('users', {
    id: {
      allowNull: false,
      primaryKey: true,
      type: DataTypes.UUID,
      defaultValue: Sequelize.UUIDV4,
    },
    email: {
      type: DataTypes.STRING,
      allowNull: false,
      unique: 'email'
    },
    password: {
      type: DataTypes.STRING,
      allowNull: false
    },
    isVerified: {
      type: DataTypes.STRING,
    },
    verifyToken: {
      type: DataTypes.STRING,
    },
    verifyExpires: {
      type: DataTypes.DATE,
    },
    verifyChanges: {
      type: DataTypes.JSON
    },
    resetToken: {
      type: DataTypes.STRING,
    },
    resetExpires: {
      type: DataTypes.DATE,
    },
  }, {
    hooks: {
      beforeCount(options) {
        options.raw = true;
      }
    }
  });

  users.associate = function (models) {};

  return users;
};

users.hook.js

const { iff, isProvider, keep, required, disallow, disablePagination, preventChanges} = require('feathers-hooks-common');
const { authenticate } = require('@feathersjs/authentication').hooks;
const errors = require('@feathersjs/errors');

const verifyHooks = require('feathers-authentication-management').hooks;
const accountService = require('../auth-management/notifier');

const sendVerificationEmail = require('../../hooks/sendVerificationEmail');

const {
  hashPassword, protect
} = require('@feathersjs/authentication-local').hooks;

module.exports = {
  before: {
    all: [      
      authenticate('jwt')
    ],
    find: [],
    get: [],
    create: [
      hashPassword('password'),
      required('password'),
      sendVerificationEmail(),
      verifyHooks.addVerification(),
    ],
    update: [disallow()],
    patch: [
      iff(
        isProvider('external'),
        preventChanges('email','verifyToken')
      )
    ],
    remove: [
      context => {
        if(context.params.user && context.id === context.params.user.id){
          throw new errors.Conflict('You cannot delete yourself.'); 
        }
      },
    ]
  },

  after: {
    all: [ 
      protect('password')
    ],
    find: [],
    get: [],
    create: [
      context => {
        accountService(context.app).notifier('resendVerifySignup', context.result)
      },
      verifyHooks.removeVerification()
    ],
    update: [disallow('external')],
    patch: [
      iff(
        isProvider('external'),    
        preventChanges(
          'email',
          'isVerified',
          'verifyToken',
          'verifyShortToken',
          'verifyExpires',
          'verifyChanges',
          'resetToken',
          'resetShortToken',
          'resetExpires'
        )
      )
    ],
    remove: []
  },
};

mailer.hook.js

const {disallow} = require('feathers-hooks-common');

module.exports = {
  before: {
    all: [disallow('external')],
  }
};

mailer.service.js

const hooks = require('./mailer.hooks');
const Mailer = require('feathers-mailer');
const smtpTransport = require('nodemailer-smtp-transport');

module.exports = function (app) {
  app.use('/mailer', Mailer(smtpTransport({
    service: 'gmail',
    auth: {
      user: "[email protected]",
      pass: "test@1234"
    }
  })));
  
  const service = app.service('mailer');
  service.hooks(hooks);
};

auth-management.hook.js

const { iff} = require('feathers-hooks-common');
const isAction = (...args) => hook => args.includes(hook.data.action);
const { authenticate } = require('@feathersjs/authentication').hooks;

module.exports = {
  before: {
    create: [
      iff(
        isAction("passwordChange", "identityChange"),
        authenticate("jwt"),
      ),
    ]
  },
};

auth-management.service.js

'use strict';
const hooks = require('./auth-management.hooks');

const authManagement = require('feathers-authentication-management');
const notifier = require('./notifier');

module.exports = function (app) {
  app.configure(authManagement(notifier(app)));
  const service = app.service('authManagement');

  service.hooks(hooks);
};

notifier.js

module.exports = function(app) {

  console.log("notifier")
  let host = app.get('host')
    function getLink(type, hash) {
      const url = 'http://localhost:3030/' + type + '?token=' + hash
      return url
    }
  
    function sendEmail(email) {
      return app.service('mailer').create(email).then(function (result) {
        console.log('Sent email', result)
      }).catch(err => {
        console.log('Error sending email', err)
      })
    }
  
    return {
      notifier: function(type, user, notifierOptions) {
        let tokenLink
        let email
        switch (type) {
          case 'resendVerifySignup': //sending the user the verification email
            tokenLink = getLink('verify', user.verifyToken)
            email = {
               from: process.env.FROM_EMAIL,
               to: user.email,
               subject: 'Verify Signup',
               html: tokenLink
            }
            return sendEmail(email)
            break
  
          case 'verifySignup': // confirming verification
            tokenLink = getLink('verify', user.verifyToken)
            email = {
               from: "[email protected]",
               to: user.email,
               subject: 'Confirm Signup',
               html: 'Thanks for verifying your email'
            }
            return sendEmail(email)
            break
  
          case 'sendResetPwd':
            tokenLink = getLink('reset', user.resetToken)
            email = {}
            return sendEmail(email)
            break
  
          case 'resetPwd':
            tokenLink = getLink('reset', user.resetToken)
            email = {}
            return sendEmail(email)
            break
  
          case 'passwordChange':
            email = {}
            return sendEmail(email)
            break
  
          case 'identityChange':
            tokenLink = getLink('verifyChanges', user.verifyToken)
            email = {}
            return sendEmail(email)
            break
  
          default:
            break
        }
      }
    }
  }

hooks/sendVerificationEmail.js

const accountService = '../services/auth-management/notifier'

module.exports =  options => hook => {
    if (!hook.params.provider) { return hook; }
    const user = hook.result
    if(hook.data && hook.data.email && user) {
      accountService(hook.app).notifier('resendVerifySignup', user)
      return hook
    }
    return hook
  }

Upvotes: 1

Views: 795

Answers (2)

wcarss
wcarss

Reputation: 11

This has been quite a while, so maybe you figured it out. But I think I did, too. :)

From your user response:

"isVerified": "0",

and from your user model:

    isVerified: {
      type: DataTypes.STRING,
    },

isVerified is a Boolean value, and the string "0" is truthy. If the library is checking if (user.isVerified) internally, "0" will cause that check to return true.

I suggest changing your user model's datatype for isVerified to DataTypes.BOOLEAN, deleting the current user, and re-creating the user. Their isVerified should show up as false rather than "0", and the problem should be fixed.

Upvotes: 1

v1p
v1p

Reputation: 133

Your notifier.js does not implement a handle for action verifySignupLong Just asking the obvious here, that have you tried using following in the request sent to /authmanagement ?

{
      "action": "verifySignup",
      "value": "3555cad67ff8e83d24b4beadcb881e"
}

Upvotes: 0

Related Questions