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",
"parserOptions": {
"ecmaVersion": 6,
"ecmaVersion": 2017,
"sourceType": "module",
"ecmaFeatures": {
"experimentalObjectRestSpread": true
},
}
},
"env": {
"node": true,
@ -14,5 +14,6 @@
"rules": {
"quotes": [2, "single"],
"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 stats from '../lib/koa-statsd';
import cors from 'kcors';
import StatsD from 'statsy';
import debugname from 'debug';
import stats from '../lib/koa-statsd';
import auth from './lib/auth';
import * as user from './routes/user';
import * as file from './routes/file';
import * as pro from './routes/pro';
import debugname from 'debug';
const debug = debugname('hostr-api');
const router = new Router();
@ -45,8 +47,7 @@ router.use(async (ctx, next) => {
code: 604,
},
};
} else {
if (!err.status) {
} else if (!err.status) {
debug(err);
if (ctx.raven) {
ctx.raven.captureError(err);
@ -57,7 +58,6 @@ router.use(async (ctx, next) => {
ctx.body = err.message;
}
}
}
ctx.type = 'application/json';
});

View file

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

View file

@ -1,18 +1,19 @@
import path from 'path';
import views from 'co-views';
const render = views(path.join(__dirname, '/../views'), { default: 'ejs' });
import Stripe from 'stripe';
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY);
import sendgridInit from 'sendgrid';
const sendgrid = sendgridInit(process.env.SENDGRID_KEY);
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 fromname = process.env.EMAIL_NAME;
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;
@ -74,7 +75,7 @@ export async function cancel(ctx) {
await stripe.customers.cancelSubscription(
transaction.data.id,
transaction.data.subscription.id,
{ at_period_end: false }
{ at_period_end: false },
);
user.plan = 'Free';

View file

@ -2,9 +2,10 @@ import uuid from 'node-uuid';
import redis from 'redis';
import co from 'co';
import passwords from 'passwords';
import debugname from 'debug';
import models from '../../models';
import debugname from 'debug';
const debug = debugname('hostr-api:user');
const redisUrl = process.env.REDIS_URL;
@ -26,31 +27,37 @@ export async function transaction(ctx) {
},
});
ctx.body = transactions.map((item) => {
return {
ctx.body = transactions.map(item => ({
id: item.id,
amount: item.amount / 100,
date: item.date,
description: item.description,
type: 'direct',
};
});
}));
}
export async function settings(ctx) {
ctx.assert(ctx.request.body, 400,
'{"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}}');
ctx.assert(
ctx.request.body, 400,
'{"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);
ctx.assert(await passwords.match(ctx.request.body.current_password, user.password), 400,
'{"error": {"message": "Incorrect password", "code": 606}}');
ctx.assert(
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) {
user.email = ctx.request.body.email;
}
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}}');
ctx.assert(
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);
}
await user.save();

2
app.js
View file

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

View file

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

View file

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

View file

@ -4,7 +4,7 @@ const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
function randomID() {
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)));
}
return rand;
@ -18,7 +18,7 @@ async function checkId(Files, fileId, attempts) {
if (file === null) {
return fileId;
}
return checkId(randomID(), ++attempts); // eslint-disable-line no-param-reassign
return checkId(Files, randomID(), attempts + 1);
}
export default function (Files) {

View file

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

View file

@ -1,4 +1,4 @@
import virustotal from './virustotal';
import getFileReport from './virustotal';
const extensions = [
'EXE',
@ -65,13 +65,13 @@ function getExtension(filename) {
return (i < 0) ? '' : filename.substr(i + 1);
}
export default function* (file) {
if (extensions.indexOf(getExtension(file.file_name.toUpperCase())) < 0) {
export default async (file) => {
if (extensions.indexOf(getExtension(file.name.toUpperCase())) < 0) {
return false;
}
const result = yield virustotal.getFileReport(file.md5);
const result = await getFileReport(file.md5);
return {
positive: result.positives >= 5,
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 session from 'koa-generic-session';
import debugname from 'debug';
const debug = debugname('hostr:redis');
const connection = new Promise((resolve, reject) => {
@ -25,8 +26,7 @@ const redisSession = new Promise((resolve, reject) =>
}).catch((err) => {
debug('koa-redis error: ', err);
reject(err);
})
);
}));
const wrapped = new Promise((resolve, reject) =>
connection.then((client) => {
@ -40,8 +40,7 @@ const wrapped = new Promise((resolve, reject) =>
debug('co-redis error: ', err);
reject(err);
throw err;
})
);
}));
export function sessionStore() {
return async (ctx, next) => {

View file

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

View file

@ -1,5 +1,6 @@
import aws from 'aws-sdk';
import debugname from 'debug';
const debug = debugname('hostr: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',
};
export function sniff(filename) {
export default (filename) => {
if (extensions[filename.split('.').pop().toLowerCase()]) {
return extensions[filename.split('.').pop().toLowerCase()];
}
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 fs from 'mz/fs';
import sizeOf from 'image-size';
import debugname from 'debug';
import models from '../models';
import createHostrId from './hostr-id';
import { formatFile } from './format';
import resize from './resize';
import malware from './malware';
import { sniff } from './type';
import sniff from './sniff';
import { upload as s3upload } from './s3';
import debugname from 'debug';
const debug = debugname('hostr-api:uploader');
const storePath = process.env.UPLOAD_STORAGE_PATH;
@ -66,14 +66,12 @@ export default class Uploader {
highWaterMark: 10000000,
});
this.upload.on('file', async (fieldname, file, filename, encoding, mimetype) => {
debug('FILE', fieldname, file, filename, encoding, mimetype);
this.upload.on('file', async (fieldname, file, filename) => {
this.upload.filename = filename;
this.file = await models.file.create({
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,
userId: this.context.user.id,
status: 'uploading',
@ -102,7 +100,7 @@ export default class Uploader {
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) {
const progressEvent = `{"type": "file-progress", "data":
{"id": "${this.file.id}", "complete": ${this.percentComplete}}}`;
@ -131,7 +129,6 @@ export default class Uploader {
this.localStream.on('end', () => {
s3upload(fs.createReadStream(join(storePath, this.path)), this.path);
});
});
this.context.req.pipe(this.upload);
});
@ -210,7 +207,7 @@ export default class Uploader {
// Check in the background
process.nextTick(async () => {
debug('Malware Scan');
const result = await malware(this);
const result = await malware(this.file);
if (result) {
this.file.malwarePositives = result.positives;
this.file.save();

View file

@ -3,9 +3,9 @@ import FormData from 'form-data';
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();
form.append('apikey', apiKey);
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',
'video',
'archive',
'other'
'other',
),
width: DataTypes.INTEGER,
height: DataTypes.INTEGER,
@ -32,14 +32,15 @@ export default function (sequelize, DataTypes) {
});
File.accessed = function accessed(id) {
sequelize.query(`
sequelize.query(
`
UPDATE files
SET "downloads" = downloads + 1, "accessedAt" = NOW()
WHERE "id" = :id`,
{
replacements: { id },
type: sequelize.QueryTypes.UPDATE,
}
},
);
};

View file

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

View file

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

View file

@ -41,7 +41,7 @@ export async function authenticate(email, password) {
activated: true,
},
});
debug(user);
const login = await models.login.create({
ip: remoteIp,
successful: false,
@ -52,7 +52,6 @@ export async function authenticate(email, password) {
debug('Password verified');
login.successful = true;
await login.save();
debug(user);
return user;
}
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) {
const userId = await this.redis.get(token);
return await models.user.findById(userId);
return models.user.findById(userId);
}
export async function fromCookie(rememberId) {
const userId = await models.remember.findById(rememberId);
return await models.user.findById(userId);
return models.user.findById(userId);
}
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) {
debug(code);
const activation = await models.activation.findOne({
where: {
id: code,

View file

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

View file

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

View file

@ -1,9 +1,11 @@
import debugname from 'debug';
import {
authenticate, setupSession, signup as signupUser, activateUser, sendResetToken,
validateResetToken, updatePassword,
} from '../lib/auth';
import models from '../../models';
import debugname from 'debug';
const debug = debugname('hostr-web:user');
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 });
return;
} else if (ctx.request.body.email && !ctx.request.body.terms) {
await ctx.render('signup', { error: 'You must agree to the terms of service.',
csrf: ctx.csrf });
await ctx.render('signup', {
error: 'You must agree to the terms of service.',
csrf: ctx.csrf,
});
return;
} 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.',
csrf: ctx.csrf });
await ctx.render('signup', {
error: 'Password must be at least 7 characters long.',
csrf: ctx.csrf,
});
return;
}
const ip = ctx.headers['x-forwarded-for'] || ctx.ip;
const email = ctx.request.body.email;
const password = ctx.request.body.password;
const { email, password } = ctx.request.body;
try {
await signupUser.call(ctx, email, password, ip);
} 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.',
csrf: '',
});
return;
}
export async function forgot(ctx) {
const token = ctx.params.token;
const { token } = ctx.params;
if (ctx.request.body.password) {
if (ctx.request.body.password.length < 7) {
@ -87,7 +91,7 @@ export async function forgot(ctx) {
if (user) {
await updatePassword(user.userId, ctx.request.body.password);
const reset = await models.reset.findById(token);
//reset.destroy();
reset.destroy();
await setupSession.call(ctx, user);
ctx.statsd.incr('auth.reset.success', 1);
ctx.redirect('/');
@ -104,11 +108,10 @@ export async function forgot(ctx) {
return;
}
await ctx.render('forgot', { csrf: ctx.csrf, token });
return;
} else if (ctx.request.body.email) {
ctx.assertCSRF(ctx.request.body);
try {
const email = ctx.request.body.email;
const { email } = ctx.request.body;
await sendResetToken.call(ctx, email);
ctx.statsd.incr('auth.reset.request', 1);
await ctx.render('forgot', {
@ -136,7 +139,7 @@ export async function logout(ctx) {
export async function activate(ctx) {
const code = ctx.params.code;
const { code } = ctx.params;
if (await activateUser.call(ctx, code)) {
ctx.statsd.incr('auth.activation', 1);
ctx.redirect('/');

View file

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