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

Which library do you typically employ for converting .mov files to mp4 format within a React application using Typescript?

As a web programming student, I have encountered a question during my project work. In our current project, users have the ability to upload images and videos. Interestingly, while videos can be uploaded successfully on Android devices, they seem to face ...

Exploring the concept of the never type in TypeScript 2

Exploring the latest features in TypeScript 2.0, I came across the never type. It appears to be a clever method for defining the type of functions that do not have a return value. If I understand correctly, the never type can be assigned to any other type ...

As I embark on building my web application using next.js, I begin by importing firebase from the "firebase" package. Unfortunately, a particular error unexpectedly surfaces in the terminal

I am currently developing a next.js web application and I have decided to utilize firebase for both database management and authentication. However, when attempting to import firebase in a specific file, I encountered the following error: Error - ./firebas ...

Getting an error message with npm and Typescript that says: "Import statement cannot be used outside

After developing and publishing a package to npm, the code snippet below represents how it starts: import * as aws from "@pulumi/aws"; import * as pulumi from "@pulumi/pulumi"; export interface ... export class controlplaneDependencies ...

Issue encountered: An error has occurred indicating that the inferred type of 'api' cannot be identified without mentioning '../../../../packages/db/node_modules/@prisma/client/runtime/library'

I am currently developing a web application at convoform.com. I have integrated turborepo into my repository which includes nextjs, prisma, and trpc. While it works fine locally with "pnpm dev", I encountered an error when trying to build it using "pnpm bu ...

Using Tailwind CSS to set the margin of an element at certain breakpoints is not functioning as expected

I attempted to customize the margin of a div element at specific screen sizes by using a {screen}: prefix, but it did not have the desired effect. Here is what I tried: <div className={'flex justify-center'}> <div className={'w-fu ...

Transform a group of objects in Typescript into a new object with a modified structure

Struggling to figure out how to modify the return value of reduce without resorting to clunky type assertions. Take this snippet for example: const list: Array<Record<string, string | number>> = [ { resourceName: "a", usage: ...

Exploring Next.js Folder Routing within Pages Subdirectories

Hello, I'm a newcomer to Nextjs and Stackoverflow so please bear with me as I explain my issue in detail. In my project, I have the following folder structure: pages api folder index.js sys-admin folder createvenue.js createuser.js index.js Withi ...

Tips on how child component can detect when the object passed from parent component has been updated in Angular

In the child component, I am receiving an object from the parent component that looks like this: { attribute: 'aaaa', attribute2: [ { value }, { value }, { value }, ] } This object is passed to th ...

Adjust the background color of a list item using Typescript

At the top of my page, there's a question followed by a list of answers and the option to add new ones. You can see an example in the image below. https://i.stack.imgur.com/NPVh7.jpg The format for each answer is "(username)'s response: at this ...

What is the correct method for typing a React functional component with properties?

Here's a react functional component I have created... const MyFunction = () => { // lots of logic MyFunction.loaded = someBoolean; return <div>just one line</div> } MyFunction.refresh = () => ....... I added two properti ...

Customize Text Style in Material-UI and Next.js

Hi! I'm currently working on a new web project and looking to switch the font to Oswald (). My tech stack includes NextJS with MUI for the components. I've been struggling to find a solution on how to make this change successfully. Any guidance ...

Sending a parameter to a route guard

I've been developing an application that involves multiple roles, each requiring its own guard to restrict access to various parts of the app. While I know it's possible to create separate guard classes for each role, I'm hoping to find a mo ...

Creating an Observable from static data in Angular that resembles an HTTP request

I have a service with the following method: export class TestModelService { public testModel: TestModel; constructor( @Inject(Http) public http: Http) { } public fetchModel(uuid: string = undefined): Observable<string> { i ...

Effortless implementation of list loading with images and text in the Ionic 2 framework

Can someone provide guidance on creating a lazy loading list with both images and text? I understand that each image in the list will require a separate http request to download from the server. Should caching be implemented for these image downloads? Addi ...

Sending the HTML input value to a Knockout view

Can someone assist me with a dilemma I'm facing? Within CRM on Demand, I have a view that needs to extract values from CRM input fields to conduct a search against CRM via web service. If duplicate records are found, the view should display them. Be ...

Encountering TypeScript errors when trying to reference Angular2 within a Gulp setup

The issue at hand is: [11:16:06] TypeScript: 103 semantic errors [11:16:06] TypeScript: emit succeeded (with errors) I am currently using node v5.7.0 and npm 3.6.0 gulp -v: [11:26:58] Requiring external module babel-register [11:26:58] CLI version 3.9 ...

Leverage JSON files for pagination in NextJS

I am currently developing a science website where the post URLs are stored in a static JSON file. ScienceTopics.json- [ { "Subject": "Mathematics", "chapters": "mathematics", "contentList": [ ...

The issue with Angular2 Material select dropdown is that it remains open even after being toggled

Exploring the world of Node.js, I am delving into utilizing the dropdown feature from Angular Material. However, an issue arises once the dropdown is opened - it cannot be closed by simply clicking another region of the page. Additionally, the dropdown lis ...

Ensure data accuracy by triggering the cache - implementing SWR hook in Next.js with TypeScript

I recently implemented the swr hook in my next.js app to take advantage of its caching and real-time updates, which has been incredibly beneficial for my project (a Facebook clone). However, I encountered a challenge. The issue arises when fetching public ...