@feelflow/ffid-sdk 5.19.0 → 5.22.0
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/README.md +1 -0
- package/dist/agency/index.cjs +4 -2
- package/dist/agency/index.js +4 -2
- package/dist/announcements/index.cjs +2 -1
- package/dist/announcements/index.js +2 -1
- package/dist/{chunk-D3PZ6SZB.js → chunk-7NRO2TBG.js} +108 -53
- package/dist/{chunk-TW2FXASO.cjs → chunk-EM5EGNGE.cjs} +108 -53
- package/dist/components/index.cjs +8 -8
- package/dist/components/index.d.cts +1 -1
- package/dist/components/index.d.ts +1 -1
- package/dist/components/index.js +1 -1
- package/dist/{ffid-client-BuDM5tmq.d.cts → ffid-client-CEJMpqRZ.d.cts} +55 -9
- package/dist/{ffid-client-D0x8TBS5.d.ts → ffid-client-DM0ZhIDV.d.ts} +55 -9
- package/dist/{index-Bkul-fRw.d.cts → index-btouGGKr.d.cts} +56 -9
- package/dist/{index-Bkul-fRw.d.ts → index-btouGGKr.d.ts} +56 -9
- package/dist/index.cjs +42 -42
- package/dist/index.d.cts +44 -8
- package/dist/index.d.ts +44 -8
- package/dist/index.js +2 -2
- package/dist/legal/index.cjs +2 -1
- package/dist/legal/index.js +2 -1
- package/dist/server/index.cjs +94 -48
- package/dist/server/index.d.cts +3 -3
- package/dist/server/index.d.ts +3 -3
- package/dist/server/index.js +94 -48
- package/dist/server/test/index.d.cts +2 -2
- package/dist/server/test/index.d.ts +2 -2
- package/dist/{types-BeVl-z12.d.cts → types-Cjb9J0rL.d.cts} +2 -1
- package/dist/{types-BeVl-z12.d.ts → types-Cjb9J0rL.d.ts} +2 -1
- package/dist/webhooks/index.cjs +2 -2
- package/dist/webhooks/index.d.cts +1 -1
- package/dist/webhooks/index.d.ts +1 -1
- package/dist/webhooks/index.js +2 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -185,6 +185,7 @@ function Header() {
|
|
|
185
185
|
refreshInterval={300000} // オプション: セッション更新間隔(ms)
|
|
186
186
|
onAuthStateChange={(user) => {}} // オプション: 認証状態変更時コールバック
|
|
187
187
|
onError={(error) => {}} // オプション: エラー時コールバック
|
|
188
|
+
cleanCallbackUrl={true} // オプション: token モードの callback URL cleanup(false で consumer に委譲、v5.22.0+)
|
|
188
189
|
>
|
|
189
190
|
{children}
|
|
190
191
|
</FFIDProvider>
|
package/dist/agency/index.cjs
CHANGED
|
@@ -16,8 +16,10 @@ var noopLogger = {
|
|
|
16
16
|
},
|
|
17
17
|
info: () => {
|
|
18
18
|
},
|
|
19
|
-
warn: (
|
|
20
|
-
|
|
19
|
+
warn: () => {
|
|
20
|
+
},
|
|
21
|
+
error: () => {
|
|
22
|
+
}
|
|
21
23
|
};
|
|
22
24
|
var consoleLogger = {
|
|
23
25
|
debug: (...args) => console.debug(SDK_LOG_PREFIX, ...args),
|
package/dist/agency/index.js
CHANGED
|
@@ -15,8 +15,10 @@ var noopLogger = {
|
|
|
15
15
|
},
|
|
16
16
|
info: () => {
|
|
17
17
|
},
|
|
18
|
-
warn: (
|
|
19
|
-
|
|
18
|
+
warn: () => {
|
|
19
|
+
},
|
|
20
|
+
error: () => {
|
|
21
|
+
}
|
|
20
22
|
};
|
|
21
23
|
var consoleLogger = {
|
|
22
24
|
debug: (...args) => console.debug(SDK_LOG_PREFIX, ...args),
|
|
@@ -264,10 +264,12 @@ var OAUTH_INTROSPECT_ENDPOINT = "/api/v1/oauth/introspect";
|
|
|
264
264
|
var CACHE_KEY_PREFIX = "ffid:introspect:";
|
|
265
265
|
var HEX_BASE = 16;
|
|
266
266
|
var HEX_BYTE_WIDTH = 2;
|
|
267
|
-
var TOKEN_LOG_PREFIX_LENGTH = 8;
|
|
268
267
|
function createVerifyAccessToken(deps) {
|
|
269
268
|
const { authMode, baseUrl, serviceCode, serviceApiKey, verifyStrategy, logger, createError, errorCodes, cache, timeout } = deps;
|
|
270
269
|
let jwtVerify2 = null;
|
|
270
|
+
let cacheReadFailureEscalated = false;
|
|
271
|
+
let cacheWriteFailureEscalated = false;
|
|
272
|
+
let missingSubtleCryptoLogged = false;
|
|
271
273
|
function getJwtVerifier() {
|
|
272
274
|
if (!jwtVerify2) {
|
|
273
275
|
jwtVerify2 = createJwtVerifier({
|
|
@@ -327,8 +329,9 @@ function createVerifyAccessToken(deps) {
|
|
|
327
329
|
}
|
|
328
330
|
const url = `${baseUrl}${OAUTH_INTROSPECT_ENDPOINT}`;
|
|
329
331
|
logger.debug("Verifying access token:", url);
|
|
330
|
-
const cacheKey = cache ? await buildCacheKey(accessToken, cacheKeySuffix) :
|
|
331
|
-
|
|
332
|
+
const cacheKey = cache ? await buildCacheKey(accessToken, cacheKeySuffix) : null;
|
|
333
|
+
let verificationCacheDegraded = cache !== void 0 && cacheKey === null;
|
|
334
|
+
if (cache && cacheKey !== null) {
|
|
332
335
|
try {
|
|
333
336
|
const cached = await cache.adapter.get(cacheKey);
|
|
334
337
|
if (cached && typeof cached === "object" && "sub" in cached) {
|
|
@@ -336,7 +339,16 @@ function createVerifyAccessToken(deps) {
|
|
|
336
339
|
return { data: cached };
|
|
337
340
|
}
|
|
338
341
|
} catch (cacheError) {
|
|
339
|
-
|
|
342
|
+
verificationCacheDegraded = true;
|
|
343
|
+
if (!cacheReadFailureEscalated) {
|
|
344
|
+
cacheReadFailureEscalated = true;
|
|
345
|
+
logger.error(
|
|
346
|
+
"Cache read failed \u2014 falling back to API call. Redis \u7B49\u306E cache adapter \u969C\u5BB3\u6642\u306F\u5168 verification \u304C introspect \u306B degrade \u3057\u307E\u3059\uFF08\u3053\u306E error \u306F instance \u3054\u3068\u306B\u521D\u56DE\u306E\u307F\u3001\u4EE5\u964D\u306F warn\uFF09:",
|
|
347
|
+
cacheError
|
|
348
|
+
);
|
|
349
|
+
} else {
|
|
350
|
+
logger.warn("Cache read failed, falling back to API call:", cacheError);
|
|
351
|
+
}
|
|
340
352
|
}
|
|
341
353
|
}
|
|
342
354
|
const fetchOptions = {
|
|
@@ -431,31 +443,40 @@ function createVerifyAccessToken(deps) {
|
|
|
431
443
|
}
|
|
432
444
|
} : base;
|
|
433
445
|
const userinfo = normalizeUserinfo(raw);
|
|
434
|
-
if (cache) {
|
|
446
|
+
if (cache && cacheKey !== null) {
|
|
435
447
|
try {
|
|
436
448
|
await cache.adapter.set(cacheKey, userinfo, cache.ttl);
|
|
437
449
|
} catch (cacheError) {
|
|
438
|
-
|
|
450
|
+
verificationCacheDegraded = true;
|
|
451
|
+
if (!cacheWriteFailureEscalated) {
|
|
452
|
+
cacheWriteFailureEscalated = true;
|
|
453
|
+
logger.error(
|
|
454
|
+
"Cache write failed \u2014 result still returned. cache adapter \u969C\u5BB3\u6642\u306F introspect \u7D50\u679C\u304C\u30AD\u30E3\u30C3\u30B7\u30E5\u3055\u308C\u305A\u5168 verification \u304C API \u547C\u3073\u51FA\u3057\u306B degrade \u3057\u307E\u3059\uFF08\u3053\u306E error \u306F instance \u3054\u3068\u306B\u521D\u56DE\u306E\u307F\u3001\u4EE5\u964D\u306F warn\uFF09:",
|
|
455
|
+
cacheError
|
|
456
|
+
);
|
|
457
|
+
} else {
|
|
458
|
+
logger.warn("Cache write failed (result still returned):", cacheError);
|
|
459
|
+
}
|
|
439
460
|
}
|
|
440
461
|
}
|
|
441
|
-
return { data: userinfo };
|
|
462
|
+
return verificationCacheDegraded ? { data: userinfo, meta: { verificationCacheDegraded: true } } : { data: userinfo };
|
|
442
463
|
}
|
|
443
464
|
async function buildCacheKey(token, suffix) {
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
}
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
465
|
+
if (typeof globalThis.crypto?.subtle?.digest !== "function") {
|
|
466
|
+
if (!missingSubtleCryptoLogged) {
|
|
467
|
+
missingSubtleCryptoLogged = true;
|
|
468
|
+
logger.error(
|
|
469
|
+
"crypto.subtle \u304C\u5229\u7528\u3067\u304D\u306A\u3044\u305F\u3081 introspect \u7D50\u679C\u306E\u30AD\u30E3\u30C3\u30B7\u30E5\u3092\u7121\u52B9\u5316\u3057\u307E\u3057\u305F\uFF08\u30CF\u30C3\u30B7\u30E5\u5316\u3055\u308C\u3066\u3044\u306A\u3044\u30C8\u30FC\u30AF\u30F3\u3092 cache key \u306B\u4F7F\u308F\u306A\u3044\u305F\u3081\u306E\u63AA\u7F6E #4119\uFF09\u3002\u5168 verification \u304C API \u547C\u3073\u51FA\u3057\u306B\u306A\u308A\u307E\u3059"
|
|
470
|
+
);
|
|
471
|
+
}
|
|
472
|
+
return null;
|
|
473
|
+
}
|
|
474
|
+
const encoder = new TextEncoder();
|
|
475
|
+
const data = encoder.encode(token);
|
|
476
|
+
const hashBuffer = await crypto.subtle.digest("SHA-256", data);
|
|
477
|
+
const hashArray = Array.from(new Uint8Array(hashBuffer));
|
|
478
|
+
const hashHex = hashArray.map((b) => b.toString(HEX_BASE).padStart(HEX_BYTE_WIDTH, "0")).join("");
|
|
479
|
+
const key = CACHE_KEY_PREFIX + hashHex;
|
|
459
480
|
return suffix ? `${key}:${suffix}` : key;
|
|
460
481
|
}
|
|
461
482
|
return verifyAccessToken;
|
|
@@ -1204,7 +1225,7 @@ function createNonContractMethods(deps) {
|
|
|
1204
1225
|
}
|
|
1205
1226
|
|
|
1206
1227
|
// src/client/version-check.ts
|
|
1207
|
-
var SDK_VERSION = "5.
|
|
1228
|
+
var SDK_VERSION = "5.22.0";
|
|
1208
1229
|
var SDK_USER_AGENT = `FFID-SDK/${SDK_VERSION} (TypeScript)`;
|
|
1209
1230
|
var SDK_VERSION_HEADER = "X-FFID-SDK-Version";
|
|
1210
1231
|
function sdkHeaders() {
|
|
@@ -1446,33 +1467,48 @@ function createOAuthTokenMethods(deps) {
|
|
|
1446
1467
|
async function signOutToken() {
|
|
1447
1468
|
const tokens = tokenStore.getTokens();
|
|
1448
1469
|
tokenStore.clearTokens();
|
|
1470
|
+
const allRevoked = {
|
|
1471
|
+
revoked: true,
|
|
1472
|
+
accessTokenRevoked: true,
|
|
1473
|
+
refreshTokenRevoked: true
|
|
1474
|
+
};
|
|
1449
1475
|
if (!tokens) {
|
|
1450
1476
|
logger.debug("No tokens to revoke");
|
|
1451
|
-
return { data:
|
|
1477
|
+
return { data: allRevoked };
|
|
1452
1478
|
}
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
);
|
|
1479
|
+
async function revokeOAuthToken(token, tokenTypeHint) {
|
|
1480
|
+
const url = `${baseUrl}${OAUTH_REVOKE_ENDPOINT}`;
|
|
1481
|
+
try {
|
|
1482
|
+
const response = await fetch(url, {
|
|
1483
|
+
method: "POST",
|
|
1484
|
+
credentials: "omit",
|
|
1485
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded", ...sdkHeaders() },
|
|
1486
|
+
body: new URLSearchParams({
|
|
1487
|
+
token,
|
|
1488
|
+
client_id: clientId,
|
|
1489
|
+
token_type_hint: tokenTypeHint
|
|
1490
|
+
}).toString()
|
|
1491
|
+
});
|
|
1492
|
+
return response.ok;
|
|
1493
|
+
} catch {
|
|
1494
|
+
return false;
|
|
1470
1495
|
}
|
|
1471
|
-
}
|
|
1472
|
-
|
|
1496
|
+
}
|
|
1497
|
+
logger.debug("Revoking tokens:", `${baseUrl}${OAUTH_REVOKE_ENDPOINT}`);
|
|
1498
|
+
const accessTokenRevoked = await revokeOAuthToken(tokens.accessToken, "access_token");
|
|
1499
|
+
if (!accessTokenRevoked) {
|
|
1500
|
+
logger.warn("\u30A2\u30AF\u30BB\u30B9\u30C8\u30FC\u30AF\u30F3\u306E\u7121\u52B9\u5316\u30EA\u30AF\u30A8\u30B9\u30C8\u304C\u30B5\u30FC\u30D0\u30FC\u3067\u5931\u6557\u3057\u307E\u3057\u305F");
|
|
1501
|
+
}
|
|
1502
|
+
const refreshTokenRevoked = tokens.refreshToken ? await revokeOAuthToken(tokens.refreshToken, "refresh_token") : true;
|
|
1503
|
+
if (tokens.refreshToken && !refreshTokenRevoked) {
|
|
1504
|
+
logger.warn("\u30EA\u30D5\u30EC\u30C3\u30B7\u30E5\u30C8\u30FC\u30AF\u30F3\u306E\u7121\u52B9\u5316\u30EA\u30AF\u30A8\u30B9\u30C8\u304C\u30B5\u30FC\u30D0\u30FC\u3067\u5931\u6557\u3057\u307E\u3057\u305F");
|
|
1505
|
+
}
|
|
1506
|
+
const revoked = accessTokenRevoked && refreshTokenRevoked;
|
|
1507
|
+
if (!revoked) {
|
|
1508
|
+
logger.warn("\u30C8\u30FC\u30AF\u30F3\u7121\u52B9\u5316\u304C\u4E00\u90E8\u307E\u305F\u306F\u5168\u90E8\u5931\u6557\u3057\u307E\u3057\u305F\uFF08\u30ED\u30FC\u30AB\u30EB\u30C8\u30FC\u30AF\u30F3\u306F\u30AF\u30EA\u30A2\u6E08\u307F\uFF09");
|
|
1473
1509
|
}
|
|
1474
1510
|
logger.debug("Token sign-out completed");
|
|
1475
|
-
return { data:
|
|
1511
|
+
return { data: { revoked, accessTokenRevoked, refreshTokenRevoked } };
|
|
1476
1512
|
}
|
|
1477
1513
|
return { exchangeCodeForTokens, refreshAccessToken, signOutToken };
|
|
1478
1514
|
}
|
|
@@ -1636,7 +1672,7 @@ function createSessionMethods(deps) {
|
|
|
1636
1672
|
}
|
|
1637
1673
|
if (response.status === NO_CONTENT_STATUS) {
|
|
1638
1674
|
logger.debug("Response: 204 No Content");
|
|
1639
|
-
return { data:
|
|
1675
|
+
return { data: { revoked: true } };
|
|
1640
1676
|
}
|
|
1641
1677
|
if (!response.ok) {
|
|
1642
1678
|
try {
|
|
@@ -1663,7 +1699,7 @@ function createSessionMethods(deps) {
|
|
|
1663
1699
|
}
|
|
1664
1700
|
}
|
|
1665
1701
|
logger.debug("Response:", response.status);
|
|
1666
|
-
return { data:
|
|
1702
|
+
return { data: { revoked: true } };
|
|
1667
1703
|
}
|
|
1668
1704
|
return { getSession, signOutCookie };
|
|
1669
1705
|
}
|
|
@@ -1881,6 +1917,9 @@ var REDIRECT_LOOP_WINDOW_MS = 6e4;
|
|
|
1881
1917
|
var REDIRECT_LOOP_THRESHOLD = 3;
|
|
1882
1918
|
var storageReadFailureLogged = false;
|
|
1883
1919
|
var storageWriteFailureLogged = false;
|
|
1920
|
+
function isLoopDetectionDegraded() {
|
|
1921
|
+
return storageReadFailureLogged || storageWriteFailureLogged;
|
|
1922
|
+
}
|
|
1884
1923
|
function logStorageReadFailure(logger, err) {
|
|
1885
1924
|
if (storageReadFailureLogged) return;
|
|
1886
1925
|
storageReadFailureLogged = true;
|
|
@@ -2025,9 +2064,16 @@ function createRedirectMethods(deps) {
|
|
|
2025
2064
|
}
|
|
2026
2065
|
const state = generateRandomState();
|
|
2027
2066
|
if (!storeState(state, logger)) {
|
|
2067
|
+
cleanupVerifierStorage(logger);
|
|
2068
|
+
cleanupStateStorage(logger);
|
|
2028
2069
|
logger.error(
|
|
2029
|
-
"redirectToAuthorize: OAuth state \u3092\u4FDD\u5B58\u3067\u304D\u307E\u305B\u3093\u3067\u3057\u305F\uFF08storage \u5229\u7528\u4E0D\u53EF\uFF09\u3002\
|
|
2070
|
+
"redirectToAuthorize: OAuth state \u3092\u4FDD\u5B58\u3067\u304D\u307E\u305B\u3093\u3067\u3057\u305F\uFF08storage \u5229\u7528\u4E0D\u53EF\uFF09\u3002\u8A8D\u53EF\u30EA\u30C0\u30A4\u30EC\u30AF\u30C8\u3092\u4E2D\u6B62\u3057\u307E\u3057\u305F"
|
|
2030
2071
|
);
|
|
2072
|
+
return {
|
|
2073
|
+
success: false,
|
|
2074
|
+
code: "state_persistence_failed",
|
|
2075
|
+
error: "OAuth state \u3092\u4FDD\u5B58\u3067\u304D\u307E\u305B\u3093\u3067\u3057\u305F\uFF08storage \u5229\u7528\u4E0D\u53EF\uFF09\u3002\u8A8D\u53EF\u30EA\u30C0\u30A4\u30EC\u30AF\u30C8\u3092\u4E2D\u6B62\u3057\u307E\u3057\u305F"
|
|
2076
|
+
};
|
|
2031
2077
|
}
|
|
2032
2078
|
const redirectUri = resolvedRedirectUri ?? window.location.origin + window.location.pathname;
|
|
2033
2079
|
const params = new URLSearchParams({
|
|
@@ -2062,7 +2108,7 @@ function createRedirectMethods(deps) {
|
|
|
2062
2108
|
rollbackLastRedirectAttempt(authorizeKey, logger);
|
|
2063
2109
|
throw err;
|
|
2064
2110
|
}
|
|
2065
|
-
return { success: true };
|
|
2111
|
+
return isLoopDetectionDegraded() ? { success: true, loopDetectionDisabled: true } : { success: true };
|
|
2066
2112
|
}
|
|
2067
2113
|
async function redirectToLogin(options) {
|
|
2068
2114
|
if (typeof window === "undefined") {
|
|
@@ -3452,7 +3498,7 @@ async function handleRedirectCallback(client, options) {
|
|
|
3452
3498
|
};
|
|
3453
3499
|
}
|
|
3454
3500
|
logger.error(
|
|
3455
|
-
"handleRedirectCallback: OAuth state mismatch or omitted (possible CSRF) \u2014 \u8A8D\u8A3C\u3092\u4E2D\u6B62\u3057\u307E\u3057\u305F"
|
|
3501
|
+
"handleRedirectCallback: OAuth state mismatch or omitted (possible CSRF) \u2014 \u8A8D\u8A3C\u3092\u4E2D\u6B62\u3057\u307E\u3057\u305F\u3002\u6D88\u8CBB\u6E08\u307F callback \u30D1\u30E9\u30E1\u30FC\u30BF\u306E\u518D\u51E6\u7406\uFF08`cleanUrl: false` \u5229\u7528\u6642\u306E cleanup \u6F0F\u308C\u3084 reload \u7B49\uFF09\u3067\u3082\u767A\u751F\u3057\u307E\u3059"
|
|
3456
3502
|
);
|
|
3457
3503
|
return {
|
|
3458
3504
|
error: client.createError(
|
|
@@ -3465,7 +3511,11 @@ async function handleRedirectCallback(client, options) {
|
|
|
3465
3511
|
const verifier = retrieveCodeVerifier(logger);
|
|
3466
3512
|
const result = await client.exchangeCodeForTokens(code, verifier ?? void 0);
|
|
3467
3513
|
if (!result.error && hasWindow) {
|
|
3468
|
-
|
|
3514
|
+
if (options?.cleanUrl !== false) {
|
|
3515
|
+
cleanCallbackUrlParams(logger);
|
|
3516
|
+
} else {
|
|
3517
|
+
logger.debug("handleRedirectCallback: cleanUrl=false \u306E\u305F\u3081 URL cleanup \u3092 consumer \u306B\u59D4\u8B72\u3057\u307E\u3057\u305F");
|
|
3518
|
+
}
|
|
3469
3519
|
}
|
|
3470
3520
|
return result;
|
|
3471
3521
|
}
|
|
@@ -3484,7 +3534,8 @@ function FFIDProvider({
|
|
|
3484
3534
|
onAuthStateChange,
|
|
3485
3535
|
onError,
|
|
3486
3536
|
authMode,
|
|
3487
|
-
clientId
|
|
3537
|
+
clientId,
|
|
3538
|
+
cleanCallbackUrl = true
|
|
3488
3539
|
}) {
|
|
3489
3540
|
const [user, setUser] = useState(null);
|
|
3490
3541
|
const [organizations, setOrganizations] = useState([]);
|
|
@@ -3636,14 +3687,17 @@ function FFIDProvider({
|
|
|
3636
3687
|
},
|
|
3637
3688
|
[organizations, client]
|
|
3638
3689
|
);
|
|
3690
|
+
const handledCodeRef = useRef(null);
|
|
3639
3691
|
useEffect(() => {
|
|
3640
3692
|
if (client.authMode !== "token") return;
|
|
3641
3693
|
if (typeof window === "undefined") return;
|
|
3642
3694
|
const urlParams = new URLSearchParams(window.location.search);
|
|
3643
3695
|
const code = urlParams.get("code");
|
|
3644
3696
|
if (!code) return;
|
|
3697
|
+
if (handledCodeRef.current === code) return;
|
|
3698
|
+
handledCodeRef.current = code;
|
|
3645
3699
|
client.logger.debug("Authorization code detected, exchanging for tokens");
|
|
3646
|
-
handleRedirectCallback(client).then((result) => {
|
|
3700
|
+
handleRedirectCallback(client, { cleanUrl: cleanCallbackUrl }).then((result) => {
|
|
3647
3701
|
if (result.error) {
|
|
3648
3702
|
client.logger.error("Token exchange failed:", result.error);
|
|
3649
3703
|
setError(result.error);
|
|
@@ -4329,7 +4383,8 @@ var quietLogger = {
|
|
|
4329
4383
|
},
|
|
4330
4384
|
warn: () => {
|
|
4331
4385
|
},
|
|
4332
|
-
error: (
|
|
4386
|
+
error: () => {
|
|
4387
|
+
}
|
|
4333
4388
|
};
|
|
4334
4389
|
var consoleLogger2 = {
|
|
4335
4390
|
debug: (...args) => console.debug(SDK_LOG_PREFIX2, ...args),
|
|
@@ -266,10 +266,12 @@ var OAUTH_INTROSPECT_ENDPOINT = "/api/v1/oauth/introspect";
|
|
|
266
266
|
var CACHE_KEY_PREFIX = "ffid:introspect:";
|
|
267
267
|
var HEX_BASE = 16;
|
|
268
268
|
var HEX_BYTE_WIDTH = 2;
|
|
269
|
-
var TOKEN_LOG_PREFIX_LENGTH = 8;
|
|
270
269
|
function createVerifyAccessToken(deps) {
|
|
271
270
|
const { authMode, baseUrl, serviceCode, serviceApiKey, verifyStrategy, logger, createError, errorCodes, cache, timeout } = deps;
|
|
272
271
|
let jwtVerify2 = null;
|
|
272
|
+
let cacheReadFailureEscalated = false;
|
|
273
|
+
let cacheWriteFailureEscalated = false;
|
|
274
|
+
let missingSubtleCryptoLogged = false;
|
|
273
275
|
function getJwtVerifier() {
|
|
274
276
|
if (!jwtVerify2) {
|
|
275
277
|
jwtVerify2 = createJwtVerifier({
|
|
@@ -329,8 +331,9 @@ function createVerifyAccessToken(deps) {
|
|
|
329
331
|
}
|
|
330
332
|
const url = `${baseUrl}${OAUTH_INTROSPECT_ENDPOINT}`;
|
|
331
333
|
logger.debug("Verifying access token:", url);
|
|
332
|
-
const cacheKey = cache ? await buildCacheKey(accessToken, cacheKeySuffix) :
|
|
333
|
-
|
|
334
|
+
const cacheKey = cache ? await buildCacheKey(accessToken, cacheKeySuffix) : null;
|
|
335
|
+
let verificationCacheDegraded = cache !== void 0 && cacheKey === null;
|
|
336
|
+
if (cache && cacheKey !== null) {
|
|
334
337
|
try {
|
|
335
338
|
const cached = await cache.adapter.get(cacheKey);
|
|
336
339
|
if (cached && typeof cached === "object" && "sub" in cached) {
|
|
@@ -338,7 +341,16 @@ function createVerifyAccessToken(deps) {
|
|
|
338
341
|
return { data: cached };
|
|
339
342
|
}
|
|
340
343
|
} catch (cacheError) {
|
|
341
|
-
|
|
344
|
+
verificationCacheDegraded = true;
|
|
345
|
+
if (!cacheReadFailureEscalated) {
|
|
346
|
+
cacheReadFailureEscalated = true;
|
|
347
|
+
logger.error(
|
|
348
|
+
"Cache read failed \u2014 falling back to API call. Redis \u7B49\u306E cache adapter \u969C\u5BB3\u6642\u306F\u5168 verification \u304C introspect \u306B degrade \u3057\u307E\u3059\uFF08\u3053\u306E error \u306F instance \u3054\u3068\u306B\u521D\u56DE\u306E\u307F\u3001\u4EE5\u964D\u306F warn\uFF09:",
|
|
349
|
+
cacheError
|
|
350
|
+
);
|
|
351
|
+
} else {
|
|
352
|
+
logger.warn("Cache read failed, falling back to API call:", cacheError);
|
|
353
|
+
}
|
|
342
354
|
}
|
|
343
355
|
}
|
|
344
356
|
const fetchOptions = {
|
|
@@ -433,31 +445,40 @@ function createVerifyAccessToken(deps) {
|
|
|
433
445
|
}
|
|
434
446
|
} : base;
|
|
435
447
|
const userinfo = normalizeUserinfo(raw);
|
|
436
|
-
if (cache) {
|
|
448
|
+
if (cache && cacheKey !== null) {
|
|
437
449
|
try {
|
|
438
450
|
await cache.adapter.set(cacheKey, userinfo, cache.ttl);
|
|
439
451
|
} catch (cacheError) {
|
|
440
|
-
|
|
452
|
+
verificationCacheDegraded = true;
|
|
453
|
+
if (!cacheWriteFailureEscalated) {
|
|
454
|
+
cacheWriteFailureEscalated = true;
|
|
455
|
+
logger.error(
|
|
456
|
+
"Cache write failed \u2014 result still returned. cache adapter \u969C\u5BB3\u6642\u306F introspect \u7D50\u679C\u304C\u30AD\u30E3\u30C3\u30B7\u30E5\u3055\u308C\u305A\u5168 verification \u304C API \u547C\u3073\u51FA\u3057\u306B degrade \u3057\u307E\u3059\uFF08\u3053\u306E error \u306F instance \u3054\u3068\u306B\u521D\u56DE\u306E\u307F\u3001\u4EE5\u964D\u306F warn\uFF09:",
|
|
457
|
+
cacheError
|
|
458
|
+
);
|
|
459
|
+
} else {
|
|
460
|
+
logger.warn("Cache write failed (result still returned):", cacheError);
|
|
461
|
+
}
|
|
441
462
|
}
|
|
442
463
|
}
|
|
443
|
-
return { data: userinfo };
|
|
464
|
+
return verificationCacheDegraded ? { data: userinfo, meta: { verificationCacheDegraded: true } } : { data: userinfo };
|
|
444
465
|
}
|
|
445
466
|
async function buildCacheKey(token, suffix) {
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
}
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
467
|
+
if (typeof globalThis.crypto?.subtle?.digest !== "function") {
|
|
468
|
+
if (!missingSubtleCryptoLogged) {
|
|
469
|
+
missingSubtleCryptoLogged = true;
|
|
470
|
+
logger.error(
|
|
471
|
+
"crypto.subtle \u304C\u5229\u7528\u3067\u304D\u306A\u3044\u305F\u3081 introspect \u7D50\u679C\u306E\u30AD\u30E3\u30C3\u30B7\u30E5\u3092\u7121\u52B9\u5316\u3057\u307E\u3057\u305F\uFF08\u30CF\u30C3\u30B7\u30E5\u5316\u3055\u308C\u3066\u3044\u306A\u3044\u30C8\u30FC\u30AF\u30F3\u3092 cache key \u306B\u4F7F\u308F\u306A\u3044\u305F\u3081\u306E\u63AA\u7F6E #4119\uFF09\u3002\u5168 verification \u304C API \u547C\u3073\u51FA\u3057\u306B\u306A\u308A\u307E\u3059"
|
|
472
|
+
);
|
|
473
|
+
}
|
|
474
|
+
return null;
|
|
475
|
+
}
|
|
476
|
+
const encoder = new TextEncoder();
|
|
477
|
+
const data = encoder.encode(token);
|
|
478
|
+
const hashBuffer = await crypto.subtle.digest("SHA-256", data);
|
|
479
|
+
const hashArray = Array.from(new Uint8Array(hashBuffer));
|
|
480
|
+
const hashHex = hashArray.map((b) => b.toString(HEX_BASE).padStart(HEX_BYTE_WIDTH, "0")).join("");
|
|
481
|
+
const key = CACHE_KEY_PREFIX + hashHex;
|
|
461
482
|
return suffix ? `${key}:${suffix}` : key;
|
|
462
483
|
}
|
|
463
484
|
return verifyAccessToken;
|
|
@@ -1206,7 +1227,7 @@ function createNonContractMethods(deps) {
|
|
|
1206
1227
|
}
|
|
1207
1228
|
|
|
1208
1229
|
// src/client/version-check.ts
|
|
1209
|
-
var SDK_VERSION = "5.
|
|
1230
|
+
var SDK_VERSION = "5.22.0";
|
|
1210
1231
|
var SDK_USER_AGENT = `FFID-SDK/${SDK_VERSION} (TypeScript)`;
|
|
1211
1232
|
var SDK_VERSION_HEADER = "X-FFID-SDK-Version";
|
|
1212
1233
|
function sdkHeaders() {
|
|
@@ -1448,33 +1469,48 @@ function createOAuthTokenMethods(deps) {
|
|
|
1448
1469
|
async function signOutToken() {
|
|
1449
1470
|
const tokens = tokenStore.getTokens();
|
|
1450
1471
|
tokenStore.clearTokens();
|
|
1472
|
+
const allRevoked = {
|
|
1473
|
+
revoked: true,
|
|
1474
|
+
accessTokenRevoked: true,
|
|
1475
|
+
refreshTokenRevoked: true
|
|
1476
|
+
};
|
|
1451
1477
|
if (!tokens) {
|
|
1452
1478
|
logger.debug("No tokens to revoke");
|
|
1453
|
-
return { data:
|
|
1479
|
+
return { data: allRevoked };
|
|
1454
1480
|
}
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
);
|
|
1481
|
+
async function revokeOAuthToken(token, tokenTypeHint) {
|
|
1482
|
+
const url = `${baseUrl}${OAUTH_REVOKE_ENDPOINT}`;
|
|
1483
|
+
try {
|
|
1484
|
+
const response = await fetch(url, {
|
|
1485
|
+
method: "POST",
|
|
1486
|
+
credentials: "omit",
|
|
1487
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded", ...sdkHeaders() },
|
|
1488
|
+
body: new URLSearchParams({
|
|
1489
|
+
token,
|
|
1490
|
+
client_id: clientId,
|
|
1491
|
+
token_type_hint: tokenTypeHint
|
|
1492
|
+
}).toString()
|
|
1493
|
+
});
|
|
1494
|
+
return response.ok;
|
|
1495
|
+
} catch {
|
|
1496
|
+
return false;
|
|
1472
1497
|
}
|
|
1473
|
-
}
|
|
1474
|
-
|
|
1498
|
+
}
|
|
1499
|
+
logger.debug("Revoking tokens:", `${baseUrl}${OAUTH_REVOKE_ENDPOINT}`);
|
|
1500
|
+
const accessTokenRevoked = await revokeOAuthToken(tokens.accessToken, "access_token");
|
|
1501
|
+
if (!accessTokenRevoked) {
|
|
1502
|
+
logger.warn("\u30A2\u30AF\u30BB\u30B9\u30C8\u30FC\u30AF\u30F3\u306E\u7121\u52B9\u5316\u30EA\u30AF\u30A8\u30B9\u30C8\u304C\u30B5\u30FC\u30D0\u30FC\u3067\u5931\u6557\u3057\u307E\u3057\u305F");
|
|
1503
|
+
}
|
|
1504
|
+
const refreshTokenRevoked = tokens.refreshToken ? await revokeOAuthToken(tokens.refreshToken, "refresh_token") : true;
|
|
1505
|
+
if (tokens.refreshToken && !refreshTokenRevoked) {
|
|
1506
|
+
logger.warn("\u30EA\u30D5\u30EC\u30C3\u30B7\u30E5\u30C8\u30FC\u30AF\u30F3\u306E\u7121\u52B9\u5316\u30EA\u30AF\u30A8\u30B9\u30C8\u304C\u30B5\u30FC\u30D0\u30FC\u3067\u5931\u6557\u3057\u307E\u3057\u305F");
|
|
1507
|
+
}
|
|
1508
|
+
const revoked = accessTokenRevoked && refreshTokenRevoked;
|
|
1509
|
+
if (!revoked) {
|
|
1510
|
+
logger.warn("\u30C8\u30FC\u30AF\u30F3\u7121\u52B9\u5316\u304C\u4E00\u90E8\u307E\u305F\u306F\u5168\u90E8\u5931\u6557\u3057\u307E\u3057\u305F\uFF08\u30ED\u30FC\u30AB\u30EB\u30C8\u30FC\u30AF\u30F3\u306F\u30AF\u30EA\u30A2\u6E08\u307F\uFF09");
|
|
1475
1511
|
}
|
|
1476
1512
|
logger.debug("Token sign-out completed");
|
|
1477
|
-
return { data:
|
|
1513
|
+
return { data: { revoked, accessTokenRevoked, refreshTokenRevoked } };
|
|
1478
1514
|
}
|
|
1479
1515
|
return { exchangeCodeForTokens, refreshAccessToken, signOutToken };
|
|
1480
1516
|
}
|
|
@@ -1638,7 +1674,7 @@ function createSessionMethods(deps) {
|
|
|
1638
1674
|
}
|
|
1639
1675
|
if (response.status === NO_CONTENT_STATUS) {
|
|
1640
1676
|
logger.debug("Response: 204 No Content");
|
|
1641
|
-
return { data:
|
|
1677
|
+
return { data: { revoked: true } };
|
|
1642
1678
|
}
|
|
1643
1679
|
if (!response.ok) {
|
|
1644
1680
|
try {
|
|
@@ -1665,7 +1701,7 @@ function createSessionMethods(deps) {
|
|
|
1665
1701
|
}
|
|
1666
1702
|
}
|
|
1667
1703
|
logger.debug("Response:", response.status);
|
|
1668
|
-
return { data:
|
|
1704
|
+
return { data: { revoked: true } };
|
|
1669
1705
|
}
|
|
1670
1706
|
return { getSession, signOutCookie };
|
|
1671
1707
|
}
|
|
@@ -1883,6 +1919,9 @@ var REDIRECT_LOOP_WINDOW_MS = 6e4;
|
|
|
1883
1919
|
var REDIRECT_LOOP_THRESHOLD = 3;
|
|
1884
1920
|
var storageReadFailureLogged = false;
|
|
1885
1921
|
var storageWriteFailureLogged = false;
|
|
1922
|
+
function isLoopDetectionDegraded() {
|
|
1923
|
+
return storageReadFailureLogged || storageWriteFailureLogged;
|
|
1924
|
+
}
|
|
1886
1925
|
function logStorageReadFailure(logger, err) {
|
|
1887
1926
|
if (storageReadFailureLogged) return;
|
|
1888
1927
|
storageReadFailureLogged = true;
|
|
@@ -2027,9 +2066,16 @@ function createRedirectMethods(deps) {
|
|
|
2027
2066
|
}
|
|
2028
2067
|
const state = generateRandomState();
|
|
2029
2068
|
if (!storeState(state, logger)) {
|
|
2069
|
+
cleanupVerifierStorage(logger);
|
|
2070
|
+
cleanupStateStorage(logger);
|
|
2030
2071
|
logger.error(
|
|
2031
|
-
"redirectToAuthorize: OAuth state \u3092\u4FDD\u5B58\u3067\u304D\u307E\u305B\u3093\u3067\u3057\u305F\uFF08storage \u5229\u7528\u4E0D\u53EF\uFF09\u3002\
|
|
2072
|
+
"redirectToAuthorize: OAuth state \u3092\u4FDD\u5B58\u3067\u304D\u307E\u305B\u3093\u3067\u3057\u305F\uFF08storage \u5229\u7528\u4E0D\u53EF\uFF09\u3002\u8A8D\u53EF\u30EA\u30C0\u30A4\u30EC\u30AF\u30C8\u3092\u4E2D\u6B62\u3057\u307E\u3057\u305F"
|
|
2032
2073
|
);
|
|
2074
|
+
return {
|
|
2075
|
+
success: false,
|
|
2076
|
+
code: "state_persistence_failed",
|
|
2077
|
+
error: "OAuth state \u3092\u4FDD\u5B58\u3067\u304D\u307E\u305B\u3093\u3067\u3057\u305F\uFF08storage \u5229\u7528\u4E0D\u53EF\uFF09\u3002\u8A8D\u53EF\u30EA\u30C0\u30A4\u30EC\u30AF\u30C8\u3092\u4E2D\u6B62\u3057\u307E\u3057\u305F"
|
|
2078
|
+
};
|
|
2033
2079
|
}
|
|
2034
2080
|
const redirectUri = resolvedRedirectUri ?? window.location.origin + window.location.pathname;
|
|
2035
2081
|
const params = new URLSearchParams({
|
|
@@ -2064,7 +2110,7 @@ function createRedirectMethods(deps) {
|
|
|
2064
2110
|
rollbackLastRedirectAttempt(authorizeKey, logger);
|
|
2065
2111
|
throw err;
|
|
2066
2112
|
}
|
|
2067
|
-
return { success: true };
|
|
2113
|
+
return isLoopDetectionDegraded() ? { success: true, loopDetectionDisabled: true } : { success: true };
|
|
2068
2114
|
}
|
|
2069
2115
|
async function redirectToLogin(options) {
|
|
2070
2116
|
if (typeof window === "undefined") {
|
|
@@ -3454,7 +3500,7 @@ async function handleRedirectCallback(client, options) {
|
|
|
3454
3500
|
};
|
|
3455
3501
|
}
|
|
3456
3502
|
logger.error(
|
|
3457
|
-
"handleRedirectCallback: OAuth state mismatch or omitted (possible CSRF) \u2014 \u8A8D\u8A3C\u3092\u4E2D\u6B62\u3057\u307E\u3057\u305F"
|
|
3503
|
+
"handleRedirectCallback: OAuth state mismatch or omitted (possible CSRF) \u2014 \u8A8D\u8A3C\u3092\u4E2D\u6B62\u3057\u307E\u3057\u305F\u3002\u6D88\u8CBB\u6E08\u307F callback \u30D1\u30E9\u30E1\u30FC\u30BF\u306E\u518D\u51E6\u7406\uFF08`cleanUrl: false` \u5229\u7528\u6642\u306E cleanup \u6F0F\u308C\u3084 reload \u7B49\uFF09\u3067\u3082\u767A\u751F\u3057\u307E\u3059"
|
|
3458
3504
|
);
|
|
3459
3505
|
return {
|
|
3460
3506
|
error: client.createError(
|
|
@@ -3467,7 +3513,11 @@ async function handleRedirectCallback(client, options) {
|
|
|
3467
3513
|
const verifier = retrieveCodeVerifier(logger);
|
|
3468
3514
|
const result = await client.exchangeCodeForTokens(code, verifier ?? void 0);
|
|
3469
3515
|
if (!result.error && hasWindow) {
|
|
3470
|
-
|
|
3516
|
+
if (options?.cleanUrl !== false) {
|
|
3517
|
+
cleanCallbackUrlParams(logger);
|
|
3518
|
+
} else {
|
|
3519
|
+
logger.debug("handleRedirectCallback: cleanUrl=false \u306E\u305F\u3081 URL cleanup \u3092 consumer \u306B\u59D4\u8B72\u3057\u307E\u3057\u305F");
|
|
3520
|
+
}
|
|
3471
3521
|
}
|
|
3472
3522
|
return result;
|
|
3473
3523
|
}
|
|
@@ -3486,7 +3536,8 @@ function FFIDProvider({
|
|
|
3486
3536
|
onAuthStateChange,
|
|
3487
3537
|
onError,
|
|
3488
3538
|
authMode,
|
|
3489
|
-
clientId
|
|
3539
|
+
clientId,
|
|
3540
|
+
cleanCallbackUrl = true
|
|
3490
3541
|
}) {
|
|
3491
3542
|
const [user, setUser] = react.useState(null);
|
|
3492
3543
|
const [organizations, setOrganizations] = react.useState([]);
|
|
@@ -3638,14 +3689,17 @@ function FFIDProvider({
|
|
|
3638
3689
|
},
|
|
3639
3690
|
[organizations, client]
|
|
3640
3691
|
);
|
|
3692
|
+
const handledCodeRef = react.useRef(null);
|
|
3641
3693
|
react.useEffect(() => {
|
|
3642
3694
|
if (client.authMode !== "token") return;
|
|
3643
3695
|
if (typeof window === "undefined") return;
|
|
3644
3696
|
const urlParams = new URLSearchParams(window.location.search);
|
|
3645
3697
|
const code = urlParams.get("code");
|
|
3646
3698
|
if (!code) return;
|
|
3699
|
+
if (handledCodeRef.current === code) return;
|
|
3700
|
+
handledCodeRef.current = code;
|
|
3647
3701
|
client.logger.debug("Authorization code detected, exchanging for tokens");
|
|
3648
|
-
handleRedirectCallback(client).then((result) => {
|
|
3702
|
+
handleRedirectCallback(client, { cleanUrl: cleanCallbackUrl }).then((result) => {
|
|
3649
3703
|
if (result.error) {
|
|
3650
3704
|
client.logger.error("Token exchange failed:", result.error);
|
|
3651
3705
|
setError(result.error);
|
|
@@ -4331,7 +4385,8 @@ var quietLogger = {
|
|
|
4331
4385
|
},
|
|
4332
4386
|
warn: () => {
|
|
4333
4387
|
},
|
|
4334
|
-
error: (
|
|
4388
|
+
error: () => {
|
|
4389
|
+
}
|
|
4335
4390
|
};
|
|
4336
4391
|
var consoleLogger2 = {
|
|
4337
4392
|
debug: (...args) => console.debug(SDK_LOG_PREFIX2, ...args),
|