This commit is contained in:
Jori Lallo
2016-04-28 22:25:37 -07:00
parent 2f9233222d
commit cce82b3d43
79 changed files with 1495 additions and 496 deletions

73
server/api/auth.js Normal file
View File

@@ -0,0 +1,73 @@
import Router from 'koa-router';
import httpErrors from 'http-errors';
import fetch from 'isomorphic-fetch';
var querystring = require('querystring');
import { presentUser, presentTeam } from '../presenters';
import { User, Team } from '../models';
const router = new Router();
router.post('auth.slack', async (ctx) => {
const { code } = ctx.request.body;
ctx.assertPresent(code, 'code is required');
const body = {
client_id: process.env.SLACK_KEY,
client_secret: process.env.SLACK_SECRET,
code: code,
redirect_uri: process.env.SLACK_REDIRECT_URI,
}
let data;
try {
const response = await fetch('https://slack.com/api/oauth.access?' + querystring.stringify(body));
data = await response.json();
} catch(e) {
throw httpErrors.BadRequest();
}
if (!data.ok) throw httpErrors.BadRequest(data.error);
// User
let userData;
let user = await User.findOne({ slackId: data.user_id });
if (user) {
user.slackAccessToken = data.access_token;
user.save();
} else {
// Find existing user
const userParams = { token: data.access_token, user: data.user_id }
const response = await fetch('https://slack.com/api/users.info?' + querystring.stringify(userParams));
userData = await response.json();
user = await User.create({
slackId: data.user_id,
username: userData.user.name,
name: userData.user.profile.real_name,
email: userData.user.profile.email,
slackData: userData.user,
slackAccessToken: data.access_token,
});
}
// Team
let team = await Team.findOne({ slackId: data.team_id });
if (!team) {
team = await Team.create({
slackId: data.team_id,
name: data.team_name,
});
}
// Add to correct team
user.setTeam(team);
ctx.body = { data: {
user: presentUser(user),
team: presentTeam(team),
accessToken: user.getJwtToken(),
}};
});
export default router;

View File

@@ -0,0 +1,61 @@
import httpErrors from 'http-errors';
import JWT from 'jsonwebtoken';
import { User } from '../models';
export default function auth({ require = true } = {}) {
return async function authMiddleware(ctx, next) {
let token;
const authorizationHeader = ctx.request.get('authorization');
if (authorizationHeader) {
const parts = authorizationHeader.split(' ');
if (parts.length == 2) {
const scheme = parts[0];
const credentials = parts[1];
if (/^Bearer$/i.test(scheme)) {
token = credentials;
}
} else {
if (require) {
throw httpErrors.Unauthorized('Bad Authorization header format. Format is "Authorization: Bearer <token>"\n');
}
}
} else if (ctx.request.query.token) {
token = ctx.request.query.token;
}
if (!token && require) {
throw httpErrors.Unauthorized('Authentication required');
}
// Get user without verifying payload signature
let payload;
try {
payload = JWT.decode(token);
} catch(_e) {
throw httpErrors.Unauthorized('Unable to decode JWT token');
}
console.log(payload)
const user = await User.findOne({
where: { id: payload.id },
});
try {
JWT.verify(token, user.jwtSecret);
} catch(e) {
throw httpErrors.Unauthorized('Invalid token');
}
ctx.state.token = token;
ctx.state.user = user;
return next();
};
};
// Export JWT methods as a convenience
export const sign = JWT.sign;
export const verify = JWT.verify;
export const decode = JWT.decode;

55
server/api/index.js Normal file
View File

@@ -0,0 +1,55 @@
import bodyParser from 'koa-bodyparser';
import httpErrors from 'http-errors';
import Koa from 'koa';
import Router from 'koa-router';
import Sequelize from 'sequelize';
import auth from './auth';
import user from './user';
import validation from './validation';
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;
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 (ctx.status === 500) {
message = 'Internal Server Error';
ctx.app.emit('error', err, ctx);
}
ctx.body = { message };
}
});
api.use(bodyParser());
api.use(validation());
router.use('/', auth.routes());
router.use('/', user.routes());
// Router is embedded in a Koa application wrapper, because koa-router does not
// allow middleware to catch any routes which were not explicitly defined.
api.use(router.routes());
// API 404 handler
api.use(async () => {
throw httpErrors.NotFound();
});
export default api;

13
server/api/user.js Normal file
View File

@@ -0,0 +1,13 @@
import Router from 'koa-router';
import auth from './authentication';
import { presentUser } from '../presenters';
import { User } from '../models';
const router = new Router();
router.post('user.info', auth(), async (ctx) => {
ctx.body = { data: presentUser(ctx.state.user) };
});
export default router;

26
server/api/validation.js Normal file
View File

@@ -0,0 +1,26 @@
import httpErrors from 'http-errors';
import validator from 'validator';
export default function validation() {
return function validationMiddleware(ctx, next) {
ctx.assertPresent = function assertPresent(value, message) {
if (value === undefined || value === null || value === '') {
throw httpErrors.BadRequest(message);
}
};
ctx.assertEmail = function assertEmail(value, message) {
if (!validator.isEmail(value)) {
throw httpErrors.BadRequest(message);
}
};
ctx.assertUuid = function assertUuid(value, message) {
if (!validator.isUUID(value)) {
throw httpErrors.BadRequest(message);
}
};
return next();
};
}