tracman-server/config/passport.js

234 lines
7.6 KiB
JavaScript
Executable File

'use strict'
const LocalStrategy = require('passport-local').Strategy
const GoogleStrategy = require('passport-google-oauth20').Strategy
const FacebookStrategy = require('passport-facebook').Strategy
const TwitterStrategy = require('passport-twitter').Strategy
const GoogleTokenStrategy = require('passport-google-id-token')
const FacebookTokenStrategy = require('passport-facebook-token')
const TwitterTokenStrategy = require('passport-twitter-token')
const sanitize = require('mongo-sanitize')
const debug = require('debug')('tracman-passport')
const env = require('./env/env.js')
const mw = require('./middleware.js')
const User = require('./models.js').user
module.exports = (passport) => {
// Serialize/deserialize users
passport.serializeUser((user, done) => {
done(null, user.id)
})
passport.deserializeUser((id, done) => {
User.findById(id, (err, user) => {
if (err) done(err)
else done(null, user)
})
})
// Local
passport.use('local', new LocalStrategy({
usernameField: 'email',
passwordField: 'password',
passReqToCallback: true
}, async (req, email, password, done) => {
debug(`Perfoming local login for ${email}`)
try {
let user = await User.findOne({'email': sanitize(email)})
// No user with that email
if (!user) {
debug(`No user with that email`)
return done(null, false, req.flash('warning', 'Incorrect email or password.'))
// User exists
} else {
debug(`User exists. Checking password...`)
// Check password
let correct_password = await user.validPassword(password)
// Password incorrect
if (!correct_password) {
debug(`Incorrect password`)
return done(null, false, req.flash('warning', 'Incorrect email or password.'))
// Successful login
} else {
user.isNewUser = !Boolean(user.lastLogin)
user.lastLogin = Date.now()
user.save()
return done(null, user)
}
}
} catch (err) {
return done(err)
}
}
))
// Social login
async function socialLogin(req, service, profileId, done) {
debug(`socialLogin() called for ${service} account ${profileId}`)
let query = {}
query['auth.' + service] = profileId
// Intent to log in
if (!req.user) {
debug(`Searching for user with query ${query}...`)
try {
let user = await User.findOne(query)
// Can't find user
if (!user) {
// Lazy update from old googleId field
if (service === 'google') {
try {
let user = await User.findOne({ 'googleID': parseInt(profileId, 10) })
// User exists with old schema
if (user) {
debug(`User ${user.id} exists with old schema. Lazily updating...`)
user.auth.google = profileId
user.googleId = undefined
try {
await user.save()
debug(`Lazily updated ${user.id}...`)
req.session.flashType = 'success'
req.session.flashMessage = 'You have been logged in. '
return done(null, user)
} catch (err) {
debug(`Failed to save user that exists with old googleId schema!`)
mw.throwErr(err, req)
return done(err)
}
// No such user
} else {
debug(`User with ${service} account of ${profileId} not found.`)
req.flash('warning', `There's no user for that ${service} account. `)
return done()
}
} catch (err) {
debug(`Failed to search for user with old googleID of ${profileId}. `)
mw.throwErr(err, req)
return done(err)
}
// No googleId either
} else {
debug(`Couldn't find ${service} user with profileID ${profileId}.`)
req.flash('warning', `There's no user for that ${service} account. `)
return done()
}
// Successfull social login
} else {
debug(`Found user: ${user.id}; logging in...`)
req.session.flashType = 'success'
req.session.flashMessage = 'You have been logged in.'
return done(null, user)
}
} catch (err) {
debug(`Failed to find user with query: ${query}`)
mw.throwErr(err, req)
return done(err)
}
// Intent to connect account
} else {
debug(`Attempting to connect ${service} account to ${req.user.id}...`)
// Check for unique profileId
debug(`Checking for unique account with query ${query}...`)
try {
let existing_user = await User.findOne(query)
// Social account already in use
if (existing_user) {
debug(`${service} account already in use with user ${existing_user.id}`)
req.session.flashType = 'warning'
req.session.flashMessage = `Another user is already connected to that ${service} account. `
return done()
// Connect to account
} else {
debug(`${service} account (${profileId}) is unique; Connecting to ${req.user.id}...`)
req.user.auth[service] = profileId
try {
await req.user.save()
debug(`Successfully connected ${service} account to ${req.user.id}`)
req.session.flashType = 'success'
req.session.flashMessage = `${mw.capitalize(service)} account connected. `
return done(null, req.user)
} catch (err) {
debug(`Failed to connect ${service} account to ${req.user.id}!`)
return done(err)
}
}
} catch (err) {
debug(`Failed to check for unique ${service} profileId of ${profileId}!`)
mw.throwErr(err, req)
return done(err)
}
}
}
// Google
passport.use('google', new GoogleStrategy({
clientID: env.googleClientId,
clientSecret: env.googleClientSecret,
callbackURL: env.url + '/login/google/cb',
passReqToCallback: true
}, (req, accessToken, refreshToken, profile, done) => {
socialLogin(req, 'google', profile.id, done)
}
)).use('google-token', new GoogleTokenStrategy({
clientID: env.googleClientId,
passReqToCallback: true
}, (req, parsedToken, googleId, done) => {
socialLogin(req, 'google', googleId, done)
}
))
// Facebook
passport.use('facebook', new FacebookStrategy({
clientID: env.facebookAppId,
clientSecret: env.facebookAppSecret,
callbackURL: env.url + '/login/facebook/cb',
passReqToCallback: true
}, (req, accessToken, refreshToken, profile, done) => {
socialLogin(req, 'facebook', profile.id, done)
}
)).use('facebook-token', new FacebookTokenStrategy({
clientID: env.facebookAppId,
clientSecret: env.facebookAppSecret,
passReqToCallback: true
}, (req, accessToken, refreshToken, profile, done) => {
socialLogin(req, 'facebook', profile.id, done)
}
))
// Twitter
passport.use(new TwitterStrategy({
consumerKey: env.twitterConsumerKey,
consumerSecret: env.twitterConsumerSecret,
callbackURL: env.url + '/login/twitter/cb',
passReqToCallback: true
}, (req, token, tokenSecret, profile, done) => {
socialLogin(req, 'twitter', profile.id, done)
}
)).use('twitter-token', new TwitterTokenStrategy({
consumerKey: env.twitterConsumerKey,
consumerSecret: env.twitterConsumerSecret,
passReqToCallback: true
}, (req, token, tokenSecret, profile, done) => {
socialLogin(req, 'twitter', profile.id, done)
}
))
return passport
}