118 lines
3.9 KiB
TypeScript
118 lines
3.9 KiB
TypeScript
import Model from "wms-core/db/Model";
|
|
import Validator from "wms-core/db/Validator";
|
|
import User from "wms-core/auth/models/User";
|
|
import argon2 from "argon2";
|
|
import AuthProof from "wms-core/auth/AuthProof";
|
|
import {UserAlreadyExistsAuthError} from "wms-core/auth/AuthGuard";
|
|
|
|
export default class UserPassword extends Model {
|
|
public static async getByEmail(email: string): Promise<UserPassword | null> {
|
|
const user = await User.fromEmail(email);
|
|
if (!user) return null;
|
|
|
|
const result = await this.models<UserPassword>(this.select().where('user_id', user.id).first());
|
|
return result && result.length > 0 ? result[0] : null;
|
|
}
|
|
|
|
private user_id?: number;
|
|
private password?: string;
|
|
|
|
protected defineProperties(): void {
|
|
this.defineProperty<number>('user_id', new Validator().defined().unique(this).exists(User, 'id'));
|
|
this.defineProperty<string>('password', new Validator().defined());
|
|
}
|
|
|
|
public async setUser(userID: number): Promise<void> {
|
|
if (this.user_id) throw new Error(`Cannot change this password's user.`);
|
|
this.user_id = userID;
|
|
}
|
|
|
|
public async setPassword(rawPassword: string): Promise<void> {
|
|
this.password = await argon2.hash(rawPassword, {
|
|
timeCost: 10,
|
|
memoryCost: 4096,
|
|
parallelism: 4,
|
|
});
|
|
}
|
|
|
|
public async verifyPassword(passwordGuess: string): Promise<boolean> {
|
|
if (!this.password) return false;
|
|
|
|
return await argon2.verify(this.password, passwordGuess);
|
|
}
|
|
|
|
public isOwnedBy(userId: number): boolean {
|
|
return this.user_id === userId;
|
|
}
|
|
}
|
|
|
|
export class PasswordAuthProof implements AuthProof {
|
|
public static getProofForSession(session: Express.Session): PasswordAuthProof | null {
|
|
const authPasswordProof = session.auth_password_proof;
|
|
if (!authPasswordProof) return null;
|
|
return new PasswordAuthProof(authPasswordProof.email, authPasswordProof.authorized);
|
|
}
|
|
|
|
private readonly email?: string;
|
|
private authorized: boolean;
|
|
private userPassword?: UserPassword;
|
|
|
|
public constructor(email: string, authorized: boolean = false) {
|
|
this.email = email;
|
|
this.authorized = authorized;
|
|
}
|
|
|
|
public async getEmail(): Promise<string> {
|
|
return this.email!;
|
|
}
|
|
|
|
public async getUser(): Promise<User | null> {
|
|
return await User.fromEmail(await this.getEmail());
|
|
}
|
|
|
|
public async isAuthorized(): Promise<boolean> {
|
|
return this.authorized;
|
|
}
|
|
|
|
public async isOwnedBy(userId: number): Promise<boolean> {
|
|
const password = await this.getUserPassword();
|
|
return password !== null && password.isOwnedBy(userId);
|
|
}
|
|
|
|
public async isValid(): Promise<boolean> {
|
|
return await this.getUserPassword() !== null;
|
|
}
|
|
|
|
public async revoke(session: Express.Session): Promise<void> {
|
|
session.auth_password_proof = undefined;
|
|
}
|
|
|
|
private async getUserPassword(): Promise<UserPassword | null> {
|
|
return this.userPassword ? this.userPassword : await UserPassword.getByEmail(await this.getEmail());
|
|
}
|
|
|
|
public async authorize(passwordGuess: string, session?: Express.Session): Promise<boolean> {
|
|
const password = await this.getUserPassword();
|
|
if (!password || !await password.verifyPassword(passwordGuess)) return false;
|
|
|
|
this.authorized = true;
|
|
if (session) {
|
|
this.save(session);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
public async register(password: string): Promise<UserPassword> {
|
|
if (await this.getUserPassword()) throw new UserAlreadyExistsAuthError(await this.getEmail());
|
|
this.userPassword = new UserPassword({});
|
|
await this.userPassword.setPassword(password);
|
|
return this.userPassword;
|
|
}
|
|
|
|
private save(session: Express.Session) {
|
|
session.auth_password_proof = {
|
|
email: this.email,
|
|
authorized: this.authorized,
|
|
};
|
|
}
|
|
} |