Slack commands and post working agagain with new flow

This commit is contained in:
Tom Moor
2018-05-29 23:33:30 -07:00
parent 57aaea60da
commit 55e1451160
6 changed files with 27 additions and 156 deletions

View File

@@ -47,7 +47,7 @@ class Slack extends React.Component<Props> {
) : (
<SlackButton
scopes={['commands', 'links:read', 'links:write']}
redirectUri={`${BASE_URL}/auth/slack/commands`}
redirectUri={`${BASE_URL}/auth/slack.commands`}
/>
)}
</p>
@@ -83,7 +83,7 @@ class Slack extends React.Component<Props> {
<strong>{collection.name}</strong>
<SlackButton
scopes={['incoming-webhook']}
redirectUri={`${BASE_URL}/auth/slack/post`}
redirectUri={`${BASE_URL}/auth/slack.post`}
state={collection.id}
label="Connect"
/>

View File

@@ -17,11 +17,7 @@ type Props = {
function SlackButton({ auth, state, label, scopes, redirectUri }: Props) {
const handleClick = () =>
(window.location.href = slackAuth(
state ? auth.saveOauthState(state) : auth.genOauthState(),
scopes,
redirectUri
));
(window.location.href = slackAuth(state, scopes, redirectUri));
return (
<Button onClick={handleClick} icon={<SpacedSlackLogo size={24} />} neutral>

View File

@@ -1,80 +0,0 @@
// @flow
import * as React from 'react';
import { Redirect } from 'react-router-dom';
import type { Location } from 'react-router-dom';
import queryString from 'query-string';
import { observable } from 'mobx';
import { observer, inject } from 'mobx-react';
import { client } from 'utils/ApiClient';
import { slackAuth } from 'shared/utils/routeHelpers';
import AuthStore from 'stores/AuthStore';
type Props = {
auth: AuthStore,
location: Location,
};
@observer
class SlackAuth extends React.Component<Props> {
@observable redirectTo: string;
componentDidMount() {
this.redirect();
}
async redirect() {
const { error, code, state } = queryString.parse(
this.props.location.search
);
if (error) {
if (error === 'access_denied') {
// User selected "Deny" access on Slack OAuth
this.redirectTo = '/dashboard';
} else {
this.redirectTo = '/auth/error';
}
} else if (code) {
if (this.props.location.pathname === '/auth/slack/commands') {
// incoming webhooks from Slack
try {
await client.post('/auth.slackCommands', { code });
this.redirectTo = '/settings/integrations/slack';
} catch (e) {
this.redirectTo = '/auth/error';
}
} else if (this.props.location.pathname === '/auth/slack/post') {
// outgoing webhooks to Slack
try {
await client.post('/auth.slackPost', {
code,
collectionId: this.props.auth.oauthState,
});
this.redirectTo = '/settings/integrations/slack';
} catch (e) {
this.redirectTo = '/auth/error';
}
} else {
// Slack authentication
const redirectTo = sessionStorage.getItem('redirectTo');
sessionStorage.removeItem('redirectTo');
const { success } = await this.props.auth.authWithSlack(code, state);
success
? (this.redirectTo = redirectTo || '/dashboard')
: (this.redirectTo = '/auth/error');
}
} else {
// signing in
window.location.href = slackAuth(this.props.auth.genOauthState());
}
}
render() {
if (this.redirectTo) return <Redirect to={this.redirectTo} />;
return null;
}
}
export default inject('auth')(SlackAuth);

View File

@@ -1,3 +0,0 @@
// @flow
import SlackAuth from './SlackAuth';
export default SlackAuth;

View File

@@ -12,7 +12,6 @@ class AuthStore {
@observable user: ?User;
@observable team: ?Team;
@observable token: ?string;
@observable oauthState: string;
@observable isLoading: boolean = false;
@observable isSuspended: boolean = false;
@observable suspendedContactEmail: ?string;
@@ -29,7 +28,6 @@ class AuthStore {
return JSON.stringify({
user: this.user,
team: this.team,
oauthState: this.oauthState,
});
}
@@ -63,56 +61,6 @@ class AuthStore {
window.location.href = `${BASE_URL}?done=${new Date().getTime()}`;
};
@action
genOauthState = () => {
const state = Math.random()
.toString(36)
.substring(7);
this.oauthState = state;
return this.oauthState;
};
@action
saveOauthState = (state: string) => {
this.oauthState = state;
return this.oauthState;
};
@action
authWithSlack = async (code: string, state: string) => {
// 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');
if (state !== this.oauthState && state !== serverState) {
return {
success: false,
};
}
let res;
try {
res = await client.post('/auth.slack', { code });
} catch (e) {
return {
success: false,
};
}
// 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'
);
this.user = res.data.user;
this.team = res.data.team;
return {
success: true,
};
};
constructor() {
// Rehydrate
let data = {};
@@ -123,7 +71,6 @@ class AuthStore {
}
this.user = data.user;
this.team = data.team;
this.oauthState = data.oauthState;
// load token from state for backwards compatability with
// sessions created pre-google auth

View File

@@ -2,7 +2,6 @@
import Router from 'koa-router';
import addHours from 'date-fns/add_hours';
import addMonths from 'date-fns/add_months';
import auth from '../middlewares/authentication';
import { slackAuth } from '../../shared/utils/routeHelpers';
import { Authentication, Integration, User, Team } from '../models';
import * as Slack from '../slack';
@@ -75,17 +74,19 @@ router.get('slack.callback', async ctx => {
ctx.redirect('/');
});
router.post('slack.commands', auth(), async ctx => {
const { code } = ctx.body;
router.get('slack.commands', async ctx => {
const { code } = ctx.request.query;
ctx.assertPresent(code, 'code is required');
const user = ctx.state.user;
const endpoint = `${process.env.URL || ''}/auth/slack.commands`;
const data = await Slack.oauthAccess(code, endpoint);
const serviceId = 'slack';
const user = await User.find({
service: 'slack',
serviceId: data.user_id,
});
const authentication = await Authentication.create({
serviceId,
serviceId: 'slack',
userId: user.id,
teamId: user.teamId,
token: data.access_token,
@@ -93,25 +94,33 @@ router.post('slack.commands', auth(), async ctx => {
});
await Integration.create({
serviceId,
serviceId: 'slack',
type: 'command',
userId: user.id,
teamId: user.teamId,
authenticationId: authentication.id,
});
ctx.redirect('/settings/integrations/slack');
});
router.post('slack.post', auth(), async ctx => {
const { code, collectionId } = ctx.body;
router.get('slack.post', async ctx => {
const { code, state } = ctx.request.query;
ctx.assertPresent(code, 'code is required');
const user = ctx.state.user;
const collectionId = state;
ctx.assertUuid(collectionId, 'collectionId must be an uuid');
const endpoint = `${process.env.URL || ''}/auth/slack.post`;
const data = await Slack.oauthAccess(code, endpoint);
const serviceId = 'slack';
const user = await User.find({
service: 'slack',
serviceId: data.user_id,
});
const authentication = await Authentication.create({
serviceId,
serviceId: 'slack',
userId: user.id,
teamId: user.teamId,
token: data.access_token,
@@ -119,7 +128,7 @@ router.post('slack.post', auth(), async ctx => {
});
await Integration.create({
serviceId,
serviceId: 'slack',
type: 'post',
userId: user.id,
teamId: user.teamId,
@@ -132,6 +141,8 @@ router.post('slack.post', auth(), async ctx => {
channelId: data.incoming_webhook.channel_id,
},
});
ctx.redirect('/settings/integrations/slack');
});
export default router;