connectbase-client 3.7.2 → 3.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +188 -0
- package/dist/connect-base.umd.js +3 -3
- package/dist/index.d.mts +47 -8
- package/dist/index.d.ts +47 -8
- package/dist/index.js +66 -9
- package/dist/index.mjs +66 -9
- package/package.json +1 -1
package/dist/index.d.mts
CHANGED
|
@@ -35,15 +35,23 @@ interface HttpClientConfig {
|
|
|
35
35
|
refreshToken?: string;
|
|
36
36
|
/**
|
|
37
37
|
* 토큰 저장 방식. **기본값은 'none' (메모리 저장)**.
|
|
38
|
-
* XSS 취약점이 하나라도 있을 경우
|
|
39
|
-
* 위험을 이해하고 명시적으로 선택한 경우에만 사용한다.
|
|
38
|
+
* XSS 취약점이 하나라도 있을 경우 영구 저장은 즉시 전 세션 탈취로 이어지므로,
|
|
39
|
+
* 영구 저장 옵션은 위험을 이해하고 명시적으로 선택한 경우에만 사용한다.
|
|
40
40
|
*
|
|
41
|
-
* - 'none' (권장·기본값):
|
|
42
|
-
*
|
|
43
|
-
*
|
|
41
|
+
* - 'none' (권장·기본값): access token 만 메모리 저장. refresh token 은 서버가 발급한
|
|
42
|
+
* HttpOnly cookie 로만 보관되어 JS 가 접근할 수 없다 (XSS 시 탈취 불가). 새로고침 후에는
|
|
43
|
+
* `autoRestoreSession`(기본 true) 으로 cookie 만으로 자동 복구된다.
|
|
44
|
+
* - 'sessionStorage': 탭 종료 시 삭제. JS 접근 가능 → XSS 로 탭 세션 탈취 가능
|
|
44
45
|
* - 'localStorage': 브라우저 종료 후에도 유지. JS 접근 가능 → XSS 로 영구 탈취 가능
|
|
45
46
|
*/
|
|
46
47
|
persistence?: TokenPersistence;
|
|
48
|
+
/**
|
|
49
|
+
* 새로고침/탭 재개 시 HttpOnly cookie 로부터 access token 을 자동 복구할지 여부.
|
|
50
|
+
* 기본값은 브라우저 환경에서 true. 비-브라우저(Node.js, RN) 에서는 무시된다.
|
|
51
|
+
*
|
|
52
|
+
* cookie 가 없는 경우(미로그인) 조용히 실패하며 콘솔 에러를 발생시키지 않는다.
|
|
53
|
+
*/
|
|
54
|
+
autoRestoreSession?: boolean;
|
|
47
55
|
/**
|
|
48
56
|
* 요청별 기본 타임아웃(ms). 개별 호출의 `timeout` 이 우선.
|
|
49
57
|
* 기본값 30000ms. 0 또는 음수 지정 시 타임아웃 비활성화.
|
|
@@ -121,6 +129,17 @@ declare class HttpClient {
|
|
|
121
129
|
*/
|
|
122
130
|
getBaseUrl(): string;
|
|
123
131
|
private refreshAccessToken;
|
|
132
|
+
/**
|
|
133
|
+
* 새로고침/탭 재개 시 HttpOnly cookie 만으로 access token 을 복구한다.
|
|
134
|
+
*
|
|
135
|
+
* 동작:
|
|
136
|
+
* - 메모리에 access token 이 이미 있으면 그대로 반환 (true).
|
|
137
|
+
* - 없으면 `/v1/auth/re-issue` 를 cookie 만으로 호출. cookie 가 있으면 access token 회복.
|
|
138
|
+
* - cookie 가 없거나(미로그인) 만료된 경우 조용히 false 반환 (콘솔 에러 없음).
|
|
139
|
+
*
|
|
140
|
+
* 비-브라우저 환경에서는 cookie 흐름이 없으므로 즉시 false 반환.
|
|
141
|
+
*/
|
|
142
|
+
tryRestoreSessionFromCookie(): Promise<boolean>;
|
|
124
143
|
private emitError;
|
|
125
144
|
private isTokenExpired;
|
|
126
145
|
private prepareHeaders;
|
|
@@ -7420,12 +7439,23 @@ interface ConnectBaseConfig {
|
|
|
7420
7439
|
onTokenExpired?: () => void;
|
|
7421
7440
|
/**
|
|
7422
7441
|
* 토큰 저장 방식.
|
|
7423
|
-
* - 'none' (기본·권장):
|
|
7424
|
-
*
|
|
7425
|
-
*
|
|
7442
|
+
* - 'none' (기본·권장): access token 만 메모리. refresh token 은 서버 HttpOnly cookie 로
|
|
7443
|
+
* 보관되어 JS 가 접근할 수 없음 (XSS 시 탈취 불가). 새로고침 후에는 `autoRestoreSession`
|
|
7444
|
+
* (기본 true) 으로 cookie 만으로 자동 복구.
|
|
7445
|
+
* - 'sessionStorage': 탭 종료 시 삭제 (XSS 시 탭 세션 탈취 가능 — 콘솔 경고 출력)
|
|
7446
|
+
* - 'localStorage': 브라우저 종료 후에도 유지 (XSS 시 영구 탈취 가능 — 콘솔 경고 출력)
|
|
7426
7447
|
* @default 'none'
|
|
7427
7448
|
*/
|
|
7428
7449
|
persistence?: TokenPersistence;
|
|
7450
|
+
/**
|
|
7451
|
+
* 새로고침/탭 재개 시 HttpOnly cookie 만으로 access token 을 자동 복구할지 여부.
|
|
7452
|
+
* 브라우저 환경에서 기본 true. cookie 가 없거나 만료되었으면 조용히 실패.
|
|
7453
|
+
*
|
|
7454
|
+
* cookie 가 없는 미로그인 상태에서 콘솔에 401 노이즈가 찍히지 않도록 silent 처리한다.
|
|
7455
|
+
*
|
|
7456
|
+
* @default true (브라우저), false (Node.js / RN)
|
|
7457
|
+
*/
|
|
7458
|
+
autoRestoreSession?: boolean;
|
|
7429
7459
|
/**
|
|
7430
7460
|
* 에러 트래커 설정
|
|
7431
7461
|
*/
|
|
@@ -7578,6 +7608,15 @@ declare class ConnectBase {
|
|
|
7578
7608
|
*/
|
|
7579
7609
|
readonly endpoint: EndpointAPI;
|
|
7580
7610
|
constructor(config?: ConnectBaseConfig);
|
|
7611
|
+
/**
|
|
7612
|
+
* 새로고침/탭 재개 후 cookie 만으로 세션을 복원한다 (브라우저 전용).
|
|
7613
|
+
*
|
|
7614
|
+
* 일반적으로는 ConnectBase 생성 시 `autoRestoreSession: true` (기본값) 으로 자동 호출되지만,
|
|
7615
|
+
* 호출 결과를 await 해서 로그인 상태에 따라 다른 UI 를 그리고 싶다면 명시적으로 호출한다.
|
|
7616
|
+
*
|
|
7617
|
+
* @returns access token 복원 성공 시 true, 미로그인/cookie 만료 시 false
|
|
7618
|
+
*/
|
|
7619
|
+
restoreSession(): Promise<boolean>;
|
|
7581
7620
|
/**
|
|
7582
7621
|
* 수동으로 토큰 설정 (기존 토큰으로 세션 복원 시)
|
|
7583
7622
|
*/
|
package/dist/index.d.ts
CHANGED
|
@@ -35,15 +35,23 @@ interface HttpClientConfig {
|
|
|
35
35
|
refreshToken?: string;
|
|
36
36
|
/**
|
|
37
37
|
* 토큰 저장 방식. **기본값은 'none' (메모리 저장)**.
|
|
38
|
-
* XSS 취약점이 하나라도 있을 경우
|
|
39
|
-
* 위험을 이해하고 명시적으로 선택한 경우에만 사용한다.
|
|
38
|
+
* XSS 취약점이 하나라도 있을 경우 영구 저장은 즉시 전 세션 탈취로 이어지므로,
|
|
39
|
+
* 영구 저장 옵션은 위험을 이해하고 명시적으로 선택한 경우에만 사용한다.
|
|
40
40
|
*
|
|
41
|
-
* - 'none' (권장·기본값):
|
|
42
|
-
*
|
|
43
|
-
*
|
|
41
|
+
* - 'none' (권장·기본값): access token 만 메모리 저장. refresh token 은 서버가 발급한
|
|
42
|
+
* HttpOnly cookie 로만 보관되어 JS 가 접근할 수 없다 (XSS 시 탈취 불가). 새로고침 후에는
|
|
43
|
+
* `autoRestoreSession`(기본 true) 으로 cookie 만으로 자동 복구된다.
|
|
44
|
+
* - 'sessionStorage': 탭 종료 시 삭제. JS 접근 가능 → XSS 로 탭 세션 탈취 가능
|
|
44
45
|
* - 'localStorage': 브라우저 종료 후에도 유지. JS 접근 가능 → XSS 로 영구 탈취 가능
|
|
45
46
|
*/
|
|
46
47
|
persistence?: TokenPersistence;
|
|
48
|
+
/**
|
|
49
|
+
* 새로고침/탭 재개 시 HttpOnly cookie 로부터 access token 을 자동 복구할지 여부.
|
|
50
|
+
* 기본값은 브라우저 환경에서 true. 비-브라우저(Node.js, RN) 에서는 무시된다.
|
|
51
|
+
*
|
|
52
|
+
* cookie 가 없는 경우(미로그인) 조용히 실패하며 콘솔 에러를 발생시키지 않는다.
|
|
53
|
+
*/
|
|
54
|
+
autoRestoreSession?: boolean;
|
|
47
55
|
/**
|
|
48
56
|
* 요청별 기본 타임아웃(ms). 개별 호출의 `timeout` 이 우선.
|
|
49
57
|
* 기본값 30000ms. 0 또는 음수 지정 시 타임아웃 비활성화.
|
|
@@ -121,6 +129,17 @@ declare class HttpClient {
|
|
|
121
129
|
*/
|
|
122
130
|
getBaseUrl(): string;
|
|
123
131
|
private refreshAccessToken;
|
|
132
|
+
/**
|
|
133
|
+
* 새로고침/탭 재개 시 HttpOnly cookie 만으로 access token 을 복구한다.
|
|
134
|
+
*
|
|
135
|
+
* 동작:
|
|
136
|
+
* - 메모리에 access token 이 이미 있으면 그대로 반환 (true).
|
|
137
|
+
* - 없으면 `/v1/auth/re-issue` 를 cookie 만으로 호출. cookie 가 있으면 access token 회복.
|
|
138
|
+
* - cookie 가 없거나(미로그인) 만료된 경우 조용히 false 반환 (콘솔 에러 없음).
|
|
139
|
+
*
|
|
140
|
+
* 비-브라우저 환경에서는 cookie 흐름이 없으므로 즉시 false 반환.
|
|
141
|
+
*/
|
|
142
|
+
tryRestoreSessionFromCookie(): Promise<boolean>;
|
|
124
143
|
private emitError;
|
|
125
144
|
private isTokenExpired;
|
|
126
145
|
private prepareHeaders;
|
|
@@ -7420,12 +7439,23 @@ interface ConnectBaseConfig {
|
|
|
7420
7439
|
onTokenExpired?: () => void;
|
|
7421
7440
|
/**
|
|
7422
7441
|
* 토큰 저장 방식.
|
|
7423
|
-
* - 'none' (기본·권장):
|
|
7424
|
-
*
|
|
7425
|
-
*
|
|
7442
|
+
* - 'none' (기본·권장): access token 만 메모리. refresh token 은 서버 HttpOnly cookie 로
|
|
7443
|
+
* 보관되어 JS 가 접근할 수 없음 (XSS 시 탈취 불가). 새로고침 후에는 `autoRestoreSession`
|
|
7444
|
+
* (기본 true) 으로 cookie 만으로 자동 복구.
|
|
7445
|
+
* - 'sessionStorage': 탭 종료 시 삭제 (XSS 시 탭 세션 탈취 가능 — 콘솔 경고 출력)
|
|
7446
|
+
* - 'localStorage': 브라우저 종료 후에도 유지 (XSS 시 영구 탈취 가능 — 콘솔 경고 출력)
|
|
7426
7447
|
* @default 'none'
|
|
7427
7448
|
*/
|
|
7428
7449
|
persistence?: TokenPersistence;
|
|
7450
|
+
/**
|
|
7451
|
+
* 새로고침/탭 재개 시 HttpOnly cookie 만으로 access token 을 자동 복구할지 여부.
|
|
7452
|
+
* 브라우저 환경에서 기본 true. cookie 가 없거나 만료되었으면 조용히 실패.
|
|
7453
|
+
*
|
|
7454
|
+
* cookie 가 없는 미로그인 상태에서 콘솔에 401 노이즈가 찍히지 않도록 silent 처리한다.
|
|
7455
|
+
*
|
|
7456
|
+
* @default true (브라우저), false (Node.js / RN)
|
|
7457
|
+
*/
|
|
7458
|
+
autoRestoreSession?: boolean;
|
|
7429
7459
|
/**
|
|
7430
7460
|
* 에러 트래커 설정
|
|
7431
7461
|
*/
|
|
@@ -7578,6 +7608,15 @@ declare class ConnectBase {
|
|
|
7578
7608
|
*/
|
|
7579
7609
|
readonly endpoint: EndpointAPI;
|
|
7580
7610
|
constructor(config?: ConnectBaseConfig);
|
|
7611
|
+
/**
|
|
7612
|
+
* 새로고침/탭 재개 후 cookie 만으로 세션을 복원한다 (브라우저 전용).
|
|
7613
|
+
*
|
|
7614
|
+
* 일반적으로는 ConnectBase 생성 시 `autoRestoreSession: true` (기본값) 으로 자동 호출되지만,
|
|
7615
|
+
* 호출 결과를 await 해서 로그인 상태에 따라 다른 UI 를 그리고 싶다면 명시적으로 호출한다.
|
|
7616
|
+
*
|
|
7617
|
+
* @returns access token 복원 성공 시 true, 미로그인/cookie 만료 시 false
|
|
7618
|
+
*/
|
|
7619
|
+
restoreSession(): Promise<boolean>;
|
|
7581
7620
|
/**
|
|
7582
7621
|
* 수동으로 토큰 설정 (기존 토큰으로 세션 복원 시)
|
|
7583
7622
|
*/
|
package/dist/index.js
CHANGED
|
@@ -109,11 +109,11 @@ var HttpClient = class {
|
|
|
109
109
|
if (typeof window === "undefined") return;
|
|
110
110
|
if (this.config.persistence === "localStorage") {
|
|
111
111
|
console.warn(
|
|
112
|
-
`[connect-base-client] persistence="localStorage" \uB294 XSS \uC2DC
|
|
112
|
+
`[connect-base-client] persistence="localStorage" \uB294 XSS \uC2DC refresh token \uC601\uAD6C \uD0C8\uCDE8 \uC704\uD5D8\uC774 \uC788\uC2B5\uB2C8\uB2E4. \uAE30\uBCF8\uAC12('none') \uC744 \uC0AC\uC6A9\uD558\uBA74 \uC11C\uBC84\uAC00 \uBC1C\uAE09\uD55C HttpOnly cookie \uB85C\uB9CC refresh token \uC774 \uBCF4\uAD00\uB418\uC5B4 JS \uAC00 \uC811\uADFC\uD560 \uC218 \uC5C6\uACE0, \uC0C8\uB85C\uACE0\uCE68 \uD6C4\uC5D0\uB3C4 autoRestoreSession \uC73C\uB85C \uC790\uB3D9 \uBCF5\uAD6C\uB429\uB2C8\uB2E4.`
|
|
113
113
|
);
|
|
114
114
|
} else if (this.config.persistence === "sessionStorage") {
|
|
115
115
|
console.warn(
|
|
116
|
-
|
|
116
|
+
`[connect-base-client] persistence="sessionStorage" \uB294 XSS \uC2DC \uD0ED \uC138\uC158 \uD0C8\uCDE8 \uC704\uD5D8\uC774 \uC788\uC2B5\uB2C8\uB2E4. \uAE30\uBCF8\uAC12('none') + HttpOnly cookie \uD750\uB984\uC744 \uAD8C\uC7A5\uD569\uB2C8\uB2E4.`
|
|
117
117
|
);
|
|
118
118
|
}
|
|
119
119
|
}
|
|
@@ -249,7 +249,7 @@ var HttpClient = class {
|
|
|
249
249
|
throw error;
|
|
250
250
|
}
|
|
251
251
|
this.isRefreshing = true;
|
|
252
|
-
if (!this.config.refreshToken) {
|
|
252
|
+
if (!this.config.refreshToken && typeof window === "undefined") {
|
|
253
253
|
this.isRefreshing = false;
|
|
254
254
|
this.config.onTokenExpired?.();
|
|
255
255
|
const error = new AuthError("Refresh token is missing. Please login again.");
|
|
@@ -262,22 +262,39 @@ var HttpClient = class {
|
|
|
262
262
|
timeout: this.config.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS
|
|
263
263
|
});
|
|
264
264
|
try {
|
|
265
|
+
const headers = {
|
|
266
|
+
"Content-Type": "application/json"
|
|
267
|
+
};
|
|
268
|
+
if (this.config.refreshToken) {
|
|
269
|
+
headers.Authorization = `Bearer ${this.config.refreshToken}`;
|
|
270
|
+
}
|
|
271
|
+
const credential = this.getCredential();
|
|
272
|
+
if (credential) {
|
|
273
|
+
headers["X-Public-Key"] = credential;
|
|
274
|
+
}
|
|
265
275
|
const response = await fetch(`${this.config.baseUrl}/v1/auth/re-issue`, {
|
|
266
276
|
method: "POST",
|
|
267
|
-
headers
|
|
268
|
-
|
|
269
|
-
"Authorization": `Bearer ${this.config.refreshToken}`
|
|
270
|
-
},
|
|
277
|
+
headers,
|
|
278
|
+
credentials: "include",
|
|
271
279
|
signal
|
|
272
280
|
});
|
|
273
281
|
if (!response.ok) {
|
|
274
282
|
throw new Error("Token refresh failed");
|
|
275
283
|
}
|
|
276
284
|
const data = await response.json();
|
|
277
|
-
|
|
285
|
+
if (!data || typeof data.access_token !== "string") {
|
|
286
|
+
throw new Error("Token refresh response missing access_token");
|
|
287
|
+
}
|
|
288
|
+
const nextRefreshToken = typeof data.refresh_token === "string" && data.refresh_token.length > 0 ? data.refresh_token : this.config.refreshToken ?? "";
|
|
289
|
+
if (nextRefreshToken) {
|
|
290
|
+
this.setTokens(data.access_token, nextRefreshToken);
|
|
291
|
+
} else {
|
|
292
|
+
this.config.accessToken = data.access_token;
|
|
293
|
+
this.persistTokens();
|
|
294
|
+
}
|
|
278
295
|
this.config.onTokenRefresh?.({
|
|
279
296
|
accessToken: data.access_token,
|
|
280
|
-
refreshToken:
|
|
297
|
+
refreshToken: nextRefreshToken
|
|
281
298
|
});
|
|
282
299
|
this.refreshFailureCount = 0;
|
|
283
300
|
this.refreshLockedUntil = 0;
|
|
@@ -303,6 +320,28 @@ var HttpClient = class {
|
|
|
303
320
|
})();
|
|
304
321
|
return this.refreshPromise;
|
|
305
322
|
}
|
|
323
|
+
/**
|
|
324
|
+
* 새로고침/탭 재개 시 HttpOnly cookie 만으로 access token 을 복구한다.
|
|
325
|
+
*
|
|
326
|
+
* 동작:
|
|
327
|
+
* - 메모리에 access token 이 이미 있으면 그대로 반환 (true).
|
|
328
|
+
* - 없으면 `/v1/auth/re-issue` 를 cookie 만으로 호출. cookie 가 있으면 access token 회복.
|
|
329
|
+
* - cookie 가 없거나(미로그인) 만료된 경우 조용히 false 반환 (콘솔 에러 없음).
|
|
330
|
+
*
|
|
331
|
+
* 비-브라우저 환경에서는 cookie 흐름이 없으므로 즉시 false 반환.
|
|
332
|
+
*/
|
|
333
|
+
async tryRestoreSessionFromCookie() {
|
|
334
|
+
if (typeof window === "undefined") return false;
|
|
335
|
+
if (this.config.accessToken && !this.isTokenExpired(this.config.accessToken)) {
|
|
336
|
+
return true;
|
|
337
|
+
}
|
|
338
|
+
try {
|
|
339
|
+
const newAccessToken = await this.refreshAccessToken();
|
|
340
|
+
return !!newAccessToken;
|
|
341
|
+
} catch {
|
|
342
|
+
return false;
|
|
343
|
+
}
|
|
344
|
+
}
|
|
306
345
|
emitError(error) {
|
|
307
346
|
try {
|
|
308
347
|
this.config.onError?.(error);
|
|
@@ -402,6 +441,7 @@ var HttpClient = class {
|
|
|
402
441
|
try {
|
|
403
442
|
const response = await fetch(`${this.config.baseUrl}${url}`, {
|
|
404
443
|
...init,
|
|
444
|
+
credentials: "include",
|
|
405
445
|
signal
|
|
406
446
|
});
|
|
407
447
|
return await this.handleResponse(response);
|
|
@@ -470,6 +510,7 @@ var HttpClient = class {
|
|
|
470
510
|
}
|
|
471
511
|
return fetch(`${this.config.baseUrl}${url}`, {
|
|
472
512
|
...init,
|
|
513
|
+
credentials: "include",
|
|
473
514
|
headers: mergedHeaders
|
|
474
515
|
});
|
|
475
516
|
}
|
|
@@ -9600,6 +9641,7 @@ var ConnectBase = class {
|
|
|
9600
9641
|
publicKey: config.publicKey,
|
|
9601
9642
|
secretKey: config.secretKey,
|
|
9602
9643
|
persistence: config.persistence,
|
|
9644
|
+
autoRestoreSession: config.autoRestoreSession,
|
|
9603
9645
|
requestTimeoutMs: config.requestTimeoutMs,
|
|
9604
9646
|
onError: config.onError,
|
|
9605
9647
|
onTokenRefresh: config.onTokenRefresh,
|
|
@@ -9629,6 +9671,21 @@ var ConnectBase = class {
|
|
|
9629
9671
|
this.analytics = new AnalyticsAPI(this.http);
|
|
9630
9672
|
this.endpoint = new EndpointAPI(this.http);
|
|
9631
9673
|
this.auth._attachAnalytics(this.analytics);
|
|
9674
|
+
const shouldAutoRestore = config.autoRestoreSession ?? true;
|
|
9675
|
+
if (shouldAutoRestore && typeof window !== "undefined") {
|
|
9676
|
+
void this.http.tryRestoreSessionFromCookie();
|
|
9677
|
+
}
|
|
9678
|
+
}
|
|
9679
|
+
/**
|
|
9680
|
+
* 새로고침/탭 재개 후 cookie 만으로 세션을 복원한다 (브라우저 전용).
|
|
9681
|
+
*
|
|
9682
|
+
* 일반적으로는 ConnectBase 생성 시 `autoRestoreSession: true` (기본값) 으로 자동 호출되지만,
|
|
9683
|
+
* 호출 결과를 await 해서 로그인 상태에 따라 다른 UI 를 그리고 싶다면 명시적으로 호출한다.
|
|
9684
|
+
*
|
|
9685
|
+
* @returns access token 복원 성공 시 true, 미로그인/cookie 만료 시 false
|
|
9686
|
+
*/
|
|
9687
|
+
async restoreSession() {
|
|
9688
|
+
return this.http.tryRestoreSessionFromCookie();
|
|
9632
9689
|
}
|
|
9633
9690
|
/**
|
|
9634
9691
|
* 수동으로 토큰 설정 (기존 토큰으로 세션 복원 시)
|
package/dist/index.mjs
CHANGED
|
@@ -68,11 +68,11 @@ var HttpClient = class {
|
|
|
68
68
|
if (typeof window === "undefined") return;
|
|
69
69
|
if (this.config.persistence === "localStorage") {
|
|
70
70
|
console.warn(
|
|
71
|
-
`[connect-base-client] persistence="localStorage" \uB294 XSS \uC2DC
|
|
71
|
+
`[connect-base-client] persistence="localStorage" \uB294 XSS \uC2DC refresh token \uC601\uAD6C \uD0C8\uCDE8 \uC704\uD5D8\uC774 \uC788\uC2B5\uB2C8\uB2E4. \uAE30\uBCF8\uAC12('none') \uC744 \uC0AC\uC6A9\uD558\uBA74 \uC11C\uBC84\uAC00 \uBC1C\uAE09\uD55C HttpOnly cookie \uB85C\uB9CC refresh token \uC774 \uBCF4\uAD00\uB418\uC5B4 JS \uAC00 \uC811\uADFC\uD560 \uC218 \uC5C6\uACE0, \uC0C8\uB85C\uACE0\uCE68 \uD6C4\uC5D0\uB3C4 autoRestoreSession \uC73C\uB85C \uC790\uB3D9 \uBCF5\uAD6C\uB429\uB2C8\uB2E4.`
|
|
72
72
|
);
|
|
73
73
|
} else if (this.config.persistence === "sessionStorage") {
|
|
74
74
|
console.warn(
|
|
75
|
-
|
|
75
|
+
`[connect-base-client] persistence="sessionStorage" \uB294 XSS \uC2DC \uD0ED \uC138\uC158 \uD0C8\uCDE8 \uC704\uD5D8\uC774 \uC788\uC2B5\uB2C8\uB2E4. \uAE30\uBCF8\uAC12('none') + HttpOnly cookie \uD750\uB984\uC744 \uAD8C\uC7A5\uD569\uB2C8\uB2E4.`
|
|
76
76
|
);
|
|
77
77
|
}
|
|
78
78
|
}
|
|
@@ -208,7 +208,7 @@ var HttpClient = class {
|
|
|
208
208
|
throw error;
|
|
209
209
|
}
|
|
210
210
|
this.isRefreshing = true;
|
|
211
|
-
if (!this.config.refreshToken) {
|
|
211
|
+
if (!this.config.refreshToken && typeof window === "undefined") {
|
|
212
212
|
this.isRefreshing = false;
|
|
213
213
|
this.config.onTokenExpired?.();
|
|
214
214
|
const error = new AuthError("Refresh token is missing. Please login again.");
|
|
@@ -221,22 +221,39 @@ var HttpClient = class {
|
|
|
221
221
|
timeout: this.config.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS
|
|
222
222
|
});
|
|
223
223
|
try {
|
|
224
|
+
const headers = {
|
|
225
|
+
"Content-Type": "application/json"
|
|
226
|
+
};
|
|
227
|
+
if (this.config.refreshToken) {
|
|
228
|
+
headers.Authorization = `Bearer ${this.config.refreshToken}`;
|
|
229
|
+
}
|
|
230
|
+
const credential = this.getCredential();
|
|
231
|
+
if (credential) {
|
|
232
|
+
headers["X-Public-Key"] = credential;
|
|
233
|
+
}
|
|
224
234
|
const response = await fetch(`${this.config.baseUrl}/v1/auth/re-issue`, {
|
|
225
235
|
method: "POST",
|
|
226
|
-
headers
|
|
227
|
-
|
|
228
|
-
"Authorization": `Bearer ${this.config.refreshToken}`
|
|
229
|
-
},
|
|
236
|
+
headers,
|
|
237
|
+
credentials: "include",
|
|
230
238
|
signal
|
|
231
239
|
});
|
|
232
240
|
if (!response.ok) {
|
|
233
241
|
throw new Error("Token refresh failed");
|
|
234
242
|
}
|
|
235
243
|
const data = await response.json();
|
|
236
|
-
|
|
244
|
+
if (!data || typeof data.access_token !== "string") {
|
|
245
|
+
throw new Error("Token refresh response missing access_token");
|
|
246
|
+
}
|
|
247
|
+
const nextRefreshToken = typeof data.refresh_token === "string" && data.refresh_token.length > 0 ? data.refresh_token : this.config.refreshToken ?? "";
|
|
248
|
+
if (nextRefreshToken) {
|
|
249
|
+
this.setTokens(data.access_token, nextRefreshToken);
|
|
250
|
+
} else {
|
|
251
|
+
this.config.accessToken = data.access_token;
|
|
252
|
+
this.persistTokens();
|
|
253
|
+
}
|
|
237
254
|
this.config.onTokenRefresh?.({
|
|
238
255
|
accessToken: data.access_token,
|
|
239
|
-
refreshToken:
|
|
256
|
+
refreshToken: nextRefreshToken
|
|
240
257
|
});
|
|
241
258
|
this.refreshFailureCount = 0;
|
|
242
259
|
this.refreshLockedUntil = 0;
|
|
@@ -262,6 +279,28 @@ var HttpClient = class {
|
|
|
262
279
|
})();
|
|
263
280
|
return this.refreshPromise;
|
|
264
281
|
}
|
|
282
|
+
/**
|
|
283
|
+
* 새로고침/탭 재개 시 HttpOnly cookie 만으로 access token 을 복구한다.
|
|
284
|
+
*
|
|
285
|
+
* 동작:
|
|
286
|
+
* - 메모리에 access token 이 이미 있으면 그대로 반환 (true).
|
|
287
|
+
* - 없으면 `/v1/auth/re-issue` 를 cookie 만으로 호출. cookie 가 있으면 access token 회복.
|
|
288
|
+
* - cookie 가 없거나(미로그인) 만료된 경우 조용히 false 반환 (콘솔 에러 없음).
|
|
289
|
+
*
|
|
290
|
+
* 비-브라우저 환경에서는 cookie 흐름이 없으므로 즉시 false 반환.
|
|
291
|
+
*/
|
|
292
|
+
async tryRestoreSessionFromCookie() {
|
|
293
|
+
if (typeof window === "undefined") return false;
|
|
294
|
+
if (this.config.accessToken && !this.isTokenExpired(this.config.accessToken)) {
|
|
295
|
+
return true;
|
|
296
|
+
}
|
|
297
|
+
try {
|
|
298
|
+
const newAccessToken = await this.refreshAccessToken();
|
|
299
|
+
return !!newAccessToken;
|
|
300
|
+
} catch {
|
|
301
|
+
return false;
|
|
302
|
+
}
|
|
303
|
+
}
|
|
265
304
|
emitError(error) {
|
|
266
305
|
try {
|
|
267
306
|
this.config.onError?.(error);
|
|
@@ -361,6 +400,7 @@ var HttpClient = class {
|
|
|
361
400
|
try {
|
|
362
401
|
const response = await fetch(`${this.config.baseUrl}${url}`, {
|
|
363
402
|
...init,
|
|
403
|
+
credentials: "include",
|
|
364
404
|
signal
|
|
365
405
|
});
|
|
366
406
|
return await this.handleResponse(response);
|
|
@@ -429,6 +469,7 @@ var HttpClient = class {
|
|
|
429
469
|
}
|
|
430
470
|
return fetch(`${this.config.baseUrl}${url}`, {
|
|
431
471
|
...init,
|
|
472
|
+
credentials: "include",
|
|
432
473
|
headers: mergedHeaders
|
|
433
474
|
});
|
|
434
475
|
}
|
|
@@ -9559,6 +9600,7 @@ var ConnectBase = class {
|
|
|
9559
9600
|
publicKey: config.publicKey,
|
|
9560
9601
|
secretKey: config.secretKey,
|
|
9561
9602
|
persistence: config.persistence,
|
|
9603
|
+
autoRestoreSession: config.autoRestoreSession,
|
|
9562
9604
|
requestTimeoutMs: config.requestTimeoutMs,
|
|
9563
9605
|
onError: config.onError,
|
|
9564
9606
|
onTokenRefresh: config.onTokenRefresh,
|
|
@@ -9588,6 +9630,21 @@ var ConnectBase = class {
|
|
|
9588
9630
|
this.analytics = new AnalyticsAPI(this.http);
|
|
9589
9631
|
this.endpoint = new EndpointAPI(this.http);
|
|
9590
9632
|
this.auth._attachAnalytics(this.analytics);
|
|
9633
|
+
const shouldAutoRestore = config.autoRestoreSession ?? true;
|
|
9634
|
+
if (shouldAutoRestore && typeof window !== "undefined") {
|
|
9635
|
+
void this.http.tryRestoreSessionFromCookie();
|
|
9636
|
+
}
|
|
9637
|
+
}
|
|
9638
|
+
/**
|
|
9639
|
+
* 새로고침/탭 재개 후 cookie 만으로 세션을 복원한다 (브라우저 전용).
|
|
9640
|
+
*
|
|
9641
|
+
* 일반적으로는 ConnectBase 생성 시 `autoRestoreSession: true` (기본값) 으로 자동 호출되지만,
|
|
9642
|
+
* 호출 결과를 await 해서 로그인 상태에 따라 다른 UI 를 그리고 싶다면 명시적으로 호출한다.
|
|
9643
|
+
*
|
|
9644
|
+
* @returns access token 복원 성공 시 true, 미로그인/cookie 만료 시 false
|
|
9645
|
+
*/
|
|
9646
|
+
async restoreSession() {
|
|
9647
|
+
return this.http.tryRestoreSessionFromCookie();
|
|
9591
9648
|
}
|
|
9592
9649
|
/**
|
|
9593
9650
|
* 수동으로 토큰 설정 (기존 토큰으로 세션 복원 시)
|