Move slack auth handling entirely to server
This commit is contained in:
10
app/index.js
10
app/index.js
@ -25,8 +25,6 @@ import Members from 'scenes/Settings/Members';
|
|||||||
import Slack from 'scenes/Settings/Slack';
|
import Slack from 'scenes/Settings/Slack';
|
||||||
import Shares from 'scenes/Settings/Shares';
|
import Shares from 'scenes/Settings/Shares';
|
||||||
import Tokens from 'scenes/Settings/Tokens';
|
import Tokens from 'scenes/Settings/Tokens';
|
||||||
import SlackAuth from 'scenes/SlackAuth';
|
|
||||||
import ErrorAuth from 'scenes/ErrorAuth';
|
|
||||||
import Error404 from 'scenes/Error404';
|
import Error404 from 'scenes/Error404';
|
||||||
|
|
||||||
import ErrorBoundary from 'components/ErrorBoundary';
|
import ErrorBoundary from 'components/ErrorBoundary';
|
||||||
@ -61,14 +59,6 @@ if (element) {
|
|||||||
<ScrollToTop>
|
<ScrollToTop>
|
||||||
<Switch>
|
<Switch>
|
||||||
<Route exact path="/" component={Home} />
|
<Route exact path="/" component={Home} />
|
||||||
<Route exact path="/auth/slack" component={SlackAuth} />
|
|
||||||
<Route
|
|
||||||
exact
|
|
||||||
path="/auth/slack/commands"
|
|
||||||
component={SlackAuth}
|
|
||||||
/>
|
|
||||||
<Route exact path="/auth/slack/post" component={SlackAuth} />
|
|
||||||
<Route exact path="/auth/error" component={ErrorAuth} />
|
|
||||||
<Route exact path="/share/:shareId" component={Document} />
|
<Route exact path="/share/:shareId" component={Document} />
|
||||||
<Auth>
|
<Auth>
|
||||||
<Layout>
|
<Layout>
|
||||||
|
@ -1,23 +0,0 @@
|
|||||||
// @flow
|
|
||||||
import * as React from 'react';
|
|
||||||
import { Link } from 'react-router-dom';
|
|
||||||
|
|
||||||
import CenteredContent from 'components/CenteredContent';
|
|
||||||
import PageTitle from 'components/PageTitle';
|
|
||||||
|
|
||||||
class ErrorAuth extends React.Component<*> {
|
|
||||||
render() {
|
|
||||||
return (
|
|
||||||
<CenteredContent>
|
|
||||||
<PageTitle title="Authentication error" />
|
|
||||||
<h1>Authentication failed</h1>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
We were unable to log you in. <Link to="/">Please try again.</Link>
|
|
||||||
</p>
|
|
||||||
</CenteredContent>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default ErrorAuth;
|
|
@ -1,3 +0,0 @@
|
|||||||
// @flow
|
|
||||||
import ErrorAuth from './ErrorAuth';
|
|
||||||
export default ErrorAuth;
|
|
@ -1,97 +1,86 @@
|
|||||||
// @flow
|
// @flow
|
||||||
import Router from 'koa-router';
|
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 auth from '../middlewares/authentication';
|
||||||
import { slackAuth } from '../../shared/utils/routeHelpers';
|
import { slackAuth } from '../../shared/utils/routeHelpers';
|
||||||
import { presentUser, presentTeam } from '../presenters';
|
|
||||||
import { Authentication, Integration, User, Team } from '../models';
|
import { Authentication, Integration, User, Team } from '../models';
|
||||||
import * as Slack from '../slack';
|
import * as Slack from '../slack';
|
||||||
|
|
||||||
const router = new Router();
|
const router = new Router();
|
||||||
|
|
||||||
router.get('auth.slack', async ctx => {
|
// start the oauth process and redirect user to Slack
|
||||||
|
router.get('slack', async ctx => {
|
||||||
const state = Math.random()
|
const state = Math.random()
|
||||||
.toString(36)
|
.toString(36)
|
||||||
.substring(7);
|
.substring(7);
|
||||||
|
|
||||||
ctx.cookies.set('state', state, {
|
ctx.cookies.set('state', state, {
|
||||||
httpOnly: false,
|
httpOnly: false,
|
||||||
expires: new Date('2100'),
|
expires: addHours(new Date(), 1),
|
||||||
});
|
});
|
||||||
ctx.redirect(slackAuth(state));
|
ctx.redirect(slackAuth(state));
|
||||||
});
|
});
|
||||||
|
|
||||||
router.post('auth.slack', async ctx => {
|
// signin callback from Slack
|
||||||
const { code } = ctx.body;
|
router.get('slack.callback', async ctx => {
|
||||||
ctx.assertPresent(code, 'code is required');
|
const { code, error, state } = ctx.request.query;
|
||||||
|
ctx.assertPresent(code || error, 'code is required');
|
||||||
|
ctx.assertPresent(state, 'state is required');
|
||||||
|
|
||||||
|
if (state !== ctx.cookies.get('state') || error) {
|
||||||
|
ctx.redirect('/?notice=auth-error');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const data = await Slack.oauthAccess(code);
|
const data = await Slack.oauthAccess(code);
|
||||||
|
|
||||||
let user = await User.findOne({
|
const [team, isFirstUser] = await Team.findOrCreate({
|
||||||
where: { service: 'slack', serviceId: data.user.id },
|
where: {
|
||||||
});
|
|
||||||
let team = await Team.findOne({ where: { slackId: data.team.id } });
|
|
||||||
const isFirstUser = !team;
|
|
||||||
|
|
||||||
if (team) {
|
|
||||||
team.name = data.team.name;
|
|
||||||
team.slackData = data.team;
|
|
||||||
await team.save();
|
|
||||||
} else {
|
|
||||||
team = await Team.create({
|
|
||||||
name: data.team.name,
|
|
||||||
slackId: data.team.id,
|
slackId: data.team.id,
|
||||||
slackData: data.team,
|
},
|
||||||
|
defaults: {
|
||||||
|
name: data.team.name,
|
||||||
|
avatarUrl: data.team.image_88,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
|
||||||
if (user) {
|
const [user] = await User.findOrCreate({
|
||||||
user.slackAccessToken = data.access_token;
|
where: {
|
||||||
user.slackData = data.user;
|
|
||||||
await user.save();
|
|
||||||
} else {
|
|
||||||
user = await User.create({
|
|
||||||
service: 'slack',
|
service: 'slack',
|
||||||
serviceId: data.user.id,
|
serviceId: data.user.id,
|
||||||
|
teamId: team.id,
|
||||||
|
},
|
||||||
|
defaults: {
|
||||||
name: data.user.name,
|
name: data.user.name,
|
||||||
email: data.user.email,
|
email: data.user.email,
|
||||||
teamId: team.id,
|
|
||||||
isAdmin: isFirstUser,
|
isAdmin: isFirstUser,
|
||||||
slackData: data.user,
|
avatarUrl: data.user.image_192,
|
||||||
slackAccessToken: data.access_token,
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
// Set initial avatar
|
|
||||||
await user.updateAvatar();
|
|
||||||
await user.save();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isFirstUser) {
|
if (isFirstUser) {
|
||||||
await team.createFirstCollection(user.id);
|
await team.createFirstCollection(user.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Signal to backend that the user is logged in.
|
ctx.cookies.set('lastSignedIn', 'slack', {
|
||||||
// This is only used to signal SSR rendering, not
|
|
||||||
// used for auth.
|
|
||||||
ctx.cookies.set('loggedIn', 'true', {
|
|
||||||
httpOnly: false,
|
httpOnly: false,
|
||||||
expires: new Date('2100'),
|
expires: new Date('2100'),
|
||||||
});
|
});
|
||||||
|
ctx.cookies.set('accessToken', user.getJwtToken(), {
|
||||||
|
httpOnly: false,
|
||||||
|
expires: addMonths(new Date(), 1),
|
||||||
|
});
|
||||||
|
|
||||||
ctx.body = {
|
ctx.redirect('/');
|
||||||
data: {
|
|
||||||
user: await presentUser(ctx, user),
|
|
||||||
team: await presentTeam(ctx, team),
|
|
||||||
accessToken: user.getJwtToken(),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
});
|
});
|
||||||
|
|
||||||
router.post('auth.slackCommands', auth(), async ctx => {
|
router.post('slack.commands', auth(), async ctx => {
|
||||||
const { code } = ctx.body;
|
const { code } = ctx.body;
|
||||||
ctx.assertPresent(code, 'code is required');
|
ctx.assertPresent(code, 'code is required');
|
||||||
|
|
||||||
const user = ctx.state.user;
|
const user = ctx.state.user;
|
||||||
const endpoint = `${process.env.URL || ''}/auth/slack/commands`;
|
const endpoint = `${process.env.URL || ''}/auth/slack.commands`;
|
||||||
const data = await Slack.oauthAccess(code, endpoint);
|
const data = await Slack.oauthAccess(code, endpoint);
|
||||||
const serviceId = 'slack';
|
const serviceId = 'slack';
|
||||||
|
|
||||||
@ -112,12 +101,12 @@ router.post('auth.slackCommands', auth(), async ctx => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
router.post('auth.slackPost', auth(), async ctx => {
|
router.post('slack.post', auth(), async ctx => {
|
||||||
const { code, collectionId } = ctx.body;
|
const { code, collectionId } = ctx.body;
|
||||||
ctx.assertPresent(code, 'code is required');
|
ctx.assertPresent(code, 'code is required');
|
||||||
|
|
||||||
const user = ctx.state.user;
|
const user = ctx.state.user;
|
||||||
const endpoint = `${process.env.URL || ''}/auth/slack/post`;
|
const endpoint = `${process.env.URL || ''}/auth/slack.post`;
|
||||||
const data = await Slack.oauthAccess(code, endpoint);
|
const data = await Slack.oauthAccess(code, endpoint);
|
||||||
const serviceId = 'slack';
|
const serviceId = 'slack';
|
||||||
|
|
||||||
|
@ -10,7 +10,7 @@ import { developers, githubUrl } from '../../shared/utils/routeHelpers';
|
|||||||
import { color } from '../../shared/styles/constants';
|
import { color } from '../../shared/styles/constants';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
notice?: 'google-hd',
|
notice?: 'google-hd' | 'auth-error',
|
||||||
lastSignedIn: string,
|
lastSignedIn: string,
|
||||||
googleSigninEnabled: boolean,
|
googleSigninEnabled: boolean,
|
||||||
slackSigninEnabled: boolean,
|
slackSigninEnabled: boolean,
|
||||||
@ -38,6 +38,12 @@ function Home(props: Props) {
|
|||||||
try signing in with your company Google account.
|
try signing in with your company Google account.
|
||||||
</Notice>
|
</Notice>
|
||||||
)}
|
)}
|
||||||
|
{props.notice === 'auth-error' && (
|
||||||
|
<Notice>
|
||||||
|
Authentication failed - we were unable to sign you in at this
|
||||||
|
time. Please try again.
|
||||||
|
</Notice>
|
||||||
|
)}
|
||||||
</Hero>
|
</Hero>
|
||||||
<Features reverse={{ mobile: true, tablet: false, desktop: false }}>
|
<Features reverse={{ mobile: true, tablet: false, desktop: false }}>
|
||||||
<Grid.Unit size={{ desktop: 1 / 3, tablet: 1 / 2 }}>
|
<Grid.Unit size={{ desktop: 1 / 3, tablet: 1 / 2 }}>
|
||||||
|
@ -44,7 +44,7 @@ export async function request(endpoint: string, body: Object) {
|
|||||||
|
|
||||||
export async function oauthAccess(
|
export async function oauthAccess(
|
||||||
code: string,
|
code: string,
|
||||||
redirect_uri: string = `${process.env.URL || ''}/auth/slack`
|
redirect_uri: string = `${process.env.URL || ''}/auth/slack.callback`
|
||||||
) {
|
) {
|
||||||
return request('oauth.access', {
|
return request('oauth.access', {
|
||||||
client_id: process.env.SLACK_KEY,
|
client_id: process.env.SLACK_KEY,
|
||||||
|
@ -8,7 +8,7 @@ export function slackAuth(
|
|||||||
'identity.avatar',
|
'identity.avatar',
|
||||||
'identity.team',
|
'identity.team',
|
||||||
],
|
],
|
||||||
redirectUri: string = `${process.env.URL}/auth/slack`
|
redirectUri: string = `${process.env.URL}/auth/slack.callback`
|
||||||
): string {
|
): string {
|
||||||
const baseUrl = 'https://slack.com/oauth/authorize';
|
const baseUrl = 'https://slack.com/oauth/authorize';
|
||||||
const params = {
|
const params = {
|
||||||
|
Reference in New Issue
Block a user