2020-11-11 19:08:33 +01:00
|
|
|
import AuthMethod from "../AuthMethod";
|
|
|
|
import PasswordAuthProof from "./PasswordAuthProof";
|
|
|
|
import User from "../models/User";
|
|
|
|
import {Request, Response} from "express";
|
|
|
|
import UserEmail from "../models/UserEmail";
|
|
|
|
import AuthComponent from "../AuthComponent";
|
|
|
|
import Application from "../../Application";
|
|
|
|
import Throttler from "../../Throttler";
|
|
|
|
import {AuthError, PendingApprovalAuthError, RegisterCallback} from "../AuthGuard";
|
2020-11-15 15:50:19 +01:00
|
|
|
import Validator, {InvalidFormatValidationError} from "../../db/Validator";
|
2020-11-11 19:08:33 +01:00
|
|
|
import Controller from "../../Controller";
|
|
|
|
import UserPasswordComponent from "./UserPasswordComponent";
|
2020-11-14 17:24:42 +01:00
|
|
|
import UserNameComponent, {USERNAME_REGEXP} from "../models/UserNameComponent";
|
2020-11-11 19:08:33 +01:00
|
|
|
import ModelFactory from "../../db/ModelFactory";
|
|
|
|
import {ServerError} from "../../HttpError";
|
2020-12-04 14:42:09 +01:00
|
|
|
import {Session} from "express-session";
|
2020-11-11 19:08:33 +01:00
|
|
|
|
|
|
|
export default class PasswordAuthMethod implements AuthMethod<PasswordAuthProof> {
|
|
|
|
public constructor(
|
|
|
|
protected readonly app: Application,
|
|
|
|
) {
|
|
|
|
}
|
|
|
|
|
|
|
|
public getName(): string {
|
|
|
|
return 'password';
|
|
|
|
}
|
|
|
|
|
2020-11-16 11:43:14 +01:00
|
|
|
public getWeightForRequest(req: Request): number {
|
|
|
|
return !req.body.identifier || !req.body.password || req.body.password.length === 0 ?
|
|
|
|
0 :
|
|
|
|
2;
|
|
|
|
}
|
|
|
|
|
2020-11-11 19:08:33 +01:00
|
|
|
public async findUserByIdentifier(identifier: string): Promise<User | null> {
|
|
|
|
const query = UserEmail.select()
|
|
|
|
.with('user')
|
|
|
|
.where('email', identifier);
|
|
|
|
|
2020-11-14 18:16:05 +01:00
|
|
|
const user = (await query
|
|
|
|
.first())?.user.getOrFail();
|
|
|
|
if (user) return user;
|
|
|
|
|
2020-11-11 19:08:33 +01:00
|
|
|
if (ModelFactory.get(User).hasComponent(UserNameComponent)) {
|
2020-11-14 18:16:05 +01:00
|
|
|
return await User.select().where('name', identifier).first();
|
2020-11-11 19:08:33 +01:00
|
|
|
}
|
|
|
|
|
2020-11-14 18:16:05 +01:00
|
|
|
return null;
|
2020-11-11 19:08:33 +01:00
|
|
|
}
|
|
|
|
|
2020-12-04 14:42:09 +01:00
|
|
|
public async getProofsForSession(session: Session): Promise<PasswordAuthProof[]> {
|
2020-11-11 19:08:33 +01:00
|
|
|
const proof = PasswordAuthProof.getProofForSession(session);
|
|
|
|
return proof ? [proof] : [];
|
|
|
|
}
|
|
|
|
|
|
|
|
public async attemptLogin(req: Request, res: Response, user: User): Promise<void> {
|
|
|
|
const passwordAuthProof = PasswordAuthProof.createProofForLogin(req.getSession());
|
|
|
|
passwordAuthProof.setResource(user);
|
|
|
|
|
|
|
|
await passwordAuthProof.authorize(req.body.password);
|
|
|
|
try {
|
2021-01-24 16:29:23 +01:00
|
|
|
await this.app.as(AuthComponent).getAuthGuard().authenticateOrRegister(
|
|
|
|
req.getSession(),
|
|
|
|
passwordAuthProof,
|
|
|
|
!!req.body.persist_session,
|
|
|
|
);
|
2020-11-11 19:08:33 +01:00
|
|
|
} catch (e) {
|
|
|
|
if (e instanceof AuthError) {
|
2020-11-15 15:49:40 +01:00
|
|
|
Throttler.throttle('login_failed_attempts_user', 3, 3 * 60 * 1000, // 3min
|
|
|
|
<string>user.getOrFail('name'), 1000, 60 * 1000); // 1min
|
|
|
|
Throttler.throttle('login_failed_attempts_ip', 50, 60 * 1000, // 1min
|
|
|
|
req.ip, 1000, 3600 * 1000); // 1h
|
2020-11-11 19:08:33 +01:00
|
|
|
|
|
|
|
if (e instanceof PendingApprovalAuthError) {
|
|
|
|
req.flash('error', 'Your account is still being reviewed.');
|
2021-01-24 22:24:04 +01:00
|
|
|
res.redirect(Controller.route('auth'));
|
2020-11-11 19:08:33 +01:00
|
|
|
return;
|
|
|
|
} else {
|
|
|
|
const err = new InvalidFormatValidationError('Invalid password.');
|
|
|
|
err.thingName = 'password';
|
2020-11-15 15:50:19 +01:00
|
|
|
throw err;
|
2020-11-11 19:08:33 +01:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
throw e;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
req.flash('success', `Welcome, ${user.name}.`);
|
2021-01-24 22:24:04 +01:00
|
|
|
res.redirect(req.getIntendedUrl() || Controller.route('home'));
|
2020-11-11 19:08:33 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
public async attemptRegister(req: Request, res: Response, identifier: string): Promise<void> {
|
|
|
|
if (!ModelFactory.get(User).hasComponent(UserNameComponent))
|
|
|
|
throw new ServerError('Cannot register with password without UserNameComponent.');
|
|
|
|
|
|
|
|
Throttler.throttle('register_password', 10, 30000, req.ip);
|
|
|
|
|
2020-11-15 15:21:26 +01:00
|
|
|
req.body.identifier = identifier;
|
2020-11-11 19:08:33 +01:00
|
|
|
|
|
|
|
await Validator.validate({
|
2020-11-15 15:21:26 +01:00
|
|
|
identifier: new Validator().defined().between(3, 64).regexp(USERNAME_REGEXP).unique(User, 'name'),
|
2020-11-11 19:08:33 +01:00
|
|
|
password: new Validator().defined().minLength(UserPasswordComponent.PASSWORD_MIN_LENGTH),
|
|
|
|
password_confirmation: new Validator().defined().sameAs('password', req.body.password),
|
|
|
|
terms: new Validator().defined(),
|
|
|
|
}, req.body);
|
|
|
|
|
|
|
|
const passwordAuthProof = PasswordAuthProof.createAuthorizedProofForRegistration(req.getSession());
|
|
|
|
try {
|
|
|
|
await this.app.as(AuthComponent).getAuthGuard().authenticateOrRegister(req.getSession(), passwordAuthProof,
|
2021-01-24 16:29:23 +01:00
|
|
|
true, undefined, async (connection, user) => {
|
2020-11-11 19:08:33 +01:00
|
|
|
const callbacks: RegisterCallback[] = [];
|
|
|
|
|
|
|
|
// Password
|
|
|
|
await user.as(UserPasswordComponent).setPassword(req.body.password);
|
|
|
|
|
|
|
|
// Username
|
2020-11-15 15:21:26 +01:00
|
|
|
user.as(UserNameComponent).name = req.body.identifier;
|
2020-11-11 19:08:33 +01:00
|
|
|
|
|
|
|
return callbacks;
|
|
|
|
}, async (connection, user) => {
|
|
|
|
passwordAuthProof.setResource(user);
|
|
|
|
return [];
|
|
|
|
});
|
|
|
|
} catch (e) {
|
|
|
|
if (e instanceof PendingApprovalAuthError) {
|
|
|
|
req.flash('info', `Your account was successfully created and is pending review from an administrator.`);
|
2021-01-24 22:24:04 +01:00
|
|
|
res.redirect(Controller.route('auth'));
|
2020-11-11 19:08:33 +01:00
|
|
|
return;
|
|
|
|
} else {
|
|
|
|
throw e;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
const user = await passwordAuthProof.getResource();
|
|
|
|
|
|
|
|
req.flash('success', `Your account was successfully created! Welcome, ${user?.as(UserNameComponent).name}.`);
|
2021-01-24 22:24:04 +01:00
|
|
|
res.redirect(req.getIntendedUrl() || Controller.route('home'));
|
2020-11-11 19:08:33 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
}
|