NestJS 和 Passport - 在 guard 中设置响应 headers

NestJS & Passport - Setting response headers in guard

在我的 NestJS 应用程序 中,我正在使用 本地护照策略 来保护登录路由,然后 returns智威汤逊。此过程正常

现在我在我的 local-strategy 中实施逻辑以 防止 brute-force(除了 main.ts 中的整体速率限制) here。当我抛出 TooManyRequests HttpException 时,我还想设置 'Retry-After' header 以便能够在前端为用户提供有用的信息。但是我无法访问守卫中的响应object。我试图实现一个没有帮助的拦截器。此外,我只能在我的本地策略中访问 'Retry-after' 的计算值,因为该值是在那里计算的。

设置这个 header 的正确方法是什么?这是我的代码,它还不能正常工作。

@Injectable()
export class LocalStrategy extends PassportStrategy(Strategy) {

private maxWrongAttemptsByIpPerDay = 100;
private maxConsecutiveFailsByUsernameAndIp = 5;

private  limiterSlowBruteByIp = new RateLimiterMongo({
    storeClient: this.connection,
    keyPrefix: 'login_fail_ip_per_day',
    points: this.maxWrongAttemptsByIpPerDay,
    duration: 60 * 60 * 24,
    blockDuration: 60 * 60 * 24, // Block for 1 day, if 100 wrong attempts per day
})

public limiterConsecutiveFailsByUsernameAndIp = new RateLimiterMongo({
    storeClient: this.connection,
    keyPrefix: 'login_fail_consecutive_username_and_ip',
    points: this.maxConsecutiveFailsByUsernameAndIp,
    duration: 60 * 60 * 24 * 90, // Store number for 90 days since first fail
    blockDuration: 60 * 60 // Block for 1 hour
})

constructor(private authService: AuthService, @InjectConnection() private connection: Connection) {
    super({passReqToCallback: true});
}

async validate(req: Request, username: string, password: string): Promise<any> {

    const usernameIpKey = this.authService.getUsernameIPkey(username, req['ip'])

    const [resUsernameAndIp, resSlowByIp] = await Promise.all([
        this.limiterConsecutiveFailsByUsernameAndIp.get(usernameIpKey),
        this.limiterSlowBruteByIp.get(req['ip'])
    ]);

    let retrySecs = 0;

    // Check if IP or Username + IP is already blocked
    if (resSlowByIp !== null && resSlowByIp.consumedPoints > this.maxWrongAttemptsByIpPerDay) {
        retrySecs = Math.round(resSlowByIp.msBeforeNext / 1000) || 1;
    } else if (resUsernameAndIp !== null && resUsernameAndIp.consumedPoints > this.maxConsecutiveFailsByUsernameAndIp) {
        retrySecs = Math.round(resUsernameAndIp.msBeforeNext / 1000) || 1;
    }

    if (retrySecs > 0) {
        // res.set('Retry-After', String(retrySecs));
        throw new HttpException('Too many requests', HttpStatus.TOO_MANY_REQUESTS);
    } else {
        const user = await this.authService.validateUser(username, password);
        if (!user) {
            // Consume 1 point from limiters on wrong attempt and block if limits reached
            try {
                const promises = [this.limiterSlowBruteByIp.consume(req['ip'])];
                if (!user) {
                    // Count failed attempts by Username + IP only for registered users
                    promises.push(this.limiterConsecutiveFailsByUsernameAndIp.consume(usernameIpKey));
                }

                await promises;

                throw new UnauthorizedException();

            } catch (rlRejected) {
                if (rlRejected instanceof Error) {
                    throw rlRejected;
                } else {
                    // res.set('Retry-After', String(Math.round(rlRejected.msBeforeNext / 1000) || 1));
                    throw new HttpException('Too many requests', HttpStatus.TOO_MANY_REQUESTS);
                }
            }
        } else {
            if (resUsernameAndIp !== null && resUsernameAndIp.consumedPoints > 0) {
                // Reset on successful authorisation
                await this.limiterConsecutiveFailsByUsernameAndIp.delete(usernameIpKey);
            }

            return user;
        }
    }
}

}

非常感谢您!

您有几个选项可以代替您当前使用的策略。

  1. 就像你说的,在 guard 中抛出你有权访问 ExecutionContext 的异常,这样你就可以 context.switchToHttp().getResponse()响应 object 并能够根据需要设置 headers(您目前正在 尝试 策略 文件)

  2. 使用像 nestjs-throttler 这样的包并使用它的装饰器来帮助设置速率限制