Reputation: 6461
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
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
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