rainbox.email/src/controllers/AccountController.ts

216 lines
9.1 KiB
TypeScript

import Controller from "wms-core/Controller";
import {RequireAuthMiddleware} from "wms-core/auth/AuthComponent";
import {Request, Response} from "express";
import {ADD_RECOVERY_EMAIL_MAIL_TEMPLATE} from "../Mails";
import Validator, {EMAIL_REGEX, InvalidFormatValidationError, ValidationBag} from "wms-core/db/Validator";
import MagicLinkController from "./MagicLinkController";
import {MagicLinkActionType} from "./MagicLinkActionType";
import UserEmail from "wms-core/auth/models/UserEmail";
import {BadRequestError, ForbiddenHttpError, NotFoundHttpError, ServerError} from "wms-core/HttpError";
import MailDomain from "../models/MailDomain";
import UserMailIdentityComponent from "../models/UserMailIdentityComponent";
import MailIdentity from "../models/MailIdentity";
import UserNameComponent from "../models/UserNameComponent";
import {WhereOperator, WhereTest} from "wms-core/db/ModelQuery";
import UserPasswordComponent from "../models/UserPasswordComponent";
export default class AccountController extends Controller {
public getRoutesPrefix(): string {
return '/account';
}
public routes(): void {
this.get('/', this.getAccount, 'account', RequireAuthMiddleware);
this.post('/change-password', this.postChangePassword, 'change-password', RequireAuthMiddleware);
this.post('/add-recovery-email', this.addRecoveryEmail, 'add-recovery-email', RequireAuthMiddleware);
this.post('/set-main-email', this.postSetMainRecoveryEmail, 'set-main-recovery-email', RequireAuthMiddleware);
this.post('/remove-email', this.postRemoveRecoveryEmail, 'remove-recovery-email', RequireAuthMiddleware);
this.post('/create-mail-identity', this.postCreateMailIdentity, 'create-mail-identity', RequireAuthMiddleware);
this.post('/delete-mail-identity', this.postDeleteMailIdentity, 'delete-mail-identity', RequireAuthMiddleware);
}
protected async getAccount(req: Request, res: Response): Promise<void> {
const user = req.as(RequireAuthMiddleware).getUser();
const userMailIdentity = user.as(UserMailIdentityComponent);
res.render('account', {
main_email: await user.mainEmail.get(),
emails: await user.emails.get(),
mailboxIdentity: await (await userMailIdentity.mainMailIdentity.get())?.toEmail(),
identities: await Promise.all((await userMailIdentity.mailIdentities.get()).map(async identity => ({
id: identity.id,
email: await identity.toEmail(),
}))),
domains: (await MailDomain.select()
.where('user_id', user.id)
.where('user_id', null, WhereTest.EQ, WhereOperator.OR)
.sortBy('user_id', 'DESC')
.get())
.map(d => ({
value: d.id,
display: d.name,
})),
});
}
protected async postChangePassword(req: Request, res: Response): Promise<void> {
await this.validate({
'current_password': new Validator().defined(),
'new_password': new Validator().defined(),
'new_password_confirmation': new Validator().sameAs('new_password', req.body.new_password),
}, req.body);
const user = req.as(RequireAuthMiddleware).getUser();
if (!await user.as(UserPasswordComponent).verifyPassword(req.body.current_password)) {
req.flash('error', 'Invalid current password.');
res.redirectBack(Controller.route('account'));
return;
}
await user.as(UserPasswordComponent).setPassword(req.body.new_password, 'new_password');
await user.save();
req.flash('success', 'Password change successfully.');
res.redirectBack(Controller.route('account'));
}
protected async addRecoveryEmail(req: Request, res: Response): Promise<void> {
await this.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 bag = new ValidationBag();
const error = new InvalidFormatValidationError('You already have this email.');
error.thingName = 'email';
bag.addMessage(error);
throw bag;
}
if (!req.sessionID) throw new ServerError('Session not initialized.');
await MagicLinkController.sendMagicLink(
this.getApp(),
req.sessionID,
MagicLinkActionType.ADD_RECOVERY_EMAIL,
Controller.route('account'),
email,
ADD_RECOVERY_EMAIL_MAIL_TEMPLATE,
{},
);
res.redirect(Controller.route('magic_link_lobby', undefined, {
redirect_uri: Controller.route('account'),
}));
}
protected async postSetMainRecoveryEmail(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.redirectBack();
}
protected async postRemoveRecoveryEmail(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.redirectBack();
}
protected async postCreateMailIdentity(req: Request, res: Response): Promise<void> {
const domain = await MailDomain.getById(req.body.mail_domain_id);
if (!domain) throw new NotFoundHttpError('domain', req.url);
const user = req.as(RequireAuthMiddleware).getUser();
const mailIdentityComponent = user.as(UserMailIdentityComponent);
const identity = MailIdentity.create({
user_id: user.id,
name: req.body.name,
mail_domain_id: req.body.mail_domain_id,
});
// Check whether this identity can be created by this user
if (domain.isPublic()) {
await this.validate({
name: new Validator<string>().defined().equals(user.as(UserNameComponent).name),
}, req.body);
const actualPublicAddressesCount = await mailIdentityComponent.getPublicAddressesCount();
const maxPublicAddressesCount = mailIdentityComponent.getMaxPublicAddressesCount();
if (actualPublicAddressesCount >= maxPublicAddressesCount) {
req.flash('error', 'You have reached maximum public email addresses.');
res.redirectBack();
return;
}
} else {
if (!domain.canCreateAddresses(user)) {
throw new ForbiddenHttpError('domain', req.url);
}
}
// Save identity
await identity.save();
// Set main mail identity if not already set
if (!mailIdentityComponent.main_mail_identity_id) {
mailIdentityComponent.main_mail_identity_id = identity.id;
await user.save();
req.flash('info', 'Congratulations! You just created your mailbox.');
}
req.flash('success', 'Mail identity ' + await identity.toEmail() + ' successfully created.');
res.redirectBack();
}
protected async postDeleteMailIdentity(req: Request, res: Response): Promise<void> {
const user = req.as(RequireAuthMiddleware).getUser();
const identity = await MailIdentity.getById(req.body.id);
if (!identity) throw new NotFoundHttpError('Mail identity', req.url);
if (identity.user_id !== user.id) throw new ForbiddenHttpError('mail identity', req.url);
if (user.as(UserMailIdentityComponent).main_mail_identity_id === identity.id) {
req.flash('error', 'Cannot delete your mailbox identity.');
res.redirectBack();
return;
}
await identity.delete();
req.flash('success', 'Identity ' + await identity.toEmail() + ' successfully deleted.');
res.redirectBack();
}
}