Fix linting

This commit is contained in:
Jonathan Cremin 2018-06-02 18:07:00 +00:00
parent 553ba9db9a
commit bb5189c9ed
35 changed files with 157 additions and 866 deletions

View file

@ -1,11 +1,11 @@
{ {
"extends": "airbnb/base", "extends": "airbnb/base",
"parserOptions": { "parserOptions": {
"ecmaVersion": 6, "ecmaVersion": 2017,
"sourceType": "module", "sourceType": "module",
"ecmaFeatures": { "ecmaFeatures": {
"experimentalObjectRestSpread": true "experimentalObjectRestSpread": true
}, }
}, },
"env": { "env": {
"node": true, "node": true,
@ -14,5 +14,6 @@
"rules": { "rules": {
"quotes": [2, "single"], "quotes": [2, "single"],
"no-underscore-dangle": [0], "no-underscore-dangle": [0],
"no-plusplus": ["error", { "allowForLoopAfterthoughts": true }]
} }
} }

14
.vscode/launch.json vendored Normal file
View file

@ -0,0 +1,14 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "launch",
"name": "Launch Program",
"program": "${workspaceFolder}/app.js"
}
]
}

View file

@ -1,12 +1,14 @@
import Router from 'koa-router'; import Router from 'koa-router';
import stats from '../lib/koa-statsd';
import cors from 'kcors'; import cors from 'kcors';
import StatsD from 'statsy'; import StatsD from 'statsy';
import debugname from 'debug';
import stats from '../lib/koa-statsd';
import auth from './lib/auth'; import auth from './lib/auth';
import * as user from './routes/user'; import * as user from './routes/user';
import * as file from './routes/file'; import * as file from './routes/file';
import * as pro from './routes/pro'; import * as pro from './routes/pro';
import debugname from 'debug';
const debug = debugname('hostr-api'); const debug = debugname('hostr-api');
const router = new Router(); const router = new Router();
@ -45,8 +47,7 @@ router.use(async (ctx, next) => {
code: 604, code: 604,
}, },
}; };
} else { } else if (!err.status) {
if (!err.status) {
debug(err); debug(err);
if (ctx.raven) { if (ctx.raven) {
ctx.raven.captureError(err); ctx.raven.captureError(err);
@ -57,7 +58,6 @@ router.use(async (ctx, next) => {
ctx.body = err.message; ctx.body = err.message;
} }
} }
}
ctx.type = 'application/json'; ctx.type = 'application/json';
}); });

View file

@ -1,7 +1,9 @@
import passwords from 'passwords'; import passwords from 'passwords';
import auth from 'basic-auth'; import auth from 'basic-auth';
import models from '../../models';
import debugname from 'debug'; import debugname from 'debug';
import models from '../../models';
const debug = debugname('hostr-api:auth'); const debug = debugname('hostr-api:auth');
const badLoginMsg = '{"error": {"message": "Incorrect login details.", "code": 607}}'; const badLoginMsg = '{"error": {"message": "Incorrect login details.", "code": 607}}';
@ -36,8 +38,10 @@ export default async (ctx, next) => {
}, },
}); });
ctx.assert(count < 25, 401, ctx.assert(
'{"error": {"message": "Too many incorrect logins.", "code": 608}}'); count < 25, 401,
'{"error": {"message": "Too many incorrect logins.", "code": 608}}',
);
user = await models.user.findOne({ user = await models.user.findOne({
where: { where: {
@ -56,8 +60,10 @@ export default async (ctx, next) => {
ctx.assert(user, 401, badLoginMsg); ctx.assert(user, 401, badLoginMsg);
debug('Checking user is activated'); debug('Checking user is activated');
debug(user.activated); debug(user.activated);
ctx.assert(user.activated === true, 401, ctx.assert(
'{"error": {"message": "Account has not been activated.", "code": 603}}'); user.activated === true, 401,
'{"error": {"message": "Account has not been activated.", "code": 603}}',
);
login.successful = true; login.successful = true;
await login.save(); await login.save();
@ -85,9 +91,11 @@ export default async (ctx, next) => {
plan: user.plan, plan: user.plan,
uploads_today: uploadedToday, uploads_today: uploadedToday,
}; };
ctx.response.set('Daily-Uploads-Remaining', ctx.response.set(
user.type === 'Pro' ? 'unlimited' : 15 - uploadedToday); 'Daily-Uploads-Remaining',
user.type === 'Pro' ? 'unlimited' : 15 - uploadedToday,
);
ctx.user = normalisedUser; ctx.user = normalisedUser;
debug('Authenticated user: ', ctx.user.email); debug('Authenticated user: ', ctx.user.email);
await next(); await next();
} };

View file

@ -1,18 +1,19 @@
import path from 'path'; import path from 'path';
import views from 'co-views'; import views from 'co-views';
const render = views(path.join(__dirname, '/../views'), { default: 'ejs' });
import Stripe from 'stripe'; import Stripe from 'stripe';
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY);
import sendgridInit from 'sendgrid'; import sendgridInit from 'sendgrid';
const sendgrid = sendgridInit(process.env.SENDGRID_KEY);
import models from '../../models'; import models from '../../models';
const render = views(path.join(__dirname, '/../views'), { default: 'ejs' });
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY);
const sendgrid = sendgridInit(process.env.SENDGRID_KEY);
const from = process.env.EMAIL_FROM; const from = process.env.EMAIL_FROM;
const fromname = process.env.EMAIL_NAME; const fromname = process.env.EMAIL_NAME;
export async function create(ctx) { export async function create(ctx) {
const stripeToken = ctx.request.body.stripeToken; const { stripeToken } = ctx.request.body;
const ip = ctx.request.headers['x-forwarded-for'] || ctx.req.connection.remoteAddress; const ip = ctx.request.headers['x-forwarded-for'] || ctx.req.connection.remoteAddress;
@ -74,7 +75,7 @@ export async function cancel(ctx) {
await stripe.customers.cancelSubscription( await stripe.customers.cancelSubscription(
transaction.data.id, transaction.data.id,
transaction.data.subscription.id, transaction.data.subscription.id,
{ at_period_end: false } { at_period_end: false },
); );
user.plan = 'Free'; user.plan = 'Free';

View file

@ -2,9 +2,10 @@ import uuid from 'node-uuid';
import redis from 'redis'; import redis from 'redis';
import co from 'co'; import co from 'co';
import passwords from 'passwords'; import passwords from 'passwords';
import debugname from 'debug';
import models from '../../models'; import models from '../../models';
import debugname from 'debug';
const debug = debugname('hostr-api:user'); const debug = debugname('hostr-api:user');
const redisUrl = process.env.REDIS_URL; const redisUrl = process.env.REDIS_URL;
@ -26,31 +27,37 @@ export async function transaction(ctx) {
}, },
}); });
ctx.body = transactions.map((item) => { ctx.body = transactions.map(item => ({
return {
id: item.id, id: item.id,
amount: item.amount / 100, amount: item.amount / 100,
date: item.date, date: item.date,
description: item.description, description: item.description,
type: 'direct', type: 'direct',
}; }));
});
} }
export async function settings(ctx) { export async function settings(ctx) {
ctx.assert(ctx.request.body, 400, ctx.assert(
'{"error": {"message": "Current Password required to update account.", "code": 612}}'); ctx.request.body, 400,
ctx.assert(ctx.request.body.current_password, 400, '{"error": {"message": "Current Password required to update account.", "code": 612}}',
'{"error": {"message": "Current Password required to update account.", "code": 612}}'); );
ctx.assert(
ctx.request.body.current_password, 400,
'{"error": {"message": "Current Password required to update account.", "code": 612}}',
);
const user = await models.user.findById(ctx.user.id); const user = await models.user.findById(ctx.user.id);
ctx.assert(await passwords.match(ctx.request.body.current_password, user.password), 400, ctx.assert(
'{"error": {"message": "Incorrect password", "code": 606}}'); await passwords.match(ctx.request.body.current_password, user.password), 400,
'{"error": {"message": "Incorrect password", "code": 606}}',
);
if (ctx.request.body.email && ctx.request.body.email !== user.email) { if (ctx.request.body.email && ctx.request.body.email !== user.email) {
user.email = ctx.request.body.email; user.email = ctx.request.body.email;
} }
if (ctx.request.body.new_password) { if (ctx.request.body.new_password) {
ctx.assert(ctx.request.body.new_password.length >= 7, 400, ctx.assert(
'{"error": {"message": "Password must be 7 or more characters long.", "code": 606}}'); ctx.request.body.new_password.length >= 7, 400,
'{"error": {"message": "Password must be 7 or more characters long.", "code": 606}}',
);
user.password = await passwords.hash(ctx.request.body.new_password); user.password = await passwords.hash(ctx.request.body.new_password);
} }
await user.save(); await user.save();

2
app.js
View file

@ -9,11 +9,11 @@ import websockify from 'koa-websocket';
import helmet from 'koa-helmet'; import helmet from 'koa-helmet';
import session from 'koa-session'; import session from 'koa-session';
import raven from 'raven'; import raven from 'raven';
import debugname from 'debug';
import * as redis from './lib/redis'; import * as redis from './lib/redis';
import api, { ws } from './api/app'; import api, { ws } from './api/app';
import web from './web/app'; import web from './web/app';
import debugname from 'debug';
const debug = debugname('hostr'); const debug = debugname('hostr');
const app = websockify(new Koa()); const app = websockify(new Koa());

View file

@ -1,5 +1,5 @@
import moment from 'moment'; import moment from 'moment';
import { sniff } from './type'; import sniff from './sniff';
const baseURL = process.env.WEB_BASE_URL; const baseURL = process.env.WEB_BASE_URL;

View file

@ -1,8 +1,8 @@
import fs from 'fs'; import fs from 'fs';
import createError from 'http-errors'; import createError from 'http-errors';
import debugname from 'debug';
import { get as getS3 } from './s3'; import { get as getS3 } from './s3';
import debugname from 'debug';
const debug = debugname('hostr:file-stream'); const debug = debugname('hostr:file-stream');
function writer(localPath, remoteRead) { function writer(localPath, remoteRead) {

View file

@ -4,7 +4,7 @@ const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
function randomID() { function randomID() {
let rand = ''; let rand = '';
for (let i = 0; i < 12; i++) { for (let i = 0; i < 12; i += 1) {
rand += chars.charAt(Math.floor((Math.random() * chars.length))); rand += chars.charAt(Math.floor((Math.random() * chars.length)));
} }
return rand; return rand;
@ -18,7 +18,7 @@ async function checkId(Files, fileId, attempts) {
if (file === null) { if (file === null) {
return fileId; return fileId;
} }
return checkId(randomID(), ++attempts); // eslint-disable-line no-param-reassign return checkId(Files, randomID(), attempts + 1);
} }
export default function (Files) { export default function (Files) {

View file

@ -3,7 +3,7 @@
* Module dependencies. * Module dependencies.
*/ */
var Stats = require('statsy'); const Stats = require('statsy');
/** /**
* Initialize stats middleware with `opts` * Initialize stats middleware with `opts`
@ -14,14 +14,13 @@ var Stats = require('statsy');
* @api public * @api public
*/ */
module.exports = function(opts){ export default function (opts) {
opts = opts || {}; const s = new Stats(opts || {});
var s = new Stats(opts);
return async (ctx, next) => { return async (ctx, next) => {
// counters // counters
s.incr('request.count'); s.incr('request.count');
s.incr('request.' + ctx.method + '.count'); s.incr(`request.${ctx.method}.count`);
// size // size
s.histogram('request.size', ctx.request.length || 0); s.histogram('request.size', ctx.request.length || 0);
@ -33,5 +32,5 @@ module.exports = function(opts){
ctx.res.on('finish', s.timer('request.duration')); ctx.res.on('finish', s.timer('request.duration'));
await next(); await next();
} };
}; }

View file

@ -1,4 +1,4 @@
import virustotal from './virustotal'; import getFileReport from './virustotal';
const extensions = [ const extensions = [
'EXE', 'EXE',
@ -65,13 +65,13 @@ function getExtension(filename) {
return (i < 0) ? '' : filename.substr(i + 1); return (i < 0) ? '' : filename.substr(i + 1);
} }
export default function* (file) { export default async (file) => {
if (extensions.indexOf(getExtension(file.file_name.toUpperCase())) < 0) { if (extensions.indexOf(getExtension(file.name.toUpperCase())) < 0) {
return false; return false;
} }
const result = yield virustotal.getFileReport(file.md5); const result = await getFileReport(file.md5);
return { return {
positive: result.positives >= 5, positive: result.positives >= 5,
result, result,
}; };
} };

View file

@ -1,37 +0,0 @@
import mongodb from 'mongodb-promisified';
const MongoClient = mongodb().MongoClient;
import debugname from 'debug';
const debug = debugname('hostr:mongo');
/* eslint no-param-reassign: ["error", { "props": false }] */
export const mongo = new Promise((resolve, reject) => {
debug('Connecting to Mongodb');
return MongoClient.connect(process.env.MONGO_URL).then((client) => {
debug('Successfully connected to Mongodb');
client.Users = client.collection('users');
client.Files = client.collection('files');
client.Transactions = client.collection('transactions');
client.Logins = client.collection('logins');
client.Remember = client.collection('remember');
client.Reset = client.collection('reset');
client.Remember.ensureIndex({ created: 1 }, { expireAfterSeconds: 2592000 });
client.Files.ensureIndex({ owner: 1, status: 1, time_added: -1 });
client.ObjectId = client.objectId = mongodb().ObjectId;
return resolve(client);
}).catch((e) => {
reject(e);
});
}).catch((e) => {
debug(e);
});
export default function () {
return function* dbMiddleware(next) {
try {
this.db = yield mongo;
} catch (e) {
debug(e);
}
yield next;
};
}

View file

@ -3,6 +3,7 @@ import coRedis from 'co-redis';
import koaRedis from 'koa-redis'; import koaRedis from 'koa-redis';
import session from 'koa-generic-session'; import session from 'koa-generic-session';
import debugname from 'debug'; import debugname from 'debug';
const debug = debugname('hostr:redis'); const debug = debugname('hostr:redis');
const connection = new Promise((resolve, reject) => { const connection = new Promise((resolve, reject) => {
@ -25,8 +26,7 @@ const redisSession = new Promise((resolve, reject) =>
}).catch((err) => { }).catch((err) => {
debug('koa-redis error: ', err); debug('koa-redis error: ', err);
reject(err); reject(err);
}) }));
);
const wrapped = new Promise((resolve, reject) => const wrapped = new Promise((resolve, reject) =>
connection.then((client) => { connection.then((client) => {
@ -40,8 +40,7 @@ const wrapped = new Promise((resolve, reject) =>
debug('co-redis error: ', err); debug('co-redis error: ', err);
reject(err); reject(err);
throw err; throw err;
}) }));
);
export function sessionStore() { export function sessionStore() {
return async (ctx, next) => { return async (ctx, next) => {

View file

@ -1,13 +1,14 @@
import fs from 'mz/fs'; import fs from 'mz/fs';
import jimp from 'jimp'; import jimp from 'jimp';
import debugname from 'debug'; import debugname from 'debug';
const debug = debugname('hostr-api:resize'); const debug = debugname('hostr-api:resize');
const types = { const types = {
jpg: jimp.MIME_JPEG, jpg: jimp.MIME_JPEG,
png: jimp.MIME_PNG, png: jimp.MIME_PNG,
gif: jimp.MIME_JPEG, gif: jimp.MIME_JPEG,
} };
function cover(path, type, size) { function cover(path, type, size) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {

View file

@ -1,5 +1,6 @@
import aws from 'aws-sdk'; import aws from 'aws-sdk';
import debugname from 'debug'; import debugname from 'debug';
const debug = debugname('hostr:s3'); const debug = debugname('hostr:s3');
const s3 = new aws.S3({ const s3 = new aws.S3({

View file

@ -1,53 +0,0 @@
import { dirname, join } from 'path';
import StatsD from 'statsy';
import Client from './ssh2-sftp-client';
import debugname from 'debug';
const debug = debugname('hostr:sftp');
const statsdOpts = { prefix: 'hostr-api', host: process.env.STATSD_HOST };
const statsd = new StatsD(statsdOpts);
export function get(remotePath) {
debug('fetching', join('hostr', 'uploads', remotePath));
const sftp = new Client();
return sftp.connect({
host: process.env.SFTP_HOST,
port: process.env.SFTP_PORT,
username: process.env.SFTP_USERNAME,
password: process.env.SFTP_PASSWORD,
})
.then(() => sftp.get(join('hostr', 'uploads', remotePath), { encoding: null }));
}
function sendFile(localPath, remotePath) {
const sftp = new Client();
return sftp.connect({
host: process.env.SFTP_HOST,
port: process.env.SFTP_PORT,
username: process.env.SFTP_USERNAME,
password: process.env.SFTP_PASSWORD,
})
.then(() => sftp.put(localPath, remotePath, true))
.catch((err) => {
if (err.message === 'No such file') {
debug('Creating directory');
return sftp.mkdir(dirname(remotePath), true)
.then(() => sftp.put(localPath, remotePath, true));
}
throw err;
});
}
export function *upload(localPath, remotePath) {
let done = false;
for (let retries = 0; retries < 5; retries++) {
try {
done = yield sendFile(localPath, remotePath);
break;
} catch (err) {
statsd.incr('file.upload.retry', 1);
debug('retry');
}
}
return done;
}

View file

@ -26,9 +26,9 @@ const extensions = {
rar: 'archive', rar: 'archive',
}; };
export function sniff(filename) { export default (filename) => {
if (extensions[filename.split('.').pop().toLowerCase()]) { if (extensions[filename.split('.').pop().toLowerCase()]) {
return extensions[filename.split('.').pop().toLowerCase()]; return extensions[filename.split('.').pop().toLowerCase()];
} }
return 'other'; return 'other';
} };

View file

@ -1,303 +0,0 @@
/**
* ssh2 sftp client for node
*/
'use strict';
let Client = require('ssh2').Client;
let SftpClient = function(){
this.client = new Client();
};
/**
* Retrieves a directory listing
*
* @param {String} path, a string containing the path to a directory
* @return {Promise} data, list info
*/
SftpClient.prototype.list = function(path) {
let reg = /-/gi;
return new Promise((resolve, reject) => {
let sftp = this.sftp;
if (sftp) {
sftp.readdir(path, (err, list) => {
if (err) {
reject(err);
return false;
}
// reset file info
list.forEach((item, i) => {
list[i] = {
type: item.longname.substr(0, 1),
name: item.filename,
size: item.attrs.size,
modifyTime: item.attrs.mtime * 1000,
accessTime: item.attrs.atime * 1000,
rights: {
user: item.longname.substr(1, 3).replace(reg, ''),
group: item.longname.substr(4,3).replace(reg, ''),
other: item.longname.substr(7, 3).replace(reg, '')
},
owner: item.attrs.uid,
group: item.attrs.gid
}
});
resolve(list);
});
} else {
reject('sftp connect error');
}
});
};
/**
* get file
*
* @param {String} path, path
* @param {Object} useCompression, config options
* @return {Promise} stream, readable stream
*/
SftpClient.prototype.get = function(path, useCompression) {
useCompression = Object.assign({}, {encoding: 'utf8'}, useCompression);
return new Promise((resolve, reject) => {
let sftp = this.sftp;
if (sftp) {
try {
let stream = sftp.createReadStream(path, useCompression);
stream.on('error', reject);
resolve(stream);
} catch(err) {
reject(err);
}
} else {
reject('sftp connect error');
}
});
};
/**
* Create file
*
* @param {String|Buffer|stream} input
* @param {String} remotePath,
* @param {Object} useCompression [description]
* @return {[type]} [description]
*/
SftpClient.prototype.put = function(input, remotePath, useCompression) {
useCompression = Object.assign({}, {encoding: 'utf8'}, useCompression);
return new Promise((resolve, reject) => {
let sftp = this.sftp;
if (sftp) {
if (typeof input === 'string') {
sftp.fastPut(input, remotePath, useCompression, (err) => {
if (err) {
reject(err);
return false;
}
resolve();
});
return false;
}
let stream = sftp.createWriteStream(remotePath, useCompression);
let data;
stream.on('error', reject);
stream.on('close', resolve);
if (input instanceof Buffer) {
data = stream.end(input);
return false;
}
data = input.pipe(stream);
} else {
reject('sftp connect error');
}
});
};
SftpClient.prototype.mkdir = function(path, recursive) {
recursive = recursive || false;
return new Promise((resolve, reject) => {
let sftp = this.sftp;
if (sftp) {
if (!recursive) {
sftp.mkdir(path, (err) => {
if (err) {
reject(err);
return false;
}
resolve();
});
return false;
}
let tokens = path.split(/\//g);
let p = '';
let mkdir = () => {
let token = tokens.shift();
if (!token && !tokens.length) {
resolve();
return false;
}
token += '/';
p = p + token;
sftp.mkdir(p, (err) => {
if (err && err.code !== 4) {
reject(err);
}
mkdir();
});
};
return mkdir();
} else {
reject('sftp connect error');
}
});
};
SftpClient.prototype.rmdir = function(path, recursive) {
recursive = recursive || false;
return new Promise((resolve, reject) => {
let sftp = this.sftp;
if (sftp) {
if (!recursive) {
return sftp.rmdir(path, (err) => {
if (err) {
reject(err);
}
resolve();
});
}
let rmdir = (p) => {
return this.list(p).then((list) => {
if (list.length > 0) {
let promises = [];
list.forEach((item) => {
let name = item.name;
let promise;
var subPath;
if (name[0] === '/') {
subPath = name;
} else {
if (p[p.length - 1] === '/') {
subPath = p + name;
} else {
subPath = p + '/' + name;
}
}
if (item.type === 'd') {
if (name !== '.' || name !== '..') {
promise = rmdir(subPath);
}
} else {
promise = this.delete(subPath);
}
promises.push(promise);
});
if (promises.length) {
return Promise.all(promises).then(() => {
return rmdir(p);
});
}
} else {
return new Promise((resolve, reject) => {
return sftp.rmdir(p, (err) => {
if (err) {
reject(err);
}
else {
resolve();
}
});
});
}
});
};
return rmdir(path).then(() => {resolve()})
.catch((err) => {reject(err)});
} else {
reject('sftp connect error');
}
});
};
SftpClient.prototype.delete = function(path) {
return new Promise((resolve, reject) => {
let sftp = this.sftp;
if (sftp) {
sftp.unlink(path, (err) => {
if (err) {
reject(err);
return false;
}
resolve();
});
} else {
reject('sftp connect error');
}
});
};
SftpClient.prototype.rename = function(srcPath, remotePath) {
return new Promise((resolve, reject) => {
let sftp = this.sftp;
if (sftp) {
sftp.rename(srcPath, remotePath, (err) => {
if (err) {
reject(err);
return false;
}
resolve();
});
} else {
reject('sftp connect error');
}
});
}
SftpClient.prototype.connect = function(config) {
var c = this.client;
return new Promise((resolve, reject) => {
this.client.on('ready', () => {
this.client.sftp((err, sftp) => {
if (err) {
reject(err);
}
this.sftp = sftp;
resolve(sftp);
});
}).on('error', (err) => {
reject(err);
}).connect(config);
});
};
SftpClient.prototype.end = function() {
return new Promise((resolve) => {
this.client.end();
resolve();
});
};
module.exports = SftpClient;

View file

@ -3,16 +3,16 @@ import Busboy from 'busboy';
import crypto from 'crypto'; import crypto from 'crypto';
import fs from 'mz/fs'; import fs from 'mz/fs';
import sizeOf from 'image-size'; import sizeOf from 'image-size';
import debugname from 'debug';
import models from '../models'; import models from '../models';
import createHostrId from './hostr-id'; import createHostrId from './hostr-id';
import { formatFile } from './format'; import { formatFile } from './format';
import resize from './resize'; import resize from './resize';
import malware from './malware'; import malware from './malware';
import { sniff } from './type'; import sniff from './sniff';
import { upload as s3upload } from './s3'; import { upload as s3upload } from './s3';
import debugname from 'debug';
const debug = debugname('hostr-api:uploader'); const debug = debugname('hostr-api:uploader');
const storePath = process.env.UPLOAD_STORAGE_PATH; const storePath = process.env.UPLOAD_STORAGE_PATH;
@ -66,14 +66,12 @@ export default class Uploader {
highWaterMark: 10000000, highWaterMark: 10000000,
}); });
this.upload.on('file', async (fieldname, file, filename, encoding, mimetype) => { this.upload.on('file', async (fieldname, file, filename) => {
debug('FILE', fieldname, file, filename, encoding, mimetype);
this.upload.filename = filename; this.upload.filename = filename;
this.file = await models.file.create({ this.file = await models.file.create({
id: await createHostrId(), id: await createHostrId(),
name: this.upload.filename.replace(/[^a-zA-Z0-9\.\-_\s]/g, '').replace(/\s+/g, ''), name: this.upload.filename.replace(/[^a-zA-Z0-9\.\-_\s]/g, '').replace(/\s+/g, ''), // eslint-disable-line no-useless-escape
originalName: this.upload.filename, originalName: this.upload.filename,
userId: this.context.user.id, userId: this.context.user.id,
status: 'uploading', status: 'uploading',
@ -102,7 +100,7 @@ export default class Uploader {
this.localStream.write(data); this.localStream.write(data);
this.percentComplete = Math.floor(this.receivedSize * 100 / this.expectedSize); this.percentComplete = Math.floor((this.receivedSize * 100) / this.expectedSize);
if (this.percentComplete > this.lastPercent && this.lastTick < Date.now() - 1000) { if (this.percentComplete > this.lastPercent && this.lastTick < Date.now() - 1000) {
const progressEvent = `{"type": "file-progress", "data": const progressEvent = `{"type": "file-progress", "data":
{"id": "${this.file.id}", "complete": ${this.percentComplete}}}`; {"id": "${this.file.id}", "complete": ${this.percentComplete}}}`;
@ -131,7 +129,6 @@ export default class Uploader {
this.localStream.on('end', () => { this.localStream.on('end', () => {
s3upload(fs.createReadStream(join(storePath, this.path)), this.path); s3upload(fs.createReadStream(join(storePath, this.path)), this.path);
}); });
}); });
this.context.req.pipe(this.upload); this.context.req.pipe(this.upload);
}); });
@ -210,7 +207,7 @@ export default class Uploader {
// Check in the background // Check in the background
process.nextTick(async () => { process.nextTick(async () => {
debug('Malware Scan'); debug('Malware Scan');
const result = await malware(this); const result = await malware(this.file);
if (result) { if (result) {
this.file.malwarePositives = result.positives; this.file.malwarePositives = result.positives;
this.file.save(); this.file.save();

View file

@ -3,9 +3,9 @@ import FormData from 'form-data';
const apiRoot = 'https://www.virustotal.com/vtapi/v2'; const apiRoot = 'https://www.virustotal.com/vtapi/v2';
export function* getFileReport(resource, apiKey = process.env.VIRUSTOTAL_KEY) { export default async (resource, apiKey = process.env.VIRUSTOTAL_KEY) => {
const form = new FormData(); const form = new FormData();
form.append('apikey', apiKey); form.append('apikey', apiKey);
form.append('resource', resource); form.append('resource', resource);
return yield fetch(`${apiRoot}/file/report`, { method: 'POST' }); return fetch(`${apiRoot}/file/report`, { method: 'POST' });
} };

View file

@ -1,75 +0,0 @@
import co from 'co';
import models from '../models';
import { mongo } from '../lib/mongo';
import debugname from 'debug';
const debug = debugname('hostr:db');
let db;
co(function *sync() {
debug('Syncing schema');
yield models.sequelize.sync();
debug('Schema synced');
db = yield mongo;
const users = yield db.Users.find({}, { sort: [['joined', 'asc']] }).toArray();
for (const user of users) {
if (user.joined === '0') {
const file = yield db.Files.findOne({
owner: user._id,
}, {
limit: 1,
sort: [['time_added', 'asc']],
});
if (file && file.time_added > 0) {
user.createdAt = new Date(file.time_added * 1000).getTime();
} else {
user.createdAt = new Date().getTime();
}
} else {
user.createdAt = new Date(user.joined * 1000).getTime();
}
}
users.sort((a, b) => (a.createdAt < b.createdAt ? -1 : 1));
for (const user of users) {
if (!user.email) {
continue;
}
const exists = yield models.user.findOne({
where: {
email: user.email,
},
});
if (exists) {
debug('User exists, continue');
continue;
}
const mongoId = user._id.toString();
const newUser = yield models.user.create({
email: user.email,
password: user.salted_password,
name: user.first_name ? `${user.first_name} ${user.last_name}` : null,
plan: user.type || 'Free',
activated: !user.activationCode,
banned: !!user.banned,
deletedAt: user.status === 'deleted' ? new Date().getTime() : null,
createdAt: user.createdAt,
updatedAt: user.createdAt,
mongoId,
});
yield newUser.save({ silent: true });
}
models.sequelize.close();
db.close();
}).catch((err) => {
models.sequelize.close();
db.close();
debug(err);
});

View file

@ -1,83 +0,0 @@
import co from 'co';
import validateIp from 'validate-ip';
import models from '../models';
import { mongo } from '../lib/mongo';
import debugname from 'debug';
const debug = debugname('hostr:db');
let db;
co(function *sync() {
debug('Syncing schema');
yield models.sequelize.sync();
debug('Schema synced');
db = yield mongo;
const users = yield models.user.findAll({});
const userIds = {};
debug('remap');
for (const user of users) {
userIds[user.mongoId] = user.id;
}
debug('remap done');
let files;
try {
files = db.Files.find({}, {
sort: [['time_added', 'desc']],
skip: 0,
});
} catch (err) {
debug(err);
}
debug('fetched files');
while (true) {
const file = yield files.next();
if (!file) {
break;
}
if (!file.time_added || !file.file_size) {
continue;
}
let ip = file.ip ? file.ip.split(',').pop().trim() : null;
if (typeof ip !== 'string' || !validateIp(ip)) {
ip = null;
}
const processed = file.status !== 'uploading';
const accessedAt = file.last_accessed ? new Date(file.last_accessed * 1000) : null;
const mongoId = file._id.toString();
yield models.file.upsert({
id: file._id.toString(),
name: file.file_name,
originalName: file.original_name || file.file_name,
size: file.file_size,
downloads: file.downloads,
deletedAt: file.status === 'deleted' ? new Date() : null,
createdAt: new Date(file.time_added * 1000),
updatedAt: new Date(file.time_added * 1000),
accessedAt,
processed,
type: file.type !== 'file' ? file.type : 'other',
width: Number.isInteger(file.width) ? file.width : null,
height: Number.isInteger(file.height) ? file.height : null,
userId: file.owner !== undefined ? userIds[file.owner] : null,
ip,
legacyId: file.system_name !== file._id ? file.system_name : null,
md5: file.md5,
malwarePositives: file.virustotal && file.virustotal.positives > 0 ?
file.virustotal.positives : null,
mongoId,
}, { /* logging: false */ });
}
models.sequelize.close();
db.close();
}).catch((err) => {
models.sequelize.close();
db.close();
debug(err);
});

View file

@ -1,54 +0,0 @@
import co from 'co';
import validateIp from 'validate-ip';
import models from '../models';
import { mongo } from '../lib/mongo';
import debugname from 'debug';
const debug = debugname('hostr:db');
let db;
co(function *sync() {
debug('Syncing schema');
yield models.sequelize.sync();
debug('Schema synced');
db = yield mongo;
const users = yield models.user.findAll({});
const userIds = {};
debug('remap');
for (const user of users) {
userIds[user._id] = user.id;
}
debug('remap done');
let logins;
try {
logins = db.Logins.find({}, {
skip: 0,
});
} catch (err) {
debug(err);
}
debug('fetched logins');
while (true) {
const login = yield logins.next();
if (!login) {
break;
}
const newLogin = yield models.login.create({
ip: login.ip,
createdAt: login.at * 1000,
successful: login.successful,
}, { /* logging: false */ });
newLogin.save();
}
models.sequelize.close();
db.close();
}).catch((err) => {
models.sequelize.close();
db.close();
debug(err);
});

View file

@ -1,46 +0,0 @@
import co from 'co';
import models from '../models';
import { mongo } from '../lib/mongo';
import debugname from 'debug';
const debug = debugname('hostr:db');
let db;
co(function *sync() {
debug('Syncing schema');
yield models.sequelize.sync();
debug('Schema synced');
db = yield mongo;
const files = db.Files.find({}, {
sort: [['time_added', 'desc']],
skip: 0,
});
debug('fetched files');
while (true) {
const file = yield files.next();
if (!file) {
break;
}
if (!file.time_added || !file.file_size || !file.malware) {
continue;
}
yield models.malware.upsert({
fileId: file._id,
positives: file.virustotal ? file.virustotal.positives : 100,
virustotal: file.virustotal || null,
}, { /* logging: false */ });
}
models.sequelize.close();
db.close();
}).catch((err) => {
models.sequelize.close();
db.close();
debug(err);
});

View file

@ -1,77 +0,0 @@
import co from 'co';
import models from '../models';
import { mongo } from '../lib/mongo';
import debugname from 'debug';
const debug = debugname('hostr:db');
let db;
co(function *sync() {
debug('Syncing schema');
yield models.sequelize.sync();
debug('Schema synced');
db = yield mongo;
const users = yield db.Users.find({}, { sort: [['joined', 'asc']] }).toArray();
for (const user of users) {
if (user.joined === '0') {
const file = yield db.Files.findOne({
owner: user._id,
}, {
limit: 1,
sort: [['time_added', 'asc']],
});
if (file && file.time_added > 0) {
user.createdAt = new Date(file.time_added * 1000).getTime();
} else {
user.createdAt = new Date().getTime();
}
} else {
user.createdAt = new Date(user.joined * 1000).getTime();
}
}
users.sort((a, b) => (a.createdAt < b.createdAt ? -1 : 1));
for (const user of users) {
if (!user.email) {
continue;
}
const exists = yield models.user.findOne({
where: {
email: user.email,
},
});
if (exists) {
debug('User exists, continue');
continue;
}
const mongoId = user._id.toString();
const newUser = yield models.user.create({
email: user.email,
password: user.salted_password,
name: user.first_name ? `${user.first_name} ${user.last_name}` : null,
plan: user.type || 'Free',
activated: !user.activationCode,
banned: !!user.banned,
deletedAt: user.status === 'deleted' ? new Date().getTime() : null,
createdAt: user.createdAt,
updatedAt: user.createdAt,
mongoId,
}, {
include: [models.activation],
});
yield newUser.save({ silent: true });
}
models.sequelize.close();
db.close();
}).catch((err) => {
models.sequelize.close();
db.close();
debug(err);
});

View file

@ -13,7 +13,7 @@ export default function (sequelize, DataTypes) {
'audio', 'audio',
'video', 'video',
'archive', 'archive',
'other' 'other',
), ),
width: DataTypes.INTEGER, width: DataTypes.INTEGER,
height: DataTypes.INTEGER, height: DataTypes.INTEGER,
@ -32,14 +32,15 @@ export default function (sequelize, DataTypes) {
}); });
File.accessed = function accessed(id) { File.accessed = function accessed(id) {
sequelize.query(` sequelize.query(
`
UPDATE files UPDATE files
SET "downloads" = downloads + 1, "accessedAt" = NOW() SET "downloads" = downloads + 1, "accessedAt" = NOW()
WHERE "id" = :id`, WHERE "id" = :id`,
{ {
replacements: { id }, replacements: { id },
type: sequelize.QueryTypes.UPDATE, type: sequelize.QueryTypes.UPDATE,
} },
); );
}; };

View file

@ -2,10 +2,6 @@ import fs from 'fs';
import path from 'path'; import path from 'path';
import Sequelize from 'sequelize'; import Sequelize from 'sequelize';
import debugname from 'debug';
const debug = debugname('hostr:models');
const config = { const config = {
dialect: 'postgres', dialect: 'postgres',
protocol: 'postgres', protocol: 'postgres',

View file

@ -2,11 +2,10 @@ import path from 'path';
import Router from 'koa-router'; import Router from 'koa-router';
import CSRF from 'koa-csrf'; import CSRF from 'koa-csrf';
import views from 'koa-views'; import views from 'koa-views';
import stats from '../lib/koa-statsd';
import StatsD from 'statsy'; import StatsD from 'statsy';
import errors from 'koa-error'; import errors from 'koa-error';
import * as redis from '../lib/redis'; import stats from '../lib/koa-statsd';
import * as index from './routes/index'; import * as index from './routes/index';
import * as file from './routes/file'; import * as file from './routes/file';
import * as user from './routes/user'; import * as user from './routes/user';
@ -26,8 +25,6 @@ router.use(async (ctx, next) => {
await next(); await next();
}); });
//router.use(redis.sessionStore());
router.use(async (ctx, next) => { router.use(async (ctx, next) => {
ctx.state = { ctx.state = {
session: ctx.session, session: ctx.session,
@ -71,15 +68,15 @@ router.get('/:id', file.landing);
router.get('/file/:id/:name', file.get); router.get('/file/:id/:name', file.get);
router.get('/file/:size/:id/:name', file.get); router.get('/file/:size/:id/:name', file.get);
router.get('/files/:id/:name', file.get); router.get('/files/:id/:name', file.get);
router.get('/download/:id/:name', function* downloadRedirect(id) { router.get('/download/:id/:name', async (ctx, id) => {
this.redirect(`/${id}`); ctx.redirect(`/${id}`);
}); });
router.get('/updaters/mac', function* macUpdater() { router.get('/updaters/mac', async (ctx) => {
this.redirect('/updaters/mac.xml'); ctx.redirect('/updaters/mac.xml');
}); });
router.get('/updaters/mac/changelog', function* macChangelog() { router.get('/updaters/mac/changelog', async (ctx) => {
yield this.render('mac-update-changelog'); await ctx.render('mac-update-changelog');
}); });
export default router; export default router;

View file

@ -41,7 +41,7 @@ export async function authenticate(email, password) {
activated: true, activated: true,
}, },
}); });
debug(user);
const login = await models.login.create({ const login = await models.login.create({
ip: remoteIp, ip: remoteIp,
successful: false, successful: false,
@ -52,7 +52,6 @@ export async function authenticate(email, password) {
debug('Password verified'); debug('Password verified');
login.successful = true; login.successful = true;
await login.save(); await login.save();
debug(user);
return user; return user;
} }
debug('Password invalid'); debug('Password invalid');
@ -182,18 +181,18 @@ Visit ${process.env.WEB_BASE_URL}/forgot/${reset.id} to set a new one.
export async function fromToken(token) { export async function fromToken(token) {
const userId = await this.redis.get(token); const userId = await this.redis.get(token);
return await models.user.findById(userId); return models.user.findById(userId);
} }
export async function fromCookie(rememberId) { export async function fromCookie(rememberId) {
const userId = await models.remember.findById(rememberId); const userId = await models.remember.findById(rememberId);
return await models.user.findById(userId); return models.user.findById(userId);
} }
export async function validateResetToken(resetId) { export async function validateResetToken(resetId) {
return await models.reset.findById(resetId); return models.reset.findById(resetId);
} }
@ -206,7 +205,6 @@ export async function updatePassword(userId, password) {
export async function activateUser(code) { export async function activateUser(code) {
debug(code);
const activation = await models.activation.findOne({ const activation = await models.activation.findOne({
where: { where: {
id: code, id: code,

View file

@ -20,7 +20,7 @@ function userAgentCheck(userAgent) {
} }
function referrerCheck(referrer) { function referrerCheck(referrer) {
return referrer && referrerRegexes.some((regex) => referrer.match(regex)); return referrer && referrerRegexes.some(regex => referrer.match(regex));
} }
function hotlinkCheck(file, userAgent, referrer) { function hotlinkCheck(file, userAgent, referrer) {
@ -52,7 +52,7 @@ export async function get(ctx) {
} }
if (file.malware) { if (file.malware) {
const alert = ctx.request.query.alert; const { alert } = ctx.request.query;
if (!alert || !alert.match(/i want to download malware/i)) { if (!alert || !alert.match(/i want to download malware/i)) {
ctx.redirect(`/${file.id}`); ctx.redirect(`/${file.id}`);
return; return;

View file

@ -1,5 +1,5 @@
import uuid from 'node-uuid'; import uuid from 'node-uuid';
import auth from '../lib/auth'; import { fromToken, fromCookie, setupSession } from '../lib/auth';
export async function main(ctx) { export async function main(ctx) {
if (ctx.session.user) { if (ctx.session.user) {
@ -11,19 +11,17 @@ export async function main(ctx) {
await ctx.redis.set(token, ctx.session.user.id, 'EX', 604800); await ctx.redis.set(token, ctx.session.user.id, 'EX', 604800);
ctx.session.user.token = token; ctx.session.user.token = token;
await ctx.render('index', { user: ctx.session.user }); await ctx.render('index', { user: ctx.session.user });
} else { } else if (ctx.query['app-token']) {
if (ctx.query['app-token']) { const user = await fromToken(ctx, ctx.query['app-token']);
const user = await auth.fromToken(ctx, ctx.query['app-token']); await setupSession(ctx, user);
await auth.setupSession(ctx, user);
ctx.redirect('/'); ctx.redirect('/');
} else if (ctx.cookies.r) { } else if (ctx.cookies.r) {
const user = await auth.fromCookie(ctx, ctx.cookies.r); const user = await fromCookie(ctx, ctx.cookies.r);
await auth.setupSession(ctx, user); await setupSession(ctx, user);
ctx.redirect('/'); ctx.redirect('/');
} else { } else {
await ctx.render('marketing'); await ctx.render('marketing');
} }
}
} }
export async function staticPage(ctx, next) { export async function staticPage(ctx, next) {

View file

@ -1,9 +1,11 @@
import debugname from 'debug';
import { import {
authenticate, setupSession, signup as signupUser, activateUser, sendResetToken, authenticate, setupSession, signup as signupUser, activateUser, sendResetToken,
validateResetToken, updatePassword, validateResetToken, updatePassword,
} from '../lib/auth'; } from '../lib/auth';
import models from '../../models'; import models from '../../models';
import debugname from 'debug';
const debug = debugname('hostr-web:user'); const debug = debugname('hostr-web:user');
export async function signin(ctx) { export async function signin(ctx) {
@ -44,17 +46,20 @@ export async function signup(ctx) {
await ctx.render('signup', { error: 'Emails do not match.', csrf: ctx.csrf }); await ctx.render('signup', { error: 'Emails do not match.', csrf: ctx.csrf });
return; return;
} else if (ctx.request.body.email && !ctx.request.body.terms) { } else if (ctx.request.body.email && !ctx.request.body.terms) {
await ctx.render('signup', { error: 'You must agree to the terms of service.', await ctx.render('signup', {
csrf: ctx.csrf }); error: 'You must agree to the terms of service.',
csrf: ctx.csrf,
});
return; return;
} else if (ctx.request.body.password && ctx.request.body.password.length < 7) { } else if (ctx.request.body.password && ctx.request.body.password.length < 7) {
await ctx.render('signup', { error: 'Password must be at least 7 characters long.', await ctx.render('signup', {
csrf: ctx.csrf }); error: 'Password must be at least 7 characters long.',
csrf: ctx.csrf,
});
return; return;
} }
const ip = ctx.headers['x-forwarded-for'] || ctx.ip; const ip = ctx.headers['x-forwarded-for'] || ctx.ip;
const email = ctx.request.body.email; const { email, password } = ctx.request.body;
const password = ctx.request.body.password;
try { try {
await signupUser.call(ctx, email, password, ip); await signupUser.call(ctx, email, password, ip);
} catch (e) { } catch (e) {
@ -66,12 +71,11 @@ export async function signup(ctx) {
message: 'Thanks for signing up, we\'ve sent you an email to activate your account.', message: 'Thanks for signing up, we\'ve sent you an email to activate your account.',
csrf: '', csrf: '',
}); });
return;
} }
export async function forgot(ctx) { export async function forgot(ctx) {
const token = ctx.params.token; const { token } = ctx.params;
if (ctx.request.body.password) { if (ctx.request.body.password) {
if (ctx.request.body.password.length < 7) { if (ctx.request.body.password.length < 7) {
@ -87,7 +91,7 @@ export async function forgot(ctx) {
if (user) { if (user) {
await updatePassword(user.userId, ctx.request.body.password); await updatePassword(user.userId, ctx.request.body.password);
const reset = await models.reset.findById(token); const reset = await models.reset.findById(token);
//reset.destroy(); reset.destroy();
await setupSession.call(ctx, user); await setupSession.call(ctx, user);
ctx.statsd.incr('auth.reset.success', 1); ctx.statsd.incr('auth.reset.success', 1);
ctx.redirect('/'); ctx.redirect('/');
@ -104,11 +108,10 @@ export async function forgot(ctx) {
return; return;
} }
await ctx.render('forgot', { csrf: ctx.csrf, token }); await ctx.render('forgot', { csrf: ctx.csrf, token });
return;
} else if (ctx.request.body.email) { } else if (ctx.request.body.email) {
ctx.assertCSRF(ctx.request.body); ctx.assertCSRF(ctx.request.body);
try { try {
const email = ctx.request.body.email; const { email } = ctx.request.body;
await sendResetToken.call(ctx, email); await sendResetToken.call(ctx, email);
ctx.statsd.incr('auth.reset.request', 1); ctx.statsd.incr('auth.reset.request', 1);
await ctx.render('forgot', { await ctx.render('forgot', {
@ -136,7 +139,7 @@ export async function logout(ctx) {
export async function activate(ctx) { export async function activate(ctx) {
const code = ctx.params.code; const { code } = ctx.params;
if (await activateUser.call(ctx, code)) { if (await activateUser.call(ctx, code)) {
ctx.statsd.incr('auth.activation', 1); ctx.statsd.incr('auth.activation', 1);
ctx.redirect('/'); ctx.redirect('/');

View file

@ -3,8 +3,6 @@ import kue from 'kue';
import raven from 'raven'; import raven from 'raven';
import debuglog from 'debug'; import debuglog from 'debug';
import models from '../models';
const debug = debuglog('hostr:worker'); const debug = debuglog('hostr:worker');
raven.config(process.env.SENTRY_DSN).install(); raven.config(process.env.SENTRY_DSN).install();