Skip to content

Commit

Permalink
Merge pull request #19 from politics-rewired/politics-rewired/slack-auth
Browse files Browse the repository at this point in the history
Add a Slack based authentication method
  • Loading branch information
ben-pr-p authored Feb 5, 2019
2 parents 2e2a2e0 + 7befb58 commit e460754
Show file tree
Hide file tree
Showing 5 changed files with 110 additions and 4 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
7 changes: 6 additions & 1 deletion src/components/Login.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,12 @@ import { isClient } from '../lib'

const Login = ({ location }) => (
<div>
{isClient() ? window.AuthService.login(location.query.nextUrl) : ''}
{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) : ''
}
</div>
)

Expand Down
87 changes: 87 additions & 0 deletions src/server/auth-passport.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,97 @@
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 options = {
clientID: process.env.SLACK_CLIENT_ID,
clientSecret: process.env.SLACK_CLIENT_SECRET,
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)
})

passport.use(strategy)

passport.serializeUser((user, done) => {
const auth0Id = user.id;
return done(null, auth0Id)
})

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
// 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 })

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({
Expand Down
17 changes: 14 additions & 3 deletions src/server/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand All @@ -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) {
Expand Down Expand Up @@ -80,6 +84,7 @@ app.use(cookieSession({
},
secret: process.env.SESSION_SECRET || global.SESSION_SECRET
}))

app.use(passport.initialize())
app.use(passport.session())

Expand Down Expand Up @@ -137,14 +142,20 @@ 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({
typeDefs: schema,
resolvers,
allowUndefinedInResolve: false
})

addMockFunctionsToSchema({
schema: executableSchema,
mocks,
Expand All @@ -158,11 +169,11 @@ app.use('/graphql', graphqlExpress((request) => ({
user: request.user
}
})))

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)

Expand Down
2 changes: 2 additions & 0 deletions src/server/middleware/render-index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 || ''}"
Expand Down

0 comments on commit e460754

Please sign in to comment.