feat: add cooldown to refresh token security (#1736)
This commit is contained in:
@ -114,6 +114,7 @@ export class TokenService {
|
|||||||
|
|
||||||
async verifyRefreshToken(refreshToken: string) {
|
async verifyRefreshToken(refreshToken: string) {
|
||||||
const secret = this.environmentService.getRefreshTokenSecret();
|
const secret = this.environmentService.getRefreshTokenSecret();
|
||||||
|
const coolDown = this.environmentService.getRefreshTokenCoolDown();
|
||||||
const jwtPayload = await this.verifyJwt(refreshToken, secret);
|
const jwtPayload = await this.verifyJwt(refreshToken, secret);
|
||||||
|
|
||||||
assert(
|
assert(
|
||||||
@ -139,7 +140,11 @@ export class TokenService {
|
|||||||
|
|
||||||
assert(user, 'User not found', NotFoundException);
|
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
|
// Revoke all user refresh tokens
|
||||||
await this.prismaService.client.refreshToken.updateMany({
|
await this.prismaService.client.refreshToken.updateMany({
|
||||||
where: {
|
where: {
|
||||||
@ -148,16 +153,14 @@ export class TokenService {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
data: {
|
data: {
|
||||||
isRevoked: true,
|
revokedAt: new Date(),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
|
||||||
assert(
|
throw new ForbiddenException(
|
||||||
!token.isRevoked,
|
'Suspicious activity detected, this refresh token has been revoked. All tokens has been revoked.',
|
||||||
'Suspicious activity detected, this refresh token has been revoked. All tokens has been revoked.',
|
);
|
||||||
ForbiddenException,
|
}
|
||||||
);
|
|
||||||
|
|
||||||
return { user, token };
|
return { user, token };
|
||||||
}
|
}
|
||||||
@ -177,7 +180,7 @@ export class TokenService {
|
|||||||
id,
|
id,
|
||||||
},
|
},
|
||||||
data: {
|
data: {
|
||||||
isRevoked: true,
|
revokedAt: new Date(),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -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";
|
||||||
@ -331,10 +331,7 @@ model Person {
|
|||||||
model RefreshToken {
|
model RefreshToken {
|
||||||
/// @Validator.IsString()
|
/// @Validator.IsString()
|
||||||
/// @Validator.IsOptional()
|
/// @Validator.IsOptional()
|
||||||
id String @id @default(uuid())
|
id String @id @default(uuid())
|
||||||
/// @Validator.IsBoolean()
|
|
||||||
/// @Validator.IsOptional()
|
|
||||||
isRevoked Boolean @default(false)
|
|
||||||
|
|
||||||
/// @TypeGraphQL.omit(input: true, output: true)
|
/// @TypeGraphQL.omit(input: true, output: true)
|
||||||
user User @relation(fields: [userId], references: [id])
|
user User @relation(fields: [userId], references: [id])
|
||||||
@ -344,6 +341,8 @@ model RefreshToken {
|
|||||||
expiresAt DateTime
|
expiresAt DateTime
|
||||||
/// @TypeGraphQL.omit(input: true, output: true)
|
/// @TypeGraphQL.omit(input: true, output: true)
|
||||||
deletedAt DateTime?
|
deletedAt DateTime?
|
||||||
|
/// @TypeGraphQL.omit(input: true, output: true)
|
||||||
|
revokedAt DateTime?
|
||||||
|
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
|
|||||||
@ -61,6 +61,10 @@ export class EnvironmentService {
|
|||||||
return this.configService.get<string>('REFRESH_TOKEN_EXPIRES_IN') ?? '90d';
|
return this.configService.get<string>('REFRESH_TOKEN_EXPIRES_IN') ?? '90d';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getRefreshTokenCoolDown(): string {
|
||||||
|
return this.configService.get<string>('REFRESH_TOKEN_COOL_DOWN') ?? '1m';
|
||||||
|
}
|
||||||
|
|
||||||
getLoginTokenSecret(): string {
|
getLoginTokenSecret(): string {
|
||||||
return this.configService.get<string>('LOGIN_TOKEN_SECRET')!;
|
return this.configService.get<string>('LOGIN_TOKEN_SECRET')!;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -76,6 +76,9 @@ export class EnvironmentVariables {
|
|||||||
@IsDuration()
|
@IsDuration()
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
REFRESH_TOKEN_EXPIRES_IN: string;
|
REFRESH_TOKEN_EXPIRES_IN: string;
|
||||||
|
@IsDuration()
|
||||||
|
@IsOptional()
|
||||||
|
REFRESH_TOKEN_COOL_DOWN: string;
|
||||||
|
|
||||||
@IsString()
|
@IsString()
|
||||||
LOGIN_TOKEN_SECRET: string;
|
LOGIN_TOKEN_SECRET: string;
|
||||||
|
|||||||
Reference in New Issue
Block a user