扩展 Request 接口以添加固定用户属性并扩展任何其他类

RRG*_*T19 11 nestjs nestjs-passport

我正在使用NestJS和 TypeScript 结合Passport JWT的实现来做一个服务器端应用程序。

首先了解一些背景:

我的 JwtStrategy (这里没有问题)

@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
  constructor(private userService: UserService) {
    super({
      jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
      secretOrKey: 'hi',
    });
  }

  async validate(payload: IJwtClaims): Promise<UserEntity> {
    const { sub: id } = payload;

    // Find the user's database record by its "id" and return it.
    const user = await this.userService.findById(id);

    if (!user) {
      throw new UnauthorizedException();
    }

    return user;
  }
}
Run Code Online (Sandbox Code Playgroud)

根据有关该validate()方法的文档:

Passport 将根据 validate() 方法的返回值构建一个用户对象,并将其作为属性附加到 Request 对象上。

由于这种行为,我可以user像这样访问处理程序中的对象:

@Get('hi')
  example(@Req() request: Request) {
    const userId = (request.user as UserEntity).id;
  }
Run Code Online (Sandbox Code Playgroud)

您是否注意到我使用了类型断言(告诉编译器将用户对象视为 UserEntity)?如果没有它,我将无法自动完成实体的属性。

作为一个快速解决方案,我创建了一个扩展接口Request并包含我自己的 type 属性的类UserEntity

import { Request } from 'express';
import { UserEntity } from 'entities/user.entity';

export class WithUserEntityRequestDto extends Request {
  user: UserEntity;
}
Run Code Online (Sandbox Code Playgroud)

现在,我的处理程序将是:

@Get('hi')
  example(@Req() request: WithUserEntityRequestDto) {
    const userId = request.user.id; // Nicer
  }
Run Code Online (Sandbox Code Playgroud)

现在真正的问题是:

我有(并且将会有更多)一个将接收有效负载的处理程序,让我们在本示例中调用它PasswordResetRequestDto

export class PasswordResetRequestDto {
  currentPassword: string;
  newPassword: string;
}
Run Code Online (Sandbox Code Playgroud)

处理程序将是:

@Get('password-reset')
  resetPassword(@Body() request: PasswordResetRequestDto) {
  }
Run Code Online (Sandbox Code Playgroud)

现在,我无权访问用户的对象。我想访问它以了解发出此请求的用户是谁。

我尝试过的:

使用 TypeScript 泛型并向我之前的WithUserEntityRequestDto类添加一个新属性,如下所示:

export class WithUserEntityRequestDto<T> extends Request {
  user: UserEntity;
  newProp: T;
}
Run Code Online (Sandbox Code Playgroud)

处理程序将是:

@Get('password-reset')
  resetPassword(@Req() request: WithUserEntityRequestDto<PasswordResetRequestDto>) {
  }
Run Code Online (Sandbox Code Playgroud)

但现在PasswordResetRequestDto将在下面newProp,这使得它不是一个可扩展的解决方案。我作为泛型传递的任何类型都将位于newProp. 另外,我不能扩展T,因为一个类不能扩展两个类。我不认为自己一直在上这样的课。

我期望实现的目标:

将类型传递给我的WithUserEntityRequestDto类以包含传递的类型属性以及默认的用户对象。例如我可以做的一种方式:

request: WithUserEntityRequestDto<AwesomeRequestDto>
request: WithUserEntityRequestDto<BankRequestDto>
Run Code Online (Sandbox Code Playgroud)

该值将类似于:

{
   user: UserEntity, // As default, always present
   // all the properties of the passed type (T),
   // all the properties of the Request interface
}
Run Code Online (Sandbox Code Playgroud)

我的目标是找到一种简单且可扩展的方法来扩展Request接口并在其上包含任何类型/类,同时让用户对象 ( UserEntity) 始终存在。

感谢您抽出宝贵的时间,任何帮助/建议/方法将不胜感激。

You*_*uba 12

Nestjs为你的问题提供了一个优雅的解决方案,那就是自定义装饰

将属性附加到请求对象是常见的做法。然后您在每个路由处理程序中手动提取它们,

你所要做的就是创建一个用户装饰器:

用户装饰器.ts

    import { createParamDecorator, ExecutionContext } from '@nestjs/common';
    
    export const User = createParamDecorator(
      (data: unknown, ctx: ExecutionContext) => {
        const request = ctx.switchToHttp().getRequest();
        return request.user;
      },
    );
Run Code Online (Sandbox Code Playgroud)

那么你可以简单地在你的控制器中使用它,如下所示:

      @Get('hi')
      example(@Req() request: Request,@User() user: UserEntity) {
        const userId = user.id; 
      }
Run Code Online (Sandbox Code Playgroud)