// auth-token.js // Copyright (C) 2024 DTP Technologies, LLC // All Rights Reserved 'use strict'; import mongoose from 'mongoose'; const AuthToken = mongoose.model('AuthToken'); import { v4 as uuidv4 } from 'uuid'; import { SiteService, SiteError } from '../../lib/site-lib.js'; export default class AuthTokenService extends SiteService { static get slug () { return 'authToken'; } static get name ( ) { return 'AuthTokenService'; } constructor (dtp) { super(dtp, AuthTokenService); } async create (purpose, data) { const NOW = new Date(); const passwordResetToken = new AuthToken(); passwordResetToken.created = NOW; passwordResetToken.purpose = purpose; passwordResetToken.token = uuidv4(); if (data) { passwordResetToken.data = data; } await passwordResetToken.save(); return passwordResetToken.toObject(); } async getByValue (tokenValue) { if (!tokenValue) { throw new SiteError(400, 'Must include an authentication token'); } if ((typeof tokenValue !== 'string') || (tokenValue.length !== 36)) { throw new SiteError(400, 'The authentication token is invalid'); } const token = await AuthToken .findOne({ token: tokenValue }) .lean(); if (!token) { throw new SiteError(400, 'Auth token not found'); } if (token.claimed) { throw new SiteError(403, 'Auth token already used'); } return token; } async claim (tokenValue) { const token = await this.getByValue(tokenValue); if (!token) { throw new SiteError(403, 'The authentication token has expired'); } if (token.claimed) { throw new SiteError(403, 'This authentication token has already been claimed'); } token.claimed = new Date(); await AuthToken.updateOne({ _id: token._id }, { $set: { claimed: token.claimed } }); return token; // with claimed date } async removeForUser (user) { await AuthToken.deleteMany({ user: user._id }); } async remove (token) { if (!token || !token._id) { throw new SiteError(400, 'Must include auth token'); } await AuthToken.deleteOne({ _id: token._id }); } }