2015-07-09 23:01:43 +01:00
|
|
|
import crypto from 'crypto';
|
|
|
|
import passwords from 'passwords';
|
|
|
|
import uuid from 'node-uuid';
|
|
|
|
import views from 'co-views';
|
2015-08-09 10:55:10 +01:00
|
|
|
const render = views(__dirname + '/../views', { default: 'ejs'});
|
2015-07-09 23:01:43 +01:00
|
|
|
import debugname from 'debug';
|
|
|
|
const debug = debugname('hostr-web:auth');
|
|
|
|
import { Mandrill } from 'mandrill-api/mandrill';
|
|
|
|
const mandrill = new Mandrill(process.env.MANDRILL_KEY);
|
|
|
|
|
2015-08-22 16:16:15 +01:00
|
|
|
export function* authenticate(email, password) {
|
|
|
|
const Users = this.db.Users;
|
|
|
|
const Logins = this.db.Logins;
|
|
|
|
const remoteIp = this.headers['x-real-ip'] || this.ip;
|
2015-07-09 23:01:43 +01:00
|
|
|
|
2015-08-23 22:12:32 +01:00
|
|
|
if (!password || password.length < 6) {
|
2015-07-09 23:01:43 +01:00
|
|
|
debug('No password, or password too short');
|
|
|
|
return new Error('Invalid login details');
|
|
|
|
}
|
|
|
|
const count = yield Logins.count({ip: remoteIp, successful: false, at: { '$gt': Math.ceil(Date.now() / 1000) - 600}});
|
|
|
|
if (count > 25) {
|
|
|
|
debug('Throttling brute force');
|
|
|
|
return new Error('Invalid login details');
|
|
|
|
}
|
|
|
|
const login = {ip: remoteIp, at: Math.ceil(Date.now() / 1000), successful: null};
|
|
|
|
yield Logins.save(login);
|
|
|
|
const user = yield Users.findOne({email: email.toLowerCase(), banned: {'$exists': false}, status: {'$ne': 'deleted'}});
|
|
|
|
if (user) {
|
|
|
|
const verified = yield passwords.verify(password, user.salted_password);
|
|
|
|
if (verified) {
|
|
|
|
debug('Password verified');
|
|
|
|
login.successful = true;
|
|
|
|
yield Logins.updateOne({_id: login._id}, login);
|
|
|
|
return user;
|
|
|
|
}
|
2015-08-23 22:12:32 +01:00
|
|
|
debug('Password invalid');
|
|
|
|
login.successful = false;
|
|
|
|
yield Logins.updateOne({_id: login._id}, login);
|
2015-07-09 23:01:43 +01:00
|
|
|
} else {
|
|
|
|
debug('Email invalid');
|
|
|
|
login.successful = false;
|
|
|
|
yield Logins.updateOne({_id: login._id}, login);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2015-08-22 16:16:15 +01:00
|
|
|
export function* setupSession(user) {
|
2015-07-09 23:01:43 +01:00
|
|
|
debug('Setting up session');
|
|
|
|
const token = uuid.v4();
|
2015-08-22 16:16:15 +01:00
|
|
|
yield this.redis.set(token, user._id, 'EX', 604800);
|
2015-07-09 23:01:43 +01:00
|
|
|
|
|
|
|
const sessionUser = {
|
|
|
|
'id': user._id,
|
|
|
|
'email': user.email,
|
|
|
|
'dailyUploadAllowance': 15,
|
|
|
|
'maxFileSize': 20971520,
|
|
|
|
'joined': user.joined,
|
|
|
|
'plan': user.type || 'Free',
|
2015-08-23 22:12:32 +01:00
|
|
|
'uploadsToday': yield this.db.Files.count({owner: user._id, 'time_added': {'$gt': Math.ceil(Date.now() / 1000) - 86400}}),
|
2015-07-09 23:01:43 +01:00
|
|
|
'token': token,
|
2015-08-23 22:12:32 +01:00
|
|
|
'md5': crypto.createHash('md5').update(user.email).digest('hex'),
|
2015-07-09 23:01:43 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
if (sessionUser.plan === 'Pro') {
|
|
|
|
sessionUser.maxFileSize = 524288000;
|
|
|
|
sessionUser.dailyUploadAllowance = 'unlimited';
|
|
|
|
}
|
|
|
|
|
2015-08-22 16:16:15 +01:00
|
|
|
this.session.user = sessionUser;
|
|
|
|
if (this.request.body.remember && this.request.body.remember === 'on') {
|
|
|
|
const Remember = this.db.Remember;
|
2015-08-23 22:12:32 +01:00
|
|
|
const rememberToken = uuid();
|
2015-07-09 23:01:43 +01:00
|
|
|
Remember.save({_id: rememberToken, 'user_id': user.id, created: new Date().getTime()});
|
2015-08-22 16:16:15 +01:00
|
|
|
this.cookies.set('r', rememberToken, { maxAge: 1209600000, httpOnly: true});
|
2015-07-09 23:01:43 +01:00
|
|
|
}
|
|
|
|
debug('Session set up');
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2015-08-22 16:16:15 +01:00
|
|
|
export function* signup(email, password, ip) {
|
|
|
|
const Users = this.db.Users;
|
2015-07-09 23:01:43 +01:00
|
|
|
const existingUser = yield Users.findOne({email: email, status: {'$ne': 'deleted'}});
|
|
|
|
if (existingUser) {
|
|
|
|
debug('Email already in use.');
|
2015-08-23 16:50:40 +01:00
|
|
|
throw new Error('Email already in use.');
|
2015-07-09 23:01:43 +01:00
|
|
|
}
|
|
|
|
const cryptedPassword = yield passwords.crypt(password);
|
2015-08-23 22:12:32 +01:00
|
|
|
const user = {
|
2015-07-09 23:01:43 +01:00
|
|
|
email: email,
|
|
|
|
'salted_password': cryptedPassword,
|
|
|
|
joined: Math.round(new Date().getTime() / 1000),
|
|
|
|
'signup_ip': ip,
|
2015-08-23 22:12:32 +01:00
|
|
|
activationCode: uuid(),
|
2015-07-09 23:01:43 +01:00
|
|
|
};
|
|
|
|
Users.insertOne(user);
|
|
|
|
|
2015-08-30 18:35:05 +02:00
|
|
|
const html = yield render('email/inlined/activate', {activationUrl: process.env.WEB_BASE_URL + '/activate/' + user.activationCode});
|
2015-07-09 23:01:43 +01:00
|
|
|
const text = `Thanks for signing up to Hostr!
|
|
|
|
Please confirm your email address by clicking the link below.
|
|
|
|
|
2015-08-30 18:35:05 +02:00
|
|
|
${process.env.WEB_BASE_URL + '/activate/' + user.activationCode}
|
2015-07-09 23:01:43 +01:00
|
|
|
|
|
|
|
— Jonathan Cremin, Hostr Founder
|
|
|
|
`;
|
|
|
|
mandrill.messages.send({message: {
|
|
|
|
html: html,
|
|
|
|
text: text,
|
|
|
|
subject: 'Welcome to Hostr',
|
|
|
|
'from_email': 'jonathan@hostr.co',
|
|
|
|
'from_name': 'Jonathan from Hostr',
|
|
|
|
to: [{
|
|
|
|
email: user.email,
|
2015-08-23 22:12:32 +01:00
|
|
|
type: 'to',
|
2015-07-09 23:01:43 +01:00
|
|
|
}],
|
|
|
|
'tags': [
|
2015-08-23 22:12:32 +01:00
|
|
|
'user-activation',
|
|
|
|
],
|
2015-07-09 23:01:43 +01:00
|
|
|
}});
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2015-08-22 16:16:15 +01:00
|
|
|
export function* sendResetToken(email) {
|
|
|
|
const Users = this.db.Users;
|
|
|
|
const Reset = this.db.Reset;
|
2015-07-09 23:01:43 +01:00
|
|
|
const user = yield Users.findOne({email: email});
|
|
|
|
if (user) {
|
2015-08-23 22:12:32 +01:00
|
|
|
const token = uuid.v4();
|
2015-07-09 23:01:43 +01:00
|
|
|
Reset.save({
|
|
|
|
'_id': user._id,
|
|
|
|
'token': token,
|
2015-08-23 22:12:32 +01:00
|
|
|
'created': Math.round(new Date().getTime() / 1000),
|
2015-07-09 23:01:43 +01:00
|
|
|
});
|
2015-08-30 18:35:05 +02:00
|
|
|
const html = yield render('email/inlined/forgot', {forgotUrl: process.env.WEB_BASE_URL + '/forgot/' + token});
|
2015-07-09 23:01:43 +01:00
|
|
|
const text = `It seems you've forgotten your password :(
|
2015-08-30 18:35:05 +02:00
|
|
|
Visit ${process.env.WEB_BASE_URL + '/forgot/' + token} to set a new one.
|
2015-07-09 23:01:43 +01:00
|
|
|
`;
|
|
|
|
mandrill.messages.send({message: {
|
|
|
|
html: html,
|
|
|
|
text: text,
|
|
|
|
subject: 'Hostr Password Reset',
|
|
|
|
'from_email': 'jonathan@hostr.co',
|
|
|
|
'from_name': 'Jonathan from Hostr',
|
|
|
|
to: [{
|
|
|
|
email: user.email,
|
2015-08-23 22:12:32 +01:00
|
|
|
type: 'to',
|
2015-07-09 23:01:43 +01:00
|
|
|
}],
|
|
|
|
'tags': [
|
2015-08-23 22:12:32 +01:00
|
|
|
'password-reset',
|
|
|
|
],
|
2015-07-09 23:01:43 +01:00
|
|
|
}});
|
|
|
|
} else {
|
2015-08-23 16:50:40 +01:00
|
|
|
throw new Error('There was an error looking up your email address.');
|
2015-07-09 23:01:43 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2015-08-22 16:16:15 +01:00
|
|
|
export function* fromToken(token) {
|
|
|
|
const Users = this.db.Users;
|
|
|
|
const reply = yield this.redis.get(token);
|
2015-07-09 23:01:43 +01:00
|
|
|
return yield Users.findOne({_id: reply});
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2015-08-22 16:16:15 +01:00
|
|
|
export function* fromCookie(cookie) {
|
|
|
|
const Remember = this.db.Remember;
|
|
|
|
const Users = this.db.Users;
|
2015-07-09 23:01:43 +01:00
|
|
|
const remember = yield Remember.findOne({_id: cookie});
|
|
|
|
return yield Users.findOne({_id: remember.user_id});
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2015-08-22 16:16:15 +01:00
|
|
|
export function* validateResetToken() {
|
|
|
|
const Reset = this.db.Reset;
|
|
|
|
return yield Reset.findOne({token: this.params.id});
|
2015-07-09 23:01:43 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2015-08-22 16:16:15 +01:00
|
|
|
export function* updatePassword(userId, password) {
|
|
|
|
const Users = this.db.Users;
|
2015-07-09 23:01:43 +01:00
|
|
|
const cryptedPassword = yield passwords.crypt(password);
|
2015-08-23 16:50:40 +01:00
|
|
|
yield Users.updateOne({_id: userId}, {'$set': {'salted_password': cryptedPassword}});
|
2015-07-09 23:01:43 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2015-08-22 16:16:15 +01:00
|
|
|
export function* activateUser(code) {
|
|
|
|
const Users = this.db.Users;
|
2015-07-09 23:01:43 +01:00
|
|
|
const user = yield Users.findOne({activationCode: code});
|
|
|
|
if (user) {
|
|
|
|
Users.updateOne({_id: user._id}, {'$unset': {activationCode: ''}});
|
2015-09-04 00:04:48 +02:00
|
|
|
yield setupSession.call(this, user);
|
|
|
|
return true;
|
2015-07-09 23:01:43 +01:00
|
|
|
}
|
2015-09-04 00:04:48 +02:00
|
|
|
return false;
|
2015-07-09 23:01:43 +01:00
|
|
|
}
|