Migrate from koa-route to koa-router

This commit is contained in:
Jonathan Cremin 2015-08-22 16:16:15 +01:00
parent 8474cca94c
commit a4bf3dd51d
17 changed files with 206 additions and 291 deletions

View file

@ -1,23 +1,15 @@
import path from 'path';
import koa from 'koa';
import Router from 'koa-router';
import csrf from 'koa-csrf';
import route from 'koa-route';
import views from 'koa-views';
import stats from 'koa-statsd';
import logger from 'koa-logger';
import favicon from 'koa-favicon';
import redisStore from 'koa-redis';
import compress from 'koa-compress';
import bodyparser from 'koa-bodyparser';
import redis from '../lib/redis';
import koaRedis from 'koa-redis'
import session from 'koa-generic-session';
import staticHandler from 'koa-file-server';
import co from 'co';
import raven from 'raven';
import StatsD from 'statsy';
// waiting for PR to be merged, can remove swig dependency when done
import errors from '../lib/koa-error';
import mongo from '../lib/mongo';
import redis from '../lib/redis';
import * as index from './routes/index';
import * as file from './routes/file';
import * as pro from './routes/pro';
@ -26,35 +18,24 @@ import * as user from './routes/user';
import debugname from 'debug';
const debug = debugname('hostr-web');
if (process.env.SENTRY_DSN) {
const ravenClient = new raven.Client(process.env.SENTRY_DSN);
ravenClient.patchGlobal();
}
const redisUrl = process.env.REDIS_URL || process.env.REDISTOGO_URL || 'redis://localhost:6379';
const app = koa();
const router = new Router();
router.use(errors({template: path.join(__dirname, 'public', '404.html')}));
let statsdOpts = {prefix: 'hostr-web', host: process.env.STATSD_HOST || 'localhost'};
let statsd = new StatsD(statsdOpts);
app.use(function*(next) {
router.use(function* (next) {
this.statsd = statsd;
yield next;
});
app.use(stats(statsdOpts));
router.use(stats(statsdOpts));
app.use(errors({template: path.join(__dirname, 'public', '404.html')}));
router.use(session({
store: koaRedis({client: redis().client})
}));
app.use(function*(next){
this.set('Server', 'Nintendo 64');
if(this.req.headers['x-forwarded-proto'] === 'http'){
return this.redirect('https://' + this.request.headers.host + this.request.url);
}
yield next;
});
app.use(function*(next){
router.use(function* (next){
this.state = {
session: this.session,
apiURL: process.env.API_URL,
baseURL: process.env.BASE_URL,
stripePublic: process.env.STRIPE_PUBLIC_KEY
@ -62,70 +43,49 @@ app.use(function*(next){
yield next;
});
app.use(mongo());
app.use(redis());
app.use(compress());
app.use(bodyparser());
app.use(favicon(path.join(__dirname, 'public/images/favicon.png')));
app.use(staticHandler({root: path.join(__dirname, 'public'), maxage: 31536000000}));
app.use(logger());
app.use(views('views', {
router.use(views('views', {
default: 'ejs'
}));
app.keys = [process.env.KEYS || 'INSECURE'];
app.use(session({
store: redisStore({client: redis().client})
}));
router.get('/', index.main);
router.get('/account', index.main);
router.get('/billing', index.main);
router.get('/pro', index.main);
app.use(route.get('/', index.main));
app.use(route.get('/account', index.main));
app.use(route.get('/billing', index.main));
app.use(route.get('/pro', index.main));
router.get('/signin', user.signin);
router.post('/signin', user.signin);
router.get('/signup', user.signup);
router.post('/signup', user.signup);
router.get('/logout', user.logout);
router.post('/logout', user.logout);
router.get('/forgot', user.forgot);
router.get('/forgot/:token', user.forgot);
router.post('/forgot/:token', user.forgot);
router.post('/forgot', user.forgot);
router.get('/activate/:code', user.activate);
app.use(route.get('/signin', user.signin));
app.use(route.post('/signin', user.signin));
app.use(route.get('/signup', user.signup));
app.use(route.post('/signup', user.signup));
app.use(route.get('/logout', user.logout));
app.use(route.post('/logout', user.logout));
app.use(route.get('/forgot', user.forgot));
app.use(route.get('/forgot/:token', user.forgot));
app.use(route.post('/forgot/:token', user.forgot));
app.use(route.post('/forgot', user.forgot));
app.use(route.get('/activate/:code', user.activate));
router.get('/terms', index.staticPage);
router.get('/privacy', index.staticPage);
router.get('/pricing', index.staticPage);
router.get('/apps', index.staticPage);
router.get('/stats', index.staticPage);
app.use(route.get('/terms', index.staticPage));
app.use(route.get('/privacy', index.staticPage));
app.use(route.get('/pricing', index.staticPage));
app.use(route.get('/apps', index.staticPage));
app.use(route.get('/stats', index.staticPage));
router.post('/pro/create', pro.create);
router.post('/pro/cancel', pro.cancel);
app.use(route.post('/pro/create', pro.create));
app.use(route.post('/pro/cancel', pro.cancel));
app.use(route.get('/:id', file.landing));
app.use(route.get('/download/:id/:name', function* (id) {
router.get('/:id', file.landing);
router.get('/file/:id/:name', file.get);
router.get('/file/:size/:id/:name', file.get);
router.get('/files/:id/:name', file.get);
router.get('/download/:id/:name', function* (id) {
this.redirect('/' + id);
}));
app.use(route.get('/file/:id/:name', file.get));
app.use(route.get('/files/:id/:name', file.get));
app.use(route.get('/file/:size/:id/:name', file.resized));
});
app.use(route.get('/updaters/mac', function* () {
router.get('/updaters/mac', function* () {
this.redirect('/updaters/mac.xml');
}));
app.use(route.get('/updaters/mac/changelog', function* () {
});
router.get('/updaters/mac/changelog', function* () {
yield this.render('mac-update-changelog');
}));
});
if (!module.parent) {
app.listen(process.env.PORT || 4041, function() {
debug('Koa HTTP server listening on port ' + (process.env.PORT || 4041));
});
setInterval(function() {
debug('%sMB', process.memoryUsage().rss / 1024 / 1024);
}, 10000);
}
export default app;
export default router;

View file

@ -7,11 +7,12 @@ import debugname from 'debug';
const debug = debugname('hostr-web:auth');
import { Mandrill } from 'mandrill-api/mandrill';
const mandrill = new Mandrill(process.env.MANDRILL_KEY);
import mongo from '../../lib/mongo';
export function* authenticate(ctx, email, password) {
const Users = ctx.db.Users;
const Logins = ctx.db.Logins;
const remoteIp = ctx.headers['x-real-ip'] || ctx.ip;
export function* authenticate(email, password) {
const Users = this.db.Users;
const Logins = this.db.Logins;
const remoteIp = this.headers['x-real-ip'] || this.ip;
if (!password || password.length < 6){
debug('No password, or password too short');
@ -45,10 +46,10 @@ export function* authenticate(ctx, email, password) {
}
export function* setupSession(ctx, user) {
export function* setupSession(user) {
debug('Setting up session');
const token = uuid.v4();
yield ctx.redis.set(token, user._id, 'EX', 604800);
yield this.redis.set(token, user._id, 'EX', 604800);
const sessionUser = {
'id': user._id,
@ -67,19 +68,19 @@ export function* setupSession(ctx, user) {
sessionUser.dailyUploadAllowance = 'unlimited';
}
ctx.session.user = sessionUser;
if (ctx.request.body.remember && ctx.request.body.remember === 'on') {
const Remember = ctx.db.Remember;
this.session.user = sessionUser;
if (this.request.body.remember && this.request.body.remember === 'on') {
const Remember = this.db.Remember;
var rememberToken = uuid();
Remember.save({_id: rememberToken, 'user_id': user.id, created: new Date().getTime()});
ctx.cookies.set('r', rememberToken, { maxAge: 1209600000, httpOnly: true});
this.cookies.set('r', rememberToken, { maxAge: 1209600000, httpOnly: true});
}
debug('Session set up');
}
export function* signup(ctx, email, password, ip) {
const Users = ctx.db.Users;
export function* signup(email, password, ip) {
const Users = this.db.Users;
const existingUser = yield Users.findOne({email: email, status: {'$ne': 'deleted'}});
if (existingUser) {
debug('Email already in use.');
@ -120,9 +121,9 @@ ${process.env.BASE_URL + '/activate/' + user.activationCode}
}
export function* sendResetToken(ctx, email) {
const Users = ctx.db.Users;
const Reset = ctx.db.Reset;
export function* sendResetToken(email) {
const Users = this.db.Users;
const Reset = this.db.Reset;
const user = yield Users.findOne({email: email});
if (user) {
var token = uuid.v4();
@ -155,40 +156,40 @@ Visit ${process.env.BASE_URL + '/forgot/' + token} to set a new one.
}
export function* fromToken(ctx, token) {
const Users = ctx.db.Users;
const reply = yield ctx.redis.get(token);
export function* fromToken(token) {
const Users = this.db.Users;
const reply = yield this.redis.get(token);
return yield Users.findOne({_id: reply});
}
export function* fromCookie(ctx, cookie) {
const Remember = ctx.db.Remember;
const Users = ctx.db.Users;
export function* fromCookie(cookie) {
const Remember = this.db.Remember;
const Users = this.db.Users;
const remember = yield Remember.findOne({_id: cookie});
return yield Users.findOne({_id: remember.user_id});
}
export function* validateResetToken(ctx) {
const Reset = ctx.db.Reset;
return yield Reset.findOne({token: ctx.params.id});
export function* validateResetToken() {
const Reset = this.db.Reset;
return yield Reset.findOne({token: this.params.id});
}
export function* updatePassword(ctx, userId, password) {
const Users = ctx.db.Users;
export function* updatePassword(userId, password) {
const Users = this.db.Users;
const cryptedPassword = yield passwords.crypt(password);
yield Users.update({_id: userId}, {'$set': {'salted_password': cryptedPassword}});
}
export function* activateUser(ctx, code) {
const Users = ctx.db.Users;
export function* activateUser(code) {
const Users = this.db.Users;
const user = yield Users.findOne({activationCode: code});
if (user) {
Users.updateOne({_id: user._id}, {'$unset': {activationCode: ''}});
yield setupSession(ctx, user);
yield setupSession(this, user);
} else {
return false;
}

View file

@ -1,5 +1,5 @@
<!DOCTYPE html>
<html>
<html style='height:100%;'>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
@ -10,29 +10,23 @@
<link href='//fonts.googleapis.com/css?family=Open+Sans:400,300,600' rel='stylesheet' type='text/css'>
<link href="/styles/app.css" rel="stylesheet" />
</head>
<body>
<section class="container header clearfix">
<div class="row">
<div class="col-md-8 col-md-offset-2" style='text-align: center;'>
<div class="logo">
<a href="/"><img src="/images/logo-dark-r.png" height="22" width="26" alt=""></a>
<body class='error'>
<div class='container main'>
<div class='row'>
<div class='col-md-12'>
<div class='error-logo'>
<a href='/'><img src='/images/logo-dark-r.png' width='26' height='22' /></a>
</div>
</div>
</div>
</section>
<section class="jumbotron error-page">
<div class="container">
<div class="row">
<div class="col-md-8 col-md-offset-2">
<h1>Oops!</h1>
<h2>It looks like you've hit an unexpected error :(</h2>
<p class="lead">Refreshing might fix the problem. If not, sit tight! We're on it!</p>
</div>
<div class='row vertical-center'>
<div class='col-md-12'>
<h2><%=err.status%></h2>
<h1>Sorry, It looks like you've hit an unexpected error.</h1>
<p class="lead">Refreshing might fix the problem. If not, sit tight! We're on it!</p>
</div>
</div>
</section>
</div>
<script>
var _gaq=[['_setAccount','UA-66209-2'],['_setDomainName', 'hostr.co'],['_trackPageview']];

View file

@ -34,7 +34,7 @@ export class FileController {
$rootScope.pageTitle = ' - ' + file.name;
if (file.status === 'uploading') {
file.percent = 0;
var ws = new ReconnectingWebSocket('wss://' + window.location.hostname + window.settings.api + '/file/' + file.id);
var ws = new ReconnectingWebSocket(window.settings.apiURL.replace(/^http/, 'ws') + '/file/' + file.id);
ws.onmessage = function (msg) {
var evt = JSON.parse(msg.data);
$rootScope.$broadcast(evt.type, evt.data);

View file

@ -20,39 +20,39 @@ const hotlinkCheck = function(file, userAgent, referrer) {
return !userAgentCheck(userAgent) && !file.width && (!referrer || !(referrer.match(/^https:\/\/hostr.co/) || referrer.match(/^http:\/\/localhost:4040/)))
};
export function* get(id, name, size) {
const file = yield this.db.Files.findOne({_id: id, 'file_name': name, 'status': 'active'});
export function* get() {
const file = yield this.db.Files.findOne({_id: this.params.id, 'file_name': this.params.name, 'status': 'active'});
this.assert(file, 404);
if (hotlinkCheck(file, this.headers['user-agent'], this.headers['referer'])) {
return this.redirect('/' + id);
return this.redirect('/' + file._id);
}
if (!file.width && this.request.query.warning != 'on') {
return this.redirect('/' + id);
return this.redirect('/' + file._id);
}
if (file.malware) {
let alert = this.request.query.alert;
if (!alert || !alert.match(/i want to download malware/i)) {
return this.redirect('/' + id);
return this.redirect('/' + file._id);
}
}
let localPath = path.join(storePath, file._id[0], file._id + '_' + file.file_name);
let remotePath = path.join(file._id[0], file._id + '_' + file.file_name);
if (size > 0) {
localPath = path.join(storePath, file._id[0], size, file._id + '_' + file.file_name);
remotePath = path.join(size, file._id + '_' + file.file_name);
if (this.params.size > 0) {
localPath = path.join(storePath, file._id[0], this.params.size, file._id + '_' + file.file_name);
remotePath = path.join(this.params.size, file._id + '_' + file.file_name);
}
console.log(localPath);
if (file.malware) {
this.statsd.incr('file.malware.download', 1);
}
let type = 'application/octet-stream';
if (file.width > 0) {
if (size) {
if (this.params.size) {
this.statsd.incr('file.view', 1);
}
type = mime.lookup(file.file_name);
@ -71,19 +71,18 @@ export function* get(id, name, size) {
this.body = yield hostrFileStream(localPath, remotePath);
}
export function* resized(size, id, name) {
yield get.call(this, id, name, size);
export function* resized() {
yield get.call(this);
}
export function* landing(id, next) {
if (id === 'config.js') {
return yield next;
}
const file = yield this.db.Files.findOne({_id: id});
export function* landing() {
const file = yield this.db.Files.findOne({_id: this.params.id, status: 'active'});
this.assert(file, 404);
if(userAgentCheck(this.headers['user-agent'])) {
return yield get.call(this, file._id, file.file_name);
this.params.name = file.file_name;
return yield get.call(this);
}
console.log('incr')
this.statsd.incr('file.landing', 1);
const formattedFile = formatFile(file);
yield this.render('file', {file: formattedFile});

View file

@ -6,7 +6,7 @@ export function* signin() {
}
this.statsd.incr('auth.attempt', 1);
const user = yield authenticate(this, this.request.body.email, this.request.body.password);
const user = yield authenticate.call(this, this.request.body.email, this.request.body.password);
if(!user) {
this.statsd.incr('auth.failure', 1);
return yield this.render('signin', {error: 'Invalid login details', csrf: this.csrf});
@ -14,7 +14,7 @@ export function* signin() {
return yield this.render('signin', {error: 'Your account hasn\'t been activated yet. Check your for an activation email.', csrf: this.csrf});
} else {
this.statsd.incr('auth.success', 1);
yield setupSession(this, user);
yield setupSession.call(this, user);
this.redirect('/');
}
}
@ -36,7 +36,7 @@ export function* signup() {
const email = this.request.body.email;
const password = this.request.body.password;
try {
yield signupUser(this, email, password, ip);
yield signupUser.call(this, email, password, ip);
} catch (e) {
return yield this.render('signup', {error: e.message, csrf: this.csrf});
}
@ -50,23 +50,23 @@ export function* forgot(token) {
const Users = this.db.Users;
if (this.request.body.email) {
var email = this.request.body.email;
yield sendResetToken(this, email);
yield sendResetToken.call(this, email);
this.statsd.incr('auth.reset.request', 1);
return yield this.render('forgot', {message: 'We\'ve sent an email with a link to reset your password. Be sure to check your spam folder if you it doesn\'t appear within a few minutes', token: null, csrf: this.csrf});
} else if (token && this.request.body.password) {
if (this.request.body.password.length < 7) {
return yield this.render('forgot', {error: 'Password needs to be at least 7 characters long.', token: token, csrf: this.csrf});
}
const tokenUser = yield validateResetToken(this, token);
const tokenUser = yield validateResetToken.call(this, token);
var userId = tokenUser._id;
yield updatePassword(this, userId, this.request.body.password);
yield updatePassword.call(this, userId, this.request.body.password);
yield Reset.remove({_id: userId});
const user = yield Users.findOne({_id: userId});
yield setupSession(this, user);
yield setupSession.call(this, user);
this.statsd.incr('auth.reset.success', 1);
this.redirect('/');
} else if (token.length) {
const tokenUser = yield validateResetToken(this, token);
const tokenUser = yield validateResetToken.call(this, token);
if (!tokenUser) {
this.statsd.incr('auth.reset.fail', 1);
return yield this.render('forgot', {error: 'Invalid password reset token. It might be expired, or has already been used.', token: null, csrf: this.csrf});
@ -88,7 +88,7 @@ export function* logout() {
export function* activate(code) {
if (yield activateUser(this, code)) {
if (yield activateUser.call(this, code)) {
this.statsd.incr('auth.activation', 1);
}
this.redirect('/');