diff --git a/app/components/SlackAuthLink/SlackAuthLink.js b/app/components/SlackAuthLink/SlackAuthLink.js
index a5b9d56a0..d4f8c164b 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 0d1e9ef3a..b4a45ec6d 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 5bb7e7fc5..6ee2d9a38 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 668f5ab56..a0926970d 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 b96ff7d1e..23e06dbd8 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 000000000..d5ecf65c3
--- /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 33e30a30a..6a74d542f 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 = {