From 75f1ce2e62a697dc5fa2f9b4e446d8662d306fc2 Mon Sep 17 00:00:00 2001 From: Todd Chaffee Date: Tue, 30 Oct 2018 18:17:07 -0300 Subject: [PATCH] feat: use mock authentication for local dev --- api-server/package-lock.json | 29 +++++++ api-server/package.json | 1 + api-server/server/boot/authentication.js | 35 ++++++-- api-server/server/component-passport.js | 44 ++++++++++ api-server/server/passport-providers.js | 5 ++ package.json | 1 + sample.env | 1 + tools/scripts/seed/seedAuthUser.js | 101 +++++++++++++++++++++++ 8 files changed, 211 insertions(+), 6 deletions(-) create mode 100644 tools/scripts/seed/seedAuthUser.js diff --git a/api-server/package-lock.json b/api-server/package-lock.json index 2b177ff5a9ae91..ac4b437acba75a 100644 --- a/api-server/package-lock.json +++ b/api-server/package-lock.json @@ -1026,6 +1026,15 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-7.0.70.tgz", "integrity": "sha512-bAcW/1aM8/s5iFKhRpu/YJiQf/b1ZwnMRqqsWRCmAqEDQF2zY8Ez3Iu9AcZKFKc3vCJc8KJVpJ6Pn54sJ1BvXQ==" }, + "@types/passport": { + "version": "0.4.6", + "resolved": "https://registry.npmjs.org/@types/passport/-/passport-0.4.6.tgz", + "integrity": "sha512-P7TxrdpAze3nvHghYPeLlHkYcFDiIkRBbp7xYz2ehX9zmi1yr/qWQMTpXsMxN5w3ESJpMzn917inK4giASaDcQ==", + "dev": true, + "requires": { + "@types/express": "*" + } + }, "@types/range-parser": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.2.tgz", @@ -8635,6 +8644,26 @@ "passport-strategy": "1.x.x" } }, + "passport-mock-strategy": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/passport-mock-strategy/-/passport-mock-strategy-1.1.1.tgz", + "integrity": "sha512-QSJoC2JB2piLVB3CIj0EaV3V8kfThN+dS7d34KNzcJaL03pYyFvfEl9Im8O8wTb+OZcmqWz6FAqUpobd1+IVNg==", + "dev": true, + "requires": { + "@types/express": "^4.11.1", + "@types/passport": "^0.4.5", + "es6-promise": "^4.2.4", + "passport": "^0.4.0" + }, + "dependencies": { + "es6-promise": { + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.5.tgz", + "integrity": "sha512-n6wvpdE43VFtJq+lUDYDBFUwV8TZbuGXLV4D6wKafg13ldznKsyEvatubnmUe31zcvelSzOHF+XbaT+Bl9ObDg==", + "dev": true + } + } + }, "passport-oauth": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/passport-oauth/-/passport-oauth-1.0.0.tgz", diff --git a/api-server/package.json b/api-server/package.json index d4f83f69d40dbe..aaec959b815d65 100644 --- a/api-server/package.json +++ b/api-server/package.json @@ -102,6 +102,7 @@ "joi-objectid": "^2.0.0", "loopback-component-explorer": "^6.3.1", "nodemon": "^1.18.4", + "passport-mock-strategy": "^1.1.1", "pm2": "^3.0.3", "prettier": "^1.14.2", "sinon": "^2.0.0", diff --git a/api-server/server/boot/authentication.js b/api-server/server/boot/authentication.js index eac3f0b8d38b1d..f5cfd0dcedf272 100644 --- a/api-server/server/boot/authentication.js +++ b/api-server/server/boot/authentication.js @@ -7,7 +7,11 @@ import { check } from 'express-validator/check'; import { homeLocation } from '../../../config/env'; import { createCookieConfig } from '../utils/cookieConfig'; -import { createPassportCallbackAuthenticator } from '../component-passport'; +import { + createPassportCallbackAuthenticator, + saveResponseAuthCookies, + loginRedirect +} from '../component-passport'; import { ifUserRedirectTo, ifNoUserRedirectTo, @@ -25,15 +29,34 @@ module.exports = function enableAuthentication(app) { // loopback.io/doc/en/lb2/Authentication-authorization-and-permissions.html app.enableAuth(); const ifUserRedirect = ifUserRedirectTo(); + const saveAuthCookies = saveResponseAuthCookies(); + const loginSuccessRedirect = loginRedirect(); const ifNoUserRedirectHome = ifNoUserRedirectTo(homeLocation); const api = app.loopback.Router(); const { AuthToken, User } = app.models; - api.get('/signin', ifUserRedirect, passport.authenticate('auth0-login', {})); - api.get( - '/auth/auth0/callback', - createPassportCallbackAuthenticator('auth0-login', { provider: 'auth0' }) - ); + // Use a local mock strategy for signing in if we are in dev mode. + // Otherwise we use auth0 login. We use a string for 'true' because values + // set in the env file will always be strings and never boolean. + if (process.env.LOCAL_MOCK_AUTH === 'true') { + api.get( + '/signin', + passport.authenticate('devlogin'), + saveAuthCookies, + loginSuccessRedirect + ); + } else { + api.get( + '/signin', + ifUserRedirect, + passport.authenticate('auth0-login', {}) + ); + + api.get( + '/auth/auth0/callback', + createPassportCallbackAuthenticator('auth0-login', { provider: 'auth0' }) + ); + } api.get('/signout', (req, res) => { req.logout(); diff --git a/api-server/server/component-passport.js b/api-server/server/component-passport.js index c9de0e6c183e58..32e261a18da347 100644 --- a/api-server/server/component-passport.js +++ b/api-server/server/component-passport.js @@ -131,6 +131,50 @@ export function setupPassport(app) { }); } +export const saveResponseAuthCookies = () => { + + return (req, res, next) => { + + const user = req.user; + + if (!user) { + return res.redirect('/signin'); + } + + const { accessToken } = user; + + const cookieConfig = { + ...createCookieConfig(req), + maxAge: 77760000000 + }; + const jwtAccess = jwt.sign({ accessToken }, jwtSecret); + res.cookie('jwt_access_token', jwtAccess, cookieConfig); + res.cookie('access_token', accessToken.id, cookieConfig); + res.cookie('userId', accessToken.userId, cookieConfig); + + return next(); + }; +}; + +export const loginRedirect = () => { + + return (req, res) => { + const successRedirect = req => { + if (!!req && req.session && req.session.returnTo) { + delete req.session.returnTo; + return `${homeLocation}/welcome`; + } + return `${homeLocation}/welcome`; + }; + + let redirect = url.parse(successRedirect(req), true); + delete redirect.search; + + redirect = url.format(redirect); + return res.redirect(redirect); + }; +}; + export const createPassportCallbackAuthenticator = (strategy, config) => ( req, res, diff --git a/api-server/server/passport-providers.js b/api-server/server/passport-providers.js index 57b987f82bc050..b0bbdbe2dd90e9 100644 --- a/api-server/server/passport-providers.js +++ b/api-server/server/passport-providers.js @@ -7,6 +7,11 @@ const successRedirect = `${homeLocation}/welcome`; const failureRedirect = '/signin'; export default { + devlogin: { + authScheme: 'mock', + provider: 'dev', + module: 'passport-mock-strategy' + }, local: { provider: 'local', module: 'passport-local', diff --git a/package.json b/package.json index 22f6bac204ceb1..82e5684e9f1fd3 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,7 @@ "lint": "echo 'Warning: TODO - Define Linting.'", "pretest-ci": "npm-run-all -s lint bootstrap", "seed": "cross-env DEBUG=fcc:* node ./tools/scripts/seed/seedChallenges", + "seed-auth-user": "cross-env DEBUG=fcc:* node ./tools/scripts/seed/seedAuthUser", "start-develop": "node ./tools/scripts/start-develop.js", "test": "npm-run-all -p test:*", "test-ci": "npm test", diff --git a/sample.env b/sample.env index 3945ba7a21ff02..4a924bd0246498 100644 --- a/sample.env +++ b/sample.env @@ -17,6 +17,7 @@ STRIPE_SECRET=sk_from_stipe_dashboard LOCAL_AUTH=true PEER=stuff DEBUG=true +LOCAL_MOCK_AUTH=true IMAGE_BASE_URL='https://s3.amazonaws.com/freecodecamp/images/' diff --git a/tools/scripts/seed/seedAuthUser.js b/tools/scripts/seed/seedAuthUser.js new file mode 100644 index 00000000000000..52203a29f650bc --- /dev/null +++ b/tools/scripts/seed/seedAuthUser.js @@ -0,0 +1,101 @@ +const path = require('path'); +require('dotenv').config({ path: path.resolve(__dirname, '../../../.env') }); +const MongoClient = require('mongodb').MongoClient; +const ObjectId = require('mongodb').ObjectID; +const debug = require('debug'); + +const log = debug('fcc:tools:seedLocalAuthUser'); +const { MONGOHQ_URL, LOCALE: lang } = process.env; + +function handleError(err, client) { + if (err) { + console.error('Oh noes!! Error seeding local auth user.'); + console.error(err); + try { + client.close(); + } catch (e) { + // no-op + } finally { + /* eslint-disable-next-line no-process-exit */ + process.exit(1); + } + } +} + +MongoClient.connect( + MONGOHQ_URL, + { useNewUrlParser: true }, + function(err, client) { + handleError(err, client); + + log('Connected successfully to mongo'); + + const db = client.db('freecodecamp'); + const user = db.collection('user'); + + user.deleteOne({_id: ObjectId('5bd30e0f1caf6ac3ddddddb5') }, (err) => { + handleError(err, client); + + try { + user.insertOne( + { + _id: ObjectId('5bd30e0f1caf6ac3ddddddb5'), + email: 'foo@bar.com', + emailVerified: true, + progressTimestamps: [], + isBanned: false, + isCheater: false, + username: 'DevelopmentUser', + about: '', + name: 'Development User', + location: '', + picture: 'https://identicon.org/?t=dev&s=256', + acceptedPrivacyTerms: true, + sendQuincyEmail: false, + currentChallengeId: '', + isHonest: false, + isFrontEndCert: false, + isDataVisCert: false, + isBackEndCert: false, + isFullStackCert: false, + isRespWebDesignCert: false, + is2018DataVisCert: false, + isFrontEndLibsCert: false, + isJsAlgoDataStructCert: false, + isApisMicroservicesCert: false, + isInfosecQaCert: false, + is2018FullStackCert: false, + completedChallenges: [], + portfolio: [], + yearsTopContributor: [], + rand: 0.6126749173148205, + theme: 'default', + profileUI: { + isLocked: true, + showAbout: false, + showCerts: false, + showDonation: false, + showHeatMap: false, + showLocation: false, + showName: false, + showPoints: false, + showPortfolio: false, + showTimeLine: false + }, + badges: { + coreTeam: [] + }, + isDonating: false, + emailAuthLinkTTL: null, + emailVerifyTTL: null + } + ); + } catch (e) { + handleError(e, client); + } finally { + log('local auth user seed complete'); + client.close(); + } + }); + } +);