Search code examples
typescriptmongoosenext.js

Problem with TypeScript Methods and Virtual Mogoose Model in Next js 13.5


I had this Model user.js

import mongoose from "mongoose";
import crypto from "crypto";
const { ObjectId } = mongoose.Schema;

const userSchema = new mongoose.Schema(
    {
        //Basic Data
        email: {
            type: String,
            trim: true,
            required: true,
            unique: true,
        },
        hashed_password: {
            type: String,
            required: true,
        },
        salt: {
            type: String,
        },
        
);

// virtual field
userSchema
    .virtual("password")
    .set(function (password) {
        // create a temporarity variable called _password
        this._password = password;
        // generate salt
        this.salt = this.makeSalt();
        // encryptPassword
        this.hashed_password = this.encryptPassword(password);
    })
    .get(function () {
        return this._password;
    });

//  Methods
userSchema.methods = {
    // Method Authenticate User
    authenticate: function (plainText) {
        const encryptP = this.encryptPassword(plainText);
        console.log("Entro a Autenticate...");
        console.log("Authenticate PlainText:", plainText);
        console.log("Authenticate Hashed:", this.hashed_password);
        console.log("Encript Pass:", encryptP);

        return this.encryptPassword(plainText) === this.hashed_password; //  Ture or False
    },
    // Method Encrypt Pass
    encryptPassword: function (password) {
        if (!password) return "";
        try {
            return crypto.createHmac("sha1", this.salt).update(password).digest("hex");
        } catch (err) {
            return "";
        }
    },

    // Method Make Salt
    makeSalt: function () {
        return Math.round(new Date().valueOf() * Math.random()) + "";
    },
};

// export default mongoose.model("User", userSchema);
module.exports = mongoose.models.User || mongoose.model("User", userSchema);

The result of all console.log is:

Entro a Autenticate...
Authenticate PlainText: 1234567890
Authenticate Hashed: b4358d67e879145916c10ba00d66d78a946f4b53
Encript Pass: b4358d67e879145916c10ba00d66d78a946f4b53

The .js file works fine...

I start a new project using Typescript using the same logic and code but with typescript.

This is my model user.ts:

import { Model, model, Schema, models } from "mongoose";

import crypto from "crypto";

// 1. Create an interface representing a document in MongoDB.
interface IUser {
    email: string;
    hashed_password?: string;
    salt?: string;
    //Virtual Field
    _password?: string;
}

// Put all user instance methods in this interface:
interface IUserMethods {
    authenticate(plainText: string): boolean;
    encryptPassword(password: string): string;
    makeSalt(): string;
}

// Create a new Model type that knows about IUserMethods...
type UserModel = Model<IUser, {}, IUserMethods>;

// 2. Create a Schema corresponding to the document interface.
const userSchema = new Schema<IUser, UserModel, IUserMethods>(
    {
        //Basic Data
        
        email: {
            type: String,
            trim: true,
            required: true,
            unique: true,
        },
        hashed_password: {
            type: String,
            required: true,
        },
        salt: {
            type: String,
        },
        
);

//Virtuals
userSchema
    .virtual("password")
    .set(function (password:string) {
        // create a temporarity variable called _password
        this._password = password;
        // generate salt
        this.salt = this.makeSalt();
        // encryptPassword
        this.hashed_password = this.encryptPassword(password);
    })
    .get(function () {
        return this._password;
    });

//  Methods
userSchema.methods = {
    // Method Authenticate User
    authenticate: function (plainText:string) {
        const encryptP: any = this.encryptPassword(plainText);

        console.log("Entro a Autenticate...");
        console.log("Authenticate PlainText:", plainText);
        console.log("Authenticate Hashed:", this.hashed_password);
        console.log("Encript Pass:", encryptP);

        return this.encryptPassword(plainText) === this.hashed_password; //  Ture or False
    },
    // Method Encrypt Pass
    encryptPassword: function (password:string) {
        if (!password) return "";
        try {
            return crypto.createHmac("sha1", this.salt).update(password).digest("hex");
        } catch (err) {
            return "";
        }
    },
    // Method Make Salt
    makeSalt: function () {
        return Math.round(new Date().valueOf() * Math.random()) + "";
    },
};

export default models.User || model<IUser, UserModel>("User", userSchema);

the result of all the console logs whit this Typescript file is:

Entro a Autenticate...
Authenticate PlainText: 1234567890
Authenticate Hashed: undefined
Encript Pass: 

The problem appears to be in the way it is handle the types...

But not a clue...

EDITED:

Based on the comments, I found that the issue is whit:

    this.salt
    this.hashed_password

in TS is shown as "undefined" in JS as a string (as should be)


Solution

  • SOLVED:

    The problem was not Typescript, was the OBJECT "this" , this object is created when the method is called.

    So the method is call like this

    userDB.authenticate(passIn);
    

    The userDB object on mongoose is the "this" object mounted. So, the this.salt and this.hashed_password must be contained on the userDB object to "Exists" on the Method called.

    The userDB object mounted on "this" did not contains salt and hashed_pass so I add them and the code WORKS just fine!