UNPKG

sails-hook-blacksails

Version:
442 lines (398 loc) 13.8 kB
import _ from 'lodash'; import crypto from 'crypto'; import passport from 'passport'; import url from 'url'; /** * Passport Service # * A painless Passport.js service for your Sails app that is guaranteed to * Rock Your Socks™. It takes all the hassle out of setting up Passport.js by * encapsulating all the boring stuff in two functions: # * passport.endpoint() * passport.callback() # * The former sets up an endpoint (/auth/:provider) for redirecting a user to a * third-party provider for authentication, while the latter sets up a callback * endpoint (/auth/:provider/callback) for receiving the response from the * third-party provider. All you have to do is define in the configuration which * third-party providers you'd like to support. It's that easy! # * Behind the scenes, the service stores all the data it needs within "Pass- * ports". These contain all the information required to associate a local user * with a profile from a third-party provider. This even holds true for the good * ol' password authentication scheme – the Authentication Service takes care of * encrypting passwords and storing them in Passports, allowing you to keep your * User model free of bloat. */ passport.protocols = sails.config.protocols; // passport.protocols = require('./protocols'); /** * Connect a third-party profile to a local user # * This is where most of the magic happens when a user is authenticating with a * third-party provider. What it does, is the following: # * 1. Given a provider and an identifier, find a matching Passport. * 2. From here, the logic branches into two paths. # * - A user is not currently logged in: * 1. If a Passport wasn't found, create a new user as well as a new * Passport that will be assigned to the user. * 2. If a Passport was found, get the user associated with the passport. # * - A user is currently logged in: * 1. If a Passport wasn't found, create a new Passport and associate it * with the already logged in user (ie. "Connect") * 2. If a Passport was found, nothing needs to happen. # * As you can see, this function handles both "authentication" and "authori- * zation" at the same time. This is due to the fact that we pass in * `passReqToCallback: true` when loading the strategies, allowing us to look * for an existing session in the request and taking action based on that. # * For more information on auth(entication|rization) in Passport.js, check out: * http://passportjs.org/guide/authenticate/ * http://passportjs.org/guide/authorize/ # * @param {Object} req * @param {Object} query * @param {Object} profile * @param {Function} next */ passport.connect = async function connect(req, query, profile, next) { try { sails.log('=== passport.connect profile ===', profile); sails.log('=== passport.connect query ===', query); let provider; let user = {}; provider = undefined; query.provider = req.param('provider'); provider = profile.provider || query.provider; if (!provider) { return next(new Error('No authentication provider was identified.')); } if (_.has(profile, 'emails')) { user.email = profile.emails[0].value || profile.emails[0]; } else if (_.has(profile, 'email')) { user.email = profile.email; } const locale = profile._json.locale; const token = query.tokens.accessToken; const identifier = query.identifier; const profileWithLocale = await FacebookService.getProfileWithLocale({ token, identifier, locale }); const profileWithLocaleHasName = _.has(profileWithLocale, 'first_name') && !_.isEmpty(profileWithLocale.first_name); user.locale = locale; if (profileWithLocaleHasName) { // NOTE: // VIPT 需要把 fitstName 以及 lastName 都合併到 firstName 中 // user.firstName = profileWithLocale.first_name; // user.lastName = profileWithLocale.last_name; user.firstName = `${profileWithLocale.last_name}${profileWithLocale.first_name}`; } else { // NOTE: // VIPT 需要把 fitstName 以及 lastName 都合併到 firstName 中 // user.firstName = profile.name.givenName; // user.lastName = profile.name.familyName; user.firstName = `${profile.name.familyName}${profile.name.givenName}`; } if (_.has(profile, 'username') && !_.isEmpty(profile.username)) { user.username = profile.username; } else if (_.has(user, 'email') && !_.isEmpty(user.email)) { user.username = user.email; } else { user.username = profile.id; } // 儲存 Facebook ID 和個人頭像照片 user.facebookId = profile.id; user.avatar = `https://graph.facebook.com/${profile.id}/picture?redirect=true&height=470&width=470`; if (_.has(profile, 'photos') && !_.isEmpty(profile.photos)) { user.avatarThumb = profile.photos[0].value; } console.log('new user', user); if (!user.username && !user.email) { throw new Error('Neither a username nor email was available'); } let dbPassport = await Passport.findOne({ where: { provider, identifier: query.identifier.toString(), }, }); const loginedUser = req.user; const loginedWithoutDbPassport = !!loginedUser && !dbPassport; if (loginedWithoutDbPassport) { // 有一般使用者登入但沒使用FB註冊過 sails.log('=== loginedWithoutdbPassport ==='); query.UserId = loginedUser.id; await Passport.create(query); return next(null, loginedUser); } if (dbPassport) { // 已用FB註冊過,直接登入 sails.log('=== dbPassport ==='); if (_.has(query, 'tokens') && query.tokens !== passport.tokens) { dbPassport.tokens = query.tokens; dbPassport = await dbPassport.save(); } sails.log('=== dbPassport passport ===', passport); user = await User.findOne({ where: { id: dbPassport.UserId, }, include: [{ model: Role, include: [Group], }], }); if (user) { return next(null, user); } throw new Error('Error user not found'); } else { // 全新使用者沒有 user 也沒有 password let checkMail; if (_.has(user, 'eamil')) { checkMail = await User.findOne({ where: { email: user.email } }); } if (checkMail) { throw new Error('Error passport email exists'); } let newUser = await User.create(user); query.UserId = newUser.id; await Passport.createDefaultLocalProviderIfNotExist(newUser); newUser = await User.findOne({ where: { id: newUser.id, }, include: [{ model: Role, include: [Group], }], }); const newFacebookPassword = await Passport.create(query); // 寄送歡迎註冊信 const languageCode = req.getLocale() || 'zh-TW'; const userMailConfig = await MessageService.greeting({ email: newUser.email || '', user: newUser, languageCode, }); const userEmail = await Notification.create(userMailConfig); await MessageService.sendMail(userEmail); // 寄送信箱認證信 // 如果有信箱 if (newUser.email) { newUser.verificationEmailToken = crypto.randomBytes(32).toString('hex').substr(0, 32); await newUser.save(); await UserService.sendVerificationEmail({ userId: newUser.id, email: newUser.email, displayName: newUser.displayName, signToken: newUser.verificationEmailToken, type: '註冊', languageCode, }); } return next(null, newUser); } } catch (err) { sails.log.error(err.stack); return next(err); } }; /** * Create an authentication endpoint # * For more information on authentication in Passport.js, check out: * http://passportjs.org/guide/authenticate/ # * @param {Object} req * @param {Object} res */ passport.endpoint = function (req, res) { let options; let provider; let strategies; strategies = sails.config.strategies; provider = req.param('provider'); options = {}; if (!strategies.hasOwnProperty(provider)) { return res.redirect('/login'); } if (strategies[provider].hasOwnProperty('scope')) { options.scope = strategies[provider].scope; } return this.authenticate(provider, options)(req, res, req.next); }; /** * Create an authentication callback endpoint # * For more information on authentication in Passport.js, check out: * http://passportjs.org/guide/authenticate/ # * @param {Object} req * @param {Object} res * @param {Function} next */ passport.callback = async function callback({ model, input, protocol, action, }, req, res, next) { action = action || req.param('action'); const provider = req.param('provider', 'local'); sails.log.info('=== start login...'); sails.log.info('=== provider ===>', provider); sails.log.info('=== action ===>', action); try { if (provider === 'local' && action) { switch (action) { case 'register': { if (req.user) { sails.log('===================================='); sails.log('logged user=>', req.user.username); sails.log('===================================='); throw Error(MESSAGE.BAD_REQUEST.USER_ALREADY_LOGIN); // req.session.authenticated = false; // req.logout(); } try { if (!_.isNil(protocol) && this.protocols[protocol]) { return await this.protocols[protocol].register(input, req, res, next); } return await this.protocols.local.register(model, req, res, next); } catch (e) { sails.log.error('register passport callback error=>', e.stack); throw e; } } case 'connect': { if (req.user) { return this.protocols.local.connect(req, res, next); } break; } case 'disconnect': { if (req.user) { return this.protocols.local.disconnect(req, res, next); } break; } default: throw new Error('Passport.Local.Action.Invalid'); } } else if (action === 'disconnect' && req.user) { return this.disconnect(req, res, next); } sails.log.info('=== start authenticate...'); return this.authenticate(provider, next)(req, res, req.next); } catch (e) { sails.log.error(e.stack); return next(e); } }; /** * Load all strategies defined in the Passport configuration # * For example, we could add this to our config to use the GitHub strategy * with permission to access a users email address (even if it's marked as * private) as well as permission to add and update a user's Gists: # github: { name: 'GitHub', protocol: 'oauth2', strategy: require('passport-github').Strategy scope: [ 'user', 'gist' ] options: { clientID: 'CLIENT_ID', clientSecret: 'CLIENT_SECRET' } } # * For more information on the providers supported by Passport.js, check out: * http://passportjs.org/guide/providers/ # */ passport.loadStrategies = function () { sails.log('Loading Strategies...'); let self; let strategies; self = this; strategies = sails.config.strategies; Object.keys(strategies).forEach((key) => { let Strategy; let baseUrl; let callback; let options; let protocol; options = { passReqToCallback: true, }; Strategy = void 0; if (key === 'local') { _.extend(options, { usernameField: 'identifier', }); _.extend(options, strategies[key].options || {}); if (strategies.local) { Strategy = strategies[key].strategy; self.use(new Strategy(options, self.protocols.local.login)); } } else if (key === 'bearer') { if (strategies.bearer) { Strategy = strategies[key].strategy; self.use(new Strategy(self.protocols.bearer.authorize)); } } else { const isIdEmpty = strategies[key].options.clientID === ''; const isIdUndefined = strategies[key].options.clientID === undefined; if (!isIdEmpty && !isIdUndefined) { protocol = strategies[key].protocol; callback = strategies[key].callback; if (!callback) { callback = `auth/${key}/callback`; } Strategy = strategies[key].strategy; baseUrl = ConfigHelper.getBaseUrl(); switch (protocol) { case 'oauth': break; case 'oauth2': options.callbackURL = url.resolve(baseUrl, callback); break; case 'openid': options.returnURL = url.resolve(baseUrl, callback); options.realm = baseUrl; options.profile = true; break; default: break; } _.extend(options, strategies[key].options); self.use(new Strategy(options, self.protocols[protocol])); } } }); }; /** * Disconnect a passport from a user # * @param {Object} req * @param {Object} res */ passport.disconnect = function (req, res, next) { const provider = req.param('provider'); const user = req.user; return Passport.findOne({ where: { provider, user: user.id, }, }).then(p => Passport.destroy(p.id).then(() => next(null, user))); }; passport.serializeUser((user, next) => { console.log('serializeUser user=>', user); return next(null, user) }); passport.deserializeUser((user, next) => next(null, user)); module.exports = passport;