import { Document, InferSchemaType, Model, Schema, StringSchemaDefinition, Types } from 'mongoose'; import { Status } from '../constants/auth'; import { COLL_STRATEGY } from '../constants/db'; import { STRATEGIES } from '../constants/strategies'; import { StrategyDocument } from './strategy'; import { verify } from '../utils/password'; export interface Auth { is2FA?: boolean; record: StringSchemaDefinition; username: string; status: Status; strategies: Types.ObjectId[] | StrategyDocument[]; } interface AuthBaseDocument extends Auth, Document { authenticate(password: string): Promise; getStrategy(method?: STRATEGIES): Promise; } export interface AuthDocument extends AuthBaseDocument { strategies: Types.ObjectId[]; } export interface AuthPopulatedDocument extends AuthBaseDocument { strategies: StrategyDocument[]; } export interface AuthModel extends Model { findByUsername(username: string): Promise; getLocalStrategyForUsername(username: string): Promise; isUsernameAvailable(username: string): Promise; } export const AuthSchema = new Schema( { is2FA: { type: Boolean, default: false }, record: { type: Types.ObjectId, unique: true }, status: { type: Number, enum: Status, default: Status.UNVERIFIED, index: true, }, strategies: [{ type: Types.ObjectId, ref: COLL_STRATEGY, default: [] }], username: { type: String, required: true, unique: true }, }, { minimize: true, timestamps: true, }, ); AuthSchema.methods.authenticate = async function (this: AuthBaseDocument, password: string) { const strategy = await this.getStrategy(); return !!strategy && verify(password, strategy.key); }; AuthSchema.methods.getStrategy = async function (this: AuthBaseDocument, method = STRATEGIES.LOCAL) { const doc = await this.populate<{ strategies: StrategyDocument[] }>('strategies'); return doc.strategies.filter((strategy) => strategy.method === method).pop() || null; }; AuthSchema.statics = { async findByUsername(username) { return this.findOne({ username }); }, async getLocalStrategyForUsername(username) { const doc = await this.findByUsername(username); return !!doc && doc.getStrategy(); }, async isUsernameAvailable(username) { return !this.findByUsername(username); }, }; export type AuthSchema = InferSchemaType;