import mongoose, { Schema, Model, Document, } from 'mongoose'; import { addMinutes } from 'date-fns'; import uniqueValidator from 'mongoose-unique-validator'; import crypto from 'crypto'; import { getOrCreateModel } from '@growi/core'; const ObjectId = mongoose.Schema.Types.ObjectId; export interface IPasswordResetOrder { token: string, email: string, // eslint-disable-next-line @typescript-eslint/no-explicit-any relatedUser: any, isRevoked: boolean, createdAt: Date, expiredAt: Date, } export interface PasswordResetOrderDocument extends IPasswordResetOrder, Document { isExpired(): Promise revokeOneTimeToken(): Promise } export interface PasswordResetOrderModel extends Model { generateOneTimeToken(): string createPasswordResetOrder(email: string): PasswordResetOrderDocument } const expiredAt = (): Date => { return addMinutes(new Date(), 10); }; const schema = new Schema({ token: { type: String, required: true, unique: true }, email: { type: String, required: true }, relatedUser: { type: ObjectId, ref: 'User' }, isRevoked: { type: Boolean, default: false, required: true }, expiredAt: { type: Date, default: expiredAt, required: true }, }, { timestamps: { createdAt: true, updatedAt: false, }, }); schema.plugin(uniqueValidator); schema.statics.generateOneTimeToken = function() { const buf = crypto.randomBytes(256); const token = buf.toString('hex'); return token; }; schema.statics.createPasswordResetOrder = async function(email) { let token; let duplicateToken; do { token = this.generateOneTimeToken(); // eslint-disable-next-line no-await-in-loop duplicateToken = await this.findOne({ token }); } while (duplicateToken != null); const passwordResetOrderData = await this.create({ token, email }); return passwordResetOrderData; }; schema.methods.isExpired = function() { return this.expiredAt.getTime() < Date.now(); }; schema.methods.revokeOneTimeToken = async function() { this.isRevoked = true; return this.save(); }; export default getOrCreateModel('PasswordResetOrder', schema);