rainbox.email/src/models/UserPasswordComponent.ts

110 lines
3.7 KiB
TypeScript

import Validator from "wms-core/db/Validator";
import User from "wms-core/auth/models/User";
import argon2, {argon2id} from "argon2";
import AuthProof from "wms-core/auth/AuthProof";
import ModelComponent from "wms-core/db/ModelComponent";
export default class UserPasswordComponent extends ModelComponent<User> {
private password?: string = undefined;
public init(): void {
this.setValidation('password').acceptUndefined().maxLength(128);
}
public async setPassword(rawPassword: string, fieldName: string = 'password'): Promise<void> {
await new Validator<string>().defined().minLength(8).maxLength(512)
.execute(fieldName, rawPassword, true);
this.password = await argon2.hash(rawPassword, {
timeCost: 10,
memoryCost: 65536,
parallelism: 4,
type: argon2id,
hashLength: 32,
});
}
public async verifyPassword(passwordGuess: string): Promise<boolean> {
if (!this.password) return false;
return await argon2.verify(this.password, passwordGuess);
}
}
export class PasswordAuthProof implements AuthProof<User> {
public static getProofForSession(session: Express.Session): PasswordAuthProof | null {
return session.auth_password_proof ? new PasswordAuthProof(session) : null;
}
public static createAuthorizedProofForRegistration(session: Express.Session): PasswordAuthProof {
const proofForSession = new PasswordAuthProof(session);
proofForSession.authorized = true;
proofForSession.forRegistration = true;
proofForSession.save();
return proofForSession;
}
public static createProofForLogin(session: Express.Session): PasswordAuthProof {
return new PasswordAuthProof(session);
}
private readonly session: Express.Session;
private authorized: boolean;
private forRegistration: boolean = false;
private userId: number | null;
private userPassword: UserPasswordComponent | null = null;
private constructor(session: Express.Session) {
this.session = session;
this.authorized = session.auth_password_proof?.authorized || false;
this.forRegistration = session.auth_password_proof?.forRegistration || false;
this.userId = session.auth_password_proof?.userId || null;
}
public async getResource(): Promise<User | null> {
if (typeof this.userId !== 'number') return null;
return await User.getById(this.userId);
}
public setResource(user: User): void {
this.userId = user.getOrFail('id');
this.save();
}
public async isAuthorized(): Promise<boolean> {
return this.authorized;
}
public async isValid(): Promise<boolean> {
return (this.forRegistration || Boolean(await this.getResource())) &&
await this.isAuthorized();
}
public async revoke(): Promise<void> {
this.session.auth_password_proof = undefined;
}
private async getUserPassword(): Promise<UserPasswordComponent | null> {
if (!this.userPassword) {
this.userPassword = (await User.getById(this.userId))?.as(UserPasswordComponent) || null;
}
return this.userPassword;
}
public async authorize(passwordGuess: string): Promise<boolean> {
const password = await this.getUserPassword();
if (!password || !await password.verifyPassword(passwordGuess)) return false;
this.authorized = true;
this.save();
return true;
}
private save() {
this.session.auth_password_proof = {
authorized: this.authorized,
forRegistration: this.forRegistration,
userId: this.userId,
};
}
}