Fix lint issues

This commit is contained in:
Alice Gaudon 2020-11-02 19:27:18 +01:00
parent f53dc7c63c
commit 3ed93dbefb
19 changed files with 159 additions and 144 deletions

View File

@ -1,11 +1,11 @@
{
"bundles": {
"app": "ts/app.ts",
"register": "js/register.js",
"register": "ts/register.ts",
"layout": "sass/layout.scss",
"error": "sass/error.scss",
"logo": "img/logo.svg",
"logo_png": "img/logox128.png",
"logo_png_xxl": "img/logox1024.png"
}
}
}

View File

@ -1,27 +0,0 @@
document.addEventListener('DOMContentLoaded', () => {
const createEmailAddress = document.getElementById('field-create_email');
const username = document.getElementById('field-username');
const email_username = document.getElementById('email_username');
const domain = document.getElementById('field-domain');
const recovery_email = document.getElementById('field-recovery_email');
const recovery_email_label = recovery_email.parentElement.querySelector('.hint');
function updateForm() {
if (createEmailAddress.checked) {
recovery_email.removeAttribute('required');
recovery_email_label.style.display = 'block';
domain.disabled = false;
} else {
recovery_email.setAttribute('required', 'required');
recovery_email_label.style.display = 'none';
domain.disabled = true;
}
username.value = username.value.toLowerCase();
email_username.innerText = username.value + '@';
}
createEmailAddress.addEventListener('change', updateForm);
username.addEventListener('change', updateForm);
username.addEventListener('keyup', updateForm);
updateForm();
});

41
assets/ts/register.ts Normal file
View File

@ -0,0 +1,41 @@
document.addEventListener('DOMContentLoaded', () => {
const createEmailAddress = document.querySelector<HTMLInputElement>('#field-create_email');
const username = document.querySelector<HTMLInputElement>('#field-username');
const email_username = document.querySelector<HTMLElement>('#email_username');
const domain = document.querySelector<HTMLInputElement>('#field-domain');
const recovery_email = document.querySelector('#field-recovery_email');
const recovery_email_label = recovery_email?.parentElement?.querySelector<HTMLElement>('.hint');
if (
!createEmailAddress ||
!recovery_email ||
!recovery_email_label ||
!domain ||
!username ||
!email_username
) {
console.warn('Some elements wern\'t found.');
return;
}
const updateForm = () => {
if (createEmailAddress.checked) {
recovery_email.removeAttribute('required');
recovery_email_label.style.display = 'block';
domain.disabled = false;
} else {
recovery_email.setAttribute('required', 'required');
recovery_email_label.style.display = 'none';
domain.disabled = true;
}
username.value = username.value.toLowerCase();
email_username.innerText = username.value + '@';
};
createEmailAddress.addEventListener('change', updateForm);
username.addEventListener('change', updateForm);
username.addEventListener('keyup', updateForm);
updateForm();
});

View File

@ -114,7 +114,7 @@ export default class App extends Application {
this.use(new AuthComponent(new class extends AuthGuard<PasswordAuthProof | MagicLink> {
public async getProofForSession(session: Express.Session): Promise<PasswordAuthProof | MagicLink | null> {
return PasswordAuthProof.getProofForSession(session) ||
MagicLink.bySessionId(session.id);
await MagicLink.bySessionId(session.id);
}
}));
@ -133,7 +133,7 @@ export default class App extends Application {
private registerControllers() {
// Priority routes / interrupting middlewares
this.use(new AccountController());
this.use(new MagicLinkController(this.magicLinkWebSocketListener!));
this.use(new MagicLinkController(this.as(MagicLinkWebSocketListener)));
this.use(new BackendController());
this.use(new MailboxBackendController());
this.use(new AuthController());

View File

@ -1,5 +1,4 @@
import ApplicationComponent from "wms-core/ApplicationComponent";
import {Express} from "express";
import ldap, {InvalidCredentialsError, Server} from "ldapjs";
import {log} from "wms-core/Logger";
import UserPasswordComponent from "./models/UserPasswordComponent";
@ -9,13 +8,13 @@ import User from "wms-core/auth/models/User";
export default class LDAPServerComponent extends ApplicationComponent {
private server?: Server;
public async start(app: Express): Promise<void> {
public async start(): Promise<void> {
this.server = ldap.createServer({
log: console
log: console,
});
this.server.bind('ou=users,dc=toot,dc=party', async (req: any, res: any, next: any) => {
this.server.bind('ou=users,dc=toot,dc=party', async (req: Record<string, string>, res: Record<string, () => void>, next: (err: unknown) => void) => {
const rdns = req.dn.toString().split(', ').map((rdn: string) => rdn.split('='));
let username;
let username: string = '';
let email;
for (const rdn of rdns) {
if (rdn[0] === 'cn') {
@ -50,12 +49,12 @@ export default class LDAPServerComponent extends ApplicationComponent {
log.debug('Fail');
next(new InvalidCredentialsError());
});
this.server.unbind((req: any, res: any, next: any) => {
this.server.unbind((req: unknown, res: unknown, next: () => void) => {
log.debug('Unbind', req);
next();
});
this.server.listen(8389, '127.0.0.1', () => {
log.info(`LDAP server listening on ${this.server!.url}`);
log.info(`LDAP server listening on ${this.server?.url}`);
});
this.server.on('close', () => {
log.info('LDAP server closed.');
@ -66,7 +65,6 @@ export default class LDAPServerComponent extends ApplicationComponent {
public async stop(): Promise<void> {
await new Promise(resolve => {
if (this.server) {
// @ts-ignore
this.server.close(() => {
resolve();
});

View File

@ -3,4 +3,4 @@ import {MailTemplate} from "wms-core/Mail";
export const ADD_RECOVERY_EMAIL_MAIL_TEMPLATE: MailTemplate = new MailTemplate(
'add_recovery_email',
(data) => 'Add ' + data.email + ' as you recovery email.',
);
);

View File

@ -1,12 +1,12 @@
import Controller from "wms-core/Controller";
import {RequireAuthMiddleware} from "wms-core/auth/AuthComponent";
import {NextFunction, Request, Response} from "express";
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} from "wms-core/HttpError";
import {BadRequestError, ForbiddenHttpError, NotFoundHttpError, ServerError} from "wms-core/HttpError";
import MailDomain from "../models/MailDomain";
import UserMailIdentityComponent from "../models/UserMailIdentityComponent";
import MailIdentity from "../models/MailIdentity";
@ -18,7 +18,7 @@ export default class AccountController extends Controller {
return '/account';
}
routes(): void {
public routes(): void {
this.get('/', this.getAccount, 'account', RequireAuthMiddleware);
this.post('/add-recovery-email', this.addRecoveryEmail, 'add-recovery-email', RequireAuthMiddleware);
this.post('/set-main-email', this.postSetMainRecoveryEmail, 'set-main-recovery-email', RequireAuthMiddleware);
@ -28,7 +28,7 @@ export default class AccountController extends Controller {
this.post('/delete-mail-identity', this.postDeleteMailIdentity, 'delete-mail-identity', RequireAuthMiddleware);
}
protected async getAccount(req: Request, res: Response, next: NextFunction): Promise<void> {
protected async getAccount(req: Request, res: Response): Promise<void> {
const user = req.as(RequireAuthMiddleware).getUser();
const userMailIdentity = user.as(UserMailIdentityComponent);
@ -40,7 +40,7 @@ export default class AccountController extends Controller {
email: await identity.toEmail(),
}))),
domains: (await MailDomain.select()
.where('user_id', user.id!)
.where('user_id', user.id)
.where('user_id', null, WhereTest.EQ, WhereOperator.OR)
.sortBy('user_id', 'DESC')
.get())
@ -51,7 +51,7 @@ export default class AccountController extends Controller {
});
}
protected async addRecoveryEmail(req: Request, res: Response, next: NextFunction): Promise<void> {
protected async addRecoveryEmail(req: Request, res: Response): Promise<void> {
await this.validate({
email: new Validator().defined().regexp(EMAIL_REGEX),
}, req.body);
@ -67,13 +67,15 @@ export default class AccountController extends Controller {
throw bag;
}
if (!req.sessionID) throw new ServerError('Session not initialized.');
await MagicLinkController.sendMagicLink(
req.sessionID!,
req.sessionID,
MagicLinkActionType.ADD_RECOVERY_EMAIL,
Controller.route('account'),
email,
ADD_RECOVERY_EMAIL_MAIL_TEMPLATE,
{}
{},
);
res.redirect(Controller.route('magic_link_lobby', undefined, {
@ -140,7 +142,9 @@ export default class AccountController extends Controller {
await this.validate({
name: new Validator<string>().defined().equals(user.as(UserNameComponent).name),
}, req.body);
if ((await mailIdentityComponent.getPublicAddressesCount()) >= mailIdentityComponent.getMaxPublicAddressesCount()) {
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;
@ -161,7 +165,7 @@ export default class AccountController extends Controller {
req.flash('info', 'Congratulations! You just created your mailbox.');
}
req.flash('success', 'Mail identity ' + (await identity.toEmail()) + ' successfully created.')
req.flash('success', 'Mail identity ' + await identity.toEmail() + ' successfully created.');
res.redirectBack();
}
@ -178,7 +182,7 @@ export default class AccountController extends Controller {
}
await identity.delete();
req.flash('success', 'Identity ' + (await identity.toEmail()) + ' successfully deleted.');
req.flash('success', 'Identity ' + await identity.toEmail() + ' successfully deleted.');
res.redirectBack();
}
}

View File

@ -1,6 +1,6 @@
import Controller from "wms-core/Controller";
import AuthComponent, {RequireAuthMiddleware, RequireGuestMiddleware} from "wms-core/auth/AuthComponent";
import {NextFunction, Request, Response} from "express";
import {Request, Response} from "express";
import Validator, {InvalidFormatValidationError, ValidationBag} from "wms-core/db/Validator";
import UserPasswordComponent, {PasswordAuthProof} from "../models/UserPasswordComponent";
import UserNameComponent, {USERNAME_REGEXP} from "../models/UserNameComponent";
@ -11,7 +11,7 @@ import User from "wms-core/auth/models/User";
import Throttler from "wms-core/Throttler";
export default class AuthController extends _AuthController {
routes(): void {
public routes(): void {
this.get('/login', this.getLogin, 'auth', RequireGuestMiddleware);
this.post('/login', this.postLogin, 'auth', RequireGuestMiddleware);
this.get('/register', this.getRegister, 'register', RequireGuestMiddleware);
@ -35,15 +35,17 @@ export default class AuthController extends _AuthController {
if (!user) throw new NotFoundHttpError(`Couldn't find a user with name ${req.body.username}`, req.url);
const passwordAuthProof = PasswordAuthProof.createProofForLogin(req.session!);
if (!req.session) throw new ServerError('Session not initialized.');
const passwordAuthProof = PasswordAuthProof.createProofForLogin(req.session);
passwordAuthProof.setResource(user);
await passwordAuthProof.authorize(req.body.password);
try {
await this.getApp().as(AuthComponent).getAuthGuard().authenticateOrRegister(req.session!, passwordAuthProof);
await this.getApp().as(AuthComponent).getAuthGuard().authenticateOrRegister(req.session, passwordAuthProof);
} catch (e) {
if (e instanceof AuthError) {
Throttler.throttle('login_failed_attempts_user', 3, 180000, user.as(UserNameComponent).name!, 1000, 60000);
Throttler.throttle('login_failed_attempts_user', 3, 180000, <string>user.getOrFail('name'), 1000, 60000);
Throttler.throttle('login_failed_attempts_ip', 5, 60000, req.ip, 1000, 60000);
if (e instanceof PendingApprovalAuthError) {
@ -54,7 +56,7 @@ export default class AuthController extends _AuthController {
const bag = new ValidationBag();
const err = new InvalidFormatValidationError('Invalid password.');
err.thingName = 'password';
bag.addMessage(err)
bag.addMessage(err);
throw bag;
}
} else {
@ -80,21 +82,24 @@ export default class AuthController extends _AuthController {
terms: new Validator().defined(),
}, req.body);
const passwordAuthProof = PasswordAuthProof.createAuthorizedProofForRegistration(req.session!);
if(!req.session) throw new ServerError('Session not initialized.');
const passwordAuthProof = PasswordAuthProof.createAuthorizedProofForRegistration(req.session);
try {
await this.getApp().as(AuthComponent).getAuthGuard().authenticateOrRegister(req.session!, passwordAuthProof, undefined, async (connection, user) => {
passwordAuthProof.setResource(user);
await this.getApp().as(AuthComponent).getAuthGuard().authenticateOrRegister(req.session, passwordAuthProof,
undefined, async (connection, user) => {
passwordAuthProof.setResource(user);
const callbacks: RegisterCallback[] = [];
const callbacks: RegisterCallback[] = [];
// Password
await user.as(UserPasswordComponent).setPassword(req.body.password);
// Password
await user.as(UserPasswordComponent).setPassword(req.body.password);
// Username
user.as(UserNameComponent).name = req.body.username;
// Username
user.as(UserNameComponent).name = req.body.username;
return callbacks;
});
return callbacks;
});
} catch (e) {
if (e instanceof PendingApprovalAuthError) {
req.flash('info', `Your account was successfully created and is pending review from an administrator.`);
@ -105,17 +110,17 @@ export default class AuthController extends _AuthController {
}
}
const user = (await passwordAuthProof.getResource())!;
const user = await passwordAuthProof.getResource();
req.flash('success', `Your account was successfully created! Welcome, ${user.as(UserNameComponent).name}.`);
req.flash('success', `Your account was successfully created! Welcome, ${user?.as(UserNameComponent).name}.`);
res.redirect(Controller.route('home'));
}
protected async getCheckAuth(req: Request, res: Response, next: NextFunction): Promise<void> {
protected async getCheckAuth(): Promise<void> {
throw new ServerError('Not implemented.');
}
protected async postAuth(req: Request, res: Response, next: NextFunction): Promise<void> {
protected async postAuth(): Promise<void> {
throw new ServerError('Not implemented.');
}
}

View File

@ -1,3 +1,3 @@
export enum MagicLinkActionType {
ADD_RECOVERY_EMAIL = 'Add a recovery email',
}
}

View File

@ -10,45 +10,42 @@ import App from "../App";
import AuthComponent from "wms-core/auth/AuthComponent";
export default class MagicLinkController extends _MagicLinkController<App> {
constructor(magicLinkWebSocketListener: MagicLinkWebSocketListener<App>) {
public constructor(magicLinkWebSocketListener: MagicLinkWebSocketListener<App>) {
super(magicLinkWebSocketListener);
}
protected async performAction(magicLink: MagicLink, req: Request, res: Response): Promise<void> {
switch (magicLink.action_type) {
case MagicLinkActionType.ADD_RECOVERY_EMAIL:
if (magicLink.session_id !== req.sessionID!) throw new BadOwnerMagicLink();
await magicLink.delete();
if (magicLink.action_type === MagicLinkActionType.ADD_RECOVERY_EMAIL) {
if (!req.session || !req.sessionID || magicLink.session_id !== req.sessionID) throw new BadOwnerMagicLink();
const authGuard = this.getApp().as(AuthComponent).getAuthGuard();
const proof = await authGuard.isAuthenticated(req.session!);
const user = await proof?.getResource();
if (!user) break;
await magicLink.delete();
const email = await magicLink.getOrFail('email');
const authGuard = this.getApp().as(AuthComponent).getAuthGuard();
const proof = await authGuard.isAuthenticated(req.session);
const user = await proof?.getResource();
if (!user) return;
// Existing email
if (await UserEmail.select().with('user').where('email', email).first()) {
req.flash('error', 'An account already exists with this email address. Please first remove it there before adding it here.');
res.redirect(Controller.route('account'));
break;
}
const userEmail = UserEmail.create({
user_id: user.id,
email: email,
main: false,
});
await userEmail.save();
if (!user.main_email_id) {
user.main_email_id = userEmail.id;
await user.save();
}
req.flash('success', `Recovery email ${userEmail.email} successfully added.`);
const email = await magicLink.getOrFail('email');
if (await UserEmail.select().with('user').where('email', email).first()) {
req.flash('error', 'An account already exists with this email address. Please first remove it there before adding it here.');
res.redirect(Controller.route('account'));
break;
return;
}
const userEmail = UserEmail.create({
user_id: user.id,
email: email,
main: false,
});
await userEmail.save();
if (!user.main_email_id) {
user.main_email_id = userEmail.id;
await user.save();
}
req.flash('success', `Recovery email ${userEmail.email} successfully added.`);
res.redirect(Controller.route('account'));
}
}
}

View File

@ -1,6 +1,5 @@
import Controller from "wms-core/Controller";
import {Request, Response} from "express";
import config from "config";
import MailDomain from "../models/MailDomain";
export default class MailAutoConfigController extends Controller {

View File

@ -80,16 +80,19 @@ export default class MailboxBackendController extends Controller {
.first();
if (!user) throw new NotFoundHttpError('User', req.url);
const mainMailIdentity = await user.as(UserMailIdentityComponent).mainMailIdentity.get();
const mailDomains = await MailDomain.select().get();
const mailIdentities = await user.as(UserMailIdentityComponent).mailIdentities.get();
res.render('backend/mailbox', {
mailbox: {
id: user.id,
name: await (await user.as(UserMailIdentityComponent).mainMailIdentity.get())?.toEmail() || 'Not created.',
name: await mainMailIdentity?.toEmail() || 'Not created.',
},
domains: (await MailDomain.select().get()).map(d => ({
domains: mailDomains.map(d => ({
display: d.name,
value: d.id,
})),
identities: await Promise.all((await user.as(UserMailIdentityComponent).mailIdentities.get()).map(async i => ({
identities: await Promise.all(mailIdentities.map(async i => ({
id: i.id,
email: await i.toEmail(),
}))),
@ -182,7 +185,7 @@ export default class MailboxBackendController extends Controller {
req.flash('info', 'Mailbox created.');
}
req.flash('success', 'Mail identity ' + (await identity.toEmail()) + ' successfully created.')
req.flash('success', 'Mail identity ' + await identity.toEmail() + ' successfully created.');
res.redirectBack();
}
@ -201,7 +204,7 @@ export default class MailboxBackendController extends Controller {
}
await identity.delete();
req.flash('success', 'Identity ' + (await identity.toEmail()) + ' successfully deleted.');
req.flash('success', 'Identity ' + await identity.toEmail() + ' successfully deleted.');
res.redirectBack();
}
}

View File

@ -18,4 +18,4 @@ export default class AddPasswordToUsers extends Migration {
public registerModels(): void {
ModelFactory.get(User).addComponent(UserPasswordComponent);
}
}
}

View File

@ -46,4 +46,4 @@ export default class CreateMailTables extends Migration {
ModelFactory.get(User).addComponent(UserMailIdentityComponent);
}
}
}

View File

@ -1,7 +1,6 @@
import Model from "wms-core/db/Model";
import User from "wms-core/auth/models/User";
import {ManyModelRelation, OneModelRelation} from "wms-core/db/ModelRelation";
import ModelFactory from "wms-core/db/ModelFactory";
import MailIdentity from "./MailIdentity";
export default class MailDomain extends Model {
@ -14,13 +13,14 @@ export default class MailDomain extends Model {
foreignKey: 'id',
});
public readonly identities: ManyModelRelation<MailDomain, MailIdentity> = new ManyModelRelation(this, MailIdentity, {
localKey: 'id',
foreignKey: 'mail_domain_id',
});
public readonly identities: ManyModelRelation<MailDomain, MailIdentity> = new ManyModelRelation(this, MailIdentity,
{
localKey: 'id',
foreignKey: 'mail_domain_id',
});
public updateWithData(data: any) {
public updateWithData(data: Pick<this, keyof this> | Record<string, unknown>): void {
super.updateWithData(data);
if (typeof this.user_id !== 'undefined' && this.user_id <= 0) {
this.user_id = undefined;

View File

@ -33,6 +33,6 @@ export default class MailIdentity extends Model {
}
public async toEmail(): Promise<string> {
return this.name + '@' + (await this.domain.get())!.name;
return this.name + '@' + (await this.domain.getOrFail())?.name;
}
}

View File

@ -7,25 +7,24 @@ import MailDomain from "./MailDomain";
export default class UserMailIdentityComponent extends ModelComponent<User> {
public main_mail_identity_id?: number = undefined;
public readonly mailIdentities: ManyModelRelation<User, MailIdentity> = new ManyModelRelation(this._model, MailIdentity, {
localKey: 'id',
foreignKey: 'user_id',
});
public readonly mailIdentities: ManyModelRelation<User, MailIdentity> = new ManyModelRelation(this._model,
MailIdentity, {
localKey: 'id',
foreignKey: 'user_id',
});
public readonly publicMailIdentities: ManyModelRelation<User, MailIdentity> = this.mailIdentities.clone()
.filter(async model => (await model.domain.get())!.isPublic());
.filter(async model => !!(await model.domain.getOrFail())?.isPublic());
public readonly mailDomains: ManyModelRelation<User, MailDomain> = new ManyModelRelation(this._model, MailDomain, {
localKey: 'id',
foreignKey: 'user_id',
});
public readonly mainMailIdentity: OneModelRelation<User, MailIdentity> = new OneModelRelation(this._model, MailIdentity, {
foreignKey: 'id',
localKey: 'main_mail_identity_id',
});
protected init(): void {
}
public readonly mainMailIdentity: OneModelRelation<User, MailIdentity> = new OneModelRelation(this._model,
MailIdentity, {
foreignKey: 'id',
localKey: 'main_mail_identity_id',
});
public getMaxPublicAddressesCount(): number {
return 1;

View File

@ -10,4 +10,4 @@ export default class UserNameComponent extends ModelComponent<User> {
this.setValidation('name').defined().between(3, 64).regexp(USERNAME_REGEXP).unique(this._model);
}
}
}

View File

@ -7,10 +7,6 @@ import ModelComponent from "wms-core/db/ModelComponent";
export default class UserPasswordComponent extends ModelComponent<User> {
private password?: string = undefined;
public constructor(props: any) {
super(props);
}
public init(): void {
this.setValidation('password').acceptUndefined().maxLength(128);
}
@ -51,22 +47,22 @@ export class PasswordAuthProof implements AuthProof<User> {
}
private readonly session: Express.Session;
private userID: number | null;
private userId: number | null;
private authorized: boolean;
private userPassword: UserPasswordComponent | null = null;
private constructor(session: Express.Session) {
this.session = session;
this.authorized = session.auth_password_proof?.authorized || false;
this.userID = session.auth_password_proof?.userID || null;
this.userId = session.auth_password_proof?.userId || null;
}
public async getResource(): Promise<User | null> {
return await User.getById(this.userID);
return await User.getById(this.userId);
}
public setResource(user: User) {
this.userID = user.id!;
public setResource(user: User): void {
this.userId = user.getOrFail('id');
this.save();
}
@ -75,7 +71,7 @@ export class PasswordAuthProof implements AuthProof<User> {
}
public async isValid(): Promise<boolean> {
if (typeof this.userID === 'number') {
if (typeof this.userId === 'number') {
return Boolean(await this.getResource());
} else {
return await this.isAuthorized();
@ -88,7 +84,7 @@ export class PasswordAuthProof implements AuthProof<User> {
private async getUserPassword(): Promise<UserPasswordComponent | null> {
if (!this.userPassword) {
this.userPassword = (await User.getById(this.userID))?.as(UserPasswordComponent) || null;
this.userPassword = (await User.getById(this.userId))?.as(UserPasswordComponent) || null;
}
return this.userPassword;
}
@ -105,7 +101,7 @@ export class PasswordAuthProof implements AuthProof<User> {
private save() {
this.session.auth_password_proof = {
authorized: this.authorized,
userID: this.userID,
userID: this.userId,
};
}
}
}