2025-06-13 09:32:37 +01:00
|
|
|
import crypto from "crypto";
|
|
|
|
import { join } from "path";
|
|
|
|
import passwords from "passwords";
|
|
|
|
import uuid from "node-uuid";
|
|
|
|
import views from "co-views";
|
|
|
|
import debugname from "debug";
|
|
|
|
import sendgrid from "@sendgrid/mail";
|
|
|
|
import models from "../../models";
|
|
|
|
|
|
|
|
const render = views(join(__dirname, "..", "views"), { default: "ejs" });
|
|
|
|
const debug = debugname("hostr-web:auth");
|
2018-08-11 12:08:16 +01:00
|
|
|
sendgrid.setApiKey(process.env.SENDGRID_KEY);
|
2015-07-09 23:01:43 +01:00
|
|
|
|
2016-06-06 15:37:00 +01:00
|
|
|
const from = process.env.EMAIL_FROM;
|
|
|
|
const fromname = process.env.EMAIL_NAME;
|
|
|
|
|
2018-06-02 15:50:39 +00:00
|
|
|
export async function authenticate(email, password) {
|
2025-06-13 09:32:37 +01:00
|
|
|
const remoteIp = this.headers["x-forwarded-for"] || this.ip;
|
2015-07-09 23:01:43 +01:00
|
|
|
|
2015-08-23 22:12:32 +01:00
|
|
|
if (!password || password.length < 6) {
|
2025-06-13 09:32:37 +01:00
|
|
|
debug("No password, or password too short");
|
|
|
|
return new Error("Invalid login details");
|
2015-07-09 23:01:43 +01:00
|
|
|
}
|
2018-06-02 15:50:39 +00:00
|
|
|
const count = await models.login.count({
|
2016-06-19 10:14:47 -07:00
|
|
|
where: {
|
2025-06-13 09:32:37 +01:00
|
|
|
ip: remoteIp.split(",")[0],
|
2016-06-19 10:14:47 -07:00
|
|
|
successful: false,
|
|
|
|
createdAt: {
|
2016-08-07 14:38:05 +01:00
|
|
|
$gt: Math.ceil(Date.now()) - 600000,
|
2016-06-19 10:14:47 -07:00
|
|
|
},
|
|
|
|
},
|
2016-06-06 15:37:00 +01:00
|
|
|
});
|
2016-06-19 10:14:47 -07:00
|
|
|
|
2015-07-09 23:01:43 +01:00
|
|
|
if (count > 25) {
|
2025-06-13 09:32:37 +01:00
|
|
|
debug("Throttling brute force");
|
|
|
|
return new Error("Invalid login details");
|
2015-07-09 23:01:43 +01:00
|
|
|
}
|
2018-06-02 15:50:39 +00:00
|
|
|
const user = await models.user.findOne({
|
2016-08-07 14:38:05 +01:00
|
|
|
where: {
|
|
|
|
email: email.toLowerCase(),
|
|
|
|
activated: true,
|
|
|
|
},
|
2016-06-19 10:14:47 -07:00
|
|
|
});
|
2018-06-02 18:07:00 +00:00
|
|
|
|
2018-06-02 15:50:39 +00:00
|
|
|
const login = await models.login.create({
|
2025-06-13 09:32:37 +01:00
|
|
|
ip: remoteIp.split(",")[0],
|
2016-06-19 10:14:47 -07:00
|
|
|
successful: false,
|
2016-06-06 15:37:00 +01:00
|
|
|
});
|
2016-06-19 10:14:47 -07:00
|
|
|
|
2016-08-07 14:38:05 +01:00
|
|
|
if (user && user.password) {
|
2019-01-19 11:06:14 +00:00
|
|
|
login.userId = user.id;
|
2018-06-02 15:50:39 +00:00
|
|
|
if (await passwords.verify(password, user.password)) {
|
2025-06-13 09:32:37 +01:00
|
|
|
debug("Password verified");
|
2015-07-09 23:01:43 +01:00
|
|
|
login.successful = true;
|
2018-06-02 15:50:39 +00:00
|
|
|
await login.save();
|
2015-07-09 23:01:43 +01:00
|
|
|
return user;
|
|
|
|
}
|
2025-06-13 09:32:37 +01:00
|
|
|
debug("Password invalid");
|
2015-07-09 23:01:43 +01:00
|
|
|
}
|
2018-06-02 15:50:39 +00:00
|
|
|
await login.save();
|
2016-08-07 14:38:05 +01:00
|
|
|
return false;
|
2015-07-09 23:01:43 +01:00
|
|
|
}
|
|
|
|
|
2018-06-02 15:50:39 +00:00
|
|
|
export async function setupSession(user) {
|
2025-06-13 09:32:37 +01:00
|
|
|
debug("Setting up session");
|
2015-07-09 23:01:43 +01:00
|
|
|
const token = uuid.v4();
|
2025-06-13 09:32:37 +01:00
|
|
|
|
|
|
|
await this.redis.set(token, user.id, "EX", 604800);
|
2015-07-09 23:01:43 +01:00
|
|
|
|
|
|
|
const sessionUser = {
|
2016-06-19 10:14:47 -07:00
|
|
|
id: user.id,
|
2016-06-06 15:37:00 +01:00
|
|
|
email: user.email,
|
|
|
|
dailyUploadAllowance: 15,
|
|
|
|
maxFileSize: 20971520,
|
2016-06-19 10:14:47 -07:00
|
|
|
joined: user.createdAt,
|
|
|
|
plan: user.plan,
|
2018-06-02 15:50:39 +00:00
|
|
|
uploadsToday: await models.file.count({ userId: user.id }),
|
2025-06-13 09:32:37 +01:00
|
|
|
md5: crypto.createHash("md5").update(user.email).digest("hex"),
|
2016-06-06 15:37:00 +01:00
|
|
|
token,
|
2015-07-09 23:01:43 +01:00
|
|
|
};
|
|
|
|
|
2025-06-13 09:32:37 +01:00
|
|
|
if (sessionUser.plan === "Pro") {
|
2015-07-09 23:01:43 +01:00
|
|
|
sessionUser.maxFileSize = 524288000;
|
2025-06-13 09:32:37 +01:00
|
|
|
sessionUser.dailyUploadAllowance = "unlimited";
|
2015-07-09 23:01:43 +01:00
|
|
|
}
|
|
|
|
|
2015-08-22 16:16:15 +01:00
|
|
|
this.session.user = sessionUser;
|
2025-06-13 09:32:37 +01:00
|
|
|
if (this.request.body.remember && this.request.body.remember === "on") {
|
2018-06-02 15:50:39 +00:00
|
|
|
const remember = await models.remember.create({
|
2016-06-19 10:14:47 -07:00
|
|
|
id: uuid(),
|
|
|
|
userId: user.id,
|
|
|
|
});
|
2025-06-13 09:32:37 +01:00
|
|
|
this.cookies.set("r", remember.id, { maxAge: 1209600000, httpOnly: true });
|
2015-07-09 23:01:43 +01:00
|
|
|
}
|
2025-06-13 09:32:37 +01:00
|
|
|
debug("Session set up");
|
2015-07-09 23:01:43 +01:00
|
|
|
}
|
|
|
|
|
2018-06-02 15:50:39 +00:00
|
|
|
export async function signup(email, password, ip) {
|
|
|
|
const existingUser = await models.user.findOne({
|
2016-08-07 14:38:05 +01:00
|
|
|
where: {
|
|
|
|
email,
|
|
|
|
activated: true,
|
|
|
|
},
|
|
|
|
});
|
2015-07-09 23:01:43 +01:00
|
|
|
if (existingUser) {
|
2025-06-13 09:32:37 +01:00
|
|
|
debug("Email already in use.");
|
|
|
|
throw new Error("Email already in use.");
|
2015-07-09 23:01:43 +01:00
|
|
|
}
|
2018-06-02 15:50:39 +00:00
|
|
|
const cryptedPassword = await passwords.crypt(password);
|
2025-06-13 09:32:37 +01:00
|
|
|
const user = await models.user.create(
|
|
|
|
{
|
2016-06-19 10:14:47 -07:00
|
|
|
email,
|
2025-06-13 09:32:37 +01:00
|
|
|
password: cryptedPassword,
|
|
|
|
ip,
|
|
|
|
plan: "Free",
|
|
|
|
activation: {
|
|
|
|
id: uuid(),
|
|
|
|
email,
|
|
|
|
},
|
2016-06-19 10:14:47 -07:00
|
|
|
},
|
2025-06-13 09:32:37 +01:00
|
|
|
{
|
|
|
|
include: [models.activation],
|
|
|
|
},
|
|
|
|
);
|
2016-06-19 10:14:47 -07:00
|
|
|
|
2018-06-02 15:50:39 +00:00
|
|
|
await user.save();
|
2015-07-09 23:01:43 +01:00
|
|
|
|
2025-06-13 09:32:37 +01:00
|
|
|
const html = await render("email/inlined/activate", {
|
2016-06-19 10:14:47 -07:00
|
|
|
activationUrl: `${process.env.WEB_BASE_URL}/activate/${user.activation.id}`,
|
2016-06-06 15:37:00 +01:00
|
|
|
});
|
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.
|
|
|
|
|
2016-06-19 10:14:47 -07:00
|
|
|
${process.env.WEB_BASE_URL}/activate/${user.activation.id}
|
2015-07-09 23:01:43 +01:00
|
|
|
|
|
|
|
— Jonathan Cremin, Hostr Founder
|
|
|
|
`;
|
2018-08-11 12:08:16 +01:00
|
|
|
sendgrid.send({
|
2016-05-01 12:30:46 +01:00
|
|
|
to: user.email,
|
2025-06-13 09:32:37 +01:00
|
|
|
subject: "Welcome to Hostr",
|
2016-06-06 15:37:00 +01:00
|
|
|
from,
|
|
|
|
fromname,
|
|
|
|
html,
|
|
|
|
text,
|
2025-06-13 09:32:37 +01:00
|
|
|
categories: ["activate"],
|
2016-05-01 12:30:46 +01:00
|
|
|
});
|
2015-07-09 23:01:43 +01:00
|
|
|
}
|
|
|
|
|
2018-06-02 15:50:39 +00:00
|
|
|
export async function sendResetToken(email) {
|
|
|
|
const user = await models.user.findOne({
|
2016-08-07 14:38:05 +01:00
|
|
|
where: {
|
|
|
|
email,
|
|
|
|
},
|
|
|
|
});
|
2015-07-09 23:01:43 +01:00
|
|
|
if (user) {
|
2018-06-02 15:50:39 +00:00
|
|
|
const reset = await models.reset.create({
|
2016-06-19 10:14:47 -07:00
|
|
|
id: uuid.v4(),
|
|
|
|
userId: user.id,
|
2016-06-06 15:37:00 +01:00
|
|
|
});
|
2025-06-13 09:32:37 +01:00
|
|
|
const html = await render("email/inlined/forgot", {
|
2016-06-19 10:14:47 -07:00
|
|
|
forgotUrl: `${process.env.WEB_BASE_URL}/forgot/${reset.id}`,
|
2015-07-09 23:01:43 +01:00
|
|
|
});
|
|
|
|
const text = `It seems you've forgotten your password :(
|
2016-06-19 10:14:47 -07:00
|
|
|
Visit ${process.env.WEB_BASE_URL}/forgot/${reset.id} to set a new one.
|
2015-07-09 23:01:43 +01:00
|
|
|
`;
|
2018-08-11 12:08:16 +01:00
|
|
|
sendgrid.send({
|
2016-05-01 12:30:46 +01:00
|
|
|
to: user.email,
|
2025-06-13 09:32:37 +01:00
|
|
|
from: "jonathan@hostr.co",
|
|
|
|
fromname: "Jonathan from Hostr",
|
|
|
|
subject: "Hostr Password Reset",
|
2016-06-06 15:37:00 +01:00
|
|
|
html,
|
|
|
|
text,
|
2025-06-13 09:32:37 +01:00
|
|
|
categories: ["password-reset"],
|
2016-05-01 12:30:46 +01:00
|
|
|
});
|
2015-07-09 23:01:43 +01:00
|
|
|
} else {
|
2025-06-13 09:32:37 +01:00
|
|
|
throw new Error("There was an error looking up your email address.");
|
2015-07-09 23:01:43 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-06-02 15:50:39 +00:00
|
|
|
export async function fromToken(token) {
|
|
|
|
const userId = await this.redis.get(token);
|
2019-06-08 07:52:57 -07:00
|
|
|
return models.user.findByPk(userId);
|
2015-07-09 23:01:43 +01:00
|
|
|
}
|
|
|
|
|
2018-06-02 15:50:39 +00:00
|
|
|
export async function fromCookie(rememberId) {
|
2019-06-08 07:52:57 -07:00
|
|
|
const userId = await models.remember.findByPk(rememberId);
|
|
|
|
return models.user.findByPk(userId);
|
2015-07-09 23:01:43 +01:00
|
|
|
}
|
|
|
|
|
2018-06-02 15:50:39 +00:00
|
|
|
export async function validateResetToken(resetId) {
|
2019-06-08 07:52:57 -07:00
|
|
|
return models.reset.findByPk(resetId);
|
2015-07-09 23:01:43 +01:00
|
|
|
}
|
|
|
|
|
2018-06-02 15:50:39 +00:00
|
|
|
export async function updatePassword(userId, password) {
|
|
|
|
const cryptedPassword = await passwords.crypt(password);
|
2019-06-08 07:52:57 -07:00
|
|
|
const user = await models.user.findByPk(userId);
|
2016-06-19 10:14:47 -07:00
|
|
|
user.password = cryptedPassword;
|
2018-06-02 15:50:39 +00:00
|
|
|
await user.save();
|
2015-07-09 23:01:43 +01:00
|
|
|
}
|
|
|
|
|
2018-06-02 15:50:39 +00:00
|
|
|
export async function activateUser(code) {
|
|
|
|
const activation = await models.activation.findOne({
|
2016-06-19 10:14:47 -07:00
|
|
|
where: {
|
|
|
|
id: code,
|
|
|
|
},
|
|
|
|
});
|
|
|
|
if (activation.updatedAt.getTime() === activation.createdAt.getTime()) {
|
|
|
|
activation.activated = true;
|
2018-06-02 15:50:39 +00:00
|
|
|
await activation.save();
|
|
|
|
const user = await activation.getUser();
|
2016-06-19 10:14:47 -07:00
|
|
|
user.activated = true;
|
2018-06-02 15:50:39 +00:00
|
|
|
await user.save();
|
|
|
|
await setupSession.call(this, user);
|
2015-09-04 00:04:48 +02:00
|
|
|
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
|
|
|
}
|