From dd2cd2f9d8e120ad7e4e1449f6c54a45da1ca43c Mon Sep 17 00:00:00 2001 From: Tom Moor Date: Tue, 21 Nov 2017 23:51:31 -0800 Subject: [PATCH] Working direct install flow --- app/components/SlackAuthLink/SlackAuthLink.js | 45 +++++-------------- app/scenes/SlackAuth/SlackAuth.js | 2 +- app/stores/AuthStore.js | 8 +++- app/utils/routeHelpers.js | 25 ----------- server/routes.js | 14 ++++++ shared/utils/routeHelpers.js | 26 +++++++++++ webpack.config.js | 7 ++- 7 files changed, 63 insertions(+), 64 deletions(-) create mode 100644 shared/utils/routeHelpers.js diff --git a/app/components/SlackAuthLink/SlackAuthLink.js b/app/components/SlackAuthLink/SlackAuthLink.js index a5b9d56a..d6f1e1dc 100644 --- a/app/components/SlackAuthLink/SlackAuthLink.js +++ b/app/components/SlackAuthLink/SlackAuthLink.js @@ -1,47 +1,22 @@ // @flow import React from 'react'; -import { observer, inject } from 'mobx-react'; +import { inject } from 'mobx-react'; +import { slackAuth } from 'shared/utils/routeHelpers'; import AuthStore from 'stores/AuthStore'; type Props = { children: React$Element<*>, - scopes?: string[], auth: AuthStore, - redirectUri: string, + scopes?: string[], + redirectUri?: string, }; -@observer -class SlackAuthLink extends React.Component { - props: Props; - - static defaultProps = { - scopes: [ - 'identity.email', - 'identity.basic', - 'identity.avatar', - 'identity.team', - ], - }; - - slackUrl = () => { - const baseUrl = 'https://slack.com/oauth/authorize'; - const params = { - client_id: SLACK_KEY, - scope: this.props.scopes ? this.props.scopes.join(' ') : '', - redirect_uri: this.props.redirectUri || SLACK_REDIRECT_URI, - state: this.props.auth.getOauthState(), - }; - - const urlParams = Object.keys(params) - .map(key => `${key}=${encodeURIComponent(params[key])}`) - .join('&'); - - return `${baseUrl}?${urlParams}`; - }; - - render() { - return {this.props.children}; - } +function SlackAuthLink({ auth, children, scopes, redirectUri }: Props) { + return ( + + {children} + + ); } export default inject('auth')(SlackAuthLink); diff --git a/app/scenes/SlackAuth/SlackAuth.js b/app/scenes/SlackAuth/SlackAuth.js index 0d1e9ef3..b4a45ec6 100644 --- a/app/scenes/SlackAuth/SlackAuth.js +++ b/app/scenes/SlackAuth/SlackAuth.js @@ -6,7 +6,7 @@ import queryString from 'query-string'; import { observable } from 'mobx'; import { observer, inject } from 'mobx-react'; import { client } from 'utils/ApiClient'; -import { slackAuth } from 'utils/routeHelpers'; +import { slackAuth } from 'shared/utils/routeHelpers'; import AuthStore from 'stores/AuthStore'; diff --git a/app/stores/AuthStore.js b/app/stores/AuthStore.js index 5bb7e7fc..6ee2d9a3 100644 --- a/app/stores/AuthStore.js +++ b/app/stores/AuthStore.js @@ -53,7 +53,10 @@ class AuthStore { @action authWithSlack = async (code: string, state: string) => { - if (state !== this.oauthState) { + // in the case of direct install from the Slack app store the state is + // created on the server and set as a cookie + const serverState = Cookie.get('state', { path: '/' }); + if (state !== this.oauthState && state !== serverState) { return { success: false, }; @@ -68,6 +71,9 @@ class AuthStore { }; } + // State can only ever be used once so now's the time to remove it. + Cookie.remove('state', { path: '/' }); + invariant( res && res.data && res.data.user && res.data.team && res.data.accessToken, 'All values should be available' diff --git a/app/utils/routeHelpers.js b/app/utils/routeHelpers.js index 668f5ab5..a0926970 100644 --- a/app/utils/routeHelpers.js +++ b/app/utils/routeHelpers.js @@ -22,31 +22,6 @@ export function documentUrl(doc: Document): string { return doc.url; } -export function slackAuth( - state: string, - scopes: string[] = [ - 'identity.email', - 'identity.basic', - 'identity.avatar', - 'identity.team', - ], - redirectUri: string = `${BASE_URL}/auth/slack` -): string { - const baseUrl = 'https://slack.com/oauth/authorize'; - const params = { - client_id: SLACK_KEY, - scope: scopes ? scopes.join(' ') : '', - redirect_uri: redirectUri, - state, - }; - - const urlParams = Object.keys(params) - .map(key => `${key}=${encodeURIComponent(params[key])}`) - .join('&'); - - return `${baseUrl}?${urlParams}`; -} - export function documentNewUrl(doc: Document): string { const newUrl = `${doc.collection.url}/new`; if (doc.parentDocumentId) { diff --git a/server/routes.js b/server/routes.js index b96ff7d1..23e06dbd 100644 --- a/server/routes.js +++ b/server/routes.js @@ -8,6 +8,7 @@ import sendfile from 'koa-sendfile'; import serve from 'koa-static'; import subdomainRedirect from './middlewares/subdomainRedirect'; import renderpage from './utils/renderpage'; +import { slackAuth } from '../shared/utils/routeHelpers'; import Home from './pages/Home'; import About from './pages/About'; @@ -44,6 +45,19 @@ if (process.env.NODE_ENV === 'production') { }); } +// slack direct install +router.get('/auth/slack/install', async ctx => { + const state = Math.random() + .toString(36) + .substring(7); + + ctx.cookies.set('state', state, { + httpOnly: false, + expires: new Date('2100'), + }); + ctx.redirect(slackAuth(state)); +}); + // static pages router.get('/about', ctx => renderpage(ctx, )); router.get('/pricing', ctx => renderpage(ctx, )); diff --git a/shared/utils/routeHelpers.js b/shared/utils/routeHelpers.js new file mode 100644 index 00000000..d5ecf65c --- /dev/null +++ b/shared/utils/routeHelpers.js @@ -0,0 +1,26 @@ +// @flow + +export function slackAuth( + state: string, + scopes: string[] = [ + 'identity.email', + 'identity.basic', + 'identity.avatar', + 'identity.team', + ], + redirectUri: string = `${process.env.URL}/auth/slack` +): string { + const baseUrl = 'https://slack.com/oauth/authorize'; + const params = { + client_id: process.env.SLACK_KEY, + scope: scopes ? scopes.join(' ') : '', + redirect_uri: redirectUri, + state, + }; + + const urlParams = Object.keys(params) + .map(key => `${key}=${encodeURIComponent(params[key])}`) + .join('&'); + + return `${baseUrl}?${urlParams}`; +} diff --git a/webpack.config.js b/webpack.config.js index 33e30a30..6a74d542 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -11,10 +11,13 @@ const definePlugin = new webpack.DefinePlugin({ JSON.parse(process.env.BUILD_PRERELEASE || 'false') ), SLACK_REDIRECT_URI: JSON.stringify(process.env.SLACK_REDIRECT_URI), - SLACK_KEY: JSON.stringify(process.env.SLACK_KEY), BASE_URL: JSON.stringify(process.env.URL), BUGSNAG_KEY: JSON.stringify(process.env.BUGSNAG_KEY), - DEPLOYMENT: JSON.stringify(process.env.DEPLOYMENT || 'hosted') + DEPLOYMENT: JSON.stringify(process.env.DEPLOYMENT || 'hosted'), + 'process.env': { + URL: JSON.stringify(process.env.URL), + SLACK_KEY: JSON.stringify(process.env.SLACK_KEY), + } }); module.exports = {