Issue with TypeScript Functions and Virtual Mongoose Schema in Next.js version 13.5

I originally created a Model called user.js with the following code:

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);

After starting a new project in Typescript and converting the code to use types, I created a model called 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);

When running the Typescript file, the result of the console logs showed an issue with handling types:

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

This problem seems to be related to how TypeScript handles the types compared to JavaScript.

However, further investigation is needed to understand and resolve this issue.

EDITED:

Upon further examination based on comments received, it was found that the issue lies with:

    this.salt
    this.hashed_password

In TypeScript, these values are shown as "undefined" while in JavaScript, they appear as strings as expected.

Answer №1

There are several issues to address in the provided code snippet. Before diving into them individually, it's important to note that the code may not be functioning correctly due to a potential undefined or incompatible data type for this.salt in the line

crypto.createHmac("sha1", this.salt)
. It is advisable to log errors within the catch block instead of returning an empty string for better troubleshooting. Now, let's discuss the identified problems:

The Key vs. Salt

In the statement below:

return crypto.createHmac("sha1", this.salt).update(password).digest("hex");

this.salt actually acts as the key, not the salt. An HMAC requires a key for hash generation and later verification. While both can serve similar purposes such as preventing rainbow table attacks, keys are intended to be kept secret unlike salts.

Selecting the Algorithm

When hashing user passwords, avoid using SHA-1 as it is deemed compromised. Refer to the OWASP password storage cheat sheet for secure recommendations like Argon2id or scrypt.

The Implementation of makeSalt Function

If the implementation of this function was hastily done for demonstration or debugging purposes, it's recommended to generate a random salt using crypto.randomBytes method. Convert the generated bytes into a string with the desired encoding like hex:

// generate a 16 byte random salt
const salt = crypto.randomBytes(16).toString('hex');
return salt;

Answer №2

Issue Resolved:

The root cause was not related to Typescript, but rather with the OBJECT "this". This object is formed when the method is invoked.

Therefore, the correct way to call the method is as follows:

userDB.authenticate(passIn);

In this case, the userDB object in mongoose represents the "this" object being utilized. Hence, the properties this.salt and this.hashed_password need to be present within the userDB object for them to be recognized during the method execution.

Upon realizing that the userDB object mounted on "this" did not include salt and hashed_pass, I added them accordingly. Subsequently, the code started functioning as intended!

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Extracting the token from a cookie for a server-side API request in Next JS: A guide

After following the guidelines provided in the Next.js repository to configure an Apollo client, the resulting code structure is as follows: import { ApolloClient } from 'apollo-client' import { InMemoryCache } from 'apollo-cache-inmemory&a ...

Contrasting Compositions with Generics

Let's consider a scenario where we have an abstract class A and three concrete classes that inherit from it: A1, A2, and A3. There is also another hierarchy tree with an abstract class B and three concrete classes B1, B2, and B3. Each concrete class A ...

The sanitizer variable becomes null when accessed outside of the NgOnInit function in Angular using TypeScript

At first, I added DomSanitizer to the component: import { DomSanitizer, SafeResourceUrl} from '@angular/platform-browser'; Next, a class was created and included in the constructor: export class BlocklyComponent implements OnInit { primar ...

Issue encountered during the installation of connect-mongo

****I encountered an error message when attempting to install connect-mongo or [email protected] How can I prevent this error, especially since I never encountered it while installing other features like express? Prior to this issue, I successfully i ...

Experimenting with CORS by sending a request from a NextJS server running locally to a Rails API server also running locally

I am utilizing two servers on localhost. One is a NextJs development server running on port 3000 and the other is a Rails Api only server running on port 4000. No additional libraries or packages have been added. I have an API endpoint at http://localhos ...

Issue encountered with Ionic and ssh2: process.binding is not supported

I am currently delving into the world of Ionic and experimenting with creating a basic application that utilizes SSH2 to establish an ssh connection between the app and a server. Here is a breakdown of the steps I took to encounter the issue: Steps to Rep ...

Transferring environment variables from docker-compose.yml to front-end Next.js

I am facing a peculiar issue where I am unable to pass my environment variables into my Next.js service while running with docker-compose up. Is there anyone who can help me identify the bug? Here is the snippet from docker-compose.yml: services: ... ...

Steps for displaying a 404 page on a server-side rendered dynamic route following a client-side page transition

I'm currently developing a next.js project using Contentful as the Content Management System. My goal is to display a 404 page for a server-side rendered dynamic route after a client-side page transition. When I directly request the page (by entering ...

Executing callback in the incorrect context

I'm facing an issue and can't seem to navigate through it. I am setting up a callback from a third party directive, but the callback is not returning with the correct scope. This means that when it reaches my controller, this refers to some other ...

What is the best approach to tackling next.js?

PS C:\Users\hp\Desktop\next\next> npm start > <a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="74040116181d170415131107591a110c0034445a455a44">[email protected]</a> start > next st ...

Is there a method to retrieve Mui state classes easily?

One thing I really appreciate is the way to style mui-components with their class names. I'm curious if there's a method to access state classes like Mui-checked using a variable. Let me delve deeper into this: I have a styled component that lo ...

Creating a task list without using JavaScript in the web browser and without relying on a database

Looking for some guidance on building a todo app for a job interview where JavaScript is disabled in the browser and no database can be used. Any JavaScript needs to be handled on the server side. I have some basic knowledge of node/express and serving H ...

Is it secure to use NEXT_PUBLIC_ to make environmental variables accessible to the browser?

As per the Next.js documentation regarding environment variables: By default, environment variables are restricted to the Node.js environment and not shared with the browser. To make a variable accessible in the browser, it needs to be prefixed with NEXT_ ...

Mongoose - Mastering the Art of Executing Multiple Update Statements in a Single Operation

In the MongoDB documentation, I found out that you can execute multiple update statements in a single command. How can this be accomplished with Node.js and Mongoose? db.runCommand({ update: <collection>, updates: [ { q: <q ...

Retrieve the outcome of a mongoose query within a designated function

Seeking help with returning a result from my mongoose find operation. While similar questions have been asked before, this one is unique. Here's an example of my user: let UserSchema = new mongoose.Schema({ variable: {type: mongoose.Schema.Object ...

Switch up the Angular base URL using ngx-translate

I successfully integrated ngx-translate into my Angular project. Now, I want to dynamically change the base href based on the language selected from the header menu. Currently, the URL appears as: "localhost:4200". However, upon launching the project, it ...

Error encountered in Next.JS when calling getInitialProps(ctx) due to undefined ctx variable

This is my code import NavLayout from "../../components/Navigation/Navigation"; export default function WorldOfWarcraft({ game }) { return (<NavLayout> {game.link} </NavLayout>) } WorldOfWarcraft.getInitialProps = as ...

How can we avoid a runtime error when attempting to filter an array that may not be present at a certain point in

Encountering a runtime error with my reducer state chunk - specifically a TypeError stating that an intermediate value is not iterable. This occurs when the object, childGroup, may not be present in the state at a given moment. Any solutions on how to avoi ...

Issue with displaying Cloudinary image

After uploading my image to Cloudinary and getting the URL, I attempted to display it in React. Here is the URL: . Unfortunately, the image did not show up as expected. You can find my codesandbox with the issue here: https://codesandbox.io/s/cool-christ ...

Unable to locate module using absolute import in a Next.js + TypeScript + Jest setup

Currently in my NextJS project, I am utilizing absolute imports and testing a component with Context Provider. The setup follows the instructions provided in this jest setup guide TEST: import { render, screen } from 'test-util'; import { Sideb ...