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;
}
}
}
}
非常感谢您!
您有几个选项可以代替您当前使用的策略。
就像你说的,在 guard 中抛出你有权访问 ExecutionContext
的异常,这样你就可以 context.switchToHttp().getResponse()
响应 object 并能够根据需要设置 headers(您目前正在 尝试 在 策略 文件)
使用像 nestjs-throttler 这样的包并使用它的装饰器来帮助设置速率限制
在我的 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;
}
}
}
}
非常感谢您!
您有几个选项可以代替您当前使用的策略。
就像你说的,在 guard 中抛出你有权访问
ExecutionContext
的异常,这样你就可以context.switchToHttp().getResponse()
响应 object 并能够根据需要设置 headers(您目前正在 尝试 在 策略 文件)使用像 nestjs-throttler 这样的包并使用它的装饰器来帮助设置速率限制