feat: add cooldown to refresh token security (#1736)

This commit is contained in:
Jérémy M 2023-09-27 15:03:50 +02:00 committed by GitHub
parent 96865b0fec
commit a4cde44b13
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 30 additions and 13 deletions

View File

@ -114,6 +114,7 @@ export class TokenService {
async verifyRefreshToken(refreshToken: string) {
const secret = this.environmentService.getRefreshTokenSecret();
const coolDown = this.environmentService.getRefreshTokenCoolDown();
const jwtPayload = await this.verifyJwt(refreshToken, secret);
assert(
@ -139,7 +140,11 @@ export class TokenService {
assert(user, 'User not found', NotFoundException);
if (token.isRevoked) {
// Check if revokedAt is less than coolDown
if (
token.revokedAt &&
token.revokedAt.getTime() <= Date.now() - ms(coolDown)
) {
// Revoke all user refresh tokens
await this.prismaService.client.refreshToken.updateMany({
where: {
@ -148,16 +153,14 @@ export class TokenService {
},
},
data: {
isRevoked: true,
revokedAt: new Date(),
},
});
}
assert(
!token.isRevoked,
'Suspicious activity detected, this refresh token has been revoked. All tokens has been revoked.',
ForbiddenException,
);
throw new ForbiddenException(
'Suspicious activity detected, this refresh token has been revoked. All tokens has been revoked.',
);
}
return { user, token };
}
@ -177,7 +180,7 @@ export class TokenService {
id,
},
data: {
isRevoked: true,
revokedAt: new Date(),
},
});

View File

@ -0,0 +1,8 @@
-- Step 1: Add the new column
ALTER TABLE "refresh_tokens" ADD COLUMN "revokedAt" TIMESTAMP(3);
-- Step 2: Update the new column based on the isRevoked column value
UPDATE "refresh_tokens" SET "revokedAt" = NOW() - INTERVAL '1 day' WHERE "isRevoked" = TRUE;
-- Step 3: Drop the isRevoked column
ALTER TABLE "refresh_tokens" DROP COLUMN "isRevoked";

View File

@ -331,10 +331,7 @@ model Person {
model RefreshToken {
/// @Validator.IsString()
/// @Validator.IsOptional()
id String @id @default(uuid())
/// @Validator.IsBoolean()
/// @Validator.IsOptional()
isRevoked Boolean @default(false)
id String @id @default(uuid())
/// @TypeGraphQL.omit(input: true, output: true)
user User @relation(fields: [userId], references: [id])
@ -344,6 +341,8 @@ model RefreshToken {
expiresAt DateTime
/// @TypeGraphQL.omit(input: true, output: true)
deletedAt DateTime?
/// @TypeGraphQL.omit(input: true, output: true)
revokedAt DateTime?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt

View File

@ -61,6 +61,10 @@ export class EnvironmentService {
return this.configService.get<string>('REFRESH_TOKEN_EXPIRES_IN') ?? '90d';
}
getRefreshTokenCoolDown(): string {
return this.configService.get<string>('REFRESH_TOKEN_COOL_DOWN') ?? '1m';
}
getLoginTokenSecret(): string {
return this.configService.get<string>('LOGIN_TOKEN_SECRET')!;
}

View File

@ -76,6 +76,9 @@ export class EnvironmentVariables {
@IsDuration()
@IsOptional()
REFRESH_TOKEN_EXPIRES_IN: string;
@IsDuration()
@IsOptional()
REFRESH_TOKEN_COOL_DOWN: string;
@IsString()
LOGIN_TOKEN_SECRET: string;