/*Canopy - The next generation of stoner streaming software Copyright (C) 2024-2025 Rainbownapkin and the TTN Community This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see .*/ //You could make an argument for making this part of the userModel //However, this is so rarely used the preformance benefits aren't worth the extra clutter //Config const config = require('../../../config.json'); //Node Imports const crypto = require("node:crypto"); //NPM Imports const {mongoose} = require('mongoose'); //Local Imports const hashUtil = require('../../utils/hashUtils'); const daysToExpire = 7; const passwordResetSchema = new mongoose.Schema({ user: { type: mongoose.SchemaTypes.ObjectID, ref: "user", required: true }, token: { type: mongoose.SchemaTypes.String, required: true, //Use a cryptographically secure algorythm to create a random hex string from 16 bytes as our reset token default: ()=>{return crypto.randomBytes(16).toString('hex')} }, ipHash: { type: mongoose.SchemaTypes.String, required: true }, date: { type: mongoose.SchemaTypes.Date, required: true, default: new Date() } }); //Presave function passwordResetSchema.pre('save', async function (next){ //If we're saving an ip if(this.isModified('ipHash')){ //Hash that sunnuvabitch this.ipHash = hashUtil.hashIP(this.ipHash); } next(); }); //statics passwordResetSchema.statics.processExpiredRequests = async function(){ //Pull all requests from the DB const requestDB = await this.find({}); //Fire em all off at once without waiting for the last one to complete since we don't fuckin' need to requestDB.forEach(async (request) => { //If the request hasn't been processed and it's been expired if(request.getDaysUntilExpiration() <= 0){ //Delete the request await this.deleteOne({_id: request._id}); } }); } //methods passwordResetSchema.methods.consume = async function(pass, confirmPass){ //Check confirmation pass if(pass != confirmPass){ throw new Error("Confirmation password does not match!"); } //Populate the user reference await this.populate('user'); //Set the users password this.user.pass = pass; //Save the user await this.user.save(); //Kill all authed sessions for security purposes await this.user.killAllSessions("Your password has been reset."); //Delete the request token now that it has been consumed await this.deleteOne(); } passwordResetSchema.methods.getResetURL = function(){ //Check for default port based on protocol if((config.protocol == 'http' && config.port == 80) || (config.protocol == 'https' && config.port == 443)){ //Return path return `${config.protocol}://${config.domain}/passwordReset?token=${this.token}`; }else{ //Return path return `${config.protocol}://${config.domain}:${config.port}/passwordReset?token=${this.token}`; } } passwordResetSchema.methods.getDaysUntilExpiration = function(){ //Get request date const expirationDate = new Date(this.date); //Get expiration days and calculate expiration date expirationDate.setDate(expirationDate.getDate() + daysToExpire); //Calculate and return days until request expiration return ((expirationDate - new Date()) / (1000 * 60 * 60 * 24)).toFixed(1); } module.exports = mongoose.model("passwordReset", passwordResetSchema);