You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Node.js TypeScript中Passport Google OAuth类型错误及done函数咨询

解决Passport Google OAuth2集成中的TypeScript类型错误及done函数解析

问题场景

我正在尝试通过Passport集成Google OAuth2,用PostgreSQL存储用户信息,核心代码如下:

import { 
    Strategy as GoogleStrategy, 
    Profile,
    VerifyCallback, 
} from 'passport-google-oauth20';
import { AppDataSource } from '../database/config';
import { User } from '../entities/User';
import passport from 'passport';

passport.use(new GoogleStrategy({
    clientID: process.env.GOOGLE_CLIENT_ID as string,
    clientSecret: process.env.GOOGLE_CLIENT_SECRET as string,
    callbackURL: "/auth/google/callback"
  },
  async function(accessToken: string, refreshToken: string, profile: Profile, done: VerifyCallback) {
    try {
      
      const defaultUser = {
        username: profile.name?.givenName,
        email: profile.emails,
        googleId: profile.id,
      }

      const userAlreadyExists = await AppDataSource.manager.findOneBy(User, {
        googleId: profile.id,
      });

      if (!userAlreadyExists) {
        await AppDataSource.manager.save(defaultUser);
      }

      done(null, userAlreadyExists);

    } catch (err) {
      console.log(err);
    }
  }
));

错误信息

done(null, userAlreadyExists) 行出现TypeScript类型错误:

const userAlreadyExists: User | null
Argument of type 'User | null' is not assignable to parameter of type 'User | undefined'.
  Type 'null' is not assignable to type 'User | undefined'.ts(2345)

附带数据库配置和用户实体代码:

数据库配置

import { DataSource } from "typeorm";
import dotenv from 'dotenv';

dotenv.config();

export const AppDataSource = new DataSource({
    type: "postgres",
    host: process.env.DATABASE_HOST,
    port: 5433,
    username: process.env.DATABASE_USERNAME,
    password: process.env.DATABASE_PASSWORD,
    database: process.env.DATABASE_NAME,
    synchronize: true,
    logging: true,
    entities: [`${__dirname}/../entities/*.{ts,js}`],
    migrations: [`${__dirname}/migrations/*.{ts,js}`],
});

用户实体

import { BeforeInsert, BeforeUpdate, Column, Entity, PrimaryGeneratedColumn } from "typeorm";
import bcrypt from 'bcryptjs';

@Entity('users')
export class User {
    @PrimaryGeneratedColumn('uuid')
    id: string;

    @Column()
    username: string;

    @Column()
    email: string;

    @Column()
    password: string;

    @Column()
    googleId: string;

    @Column({
        default: ''
    })
    tokenResetPass: string;

    @Column({
        default: false,
    })
    isAdmin: boolean;


    @BeforeInsert()
    @BeforeUpdate()
    hashPassword() {
        this.password = bcrypt.hashSync(this.password, 8);
    }

}

错误解决方法

1. 修正业务逻辑,确保传入done的是有效User对象

当前代码创建新用户后未将其赋值给userAlreadyExists,导致用户不存在时done接收到null。同时defaultUser的字段存在类型不匹配问题,一并修复:

async function(accessToken: string, refreshToken: string, profile: Profile, done: VerifyCallback) {
    try {
      
      // 修正字段类型,匹配User实体要求
      const defaultUser = {
        username: profile.name?.givenName as string,
        email: profile.emails?.[0].value as string,
        googleId: profile.id,
        password: '' // 谷歌登录用户无需密码,空字符串会被BeforeInsert自动哈希
      }

      let userAlreadyExists = await AppDataSource.manager.findOneBy(User, {
        googleId: profile.id,
      });

      if (!userAlreadyExists) {
        // 保存新用户并赋值给变量
        userAlreadyExists = await AppDataSource.manager.save(User, defaultUser);
      }

      done(null, userAlreadyExists); // 此时userAlreadyExists必为User类型

    } catch (err) {
      done(err as Error); // 将错误传递给Passport处理,而非仅打印
    }
}

2. 类型兼容处理(可选,不推荐)

若暂时不想修改业务逻辑,可将null转换为undefined以匹配VerifyCallback的参数要求:

done(null, userAlreadyExists ?? undefined);

但此方法不够严谨,因为用户不存在时应创建并返回新用户,而非传递undefined。

done函数的工作原理与作用

done是Passport验证回调的核心函数,用于向Passport传递验证结果:

  • 第一个参数:传递错误对象。若验证过程出错,传入Error实例,Passport会触发错误处理逻辑。
  • 第二个参数:传递验证通过的用户对象。若用户不存在或验证失败,传递false或undefined,Passport会拒绝请求。
  • 第三个参数(可选):传递附加信息(如错误提示),可配合Flash中间件展示给用户。

它的核心工作流程:

  1. 调用done(null, user)时,Passport会通过passport.serializeUser将用户对象序列化到Session中。
  2. 后续请求时,Passport通过passport.deserializeUser从Session中取出用户ID,反序列化为用户对象并挂载到req.user上,供后续路由使用。
  3. 调用done(err)时,Passport会返回500错误或跳转到预设的错误页面。
  4. 调用done(null, false, { message: '验证失败' })时,Passport会拒绝请求,并将提示信息传递给Flash中间件。

内容的提问来源于stack exchange,提问作者Routfin

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.06 10:01:13