213 lines
8.7 KiB
TypeScript
213 lines
8.7 KiB
TypeScript
import Controller from "../Controller";
|
|
import {RequireAuthMiddleware} from "./AuthComponent";
|
|
import {Request, Response} from "express";
|
|
import {BadRequestError, ForbiddenHttpError, NotFoundHttpError} from "../HttpError";
|
|
import config from "config";
|
|
import Validator, {EMAIL_REGEX, InvalidFormatValidationError} from "../db/Validator";
|
|
import UserPasswordComponent from "./password/UserPasswordComponent";
|
|
import User from "./models/User";
|
|
import ModelFactory from "../db/ModelFactory";
|
|
import UserEmail from "./models/UserEmail";
|
|
import MagicLinkController from "./magic_link/MagicLinkController";
|
|
import {MailTemplate} from "../mail/Mail";
|
|
import {ADD_EMAIL_MAIL_TEMPLATE, REMOVE_PASSWORD_MAIL_TEMPLATE} from "../Mails";
|
|
import AuthMagicLinkActionType from "./magic_link/AuthMagicLinkActionType";
|
|
import UserNameComponent from "./models/UserNameComponent";
|
|
import Time from "../Time";
|
|
|
|
export default class AccountController extends Controller {
|
|
|
|
public constructor(
|
|
private readonly addEmailMailTemplate: MailTemplate = ADD_EMAIL_MAIL_TEMPLATE,
|
|
private readonly removePasswordMailTemplate: MailTemplate = REMOVE_PASSWORD_MAIL_TEMPLATE,
|
|
) {
|
|
super();
|
|
}
|
|
|
|
public getRoutesPrefix(): string {
|
|
return '/account';
|
|
}
|
|
|
|
public routes(): void {
|
|
this.get('/', this.getAccount, 'account', RequireAuthMiddleware);
|
|
|
|
if (ModelFactory.get(User).hasComponent(UserNameComponent)) {
|
|
this.post('/change-name', this.postChangeName, 'change-name', RequireAuthMiddleware);
|
|
}
|
|
|
|
if (ModelFactory.get(User).hasComponent(UserPasswordComponent)) {
|
|
this.post('/change-password', this.postChangePassword, 'change-password', RequireAuthMiddleware);
|
|
this.post('/remove-password', this.postRemovePassword, 'remove-password', RequireAuthMiddleware);
|
|
}
|
|
|
|
this.post('/add-email', this.addEmail, 'add-email', RequireAuthMiddleware);
|
|
this.post('/set-main-email', this.postSetMainEmail, 'set-main-email', RequireAuthMiddleware);
|
|
this.post('/remove-email', this.postRemoveEmail, 'remove-email', RequireAuthMiddleware);
|
|
}
|
|
|
|
protected async getAccount(req: Request, res: Response): Promise<void> {
|
|
const user = req.as(RequireAuthMiddleware).getUser();
|
|
|
|
const passwordComponent = user.asOptional(UserPasswordComponent);
|
|
|
|
const nameComponent = user.asOptional(UserNameComponent);
|
|
const nameChangeWaitPeriod = config.get<number>('auth.name_change_wait_period');
|
|
const nameChangedAt = nameComponent?.getNameChangedAt()?.getTime() || Date.now();
|
|
const nameChangeRemainingTime = new Date(nameChangedAt + nameChangeWaitPeriod);
|
|
|
|
res.render('auth/account/account', {
|
|
main_email: await user.mainEmail.get(),
|
|
emails: await user.emails.get(),
|
|
display_email_warning: config.get('app.display_email_warning'),
|
|
has_password_component: !!passwordComponent,
|
|
has_password: passwordComponent?.hasPassword(),
|
|
has_name_component: !!nameComponent,
|
|
name_change_wait_period: Time.humanizeDuration(nameChangeWaitPeriod, false, true),
|
|
can_change_name: nameComponent?.canChangeName(),
|
|
can_change_name_in: Time.humanizeTimeTo(nameChangeRemainingTime),
|
|
});
|
|
}
|
|
|
|
protected async postChangeName(req: Request, res: Response): Promise<void> {
|
|
await Validator.validate({
|
|
'name': new Validator().defined(),
|
|
}, req.body);
|
|
|
|
const user = req.as(RequireAuthMiddleware).getUser();
|
|
const userNameComponent = user.as(UserNameComponent);
|
|
|
|
if (!userNameComponent.setName(req.body.name)) {
|
|
const nameChangedAt = userNameComponent.getNameChangedAt()?.getTime() || Date.now();
|
|
const nameChangeWaitPeriod = config.get<number>('auth.name_change_wait_period');
|
|
req.flash('error', `Your can't change your name until ${new Date(nameChangedAt + nameChangeWaitPeriod)}.`);
|
|
res.redirect(Controller.route('account'));
|
|
return;
|
|
}
|
|
|
|
await user.save();
|
|
|
|
req.flash('success', `Your name was successfully changed to ${req.body.name}.`);
|
|
res.redirect(Controller.route('account'));
|
|
}
|
|
|
|
protected async postChangePassword(req: Request, res: Response): Promise<void> {
|
|
const validationMap = {
|
|
'new_password': new Validator().defined(),
|
|
'new_password_confirmation': new Validator().sameAs('new_password', req.body.new_password),
|
|
};
|
|
await Validator.validate(validationMap, req.body);
|
|
const user = req.as(RequireAuthMiddleware).getUser();
|
|
const passwordComponent = user.as(UserPasswordComponent);
|
|
if (passwordComponent.hasPassword() && !await passwordComponent.verifyPassword(req.body.current_password)) {
|
|
req.flash('error', 'Invalid current password.');
|
|
res.redirect(Controller.route('account'));
|
|
return;
|
|
}
|
|
|
|
await passwordComponent.setPassword(req.body.new_password, 'new_password');
|
|
await user.save();
|
|
|
|
req.flash('success', 'Password changed successfully.');
|
|
res.redirect(Controller.route('account'));
|
|
}
|
|
|
|
protected async postRemovePassword(req: Request, res: Response): Promise<void> {
|
|
const user = req.as(RequireAuthMiddleware).getUser();
|
|
const mainEmail = await user.mainEmail.get();
|
|
if (!mainEmail || !mainEmail.email) {
|
|
req.flash('error', 'You can\'t remove your password without adding an email address first.');
|
|
res.redirect(Controller.route('account'));
|
|
|
|
return;
|
|
}
|
|
|
|
await MagicLinkController.sendMagicLink(
|
|
this.getApp(),
|
|
req.getSession().id,
|
|
AuthMagicLinkActionType.REMOVE_PASSWORD,
|
|
Controller.route('account'),
|
|
mainEmail.email,
|
|
this.removePasswordMailTemplate,
|
|
{},
|
|
);
|
|
|
|
res.redirect(Controller.route('magic_link_lobby', undefined, {
|
|
redirect_uri: Controller.route('account'),
|
|
}));
|
|
}
|
|
|
|
|
|
protected async addEmail(req: Request, res: Response): Promise<void> {
|
|
await Validator.validate({
|
|
email: new Validator().defined().regexp(EMAIL_REGEX),
|
|
}, req.body);
|
|
|
|
const email = req.body.email;
|
|
|
|
// Existing email
|
|
if (await UserEmail.select().where('email', email).first()) {
|
|
const error = new InvalidFormatValidationError('You already have this email.');
|
|
error.thingName = 'email';
|
|
throw error;
|
|
}
|
|
|
|
await MagicLinkController.sendMagicLink(
|
|
this.getApp(),
|
|
req.getSession().id,
|
|
AuthMagicLinkActionType.ADD_EMAIL,
|
|
Controller.route('account'),
|
|
email,
|
|
this.addEmailMailTemplate,
|
|
{
|
|
email: email,
|
|
},
|
|
);
|
|
|
|
res.redirect(Controller.route('magic_link_lobby', undefined, {
|
|
redirect_uri: Controller.route('account'),
|
|
}));
|
|
}
|
|
|
|
protected async postSetMainEmail(req: Request, res: Response): Promise<void> {
|
|
if (!req.body.id)
|
|
throw new BadRequestError('Missing id field', 'Check form parameters.', req.url);
|
|
|
|
const user = req.as(RequireAuthMiddleware).getUser();
|
|
|
|
const userEmail = await UserEmail.getById(req.body.id);
|
|
if (!userEmail)
|
|
throw new NotFoundHttpError('email', req.url);
|
|
if (userEmail.user_id !== user.id)
|
|
throw new ForbiddenHttpError('email', req.url);
|
|
if (userEmail.id === user.main_email_id)
|
|
throw new BadRequestError('This address is already your main address',
|
|
'Try refreshing the account page.', req.url);
|
|
|
|
user.main_email_id = userEmail.id;
|
|
await user.save();
|
|
|
|
req.flash('success', 'This email was successfully set as your main address.');
|
|
res.redirect(Controller.route('account'));
|
|
}
|
|
|
|
protected async postRemoveEmail(req: Request, res: Response): Promise<void> {
|
|
if (!req.body.id)
|
|
throw new BadRequestError('Missing id field', 'Check form parameters.', req.url);
|
|
|
|
const user = req.as(RequireAuthMiddleware).getUser();
|
|
|
|
const userEmail = await UserEmail.getById(req.body.id);
|
|
if (!userEmail)
|
|
throw new NotFoundHttpError('email', req.url);
|
|
if (userEmail.user_id !== user.id)
|
|
throw new ForbiddenHttpError('email', req.url);
|
|
if (userEmail.id === user.main_email_id)
|
|
throw new BadRequestError('Cannot remove main email address', 'Try refreshing the account page.', req.url);
|
|
|
|
await userEmail.delete();
|
|
|
|
req.flash('success', 'This email was successfully removed from your account.');
|
|
res.redirect(Controller.route('account'));
|
|
}
|
|
}
|