'Property 'serializeUser' does not exist on type 'UserModel'.ts(2339)

This is my models/users.ts file:

import mongoose from 'mongoose';
const Schema = mongoose.Schema;
import passportLocalMongoose from 'passport-local-mongoose';

export interface digitalWalletDocument extends mongoose.Document{
    currencyName: string;
    value: number;

}

const digitalWalletSchema = new Schema<digitalWalletDocument>({
    currencyName : {
        type : String
    },
    value : {
        type : Number
    }
});


export interface UserDocument extends mongoose.Document{
    username :  String;
    firstName : String;
    lastName : String;
    email : String;
    telNumber : Number;
    nationalCode : Number;
    admin : Boolean;
    image :  String;
    wallet : Number;
    rate : Number;
    shabaCode :Number;
    cardNumber : Number;
    birthDay : String;
    status : String;
    address : String;
    documnetPath : String;
    description : String;
    purchased : String;
    digitalWallet : digitalWalletDocument;
}

export const userSchema = new Schema<UserDocument>({
    username : {
        type : String,
        sparse : true ,
        unique : true
    },
    firstName : {
        type: String
    },
    lastName : {
        type : String
    },
    email : {
        type : String,
        unique : true
    },
    telNumber : {
        type : Number
    },
    nationalCode : {
        type : Number,
        sparse : true,
        unique : true
    },
    admin : {
        type:Boolean,
        default : false
    },
    image : {
        type : String
    },
    wallet: {
        type : Number,
        default : 0
    },
    rate : {
        type : Number,
        default : 0
    },
    shabaCode : {
        type : Number
    },
    cardNumber : {
        type : Number
    },
    birthDay : {
        type : String
    },
    status : {
        type : String
    },
    address : {
        type : String
    },
    documnetPath : {
        type : String
    },
    description : {
        type: String
    },
    purchased : [String],
    digitalWallet : [digitalWalletSchema]
}, {timestamps : true});

export interface UserModel extends mongoose.Model<UserDocument> {};

userSchema.plugin(passportLocalMongoose,{usernameField : 'email'});

export default mongoose.model<UserDocument,UserModel>('User' , userSchema);

And the following is the authentication.ts file that uses the user.ts file:

import express, {Request, Response, NextFunction} from 'express';
import mongoose from 'mongoose';
import passport from 'passport';
import passportLocal from 'passport-local';
const LocalStrategy = passportLocal.Strategy;
import passportJWT from 'passport-jwt';
const passportJWTStrategy = passportJWT.Strategy;
const ExtractJWT = passportJWT.ExtractJwt;
import JWT from 'jsonwebtoken';
import User from './models/users';
import config from './config';

export const local = passport.use(new LocalStrategy( User.authenticate()));

passport.serializeUser(User.serializeUser());
passport.deserializeUser(User.deserializeUser());
    
exports.getToken = (user: any) => {
    return JWT.sign(user, config.secretKey,{ expiresIn : "2d"});
};

const opt = {
    secretOrKey : config.secretKey,
    jwtFromRequest : ExtractJWT.fromAuthHeaderAsBearerToken()
};


export const jwtAuth = passport.use(new passportJWTStrategy(opt , (payload ,done) => {

    User.findOne({_id : payload._id},(err: any , user: typeof User ) => {
        if(err){
            return done(err,false);
        }
        else if(user){
            return done(null , user);
        }
        else{
            return done(null, false);
        }
    });
}));

export const verifyUser = passport.authenticate('jwt', {session : false});

export function verifyAdmin(req: Request, res: Response, next: NextFunction) {
    if(req.user.admin){
        return next();
    }
    else{
        let err = new Error('Only Admin can access to this web page or resources!');
        err.status = 403;
        return next(err);
    }
}

I get the following error messages:

Property 'authenticate' does not exist on type 'UserModel'.ts(2339)
Property 'serializeUser' does not exist on type 'UserModel'.ts(2339)
Property 'deserializeUser' does not exist on type 'UserModel'.ts(2339)

The sources of errors are these lines:

export const local = passport.use(new LocalStrategy(User.authenticate()));
passport.serializeUser(User.serializeUser());
passport.deserializeUser(User.deserializeUser());

I don't know what is the problem and how can I fix it? I tried to search on the internet and find the similar questions and do what the similar questions' answers suggest but none of them worked for me!

EDIT: I changed the code as what @Linda suggests in her answer. But a new error arose as following:

No overload matches this call. Overload 1 of 2, '(fn: (user: User, done: (err: any, id?: any) => void) => void): void', gave the following error. Argument of type '(user: UserDocument, cb: (err: any, id?: any) => void) => void' is not assignable to parameter of type '(user: User, done: (err: any, id?: any) => void) => void'. Types of parameters 'user' and 'user' are incompatible. Type 'User' is missing the following properties from type 'UserDocument': username, firstName, lastName, email, and 66 more.
Overload 2 of 2, '(fn: (req: IncomingMessage, user: User, done: (err: any, id?: unknown) => void) => void): void', gave the following error.

The error happens at this line of the code:

passport.serializeUser(User.serializeUser());

On the authentication.ts file.

When I delete the parenthesis in front of the User.serializeUser() the error disappears but I am not sure if it was the case.



Solution 1:[1]

Problem

The fields which are giving you errors are the ones which are added by the passport-local-mongoose package.

When you call:

userSchema.plugin(passportLocalMongoose, {usernameField : 'email'});

You are adding these additional fields like authenticate() and serializeUser() to your model.

Unfortunately the mongoose type declaration for Schema.plugin() says that it returns the same object type that it started with:

/** Registers a plugin for this schema. */
plugin(fn: (schema: Schema<DocType>, opts?: any) => void, opts?: any): this;

So your code cannot detect those added properties automatically.

Solution

Step 1:

Install @types/passport-local-mongoose to load type declarations for the passport-local-mongoose package.

npm i --save-dev @types/passport-local-mongoose

The additional types are added to the mongoose namespace, so you can access them using mongoose.TypeName the same as you would access the standard mongoose types.

Step 2:

Declare that your UserModel interface is a PassportLocalModel rather than a plain mongoose Model.

export interface UserModel extends mongoose.PassportLocalModel<UserDocument> {};

You need to keep the generic type arguments that you already have where you call mongoose.model() here:

export default mongoose.model<UserDocument, UserModel>('User' , userSchema);

But now your User model will have the methods of a PassportLocalModel!


As a side note, you want to use lowercase string and number in your TypeScript interfaces.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Linda Paiste