If in next-auth.functions.js pass Model instance of Mongoose rather then Object Collection instance of MongoClient, in this case user.emailToken stays after signing in till next email sign in.
// User.js
const mongoose = require('mongoose')
const UserSchema = new mongoose.Schema({
_id : mongoose.Schema.Types.ObjectId,
pass : String,
name : String,
email : String,
google : Object,
admin : Boolean,
emailVerified: Boolean,
emailToken : String
})
mongoose.model('User', UserSchema)
// next-auth.functions.js
const mongoose = require('mongoose')
require('./models/User')
const User = mongoose.model('User')
module.exports = () => {
new Promise((resolve, reject) => {
if (!User) reject('new Error(\'\\n connection error\')')
resolve(User)
}).then((User) => {
return Promise.resolve({
find: ({id, email, emailToken, provider} = {}) => {
let query = {}
if (id) {
query = {_id: ObjectId(id)}
} else if (email) {
query = {email: email}
} else if (emailToken) {
query = {emailToken: emailToken}
} else if (provider) {
query = {[`${provider.name}.id`]: provider.id}
}
return new Promise((resolve, reject) => {
User.findOne(query, (err, user) => {
return err ? reject((err)) : resolve(user)
})
})
},
insert: (user, oAuthProfile) => {
return new Promise((resolve, reject) => {
User.insert(user, (err, response) => {
if (err) return reject(err)
if (!user._id && response._id) user._id = response._id
return resolve(user)
})
})
},
update: (user, profile) => {
return new Promise((resolve, reject) => {
User.update({_id: ObjectId(user._id)}, user, {}, err => {
**// it passes user.emailToken after sign in**
return err ? reject(err) : resolve(user)
})
})
},
remove: (id) => {
return new Promise((resolve, reject) => {
User.remove({_id: ObjectId(id)}, (err) => {
if (err) return reject(err)
return resolve(true)
})
})
},
serialize: (user) => {
if (user.id) {
return Promise.resolve(user.id)
} else if (user._id) {
return Promise.resolve(user._id)
} else {
return Promise.reject(new Error("Unable to serialise user"))
}
},
deserialize: (id) => {
return new Promise((resolve, reject) => {
User.findOne({_id: ObjectId(id)}, (err, user) => {
!!err && reject(err)
!user && resolve(null)
return resolve({
id : user._id,
name : user.name,
email : user.email,
emailVerified: user.emailVerified,
admin : user.admin || false,
})
})
})
},
sendSignInEmail: ({
email = null,
url = null
} = {}) => {
nodemailer
.createTransport(nodemailerTransport)
.sendMail({
to : email,
from : process.env.EMAIL_FROM,
subject: 'Sign in link',
text : `Use the link below to sign in:\n\n${url}\n\n`,
html : `<p>Use the link below to sign in:</p><p>${url}</p>`
}, (err) => {
if (err) {
console.error('Error sending email to ' + email, err)
}
})
if (process.env.NODE_ENV === 'development') {
console.log('------>>>>> Generated sign in link ' + url + ' for ' + email)
}
}
})
})
}
}
or am i wrong somewhere?
Thanks for sharing your config!
Hmm yes this might be a problem with how NextAuth currently works and about the design being too mongo specific. The email token SHOULD NOT be present on the user object on sign in.
To get this to work with Mongoose you will need to check for the property and explicitly do something like unset it and save the change (or call update with unset).
https://stackoverflow.com/questions/4486926/delete-a-key-from-a-mongodb-document-using-mongoose
e.g.
update(user, profile, { delete: 'emailToken' })update(user, profile, { delete: 'facebook' })update(user, profile, { delete: 'google' })This is a bit simple but is non breaking and would make it easier to support as you could just check for a options.delete and tell Mongoose to remove that key if it is present.
I am happy to add this today if you want.
Thank you again for sharing your config. If you don't mind, I'd like to include it in the examples as people ask for a Mongoose example quite a bit.
Thank you for a cool lib!
It would be nice to have at least a solution with a third property, like above!
Thank you anyway once again!
Greets!
Okies, I'm having problems with Mongoose not connecting to Mongo which is super weird.
I have published the above as 1.8.1 though (third option on update()).
I'd be really interested if it works for you. We still don't have a Mongoose reference example yet.
I'll probably try and figure out why Mongoose isn't working over the weekend.
It works for me.
Thank you!
Thanks so much for letting me know! :-)
I will figure out what's wrong with my local instance and use the config you've provided as a base for it so other folks have something they can copy/paste to get started (at least until we have 2.0).
Hello Iain.
I've found a couple 馃悶 in config i posted above. Here are som corrections if needed:
import mongoose from 'mongoose'
import User from './server/models/User'
// Use Node Mailer for email sign in
import nodemailer from 'nodemailer'
import nodemailerSmtpTransport from 'nodemailer-smtp-transport'
import nodemailerDirectTransport from 'nodemailer-direct-transport'
// Load environment variables from a .env file if one exists
require( 'dotenv' ).load()
// Connect mongoose to DB
const MONGO_URI = process.env.MONGO_URI
mongoose.connect( MONGO_URI, {dbName: '<BD_NAME>'} )
mongoose.connection.on( 'connected', () => {
console.log( '馃敆 Mongoose successfully connected to DB' )
} )
const ObjectId = mongoose.Types.ObjectId
// Send email direct from localhost if no mail server configured
let nodemailerTransport = nodemailerDirectTransport()
if (process.env.EMAIL_SERVER && process.env.EMAIL_USERNAME && process.env.EMAIL_PASSWORD) {
nodemailerTransport = nodemailerSmtpTransport( {
host : process.env.EMAIL_SERVER,
port : process.env.EMAIL_PORT || 25,
secure: process.env.EMAIL_SECURE,
auth : {
user: process.env.EMAIL_USERNAME,
pass: process.env.EMAIL_PASSWORD
}
} )
}
module.exports = () => {
return new Promise( ( resolve, reject ) => {
return mongoose.connection ? resolve( User ) : reject( 'Error! No connection with DB' )
} )
.then( User => {
return Promise.resolve( {
// If a user is not found find() should return null (with no error).
find : ( {id, email, emailToken, provider} = {} ) => {
let query = {}
if (id) {
query = {_id: ObjectId( id )}
} else if (email) {
query = {email: email}
} else if (emailToken) {
query = {emailToken: emailToken}
} else if (provider) {
query = {[ `${provider.name}.id` ]: provider.id}
}
return new Promise( ( resolve, reject ) => {
User
.findOne( query )
.then( user => resolve( user ? user.toJSON() : null ) )
.catch( err => reject( err ) )
} )
},
insert : ( user, oAuthProfile ) => {
return new Promise( ( resolve, reject ) => {
// next-auth returns normalized user, so the rest of fields needed from oAuthProfile must be added here
if (user.google) user.google.avatarURL = oAuthProfile && oAuthProfile.photos ? oAuthProfile.photos[ 0 ].value : null
User
.create( user )
.then( response => {
if (!user._id && response._id) user._id = response._id
resolve( user )
} )
.catch( err => reject( err ) )
} )
},
update : ( user, profile, field ) => {
return new Promise( ( resolve, reject ) => {
const mod = field ? {$unset: {[ field.delete ]: 1}} : user
User
.update( {_id: ObjectId( user._id )}, mod, {new: true} )
.then( resp => resolve( user.toJSON() ) )
.catch( err => reject( err ) )
} )
},
remove : ( id ) => {
return new Promise( ( resolve, reject ) => {
User.remove( {_id: ObjectId( id )}, ( err ) => {
if (err) return reject( err )
return resolve( true )
} )
} )
},
serialize :
( user ) => {
// Supports serialization from Mongo Object *and* deserialize() object
if (user.id) {
// Handle responses from deserialize()
return Promise.resolve( user.id )
} else if (user._id) {
// Handle responses from find(), insert(), update()
return Promise.resolve( user._id )
} else {
return Promise.reject( new Error( "Unable to serialise user" ) )
}
},
deserialize :
( id ) => {
return new Promise( ( resolve, reject ) => {
User.findOne( {_id: ObjectId( id )}, ( err, user ) => {
if (err) return reject( err )
// If user not found (e.g. account deleted) return null object
if (!user) return resolve( null )
return resolve( {
id : user._id,
name : user.name,
email : user.email,
emailVerified: user.emailVerified,
admin : user.admin || false
} )
} )
} )
},
sendSignInEmail:
( {email, url, req} ) => {
nodemailer
.createTransport( nodemailerTransport )
.sendMail( {
to : email,
from : process.env.EMAIL_FROM,
subject: 'Sign in link',
text : `Use the link below to sign in:\n\n${url}\n\n`,
html : `<p>Use the link below to sign in:</p><p>${url}</p>`
}, ( err ) => {
if (err) {
console.error( 'Error sending email to ' + email, err )
}
} )
if (process.env.NODE_ENV === 'development') {
console.log( 'Generated sign in link ' + url + ' for ' + email )
}
}
}
)
}
)
}
should work )
Thanks for sharing! Hope to get back to this (for 2.0) very soon!
Um, apparently 'very soon' has been 2 years. 馃檮
The good news is have actually been on it and version 2.0 is right around the corner!
Most helpful comment
Thanks for sharing! Hope to get back to this (for 2.0) very soon!