WIP: Successful Google Auth, broke pretty much everything else in the process

This commit is contained in:
Tom Moor 2018-05-28 11:36:37 -07:00
parent 1ba5c1cf96
commit ddd2b82d20
33 changed files with 443 additions and 387 deletions

View File

@ -14,10 +14,13 @@ DEPLOYMENT=self
ENABLE_UPDATES=true
DEBUG=sql,cache,presenters,events
# Third party credentials (required)
# Slack signin credentials (at least one is required)
SLACK_KEY=71315967491.XXXXXXXXXX
SLACK_SECRET=d2dc414f9953226bad0a356cXXXXYYYY
GOOGLE_CLIENT_ID=
GOOGLE_CLIENT_SECRET=
# Third party credentials (optional)
SLACK_VERIFICATION_TOKEN=PLxk6OlXXXXXVj3YYYY
SLACK_APP_ID=A0XXXXXXX

View File

@ -15,7 +15,12 @@ type Props = {
let authenticatedStores;
const Auth = ({ children }: Props) => {
if (stores.auth.authenticated && stores.auth.team && stores.auth.user) {
if (stores.auth.authenticated) {
if (!stores.auth.team || !stores.auth.user) {
stores.auth.fetch();
return null;
}
// Only initialize stores once. Kept in global scope because otherwise they
// will get overridden on route change
if (!authenticatedStores) {
@ -42,7 +47,6 @@ const Auth = ({ children }: Props) => {
};
}
stores.auth.fetch();
authenticatedStores.collections.fetchPage({ limit: 100 });
}

View File

@ -123,7 +123,9 @@ class AuthStore {
}
this.user = data.user;
this.team = data.team;
this.token = data.token;
this.token = Cookie.get('accessToken') || data.token;
console.log('TOKEN', this.token);
this.oauthState = data.oauthState;
autorun(() => {

View File

@ -99,6 +99,7 @@
"file-loader": "^1.1.6",
"flow-typed": "^2.4.0",
"fs-extra": "^4.0.2",
"google-auth-library": "^1.5.0",
"history": "3.0.0",
"html-webpack-plugin": "2.17.0",
"http-errors": "1.4.0",
@ -168,11 +169,11 @@
"styled-components-breakpoint": "^1.0.1",
"styled-components-grid": "^1.0.0-preview.15",
"styled-normalize": "^2.2.1",
"uglifyjs-webpack-plugin": "1.2.5",
"url-loader": "^0.6.2",
"uuid": "2.0.2",
"validator": "5.2.0",
"webpack": "3.10.0",
"uglifyjs-webpack-plugin": "1.2.5",
"webpack-manifest-plugin": "^1.3.2"
},
"devDependencies": {

View File

@ -1,7 +1,7 @@
// @flow
import Router from 'koa-router';
import auth from './middlewares/authentication';
import auth from '../middlewares/authentication';
import pagination from './middlewares/pagination';
import { presentApiKey } from '../presenters';
import { ApiKey } from '../models';

View File

@ -1,9 +1,8 @@
// @flow
import Router from 'koa-router';
import auth from './middlewares/authentication';
import auth from '../middlewares/authentication';
import { presentUser, presentTeam } from '../presenters';
import { Authentication, Integration, User, Team } from '../models';
import * as Slack from '../slack';
import { Team } from '../models';
const router = new Router();
@ -19,126 +18,4 @@ router.post('auth.info', auth(), async ctx => {
};
});
router.post('auth.slack', async ctx => {
const { code } = ctx.body;
ctx.assertPresent(code, 'code is required');
const data = await Slack.oauthAccess(code);
let user = await User.findOne({ where: { slackId: data.user.id } });
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,
slackData: data.team,
});
}
if (user) {
user.slackAccessToken = data.access_token;
user.slackData = data.user;
await user.save();
} else {
user = await User.create({
slackId: data.user.id,
name: data.user.name,
email: data.user.email,
teamId: team.id,
isAdmin: isFirstUser,
slackData: data.user,
slackAccessToken: data.access_token,
});
// Set initial avatar
await user.updateAvatar();
await user.save();
}
if (isFirstUser) {
await team.createFirstCollection(user.id);
}
// Signal to backend that the user is logged in.
// This is only used to signal SSR rendering, not
// used for auth.
ctx.cookies.set('loggedIn', 'true', {
httpOnly: false,
expires: new Date('2100'),
});
ctx.body = {
data: {
user: await presentUser(ctx, user),
team: await presentTeam(ctx, team),
accessToken: user.getJwtToken(),
},
};
});
router.post('auth.slackCommands', auth(), async ctx => {
const { code } = ctx.body;
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 authentication = await Authentication.create({
serviceId,
userId: user.id,
teamId: user.teamId,
token: data.access_token,
scopes: data.scope.split(','),
});
await Integration.create({
serviceId,
type: 'command',
userId: user.id,
teamId: user.teamId,
authenticationId: authentication.id,
});
});
router.post('auth.slackPost', auth(), async ctx => {
const { code, collectionId } = ctx.body;
ctx.assertPresent(code, 'code is required');
const user = ctx.state.user;
const endpoint = `${process.env.URL || ''}/auth/slack/post`;
const data = await Slack.oauthAccess(code, endpoint);
const serviceId = 'slack';
const authentication = await Authentication.create({
serviceId,
userId: user.id,
teamId: user.teamId,
token: data.access_token,
scopes: data.scope.split(','),
});
await Integration.create({
serviceId,
type: 'post',
userId: user.id,
teamId: user.teamId,
authenticationId: authentication.id,
collectionId,
events: [],
settings: {
url: data.incoming_webhook.url,
channel: data.incoming_webhook.channel,
channelId: data.incoming_webhook.channel_id,
},
});
});
export default router;

View File

@ -1,164 +0,0 @@
/* eslint-disable flowtype/require-valid-file-annotation */
import TestServer from 'fetch-test-server';
import app from '..';
import { flushdb, seed } from '../test/support';
const server = new TestServer(app.callback());
beforeEach(flushdb);
afterAll(server.close);
describe.skip('#auth.signup', async () => {
it('should signup a new user', async () => {
const welcomeEmailMock = jest.fn();
jest.doMock('../mailer', () => {
return {
welcome: welcomeEmailMock,
};
});
const res = await server.post('/api/auth.signup', {
body: {
username: 'testuser',
name: 'Test User',
email: 'new.user@example.com',
password: 'test123!',
},
});
const body = await res.json();
expect(res.status).toEqual(200);
expect(body.ok).toBe(true);
expect(body.data.user).toBeTruthy();
expect(welcomeEmailMock).toBeCalledWith('new.user@example.com');
});
it('should require params', async () => {
const res = await server.post('/api/auth.signup', {
body: {
username: 'testuser',
},
});
const body = await res.json();
expect(res.status).toEqual(400);
expect(body).toMatchSnapshot();
});
it('should require valid email', async () => {
const res = await server.post('/api/auth.signup', {
body: {
username: 'testuser',
name: 'Test User',
email: 'example.com',
password: 'test123!',
},
});
const body = await res.json();
expect(res.status).toEqual(400);
expect(body).toMatchSnapshot();
});
it('should require unique email', async () => {
await seed();
const res = await server.post('/api/auth.signup', {
body: {
username: 'testuser',
name: 'Test User',
email: 'user1@example.com',
password: 'test123!',
},
});
const body = await res.json();
expect(res.status).toEqual(400);
expect(body).toMatchSnapshot();
});
it('should require unique username', async () => {
await seed();
const res = await server.post('/api/auth.signup', {
body: {
username: 'user1',
name: 'Test User',
email: 'userone@example.com',
password: 'test123!',
},
});
const body = await res.json();
expect(res.status).toEqual(400);
expect(body).toMatchSnapshot();
});
});
describe.skip('#auth.login', () => {
test('should login with email', async () => {
await seed();
const res = await server.post('/api/auth.login', {
body: {
username: 'user1@example.com',
password: 'test123!',
},
});
const body = await res.json();
expect(res.status).toEqual(200);
expect(body.ok).toBe(true);
expect(body.data.user).toMatchSnapshot();
});
test('should login with username', async () => {
await seed();
const res = await server.post('/api/auth.login', {
body: {
username: 'user1',
password: 'test123!',
},
});
const body = await res.json();
expect(res.status).toEqual(200);
expect(body.ok).toBe(true);
expect(body.data.user).toMatchSnapshot();
});
test('should validate password', async () => {
await seed();
const res = await server.post('/api/auth.login', {
body: {
email: 'user1@example.com',
password: 'bad_password',
},
});
const body = await res.json();
expect(res.status).toEqual(400);
expect(body).toMatchSnapshot();
});
test('should require either username or email', async () => {
const res = await server.post('/api/auth.login', {
body: {
password: 'test123!',
},
});
const body = await res.json();
expect(res.status).toEqual(400);
expect(body).toMatchSnapshot();
});
test('should require password', async () => {
await seed();
const res = await server.post('/api/auth.login', {
body: {
email: 'user1@example.com',
},
});
const body = await res.json();
expect(res.status).toEqual(400);
expect(body).toMatchSnapshot();
});
});

View File

@ -1,7 +1,7 @@
// @flow
import Router from 'koa-router';
import auth from './middlewares/authentication';
import auth from '../middlewares/authentication';
import pagination from './middlewares/pagination';
import { presentCollection } from '../presenters';
import { Collection } from '../models';

View File

@ -1,7 +1,7 @@
// @flow
import Router from 'koa-router';
import Sequelize from 'sequelize';
import auth from './middlewares/authentication';
import auth from '../middlewares/authentication';
import pagination from './middlewares/pagination';
import { presentDocument, presentRevision } from '../presenters';
import { Document, Collection, Share, Star, View, Revision } from '../models';

View File

@ -2,8 +2,6 @@
import bodyParser from 'koa-bodyparser';
import Koa from 'koa';
import Router from 'koa-router';
import Sequelize from 'sequelize';
import _ from 'lodash';
import auth from './auth';
import user from './user';
@ -16,58 +14,24 @@ import shares from './shares';
import team from './team';
import integrations from './integrations';
import validation from './middlewares/validation';
import methodOverride from '../middlewares/methodOverride';
import cache from '../middlewares/cache';
import errorHandling from './middlewares/errorHandling';
import validation from '../middlewares/validation';
import methodOverride from './middlewares/methodOverride';
import cache from './middlewares/cache';
import apiWrapper from './middlewares/apiWrapper';
const api = new Koa();
const router = new Router();
// API error handler
api.use(async (ctx, next) => {
try {
await next();
} catch (err) {
ctx.status = err.status || 500;
let message = err.message || err.name;
let error;
if (err instanceof Sequelize.ValidationError) {
// super basic form error handling
ctx.status = 400;
if (err.errors && err.errors[0]) {
message = `${err.errors[0].message} (${err.errors[0].path})`;
}
}
if (message.match('Authorization error')) {
ctx.status = 403;
error = 'authorization_error';
}
if (ctx.status === 500) {
message = 'Internal Server Error';
error = 'internal_server_error';
ctx.app.emit('error', err, ctx);
}
ctx.body = {
ok: false,
error: _.snakeCase(err.id || error),
status: err.status,
message,
data: err.errorData ? err.errorData : undefined,
};
}
});
// middlewares
api.use(errorHandling());
api.use(bodyParser());
api.use(methodOverride());
api.use(cache());
api.use(validation());
api.use(apiWrapper());
// routes
router.use('/', auth.routes());
router.use('/', user.routes());
router.use('/', collections.routes());

View File

@ -2,7 +2,7 @@
import Router from 'koa-router';
import Integration from '../models/Integration';
import pagination from './middlewares/pagination';
import auth from './middlewares/authentication';
import auth from '../middlewares/authentication';
import { presentIntegration } from '../presenters';
import policy from '../policies';

View File

@ -4,7 +4,7 @@ import { type Context } from 'koa';
export default function apiWrapper() {
return async function apiWrapperMiddleware(
ctx: Context,
next: () => Promise<void>
next: () => Promise<*>
) {
await next();

View File

@ -1,10 +1,11 @@
// @flow
import debug from 'debug';
import { type Context } from 'koa';
const debugCache = debug('cache');
export default function cache() {
return async function cacheMiddleware(ctx: Object, next: Function) {
return async function cacheMiddleware(ctx: Context, next: () => Promise<*>) {
ctx.cache = {};
ctx.cache.set = async (id, value) => {

View File

@ -0,0 +1,46 @@
// @flow
import Sequelize from 'sequelize';
import { snakeCase } from 'lodash';
import { type Context } from 'koa';
export default function errorHandling() {
return async function errorHandlingMiddleware(
ctx: Context,
next: () => Promise<*>
) {
try {
await next();
} catch (err) {
ctx.status = err.status || 500;
let message = err.message || err.name;
let error;
if (err instanceof Sequelize.ValidationError) {
// super basic form error handling
ctx.status = 400;
if (err.errors && err.errors[0]) {
message = `${err.errors[0].message} (${err.errors[0].path})`;
}
}
if (message.match('Authorization error')) {
ctx.status = 403;
error = 'authorization_error';
}
if (ctx.status === 500) {
message = 'Internal Server Error';
error = 'internal_server_error';
ctx.app.emit('error', err, ctx);
}
ctx.body = {
ok: false,
error: snakeCase(err.id || error),
status: err.status,
message,
data: err.errorData ? err.errorData : undefined,
};
}
};
}

View File

@ -5,7 +5,7 @@ import { type Context } from 'koa';
export default function methodOverride() {
return async function methodOverrideMiddleware(
ctx: Context,
next: () => Promise<void>
next: () => Promise<*>
) {
if (ctx.method === 'POST') {
// $FlowFixMe

View File

@ -6,7 +6,7 @@ import { type Context } from 'koa';
export default function pagination(options?: Object) {
return async function paginationMiddleware(
ctx: Context,
next: () => Promise<void>
next: () => Promise<*>
) {
const opts = {
defaultLimit: 15,

View File

@ -1,6 +1,6 @@
// @flow
import Router from 'koa-router';
import auth from './middlewares/authentication';
import auth from '../middlewares/authentication';
import pagination from './middlewares/pagination';
import { presentShare } from '../presenters';
import { Document, User, Share } from '../models';

View File

@ -2,7 +2,7 @@
import Router from 'koa-router';
import { User } from '../models';
import auth from './middlewares/authentication';
import auth from '../middlewares/authentication';
import pagination from './middlewares/pagination';
import { presentUser } from '../presenters';

View File

@ -4,7 +4,7 @@ import Router from 'koa-router';
import { makePolicy, signPolicy, publicS3Endpoint } from '../utils/s3';
import { ValidationError } from '../errors';
import { Event, User, Team } from '../models';
import auth from './middlewares/authentication';
import auth from '../middlewares/authentication';
import { presentUser } from '../presenters';
import policy from '../policies';

View File

@ -1,6 +1,6 @@
// @flow
import Router from 'koa-router';
import auth from './middlewares/authentication';
import auth from '../middlewares/authentication';
import { presentView } from '../presenters';
import { View, Document } from '../models';
import policy from '../policies';

86
server/auth/google.js Normal file
View File

@ -0,0 +1,86 @@
// @flow
import Router from 'koa-router';
import addMonths from 'date-fns/add_months';
import { OAuth2Client } from 'google-auth-library';
import { User, Team } from '../models';
const router = new Router();
const client = new OAuth2Client(
process.env.GOOGLE_CLIENT_ID,
process.env.GOOGLE_CLIENT_SECRET,
`${process.env.URL}/auth/google.callback`
);
// start the oauth process and redirect user to Google
router.get('google', async ctx => {
// Generate the url that will be used for the consent dialog.
const authorizeUrl = client.generateAuthUrl({
access_type: 'offline',
scope: [
'https://www.googleapis.com/auth/userinfo.profile',
'https://www.googleapis.com/auth/userinfo.email',
],
prompt: 'consent',
});
ctx.redirect(authorizeUrl);
});
// signin callback from Google
router.get('google.callback', async ctx => {
const { code } = ctx.request.query;
ctx.assertPresent(code, 'code is required');
const response = await client.getToken(code);
client.setCredentials(response.tokens);
console.log('Tokens acquired.');
console.log(response.tokens);
const profile = await client.request({
url: 'https://www.googleapis.com/oauth2/v1/userinfo',
});
const teamName = profile.data.hd.split('.')[0];
const [team, isFirstUser] = await Team.findOrCreate({
where: {
slackId: profile.data.hd,
},
defaults: {
name: teamName,
avatarUrl: `https://logo.clearbit.com/${profile.data.hd}`,
},
});
const [user, isFirstSignin] = await User.findOrCreate({
where: {
slackId: profile.data.id,
teamId: team.id,
},
defaults: {
name: profile.data.name,
email: profile.data.email,
isAdmin: isFirstUser,
avatarUrl: profile.data.picture,
},
});
if (!isFirstSignin) {
await user.save();
}
if (isFirstUser) {
await team.createFirstCollection(user.id);
}
ctx.cookies.set('lastLoggedIn', 'google', {
httpOnly: false,
expires: new Date('2100'),
});
ctx.cookies.set('accessToken', user.getJwtToken(), {
httpOnly: false,
expires: addMonths(new Date(), 6),
});
ctx.redirect('/');
});
export default router;

20
server/auth/index.js Normal file
View File

@ -0,0 +1,20 @@
// @flow
import bodyParser from 'koa-bodyparser';
import Koa from 'koa';
import Router from 'koa-router';
import validation from '../middlewares/validation';
import slack from './slack';
import google from './google';
const auth = new Koa();
const router = new Router();
router.use('/', slack.routes());
router.use('/', google.routes());
auth.use(bodyParser());
auth.use(validation());
auth.use(router.routes());
export default auth;

145
server/auth/slack.js Normal file
View File

@ -0,0 +1,145 @@
// @flow
import Router from 'koa-router';
import auth from '../middlewares/authentication';
import { slackAuth } from '../../shared/utils/routeHelpers';
import { presentUser, presentTeam } from '../presenters';
import { Authentication, Integration, User, Team } from '../models';
import * as Slack from '../slack';
const router = new Router();
router.get('auth.slack', 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));
});
router.post('auth.slack', async ctx => {
const { code } = ctx.body;
ctx.assertPresent(code, 'code is required');
const data = await Slack.oauthAccess(code);
let user = await User.findOne({ where: { slackId: data.user.id } });
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,
slackData: data.team,
});
}
if (user) {
user.slackAccessToken = data.access_token;
user.slackData = data.user;
await user.save();
} else {
user = await User.create({
slackId: data.user.id,
name: data.user.name,
email: data.user.email,
teamId: team.id,
isAdmin: isFirstUser,
slackData: data.user,
slackAccessToken: data.access_token,
});
// Set initial avatar
await user.updateAvatar();
await user.save();
}
if (isFirstUser) {
await team.createFirstCollection(user.id);
}
// Signal to backend that the user is logged in.
// This is only used to signal SSR rendering, not
// used for auth.
ctx.cookies.set('loggedIn', 'true', {
httpOnly: false,
expires: new Date('2100'),
});
ctx.body = {
data: {
user: await presentUser(ctx, user),
team: await presentTeam(ctx, team),
accessToken: user.getJwtToken(),
},
};
});
router.post('auth.slackCommands', auth(), async ctx => {
const { code } = ctx.body;
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 authentication = await Authentication.create({
serviceId,
userId: user.id,
teamId: user.teamId,
token: data.access_token,
scopes: data.scope.split(','),
});
await Integration.create({
serviceId,
type: 'command',
userId: user.id,
teamId: user.teamId,
authenticationId: authentication.id,
});
});
router.post('auth.slackPost', auth(), async ctx => {
const { code, collectionId } = ctx.body;
ctx.assertPresent(code, 'code is required');
const user = ctx.state.user;
const endpoint = `${process.env.URL || ''}/auth/slack/post`;
const data = await Slack.oauthAccess(code, endpoint);
const serviceId = 'slack';
const authentication = await Authentication.create({
serviceId,
userId: user.id,
teamId: user.teamId,
token: data.access_token,
scopes: data.scope.split(','),
});
await Integration.create({
serviceId,
type: 'post',
userId: user.id,
teamId: user.teamId,
authenticationId: authentication.id,
collectionId,
events: [],
settings: {
url: data.incoming_webhook.url,
channel: data.incoming_webhook.channel,
channelId: data.incoming_webhook.channel_id,
},
});
});
export default router;

View File

@ -8,6 +8,7 @@ import bugsnag from 'bugsnag';
import onerror from 'koa-onerror';
import updates from './utils/updates';
import auth from './auth';
import api from './api';
import emails from './emails';
import routes from './routes';
@ -79,6 +80,7 @@ if (process.env.NODE_ENV === 'development') {
}
}
app.use(mount('/auth', auth));
app.use(mount('/api', api));
app.use(mount(routes));

View File

@ -1,14 +1,11 @@
// @flow
import JWT from 'jsonwebtoken';
import { type Context } from 'koa';
import { User, ApiKey } from '../../models';
import { AuthenticationError, UserSuspendedError } from '../../errors';
import { User, ApiKey } from '../models';
import { AuthenticationError, UserSuspendedError } from '../errors';
export default function auth(options?: { required?: boolean } = {}) {
return async function authMiddleware(
ctx: Context,
next: () => Promise<void>
) {
return async function authMiddleware(ctx: Context, next: () => Promise<*>) {
let token;
const authorizationHeader = ctx.request.get('authorization');

View File

@ -1,10 +1,11 @@
// @flow
import validator from 'validator';
import { ParamRequiredError, ValidationError } from '../../errors';
import { validateColorHex } from '../../../shared/utils/color';
import { type Context } from 'koa';
import { ParamRequiredError, ValidationError } from '../errors';
import { validateColorHex } from '../../shared/utils/color';
export default function validation() {
return function validationMiddleware(ctx: Object, next: Function) {
return function validationMiddleware(ctx: Context, next: () => Promise<*>) {
ctx.assertPresent = (value, message) => {
if (value === undefined || value === null || value === '') {
throw new ParamRequiredError(message);

View File

@ -9,7 +9,11 @@ import SignupButton from './components/SignupButton';
import { developers, githubUrl } from '../../shared/utils/routeHelpers';
import { color } from '../../shared/styles/constants';
function Home() {
type Props = {
lastLoggedIn: string,
};
function Home({ lastLoggedIn }: Props) {
return (
<span>
<Helmet>
@ -23,7 +27,7 @@ function Home() {
logs, brainstorming, & more
</HeroText>
<p>
<SignupButton />
<SignupButton lastLoggedIn={lastLoggedIn} />
</p>
</Hero>
<Features reverse={{ mobile: true, tablet: false, desktop: false }}>

View File

@ -2,15 +2,32 @@
import * as React from 'react';
import styled from 'styled-components';
import { signin } from '../../../shared/utils/routeHelpers';
import Flex from '../../../shared/components/Flex';
import SlackLogo from '../../../shared/components/SlackLogo';
import { color } from '../../../shared/styles/constants';
const SlackSignin = () => {
type Props = {
lastLoggedIn: string,
};
const SlackSignin = ({ lastLoggedIn }: Props) => {
return (
<Button href={signin()}>
<SlackLogo />
<Spacer>Sign In with Slack</Spacer>
</Button>
<Flex justify="center">
<Flex>
<Button href={signin('slack')}>
<SlackLogo />
<Spacer>Sign In with Slack</Spacer>
</Button>
{lastLoggedIn === 'slack' && 'You signed in with Slack previously'}
</Flex>
&nbsp;
<Flex>
<Button href={signin('google')}>
<Spacer>Sign In with Google</Spacer>
</Button>
{lastLoggedIn === 'google' && 'You signed in with Google previously'}
</Flex>
</Flex>
);
};

View File

@ -19,8 +19,6 @@ export default (
user: User,
options: Options = {}
): UserPresentation => {
ctx.cache.set(user.id, user);
const userData = {};
userData.id = user.id;
userData.username = user.username;

View File

@ -7,7 +7,6 @@ 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 { robotsResponse } from './utils/robots';
import { NotFoundError } from './errors';
@ -48,19 +47,6 @@ 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, <About />));
router.get('/pricing', ctx => renderpage(ctx, <Pricing />));
@ -76,10 +62,14 @@ router.get('/changelog', async ctx => {
// home page
router.get('/', async ctx => {
if (ctx.cookies.get('loggedIn')) {
const lastLoggedIn = ctx.cookies.get('lastLoggedIn');
const accessToken = ctx.cookies.get('accessToken');
console.log(lastLoggedIn, accessToken);
if (accessToken) {
await renderapp(ctx);
} else {
await renderpage(ctx, <Home />);
await renderpage(ctx, <Home lastLoggedIn={lastLoggedIn} />);
}
});

View File

@ -57,8 +57,8 @@ export function changelog(): string {
return '/changelog';
}
export function signin(): string {
return '/auth/slack';
export function signin(service: string = 'slack'): string {
return `/auth/${service}`;
}
export function about(): string {

View File

@ -490,6 +490,13 @@ aws4@^1.2.1, aws4@^1.6.0:
version "1.6.0"
resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.6.0.tgz#83ef5ca860b2b32e4a0deedee8c771b9db57471e"
axios@^0.18.0:
version "0.18.0"
resolved "https://registry.yarnpkg.com/axios/-/axios-0.18.0.tgz#32d53e4851efdc0a11993b6cd000789d70c05102"
dependencies:
follow-redirects "^1.3.0"
is-buffer "^1.1.5"
axobject-query@^0.1.0:
version "0.1.0"
resolved "https://registry.yarnpkg.com/axobject-query/-/axobject-query-0.1.0.tgz#62f59dbc59c9f9242759ca349960e7a2fe3c36c0"
@ -3441,7 +3448,7 @@ express-session@~1.11.3:
uid-safe "~2.0.0"
utils-merge "1.0.0"
extend@^3.0.0, extend@~3.0.0, extend@~3.0.1:
extend@^3.0.0, extend@^3.0.1, extend@~3.0.0, extend@~3.0.1:
version "3.0.1"
resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.1.tgz#a755ea7bc1adfcc5a31ce7e762dbaadc5e636444"
@ -3739,6 +3746,12 @@ flush-write-stream@^1.0.0:
inherits "^2.0.1"
readable-stream "^2.0.4"
follow-redirects@^1.3.0:
version "1.5.0"
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.5.0.tgz#234f49cf770b7f35b40e790f636ceba0c3a0ab77"
dependencies:
debug "^3.1.0"
for-in@^1.0.1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80"
@ -3927,6 +3940,14 @@ gaze@^0.5.1:
dependencies:
globule "~0.1.0"
gcp-metadata@^0.6.3:
version "0.6.3"
resolved "https://registry.yarnpkg.com/gcp-metadata/-/gcp-metadata-0.6.3.tgz#4550c08859c528b370459bd77a7187ea0bdbc4ab"
dependencies:
axios "^0.18.0"
extend "^3.0.1"
retry-axios "0.3.2"
generic-pool@2.4.3:
version "2.4.3"
resolved "https://registry.yarnpkg.com/generic-pool/-/generic-pool-2.4.3.tgz#780c36f69dfad05a5a045dd37be7adca11a4f6ff"
@ -4099,6 +4120,18 @@ good-listener@^1.2.2:
dependencies:
delegate "^3.1.2"
google-auth-library@^1.5.0:
version "1.5.0"
resolved "https://registry.yarnpkg.com/google-auth-library/-/google-auth-library-1.5.0.tgz#d9068f8bad9017224a4c41abcdcb6cf6a704e83b"
dependencies:
axios "^0.18.0"
gcp-metadata "^0.6.3"
gtoken "^2.3.0"
jws "^3.1.4"
lodash.isstring "^4.0.1"
lru-cache "^4.1.2"
retry-axios "^0.3.2"
google-closure-compiler-js@^20170423.0.0:
version "20170423.0.0"
resolved "https://registry.yarnpkg.com/google-closure-compiler-js/-/google-closure-compiler-js-20170423.0.0.tgz#e9e8b40dadfdf0e64044c9479b5d26d228778fbc"
@ -4107,6 +4140,13 @@ google-closure-compiler-js@^20170423.0.0:
vinyl "^2.0.1"
webpack-core "^0.6.8"
google-p12-pem@^1.0.0:
version "1.0.2"
resolved "https://registry.yarnpkg.com/google-p12-pem/-/google-p12-pem-1.0.2.tgz#c8a3843504012283a0dbffc7430b7c753ecd4b07"
dependencies:
node-forge "^0.7.4"
pify "^3.0.0"
got@^3.2.0:
version "3.3.1"
resolved "https://registry.yarnpkg.com/got/-/got-3.3.1.tgz#e5d0ed4af55fc3eef4d56007769d98192bcb2eca"
@ -4163,6 +4203,16 @@ growly@^1.2.0, growly@^1.3.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/growly/-/growly-1.3.0.tgz#f10748cbe76af964b7c96c93c6bcc28af120c081"
gtoken@^2.3.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/gtoken/-/gtoken-2.3.0.tgz#4e0ffc16432d7041a1b3dbc1d97aac17a5dc964a"
dependencies:
axios "^0.18.0"
google-p12-pem "^1.0.0"
jws "^3.1.4"
mime "^2.2.0"
pify "^3.0.0"
gulp-help@~1.6.1:
version "1.6.1"
resolved "https://registry.yarnpkg.com/gulp-help/-/gulp-help-1.6.1.tgz#261db186e18397fef3f6a2c22e9c315bfa88ae0c"
@ -6406,7 +6456,7 @@ lru-cache@^4.0.1:
pseudomap "^1.0.2"
yallist "^2.1.2"
lru-cache@^4.1.1:
lru-cache@^4.1.1, lru-cache@^4.1.2:
version "4.1.3"
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.3.tgz#a1175cf3496dfc8436c156c334b4955992bce69c"
dependencies:
@ -6603,6 +6653,10 @@ mime@^1.4.1:
version "1.6.0"
resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1"
mime@^2.2.0:
version "2.3.1"
resolved "https://registry.yarnpkg.com/mime/-/mime-2.3.1.tgz#b1621c54d63b97c47d3cfe7f7215f7d64517c369"
mimic-fn@^1.0.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-1.1.0.tgz#e667783d92e89dbd342818b5230b9d62a672ad18"
@ -6855,6 +6909,10 @@ node-fetch@^1.0.1, node-fetch@^1.5.1:
encoding "^0.1.11"
is-stream "^1.0.1"
node-forge@^0.7.4:
version "0.7.5"
resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.7.5.tgz#6c152c345ce11c52f465c2abd957e8639cd674df"
node-int64@^0.4.0:
version "0.4.0"
resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b"
@ -8762,6 +8820,10 @@ retry-as-promised@^2.3.1:
bluebird "^3.4.6"
debug "^2.6.9"
retry-axios@0.3.2, retry-axios@^0.3.2:
version "0.3.2"
resolved "https://registry.yarnpkg.com/retry-axios/-/retry-axios-0.3.2.tgz#5757c80f585b4cc4c4986aa2ffd47a60c6d35e13"
rich-markdown-editor@1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/rich-markdown-editor/-/rich-markdown-editor-1.1.2.tgz#c44f14425b5b5f0da3adce8bf389ed6e20b705a4"