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中间件展示给用户。
它的核心工作流程:
- 调用
done(null, user)时,Passport会通过passport.serializeUser将用户对象序列化到Session中。 - 后续请求时,Passport通过
passport.deserializeUser从Session中取出用户ID,反序列化为用户对象并挂载到req.user上,供后续路由使用。 - 调用
done(err)时,Passport会返回500错误或跳转到预设的错误页面。 - 调用
done(null, false, { message: '验证失败' })时,Passport会拒绝请求,并将提示信息传递给Flash中间件。
内容的提问来源于stack exchange,提问作者Routfin
相关产品推荐
相关产品推荐

