# JSDoc: Source: schemas/user/passwordResetSchema.js

# Source: schemas/user/passwordResetSchema.js

    /*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 <https://www.gnu.org/licenses/>.*/

    //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.js');
    const loggerUtils = require('../../utils/loggerUtils.js')

    /**
     * Password reset token retention time
     */
    const daysToExpire = 7;

    /**
     * DB Schema for documents containing a single expiring password reset token
     */
    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()
        }
    });

    /**
     * Pre-save function, ensures IP's are hashed before saving
     */
    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();
    });

    /**
     * Schedulable function for processing expired reset requests
     */
    passwordResetSchema.statics.processExpiredRequests = async function(){
        //Pull all requests from the DB
        //Tested finding request by date, but mongoose kept throwing casting errors.
        //This seems to be an intermittent issue online. Maybe it will work in a future version?
        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
        for(let requestIndex in requestDB){
            //pull request from requestDB by index
            const request = requestDB[requestIndex];

            //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
    /**
     * Resets password and consumes token
     * @param {String} pass - New password to set
     * @param {String} confirmPass - Confirmation String to ensure new pass is correct
     */
    passwordResetSchema.methods.consume = async function(pass, confirmPass){
        //Check confirmation pass
        if(pass != confirmPass){
            throw loggerUtils.exceptionSmith("Confirmation password does not match!", "validation");
        }

        //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();
    }

    /**
     * Generates password reset URL off of the token object
     * @returns {String} Reset URL
     */
    passwordResetSchema.methods.getResetURL = function(){
        //Check for default port based on protocol
        if((config.protocol == 'http' && config.port == 80) || (config.protocol == 'https' && config.port == 443) || config.proxied){
            //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}`;
        }
    }

    /**
     * Returns number of days until token expiration
     * @returns {Number} Number of days until token expiration
     */
    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);