From c412a3c8dca26e8fc109d381464bf2db96c7714e Mon Sep 17 00:00:00 2001 From: ben-pr-p Date: Tue, 5 Feb 2019 09:11:24 -0500 Subject: [PATCH 1/4] basically working, although not properly managing session. need initial server side redirect from /login --- package.json | 1 + src/components/Login.jsx | 5 +- src/server/auth-passport.js | 79 +++++++++++++++++++++++++++ src/server/index.js | 14 ++++- src/server/middleware/render-index.js | 2 + 5 files changed, 97 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index 7f0861229..3858ebcef 100644 --- a/package.json +++ b/package.json @@ -65,6 +65,7 @@ }, "homepage": "https://github.com/MoveOnOrg/Spoke/#readme", "dependencies": { + "@aoberoi/passport-slack": "^1.0.5", "aphrodite": "^0.4.1", "apollo-client": "^0.4.7", "apollo-server-express": "^1.2.0", diff --git a/src/components/Login.jsx b/src/components/Login.jsx index 01acfb560..f46cec651 100644 --- a/src/components/Login.jsx +++ b/src/components/Login.jsx @@ -4,7 +4,10 @@ import { isClient } from '../lib' const Login = ({ location }) => (
- {isClient() ? window.AuthService.login(location.query.nextUrl) : ''} + {window.PASSPORT_STRATEGY == 'slack' + ? window.location.reload() + : isClient() ? window.AuthService.login(location.query.nextUrl) : '' + }
) diff --git a/src/server/auth-passport.js b/src/server/auth-passport.js index da8a5a960..23fa9c127 100644 --- a/src/server/auth-passport.js +++ b/src/server/auth-passport.js @@ -1,10 +1,89 @@ import passport from 'passport' import Auth0Strategy from 'passport-auth0' +import passportSlack from '@aoberoi/passport-slack' import AuthHasher from 'passport-local-authenticate' import { Strategy as LocalStrategy } from 'passport-local' import { userLoggedIn } from './models/cacheable_queries' import { User, Organization } from './models' import wrap from './wrap' +import { split } from 'apollo-link'; + +export function setupSlackPassport() { + const strategy = new passportSlack.Strategy({ + clientID: process.env.SLACK_CLIENT_ID, + clientSecret: process.env.SLACK_CLIENT_SECRET, + callbackURL: `${process.env.BASE_URL}/login-callback` + }, function (accessToken, scopes, team, { bot, incomingWebhook }, { user: userProfile , team: teamProfile }, done) { + done(null, userProfile) + }) + + passport.use(strategy) + + passport.serializeUser((user, done) => { + return done(null, user) + }) + + passport.deserializeUser(wrap(async (id, done) => { + const user = await userLoggedIn(id) + return done(null, user || false) + })) + + return { + first: passport.authenticate('slack', { + scope: ['identity.basic', 'identity.email', 'identity.team'] + }), + callback: passport.authenticate('slack', { + failureRedirect: '/login', + }), + after: async (req, res) => { + const user = req.user + const auth0Id = user && user.id + if (!auth0Id) { throw new Error('Null user in login callback') } + const existingUser = await User.filter({ auth0_id: auth0Id }) + + if (existingUser.length === 0) { + let first_name, last_name; + const splitName = user.name.split(' ') + if (splitName.length == 1) { + first_name = splitName[0] + last_name = '' + } else if (splitName.length == 2) { + first_name = splitName[0] + last_name = splitName[1] + } else { + first_name = splitName[0] + last_name = splitName.slice(1, splitName.length + 1).join(' ') + } + + const userData = { + auth0_id: auth0Id, + // eslint-disable-next-line no-underscore-dangle + first_name, + // eslint-disable-next-line no-underscore-dangle + last_name, + cell: 'unknown', + // eslint-disable-next-line no-underscore-dangle + email: user.email, + is_superadmin: false + } + + await User.save(userData) + + const organizations = await Organization.filter({}) + + if (organizations[0]) { + const uuid = organizations[0].uuid + const joinUrl = `${process.env.BASE_URL}/${uuid}/join` + return res.redirect(req.query.state == '/' ? joinUrl : req.query.state) + } else { + return res.redirect(req.query.state || '/') + } + } + + return res.redirect(req.query.state || '/') + } + } +} export function setupAuth0Passport() { const strategy = new Auth0Strategy({ diff --git a/src/server/index.js b/src/server/index.js index f31af40e1..931185871 100644 --- a/src/server/index.js +++ b/src/server/index.js @@ -11,7 +11,7 @@ import mocks from './api/mocks' import { createLoaders, createTablesIfNecessary } from './models' import passport from 'passport' import cookieSession from 'cookie-session' -import { setupAuth0Passport, setupLocalAuthPassport } from './auth-passport' +import { setupAuth0Passport, setupLocalAuthPassport, setupSlackPassport } from './auth-passport' import wrap from './wrap' import { log } from '../lib' import nexmo from './api/lib/nexmo' @@ -36,6 +36,10 @@ if (!process.env.PASSPORT_STRATEGY && !global.PASSPORT_STRATEGY) { if (loginStrategy === 'localauthexperimental') { loginCallbacks = setupLocalAuthPassport() } + + if (loginStrategy === 'slack') { + loginCallbacks = setupSlackPassport() + } } if (!process.env.SUPPRESS_SEED_CALLS) { @@ -137,7 +141,12 @@ app.get('/logout-callback', (req, res) => { }) if (loginCallbacks) { - app.get('/login-callback', ...loginCallbacks) + if ((process.env.PASSPORT_STRATEGY || global.PASSPORT_STRATEGY) == 'slack') { + app.get('/login', loginCallbacks.first) + app.get('/login-callback', loginCallbacks.callback, loginCallbacks.after) + } else { + app.get('/login-callback', ...loginCallbacks) + } } const executableSchema = makeExecutableSchema({ @@ -162,7 +171,6 @@ app.get('/graphiql', graphiqlExpress({ endpointURL: '/graphql' })) - // This middleware should be last. Return the React app only if no other route is hit. app.use(appRenderer) diff --git a/src/server/middleware/render-index.js b/src/server/middleware/render-index.js index 2129c849f..fd89882f1 100644 --- a/src/server/middleware/render-index.js +++ b/src/server/middleware/render-index.js @@ -63,6 +63,8 @@ export default function renderIndex(html, css, assetMap, store) { window.RENDERED_CLASS_NAMES=${JSON.stringify(css.renderedClassNames)} window.AUTH0_CLIENT_ID="${process.env.AUTH0_CLIENT_ID}" window.AUTH0_DOMAIN="${process.env.AUTH0_DOMAIN}" + window.PASSPORT_STRATEGY="${process.env.PASSPORT_STRATEGY}" + window.SLACK_CLIENT_ID="${process.env.SLACK_CLIENT_ID}" window.SUPPRESS_SELF_INVITE="${process.env.SUPPRESS_SELF_INVITE || ''}" window.NODE_ENV="${process.env.NODE_ENV}" window.PRIVACY_URL="${process.env.PRIVACY_URL || ''}" From cbb67e0802ab98331e4388dfffb56cf157a8a8ec Mon Sep 17 00:00:00 2001 From: ben-pr-p Date: Tue, 5 Feb 2019 09:35:17 -0500 Subject: [PATCH 2/4] proper serialization and deserialization --- src/server/auth-passport.js | 6 ++++-- src/server/index.js | 3 +++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/server/auth-passport.js b/src/server/auth-passport.js index 23fa9c127..1537da22c 100644 --- a/src/server/auth-passport.js +++ b/src/server/auth-passport.js @@ -12,7 +12,8 @@ export function setupSlackPassport() { const strategy = new passportSlack.Strategy({ clientID: process.env.SLACK_CLIENT_ID, clientSecret: process.env.SLACK_CLIENT_SECRET, - callbackURL: `${process.env.BASE_URL}/login-callback` + callbackURL: `${process.env.BASE_URL}/login-callback`, + authorizationURL: 'https://runbernierun2020.slack.com/oauth/authorize' }, function (accessToken, scopes, team, { bot, incomingWebhook }, { user: userProfile , team: teamProfile }, done) { done(null, userProfile) }) @@ -20,7 +21,8 @@ export function setupSlackPassport() { passport.use(strategy) passport.serializeUser((user, done) => { - return done(null, user) + const auth0Id = user.id; + return done(null, auth0Id) }) passport.deserializeUser(wrap(async (id, done) => { diff --git a/src/server/index.js b/src/server/index.js index 931185871..2d2f2025f 100644 --- a/src/server/index.js +++ b/src/server/index.js @@ -84,6 +84,7 @@ app.use(cookieSession({ }, secret: process.env.SESSION_SECRET || global.SESSION_SECRET })) + app.use(passport.initialize()) app.use(passport.session()) @@ -154,6 +155,7 @@ const executableSchema = makeExecutableSchema({ resolvers, allowUndefinedInResolve: false }) + addMockFunctionsToSchema({ schema: executableSchema, mocks, @@ -167,6 +169,7 @@ app.use('/graphql', graphqlExpress((request) => ({ user: request.user } }))) + app.get('/graphiql', graphiqlExpress({ endpointURL: '/graphql' })) From fd60163bc026aeaa1a72718d638e64536629ecfa Mon Sep 17 00:00:00 2001 From: ben-pr-p Date: Tue, 5 Feb 2019 09:39:06 -0500 Subject: [PATCH 3/4] move team name to environment variable --- src/server/auth-passport.js | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/server/auth-passport.js b/src/server/auth-passport.js index 1537da22c..b144ab6c1 100644 --- a/src/server/auth-passport.js +++ b/src/server/auth-passport.js @@ -9,12 +9,17 @@ import wrap from './wrap' import { split } from 'apollo-link'; export function setupSlackPassport() { - const strategy = new passportSlack.Strategy({ + const options = { clientID: process.env.SLACK_CLIENT_ID, clientSecret: process.env.SLACK_CLIENT_SECRET, - callbackURL: `${process.env.BASE_URL}/login-callback`, - authorizationURL: 'https://runbernierun2020.slack.com/oauth/authorize' - }, function (accessToken, scopes, team, { bot, incomingWebhook }, { user: userProfile , team: teamProfile }, done) { + callbackURL: `${process.env.BASE_URL}/login-callback` + } + + if (process.env.SLACK_TEAM_NAME) { + options.authorizationURL = `https://${process.env.SLACK_TEAM_NAME}.slack.com/oauth/authorize` + } + + const strategy = new passportSlack.Strategy(options, function (accessToken, scopes, team, { bot, incomingWebhook }, { user: userProfile , team: teamProfile }, done) { done(null, userProfile) }) From 7befb585ae70f71cc509ee798e3c48b492e017ac Mon Sep 17 00:00:00 2001 From: ben-pr-p Date: Tue, 5 Feb 2019 10:54:44 -0500 Subject: [PATCH 4/4] add comments to weird things --- src/components/Login.jsx | 2 ++ src/server/auth-passport.js | 3 ++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/components/Login.jsx b/src/components/Login.jsx index f46cec651..9aef4ed03 100644 --- a/src/components/Login.jsx +++ b/src/components/Login.jsx @@ -5,6 +5,8 @@ import { isClient } from '../lib' const Login = ({ location }) => (
{window.PASSPORT_STRATEGY == 'slack' + // If Slack strategy, the server needs to initiate the redirect + // Force reload will hit the server redirect (as opposed to client routing) ? window.location.reload() : isClient() ? window.AuthService.login(location.query.nextUrl) : '' } diff --git a/src/server/auth-passport.js b/src/server/auth-passport.js index b144ab6c1..d4bb308b3 100644 --- a/src/server/auth-passport.js +++ b/src/server/auth-passport.js @@ -44,7 +44,8 @@ export function setupSlackPassport() { }), after: async (req, res) => { const user = req.user - const auth0Id = user && user.id + // set slack_id to auth0Id to avoid changing the schema + const auth0Id = user && user.id if (!auth0Id) { throw new Error('Null user in login callback') } const existingUser = await User.filter({ auth0_id: auth0Id })