PirateApp
PirateApp

Reputation: 6220

Typescript declaration merging errors with passport, passport-local and express-session

I am getting 2 errors currently

Property 'emailVerified' does not exist on type 'User'

and

Property 'id' does not exist on type 'User'

I tried creating declarations as per some of the answers HERE and HERE and HERE None of these seem to work. I would really appreciate if someone can tell me where I am going wrong

tsconfig.json

{
  "compilerOptions": {
    "lib": ["es2020"],
    "module": "commonjs",
    "moduleResolution": "node",
    "target": "es2020",
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
    "noImplicitAny": false,
    "outDir": "dist",
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true,
    "baseUrl": ".",
    "paths": {
      "server/*": ["src/server/*"],
      "tests/*": ["src/tests/*"],
      "data/*": ["src/data/*"],
      "config": ["src/config"]
    },
    "typeRoots": ["./src/@types", "./node_modules/@types"]
  }
}

src/@types/express/index.d.ts

declare global {
  export namespace Express {
    export interface User {
      id: string;
      emailVerified: boolean;
    }
  }
}

passport.ts

import { Express, Request } from 'express';
import passport from 'passport';
import {
  IStrategyOptionsWithRequest,
  IVerifyOptions,
  Strategy as LocalStrategy,
} from 'passport-local';
import { AuthService } from 'server/services';
import { isHashEqual } from 'server/utils/functions';

const strategyOptions: IStrategyOptionsWithRequest = {
  usernameField: 'email',
  passwordField: 'password',
  passReqToCallback: true,
};

passport.serializeUser(
  (
    user: Express.User,
    done: (err: any, user?: Express.User | false | null) => void,
  ) => {
    console.debug('serializeUser called with user %o', user);
    done(null, user.id);
  },
);
passport.deserializeUser(
  async (
    accountId: string,
    done: (err: any, user?: Express.User | false | null) => void,
  ) => {
    try {
      const user = await AuthService.get(accountId);
      if (typeof user !== 'undefined' && user !== null) {
        console.debug(
          'deserializeUser found user %o for accountId %s',
          user,
          accountId,
        );
        done(null, user.toJSON());
      } else {
        console.debug(
          'deserializeUser did not find user for accountId %s',
          accountId,
        );
        done(null, false);
      }
    } catch (error) {
      console.error(error, 'deserializeUser encountered an error');
      done(error, false);
    }
  },
);

passport.use(
  'local',
  new LocalStrategy(
    strategyOptions,
    async (
      req: Request,
      email: string,
      password: string,
      done: (error: any, user?: any, options?: IVerifyOptions) => void,
    ) => {
      try {
        const account = await AuthService.getByEmail(email);
        if (!account) {
          console.debug('LocalStrategy incorrect email');
          return done(null, false, { message: 'Incorrect email or password' });
        }
        if (!(await isHashEqual(password, account.password))) {
          console.debug('LocalStrategy password not matching with hash');
          return done(null, false, { message: 'Incorrect email or password' });
        }
        delete account.password;
        // Dont log the user account object before you delete the password
        console.debug('LocalStrategy returning account %o', account);
        return done(null, account);
      } catch (error) {
        console.error(error, 'LocalStrategy encountered an error');
        return done(error);
      }
    },
  ),
);

and the controller file

auth.controller.ts

  static async verifyEmail(req: Request, res: Response, next: NextFunction) {
    try {
      const { accountId, token } = req.params;
      const result = await VerificationTokenService.getNonExpired(accountId);
      if (!result) {
        return next(
          new IncorrectAccountIdOrExpiredToken(
            'Incorrect account id or expired token',
          ),
        );
      }
      if (!(await isHashEqual(token, result.token))) {
        return next(new IncorrectToken('Incorrect token'));
      }
      await AuthService.updateEmailVerified(accountId);
      if (req.isAuthenticated()) {
        req.user.emailVerified = true;
        console.log('verifyEmail: logged in user email verified %o', req.user);
      }
      res.locals.data = true;
      return next();
    } catch (error) {
      return next(error);
    }
  }

Visually, this is what the error looks like on VSCode and similar error for the emailVerified property

Can someone kindly tell me how to fix this?

enter image description here

Upvotes: 0

Views: 412

Answers (1)

David Morgan
David Morgan

Reputation: 1

Does it work if you add import * as express from 'express'; to the top of your own type declaration file?

I've found this to work but it seems unstable. When I add it everything compiles and VS Code shows no error. But then I can remove it again and it still appears to work, even if I run tsc --build --clean, although VS Code shows an error.

Upvotes: 0

Related Questions