Skip to content

Commit 5792501

Browse files
committed
feat: enhance token refresh logic with issued time
1 parent 03cf31c commit 5792501

File tree

3 files changed

+84
-8
lines changed

3 files changed

+84
-8
lines changed

src/token-storage/aws-storage.service.ts

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ export class AWSStorageService extends TokenStorageService {
1313
'secret_key',
1414
'session_token',
1515
'expired_time',
16+
'issued_time',
1617
'kms_arn',
1718
];
1819

@@ -44,8 +45,32 @@ export class AWSStorageService extends TokenStorageService {
4445

4546
async shouldRefreshToken(): Promise<boolean> {
4647
const expiredTime = +(await this.storage.getItem(`${this.prefix}.expired_time`));
48+
const issuedTime = +(await this.storage.getItem(`${this.prefix}.issued_time`));
4749
const now = new Date().getTime();
48-
return now >= expiredTime;
50+
51+
if (!expiredTime) {
52+
return true;
53+
}
54+
if (now >= expiredTime) {
55+
return true;
56+
}
57+
58+
// 전략 1: 고정 버퍼 (5분 전에 refresh)
59+
const bufferTime = 5 * 60 * 1000;
60+
if (now >= expiredTime - bufferTime) {
61+
return true;
62+
}
63+
64+
// 전략 2: 토큰 수명의 75% 지점에서 refresh
65+
if (issuedTime) {
66+
const tokenLifetime = expiredTime - issuedTime;
67+
const refreshThreshold = issuedTime + tokenLifetime * 0.75;
68+
if (now >= refreshThreshold) {
69+
return true;
70+
}
71+
}
72+
73+
return false;
4974
}
5075

5176
async getCachedCredentials(): Promise<LemonCredentials> {
@@ -94,6 +119,11 @@ export class AWSStorageService extends TokenStorageService {
94119
const expiredTime = this.calculateTokenExpiration(Expiration, identityToken);
95120
this.storage.setItem(`${this.prefix}.expired_time`, expiredTime.toString());
96121

122+
const issuedTime = this.calculateTokenIssuedTime(identityToken);
123+
if (issuedTime) {
124+
this.storage.setItem(`${this.prefix}.issued_time`, issuedTime.toString());
125+
}
126+
97127
return;
98128
}
99129

src/token-storage/azure-storage.service.ts

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ export class AzureStorageService extends TokenStorageService {
1717
'access_token',
1818
'host_key',
1919
'expired_time',
20+
'issued_time',
2021
'client_id',
2122
];
2223

@@ -58,8 +59,32 @@ export class AzureStorageService extends TokenStorageService {
5859
*/
5960
async shouldRefreshToken(): Promise<boolean> {
6061
const expiredTime = +(await this.storage.getItem(`${this.prefix}.expired_time`));
62+
const issuedTime = +(await this.storage.getItem(`${this.prefix}.issued_time`));
6163
const now = new Date().getTime();
62-
return now >= expiredTime;
64+
65+
if (!expiredTime) {
66+
return true;
67+
}
68+
if (now >= expiredTime) {
69+
return true;
70+
}
71+
72+
// 전략 1: 고정 버퍼 (5분 전에 refresh)
73+
const bufferTime = 5 * 60 * 1000;
74+
if (now >= expiredTime - bufferTime) {
75+
return true;
76+
}
77+
78+
// 전략 2: 토큰 수명의 75% 지점에서 refresh
79+
if (issuedTime) {
80+
const tokenLifetime = expiredTime - issuedTime;
81+
const refreshThreshold = issuedTime + tokenLifetime * 0.75;
82+
if (now >= refreshThreshold) {
83+
return true;
84+
}
85+
}
86+
87+
return false;
6388
}
6489

6590
/**
@@ -103,6 +128,12 @@ export class AzureStorageService extends TokenStorageService {
103128

104129
const expiredTime = this.calculateTokenExpiration(Expiration, identityToken);
105130
this.storage.setItem(`${this.prefix}.expired_time`, expiredTime.toString());
131+
132+
const issuedTime = this.calculateTokenIssuedTime(identityToken);
133+
if (issuedTime) {
134+
this.storage.setItem(`${this.prefix}.issued_time`, issuedTime.toString());
135+
}
136+
106137
return;
107138
}
108139

src/token-storage/token-storage.service.ts

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { CloudProvider, Storage, WebCoreConfig } from '../types';
22
import { LocalStorageService } from '../utils';
3-
import { jwtDecode } from 'jwt-decode';
3+
import { jwtDecode, JwtPayload } from 'jwt-decode';
44

55
export const USE_X_LEMON_IDENTITY_KEY = 'use_x_lemon_identity_key';
66
export const USE_X_LEMON_LANGUAGE_KEY = 'use_x_lemon_language_key';
@@ -71,15 +71,15 @@ export abstract class TokenStorageService {
7171

7272
calculateTokenExpiration(serverExpiration?: string, jwtToken?: string): number {
7373
const SAFETY_BUFFER = 5 * 60 * 1000; // 5 minutes
74-
const FALLBACK_DURATION = 30 * 60 * 1000; // 30 minutes
74+
const FALLBACK_DURATION = 15 * 60 * 1000; // 15 minutes
7575

7676
if (serverExpiration) {
7777
return new Date(serverExpiration).getTime() - SAFETY_BUFFER;
7878
}
7979

8080
if (jwtToken) {
8181
try {
82-
const jwtExpiration = this.extractJWTExpiration(jwtToken);
82+
const jwtExpiration = this.extractJWT(jwtToken)?.exp || null;
8383
if (jwtExpiration) {
8484
return jwtExpiration * 1000 - SAFETY_BUFFER; // JWT exp is in seconds
8585
}
@@ -92,10 +92,25 @@ export abstract class TokenStorageService {
9292
return new Date().getTime() + FALLBACK_DURATION;
9393
}
9494

95-
extractJWTExpiration(jwt: string): number | null {
95+
calculateTokenIssuedTime(jwtToken?: string): number | null {
96+
if (jwtToken) {
97+
try {
98+
const jwtIssuedAt = this.extractJWT(jwtToken)?.iat || null;
99+
if (jwtIssuedAt) {
100+
return jwtIssuedAt * 1000;
101+
}
102+
} catch (error) {
103+
console.warn('Failed to parse JWT expiration:', error);
104+
return null;
105+
}
106+
}
107+
108+
return null;
109+
}
110+
111+
extractJWT(jwt: string): JwtPayload | null {
96112
try {
97-
const decoded = jwtDecode(jwt);
98-
return decoded.exp || null;
113+
return jwtDecode(jwt);
99114
} catch {
100115
return null;
101116
}

0 commit comments

Comments
 (0)