TypeScript:req.user可能为“undefined”-Express和Passport.js

4

我有一个使用passport工作的Node.js TypeScript身份验证系统。

我的问题是,当我在路由中使用req.user时,会出现以下错误:Object is possibly 'undefined'

这是Typescript的正常行为,但我正在使用一个中间件来保护我想要在其中使用req.user的路由,并且通过这种方式,req.user不能是未定义的。

这就是我扩展Express.User类型的地方:

import { users } from "@prisma/client";

declare global {
    namespace Express {
        interface User extends users {}
    }
}

这是我正在使用的中间件,用于保护已登录用户的路由:

export function checkIsAuthenticated(req: Request, res: Response, next: NextFunction) {
    if (req.isAuthenticated()) {
        if (!req.user) req.logOut();
        else return next();
    }
    res.status(400).json({
        errors: [{ message: "no user logged in" }],
    });
}

这是获取用户信息的路由:

export function userRoute(req: Request, res: Response) { // defining the route
    res.json({
        id: req.user.id,               // I'm getting the error in these 4 lines
        username: req.user.username,   //
        email: req.user.email,         //
        role: req.user.role,           //
    });
}

router.get("/user", checkIsAuthenticated, userRoute); // using the route

我不想检查用户是否已定义,因为我不想在每个路由中都这样做,而且那不是一个好的实践。这就是为什么有中间件存在的原因。

我不擅长Typescript,所以我需要一些帮助来解决它。


“我不想检查用户是否已定义,因为我不想在每个路由中都这样做,而且这并不是一个好的实践。” 我不会说这是不好的实践。例如,如果您在创建其中一个路由时忘记了身份验证中间件,它将为您提供一个明确的错误提示信息。 - T.J. Crowder
2个回答

3
我不想在每个路由中都检查用户是否定义,因为这不是一个好的做法。我认为,检查请求中是否有用户并在意外使用未经过身份验证的处理程序时提供有用的错误消息是没有问题的。代码示例如下:
type RequestWithUser = Request & {user: typeOfUserObject};
function assertHasUser(req: Request): asserts req is RequestWithUser {
    if (!( "user" in req)) {
        throw new Error("Request object without user found unexpectedly");
    }
}

然后针对这些路由设置处理程序:

export function userRoute(req: Request, res: Response) {
    assertHasUser(req);
    // ...you can use `req.user` here...
});
示例

如果您不想使用 RequestWithUser 类型,只需使用 asserts req is Request & {user: typeOfUserObject},但通常使用别名会更有用。


非常感谢您的帮助!如果Express能够检测到我在中间件中检查请求属性,那就太好了,但我认为这是JS或TS目前的限制,我们现在必须想办法解决它。 - Erfan Asbari
我真的很喜欢你的解决方案。但现在我已经将所有的请求体和参数验证从控制器移动到中间件中,我在想是否应该对所有这些参数使用assert函数。这似乎有点啰嗦。 - Florian Walther
@T.J.Crowder 这很有道理。虽然我的意思是断言req.userreq.file的类型(例如),而不是整个请求对象。 我想使用您的方法,但如何针对不同的属性组合进行操作? 在我的情况下,req.file也可以通过中间件“保证”。我能否创建类型组合来断言req.user AND req.filereq.user OR req.file(以及将来可能出现的更多组合)?创建一个RequestWithUserAndFile类型似乎有些笨拙。 - Florian Walther
1
@T.J.Crowder,非常感谢您提供的代码示例和所有的帮助!我会尝试将其应用到我的代码中! - Florian Walther
@FlorianWalther - 我不太理解这个问题。链接示例没有特定于文件的内容。如果你导出 getWithuser 等函数,你可以将它们导入到任何文件中,并在该文件中使用它们来设置路由。 - T.J. Crowder
显示剩余9条评论

-1

export function userRoute(req: Request, res: Response) { // defining the route
    res.json({
        id: req.user!.id,               // you tell typescript that req.user for sure not. null
        username: req.user!.username,   //
        email: req.user!.email,         //
        role: req.user!.role,           //
    });
}

router.get("/user", checkIsAuthenticated, userRoute);


那样做是可行的,但最佳实践是避免这些断言。 - T.J. Crowder

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接