Update stuff
This commit is contained in:
parent
0254e42b9c
commit
553ba9db9a
40 changed files with 7343 additions and 717 deletions
47
api/app.js
47
api/app.js
|
@ -1,5 +1,5 @@
|
|||
import Router from 'koa-router';
|
||||
import stats from 'koa-statsd';
|
||||
import stats from '../lib/koa-statsd';
|
||||
import cors from 'kcors';
|
||||
import StatsD from 'statsy';
|
||||
import auth from './lib/auth';
|
||||
|
@ -14,9 +14,9 @@ const router = new Router();
|
|||
const statsdOpts = { prefix: 'hostr-api', host: process.env.STATSD_HOST };
|
||||
router.use(stats(statsdOpts));
|
||||
const statsd = new StatsD(statsdOpts);
|
||||
router.use(function* statsMiddleware(next) {
|
||||
this.statsd = statsd;
|
||||
yield next;
|
||||
router.use(async (ctx, next) => {
|
||||
ctx.statsd = statsd;
|
||||
await next();
|
||||
});
|
||||
|
||||
router.use(cors({
|
||||
|
@ -24,21 +24,22 @@ router.use(cors({
|
|||
credentials: true,
|
||||
}));
|
||||
|
||||
router.use('*', function* authMiddleware(next) {
|
||||
router.use(async (ctx, next) => {
|
||||
try {
|
||||
yield next;
|
||||
if (this.response.status === 404 && !this.response.body) {
|
||||
this.throw(404);
|
||||
await next();
|
||||
|
||||
if (ctx.response.status === 404 && !ctx.response.body) {
|
||||
ctx.throw(404);
|
||||
}
|
||||
} catch (err) {
|
||||
if (err.status === 401) {
|
||||
this.statsd.incr('auth.failure', 1);
|
||||
this.set('WWW-Authenticate', 'Basic');
|
||||
this.status = 401;
|
||||
this.body = err.message;
|
||||
ctx.statsd.incr('auth.failure', 1);
|
||||
ctx.set('WWW-Authenticate', 'Basic');
|
||||
ctx.status = 401;
|
||||
ctx.body = err.message;
|
||||
} else if (err.status === 404) {
|
||||
this.status = 404;
|
||||
this.body = {
|
||||
ctx.status = 404;
|
||||
ctx.body = {
|
||||
error: {
|
||||
message: 'File not found',
|
||||
code: 604,
|
||||
|
@ -47,19 +48,20 @@ router.use('*', function* authMiddleware(next) {
|
|||
} else {
|
||||
if (!err.status) {
|
||||
debug(err);
|
||||
if (this.raven) {
|
||||
this.raven.captureError(err);
|
||||
if (ctx.raven) {
|
||||
ctx.raven.captureError(err);
|
||||
}
|
||||
throw err;
|
||||
} else {
|
||||
this.status = err.status;
|
||||
this.body = err.message;
|
||||
ctx.status = err.status;
|
||||
ctx.body = err.message;
|
||||
}
|
||||
}
|
||||
}
|
||||
this.type = 'application/json';
|
||||
ctx.type = 'application/json';
|
||||
});
|
||||
|
||||
router.delete('/file/:id', auth, file.del);
|
||||
router.get('/user', auth, user.get);
|
||||
router.get('/user/token', auth, user.token);
|
||||
router.get('/token', auth, user.token);
|
||||
|
@ -70,12 +72,11 @@ router.delete('/user/pro', auth, pro.cancel);
|
|||
router.get('/file', auth, file.list);
|
||||
router.post('/file', auth, file.post);
|
||||
router.get('/file/:id', file.get);
|
||||
router.delete('/file/:id', auth, file.del);
|
||||
router.delete('/file/:id', auth, file.del);
|
||||
|
||||
|
||||
// Hack, if no route matches here, router does not dispatch at all
|
||||
router.get('/(.*)', function* errorMiddleware() {
|
||||
this.throw(404);
|
||||
router.get('/(.*)', async (ctx) => {
|
||||
ctx.throw(404);
|
||||
});
|
||||
|
||||
export const ws = new Router();
|
||||
|
|
|
@ -6,27 +6,27 @@ const debug = debugname('hostr-api:auth');
|
|||
|
||||
const badLoginMsg = '{"error": {"message": "Incorrect login details.", "code": 607}}';
|
||||
|
||||
export default function* (next) {
|
||||
export default async (ctx, next) => {
|
||||
let user = false;
|
||||
const remoteIp = this.req.headers['x-forwarded-for'] || this.req.connection.remoteAddress;
|
||||
const login = yield models.login.create({
|
||||
const remoteIp = ctx.req.headers['x-forwarded-for'] || ctx.req.connection.remoteAddress;
|
||||
const login = await models.login.create({
|
||||
ip: remoteIp,
|
||||
successful: false,
|
||||
});
|
||||
if (this.req.headers.authorization && this.req.headers.authorization[0] === ':') {
|
||||
if (ctx.req.headers.authorization && ctx.req.headers.authorization[0] === ':') {
|
||||
debug('Logging in with token');
|
||||
const userToken = yield this.redis.get(this.req.headers.authorization.substr(1));
|
||||
this.assert(userToken, 401, '{"error": {"message": "Invalid token.", "code": 606}}');
|
||||
const userToken = await ctx.redis.get(ctx.req.headers.authorization.substr(1));
|
||||
ctx.assert(userToken, 401, '{"error": {"message": "Invalid token.", "code": 606}}');
|
||||
debug('Token found');
|
||||
user = yield models.user.findById(userToken);
|
||||
user = await models.user.findById(userToken);
|
||||
if (!user) {
|
||||
login.save();
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
const authUser = auth(this);
|
||||
this.assert(authUser, 401, badLoginMsg);
|
||||
const count = yield models.login.count({
|
||||
const authUser = auth(ctx);
|
||||
ctx.assert(authUser, 401, badLoginMsg);
|
||||
const count = await models.login.count({
|
||||
where: {
|
||||
ip: remoteIp,
|
||||
successful: false,
|
||||
|
@ -36,38 +36,38 @@ export default function* (next) {
|
|||
},
|
||||
});
|
||||
|
||||
this.assert(count < 25, 401,
|
||||
ctx.assert(count < 25, 401,
|
||||
'{"error": {"message": "Too many incorrect logins.", "code": 608}}');
|
||||
|
||||
user = yield models.user.findOne({
|
||||
user = await models.user.findOne({
|
||||
where: {
|
||||
email: authUser.name,
|
||||
activated: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!user || !(yield passwords.match(authUser.pass, user.password))) {
|
||||
if (!user || !(await passwords.match(authUser.pass, user.password))) {
|
||||
login.save();
|
||||
this.throw(401, badLoginMsg);
|
||||
ctx.throw(401, badLoginMsg);
|
||||
return;
|
||||
}
|
||||
}
|
||||
debug('Checking user');
|
||||
this.assert(user, 401, badLoginMsg);
|
||||
ctx.assert(user, 401, badLoginMsg);
|
||||
debug('Checking user is activated');
|
||||
debug(user.activated);
|
||||
this.assert(user.activated === true, 401,
|
||||
ctx.assert(user.activated === true, 401,
|
||||
'{"error": {"message": "Account has not been activated.", "code": 603}}');
|
||||
|
||||
login.successful = true;
|
||||
yield login.save();
|
||||
await login.save();
|
||||
|
||||
const uploadedTotal = yield models.file.count({
|
||||
const uploadedTotal = await models.file.count({
|
||||
where: {
|
||||
userId: user.id,
|
||||
},
|
||||
});
|
||||
const uploadedToday = yield models.file.count({
|
||||
const uploadedToday = await models.file.count({
|
||||
where: {
|
||||
userId: user.id,
|
||||
createdAt: {
|
||||
|
@ -85,9 +85,9 @@ export default function* (next) {
|
|||
plan: user.plan,
|
||||
uploads_today: uploadedToday,
|
||||
};
|
||||
this.response.set('Daily-Uploads-Remaining',
|
||||
ctx.response.set('Daily-Uploads-Remaining',
|
||||
user.type === 'Pro' ? 'unlimited' : 15 - uploadedToday);
|
||||
this.user = normalisedUser;
|
||||
debug('Authenticated user: ', this.user.email);
|
||||
yield next;
|
||||
ctx.user = normalisedUser;
|
||||
debug('Authenticated user: ', ctx.user.email);
|
||||
await next();
|
||||
}
|
||||
|
|
|
@ -6,107 +6,100 @@ import Uploader from '../../lib/uploader';
|
|||
|
||||
const redisUrl = process.env.REDIS_URL;
|
||||
|
||||
export function* post(next) {
|
||||
if (!this.request.is('multipart/*')) {
|
||||
yield next;
|
||||
export async function post(ctx, next) {
|
||||
if (!ctx.request.is('multipart/*')) {
|
||||
await next();
|
||||
return;
|
||||
}
|
||||
|
||||
const uploader = new Uploader(this);
|
||||
const uploader = new Uploader(ctx);
|
||||
|
||||
yield uploader.checkLimit();
|
||||
yield uploader.accept();
|
||||
await uploader.checkLimit();
|
||||
|
||||
uploader.acceptedEvent();
|
||||
await uploader.accept();
|
||||
await uploader.processImage();
|
||||
await uploader.finalise();
|
||||
|
||||
yield uploader.receive();
|
||||
|
||||
yield uploader.promise;
|
||||
|
||||
uploader.processingEvent();
|
||||
|
||||
yield uploader.processImage();
|
||||
|
||||
yield uploader.finalise();
|
||||
|
||||
this.status = 201;
|
||||
this.body = formatFile(uploader.file);
|
||||
ctx.status = 201;
|
||||
ctx.body = formatFile(uploader.file);
|
||||
|
||||
uploader.completeEvent();
|
||||
uploader.malwareScan();
|
||||
}
|
||||
|
||||
|
||||
export function* list() {
|
||||
export async function list(ctx) {
|
||||
let limit = 20;
|
||||
if (this.request.query.perpage === '0') {
|
||||
if (ctx.request.query.perpage === '0') {
|
||||
limit = 1000;
|
||||
} else if (this.request.query.perpage > 0) {
|
||||
limit = parseInt(this.request.query.perpage / 1, 10);
|
||||
} else if (ctx.request.query.perpage > 0) {
|
||||
limit = parseInt(ctx.request.query.perpage / 1, 10);
|
||||
}
|
||||
|
||||
let offset = 0;
|
||||
if (this.request.query.page) {
|
||||
offset = parseInt(this.request.query.page - 1, 10) * limit;
|
||||
if (ctx.request.query.page) {
|
||||
offset = parseInt(ctx.request.query.page - 1, 10) * limit;
|
||||
}
|
||||
|
||||
const files = yield models.file.findAll({
|
||||
const files = await models.file.findAll({
|
||||
where: {
|
||||
userId: this.user.id,
|
||||
userId: ctx.user.id,
|
||||
processed: true,
|
||||
},
|
||||
order: '"createdAt" DESC',
|
||||
order: [
|
||||
['createdAt', 'DESC'],
|
||||
],
|
||||
offset,
|
||||
limit,
|
||||
});
|
||||
|
||||
this.statsd.incr('file.list', 1);
|
||||
this.body = files.map(formatFile);
|
||||
ctx.statsd.incr('file.list', 1);
|
||||
ctx.body = files.map(formatFile);
|
||||
}
|
||||
|
||||
|
||||
export function* get() {
|
||||
const file = yield models.file.findOne({
|
||||
export async function get(ctx) {
|
||||
const file = await models.file.findOne({
|
||||
where: {
|
||||
id: this.params.id,
|
||||
id: ctx.params.id,
|
||||
},
|
||||
});
|
||||
this.assert(file, 404, '{"error": {"message": "File not found", "code": 604}}');
|
||||
const user = yield file.getUser();
|
||||
this.assert(user && !user.banned, 404, '{"error": {"message": "File not found", "code": 604}}');
|
||||
this.statsd.incr('file.get', 1);
|
||||
this.body = formatFile(file);
|
||||
ctx.assert(file, 404, '{"error": {"message": "File not found", "code": 604}}');
|
||||
const user = await file.getUser();
|
||||
ctx.assert(user && !user.banned, 404, '{"error": {"message": "File not found", "code": 604}}');
|
||||
ctx.statsd.incr('file.get', 1);
|
||||
ctx.body = formatFile(file);
|
||||
}
|
||||
|
||||
|
||||
export function* del() {
|
||||
const file = yield models.file.findOne({
|
||||
export async function del(ctx) {
|
||||
const file = await models.file.findOne({
|
||||
where: {
|
||||
id: this.params.id,
|
||||
userId: this.user.id,
|
||||
id: ctx.params.id,
|
||||
userId: ctx.user.id,
|
||||
},
|
||||
});
|
||||
this.assert(file, 401, '{"error": {"message": "File not found", "code": 604}}');
|
||||
yield file.destroy();
|
||||
const event = { type: 'file-deleted', data: { id: this.params.id } };
|
||||
yield this.redis.publish(`/file/${this.params.id}`, JSON.stringify(event));
|
||||
yield this.redis.publish(`/user/${this.user.id}`, JSON.stringify(event));
|
||||
this.statsd.incr('file.delete', 1);
|
||||
this.status = 204;
|
||||
this.body = '';
|
||||
ctx.assert(file, 401, '{"error": {"message": "File not found", "code": 604}}');
|
||||
await file.destroy();
|
||||
const event = { type: 'file-deleted', data: { id: ctx.params.id } };
|
||||
await ctx.redis.publish(`/file/${ctx.params.id}`, JSON.stringify(event));
|
||||
await ctx.redis.publish(`/user/${ctx.user.id}`, JSON.stringify(event));
|
||||
ctx.statsd.incr('file.delete', 1);
|
||||
ctx.status = 204;
|
||||
ctx.body = '';
|
||||
}
|
||||
|
||||
|
||||
export function* events() {
|
||||
export async function events(ctx) {
|
||||
const pubsub = redis.createClient(redisUrl);
|
||||
pubsub.on('ready', () => {
|
||||
pubsub.subscribe(this.path);
|
||||
pubsub.subscribe(ctx.path);
|
||||
});
|
||||
|
||||
pubsub.on('message', (channel, message) => {
|
||||
this.websocket.send(message);
|
||||
ctx.websocket.send(message);
|
||||
});
|
||||
this.websocket.on('close', () => {
|
||||
ctx.websocket.on('close', () => {
|
||||
pubsub.quit();
|
||||
});
|
||||
}
|
||||
|
|
|
@ -11,29 +11,29 @@ import models from '../../models';
|
|||
const from = process.env.EMAIL_FROM;
|
||||
const fromname = process.env.EMAIL_NAME;
|
||||
|
||||
export function* create() {
|
||||
const stripeToken = this.request.body.stripeToken;
|
||||
export async function create(ctx) {
|
||||
const stripeToken = ctx.request.body.stripeToken;
|
||||
|
||||
const ip = this.request.headers['x-forwarded-for'] || this.req.connection.remoteAddress;
|
||||
const ip = ctx.request.headers['x-forwarded-for'] || ctx.req.connection.remoteAddress;
|
||||
|
||||
const createCustomer = {
|
||||
card: stripeToken.id,
|
||||
plan: 'usd_monthly',
|
||||
email: this.user.email,
|
||||
email: ctx.user.email,
|
||||
};
|
||||
|
||||
const customer = yield stripe.customers.create(createCustomer);
|
||||
const customer = await stripe.customers.create(createCustomer);
|
||||
|
||||
this.assert(customer.subscription.status === 'active', 400, '{"status": "error"}');
|
||||
ctx.assert(customer.subscription.status === 'active', 400, '{"status": "error"}');
|
||||
|
||||
delete customer.subscriptions;
|
||||
|
||||
const user = yield models.user.findById(this.user.id);
|
||||
const user = await models.user.findById(ctx.user.id);
|
||||
user.plan = 'Pro';
|
||||
yield user.save();
|
||||
await user.save();
|
||||
|
||||
const transaction = yield models.transaction.create({
|
||||
userId: this.user.id,
|
||||
const transaction = await models.transaction.create({
|
||||
userId: ctx.user.id,
|
||||
amount: customer.subscription.plan.amount,
|
||||
description: customer.subscription.plan.name,
|
||||
data: customer,
|
||||
|
@ -41,12 +41,12 @@ export function* create() {
|
|||
ip,
|
||||
});
|
||||
|
||||
yield transaction.save();
|
||||
await transaction.save();
|
||||
|
||||
this.user.plan = 'Pro';
|
||||
this.body = { status: 'active' };
|
||||
ctx.user.plan = 'Pro';
|
||||
ctx.body = { status: 'active' };
|
||||
|
||||
const html = yield render('email/inlined/pro');
|
||||
const html = await render('email/inlined/pro');
|
||||
const text = `Hey, thanks for upgrading to Hostr Pro!
|
||||
|
||||
You've signed up for Hostr Pro Monthly at $6/Month.
|
||||
|
@ -55,7 +55,7 @@ export function* create() {
|
|||
`;
|
||||
|
||||
const mail = new sendgrid.Email({
|
||||
to: this.user.email,
|
||||
to: ctx.user.email,
|
||||
subject: 'Hostr Pro',
|
||||
from,
|
||||
fromname,
|
||||
|
@ -66,20 +66,20 @@ export function* create() {
|
|||
sendgrid.send(mail);
|
||||
}
|
||||
|
||||
export function* cancel() {
|
||||
const user = yield models.user.findById(this.user.id);
|
||||
const transactions = yield user.getTransactions();
|
||||
export async function cancel(ctx) {
|
||||
const user = await models.user.findById(ctx.user.id);
|
||||
const transactions = await user.getTransactions();
|
||||
const transaction = transactions[0];
|
||||
|
||||
yield stripe.customers.cancelSubscription(
|
||||
await stripe.customers.cancelSubscription(
|
||||
transaction.data.id,
|
||||
transaction.data.subscription.id,
|
||||
{ at_period_end: false }
|
||||
);
|
||||
|
||||
user.plan = 'Free';
|
||||
yield user.save();
|
||||
await user.save();
|
||||
|
||||
this.user.plan = 'Free';
|
||||
this.body = { status: 'inactive' };
|
||||
ctx.user.plan = 'Free';
|
||||
ctx.body = { status: 'inactive' };
|
||||
}
|
||||
|
|
|
@ -9,24 +9,24 @@ const debug = debugname('hostr-api:user');
|
|||
|
||||
const redisUrl = process.env.REDIS_URL;
|
||||
|
||||
export function* get() {
|
||||
this.body = this.user;
|
||||
export async function get(ctx) {
|
||||
ctx.body = ctx.user;
|
||||
}
|
||||
|
||||
export function* token() {
|
||||
export async function token(ctx) {
|
||||
const token = uuid.v4(); // eslint-disable-line no-shadow
|
||||
yield this.redis.set(token, this.user.id, 'EX', 86400);
|
||||
this.body = { token };
|
||||
await ctx.redis.set(token, ctx.user.id, 'EX', 86400);
|
||||
ctx.body = { token };
|
||||
}
|
||||
|
||||
export function* transaction() {
|
||||
const transactions = yield models.transaction.findAll({
|
||||
export async function transaction(ctx) {
|
||||
const transactions = await models.transaction.findAll({
|
||||
where: {
|
||||
userId: this.user.id,
|
||||
userId: ctx.user.id,
|
||||
},
|
||||
});
|
||||
|
||||
this.body = transactions.map((item) => {
|
||||
ctx.body = transactions.map((item) => {
|
||||
return {
|
||||
id: item.id,
|
||||
amount: item.amount / 100,
|
||||
|
@ -37,57 +37,57 @@ export function* transaction() {
|
|||
});
|
||||
}
|
||||
|
||||
export function* settings() {
|
||||
this.assert(this.request.body, 400,
|
||||
export async function settings(ctx) {
|
||||
ctx.assert(ctx.request.body, 400,
|
||||
'{"error": {"message": "Current Password required to update account.", "code": 612}}');
|
||||
this.assert(this.request.body.current_password, 400,
|
||||
ctx.assert(ctx.request.body.current_password, 400,
|
||||
'{"error": {"message": "Current Password required to update account.", "code": 612}}');
|
||||
const user = yield models.user.findById(this.user.id);
|
||||
this.assert(yield passwords.match(this.request.body.current_password, user.password), 400,
|
||||
const user = await models.user.findById(ctx.user.id);
|
||||
ctx.assert(await passwords.match(ctx.request.body.current_password, user.password), 400,
|
||||
'{"error": {"message": "Incorrect password", "code": 606}}');
|
||||
if (this.request.body.email && this.request.body.email !== user.email) {
|
||||
user.email = this.request.body.email;
|
||||
if (ctx.request.body.email && ctx.request.body.email !== user.email) {
|
||||
user.email = ctx.request.body.email;
|
||||
}
|
||||
if (this.request.body.new_password) {
|
||||
this.assert(this.request.body.new_password.length >= 7, 400,
|
||||
if (ctx.request.body.new_password) {
|
||||
ctx.assert(ctx.request.body.new_password.length >= 7, 400,
|
||||
'{"error": {"message": "Password must be 7 or more characters long.", "code": 606}}');
|
||||
user.password = yield passwords.hash(this.request.body.new_password);
|
||||
user.password = await passwords.hash(ctx.request.body.new_password);
|
||||
}
|
||||
yield user.save();
|
||||
this.body = {};
|
||||
await user.save();
|
||||
ctx.body = {};
|
||||
}
|
||||
|
||||
export function* events() {
|
||||
export async function events(ctx) {
|
||||
const pubsub = redis.createClient(redisUrl);
|
||||
pubsub.on('message', (channel, message) => {
|
||||
this.websocket.send(message);
|
||||
ctx.websocket.send(message);
|
||||
});
|
||||
pubsub.on('ready', () => {
|
||||
this.websocket.on('message', co.wrap(function* wsMessage(message) {
|
||||
ctx.websocket.on('message', co.wrap(async (message) => {
|
||||
let json;
|
||||
try {
|
||||
json = JSON.parse(message);
|
||||
} catch (err) {
|
||||
debug('Invalid JSON for socket auth');
|
||||
this.websocket.send('Invalid authentication message. Bad JSON?');
|
||||
this.raven.captureError(err);
|
||||
ctx.websocket.send('Invalid authentication message. Bad JSON?');
|
||||
ctx.raven.captureError(err);
|
||||
}
|
||||
try {
|
||||
const reply = yield this.redis.get(json.authorization);
|
||||
const reply = await ctx.redis.get(json.authorization);
|
||||
if (reply) {
|
||||
pubsub.subscribe(`/user/${reply}`);
|
||||
this.websocket.send('{"status":"active"}');
|
||||
ctx.websocket.send('{"status":"active"}');
|
||||
debug('Subscribed to: /user/%s', reply);
|
||||
} else {
|
||||
this.websocket.send('Invalid authentication token.');
|
||||
ctx.websocket.send('Invalid authentication token.');
|
||||
}
|
||||
} catch (err) {
|
||||
debug(err);
|
||||
this.raven.captureError(err);
|
||||
ctx.raven.captureError(err);
|
||||
}
|
||||
}.bind(this)));
|
||||
}));
|
||||
});
|
||||
this.websocket.on('close', () => {
|
||||
ctx.websocket.on('close', () => {
|
||||
debug('Socket closed');
|
||||
pubsub.quit();
|
||||
});
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue