@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.
Files changed (34) hide show
  1. package/README.md +1 -0
  2. package/dist/agency/index.cjs +4 -2
  3. package/dist/agency/index.js +4 -2
  4. package/dist/announcements/index.cjs +2 -1
  5. package/dist/announcements/index.js +2 -1
  6. package/dist/{chunk-D3PZ6SZB.js → chunk-7NRO2TBG.js} +108 -53
  7. package/dist/{chunk-TW2FXASO.cjs → chunk-EM5EGNGE.cjs} +108 -53
  8. package/dist/components/index.cjs +8 -8
  9. package/dist/components/index.d.cts +1 -1
  10. package/dist/components/index.d.ts +1 -1
  11. package/dist/components/index.js +1 -1
  12. package/dist/{ffid-client-BuDM5tmq.d.cts → ffid-client-CEJMpqRZ.d.cts} +55 -9
  13. package/dist/{ffid-client-D0x8TBS5.d.ts → ffid-client-DM0ZhIDV.d.ts} +55 -9
  14. package/dist/{index-Bkul-fRw.d.cts → index-btouGGKr.d.cts} +56 -9
  15. package/dist/{index-Bkul-fRw.d.ts → index-btouGGKr.d.ts} +56 -9
  16. package/dist/index.cjs +42 -42
  17. package/dist/index.d.cts +44 -8
  18. package/dist/index.d.ts +44 -8
  19. package/dist/index.js +2 -2
  20. package/dist/legal/index.cjs +2 -1
  21. package/dist/legal/index.js +2 -1
  22. package/dist/server/index.cjs +94 -48
  23. package/dist/server/index.d.cts +3 -3
  24. package/dist/server/index.d.ts +3 -3
  25. package/dist/server/index.js +94 -48
  26. package/dist/server/test/index.d.cts +2 -2
  27. package/dist/server/test/index.d.ts +2 -2
  28. package/dist/{types-BeVl-z12.d.cts → types-Cjb9J0rL.d.cts} +2 -1
  29. package/dist/{types-BeVl-z12.d.ts → types-Cjb9J0rL.d.ts} +2 -1
  30. package/dist/webhooks/index.cjs +2 -2
  31. package/dist/webhooks/index.d.cts +1 -1
  32. package/dist/webhooks/index.d.ts +1 -1
  33. package/dist/webhooks/index.js +2 -2
  34. package/package.json +1 -1
@@ -261,10 +261,12 @@ var OAUTH_INTROSPECT_ENDPOINT = "/api/v1/oauth/introspect";
261
261
  var CACHE_KEY_PREFIX = "ffid:introspect:";
262
262
  var HEX_BASE = 16;
263
263
  var HEX_BYTE_WIDTH = 2;
264
- var TOKEN_LOG_PREFIX_LENGTH = 8;
265
264
  function createVerifyAccessToken(deps) {
266
265
  const { authMode, baseUrl, serviceCode, serviceApiKey, verifyStrategy, logger, createError, errorCodes, cache, timeout } = deps;
267
266
  let jwtVerify2 = null;
267
+ let cacheReadFailureEscalated = false;
268
+ let cacheWriteFailureEscalated = false;
269
+ let missingSubtleCryptoLogged = false;
268
270
  function getJwtVerifier() {
269
271
  if (!jwtVerify2) {
270
272
  jwtVerify2 = createJwtVerifier({
@@ -324,8 +326,9 @@ function createVerifyAccessToken(deps) {
324
326
  }
325
327
  const url = `${baseUrl}${OAUTH_INTROSPECT_ENDPOINT}`;
326
328
  logger.debug("Verifying access token:", url);
327
- const cacheKey = cache ? await buildCacheKey(accessToken, cacheKeySuffix) : "";
328
- if (cache) {
329
+ const cacheKey = cache ? await buildCacheKey(accessToken, cacheKeySuffix) : null;
330
+ let verificationCacheDegraded = cache !== void 0 && cacheKey === null;
331
+ if (cache && cacheKey !== null) {
329
332
  try {
330
333
  const cached = await cache.adapter.get(cacheKey);
331
334
  if (cached && typeof cached === "object" && "sub" in cached) {
@@ -333,7 +336,16 @@ function createVerifyAccessToken(deps) {
333
336
  return { data: cached };
334
337
  }
335
338
  } catch (cacheError) {
336
- logger.warn("Cache read failed, falling back to API call:", cacheError);
339
+ verificationCacheDegraded = true;
340
+ if (!cacheReadFailureEscalated) {
341
+ cacheReadFailureEscalated = true;
342
+ logger.error(
343
+ "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:",
344
+ cacheError
345
+ );
346
+ } else {
347
+ logger.warn("Cache read failed, falling back to API call:", cacheError);
348
+ }
337
349
  }
338
350
  }
339
351
  const fetchOptions = {
@@ -428,31 +440,40 @@ function createVerifyAccessToken(deps) {
428
440
  }
429
441
  } : base;
430
442
  const userinfo = normalizeUserinfo(raw);
431
- if (cache) {
443
+ if (cache && cacheKey !== null) {
432
444
  try {
433
445
  await cache.adapter.set(cacheKey, userinfo, cache.ttl);
434
446
  } catch (cacheError) {
435
- logger.warn("Cache write failed (result still returned):", cacheError);
447
+ verificationCacheDegraded = true;
448
+ if (!cacheWriteFailureEscalated) {
449
+ cacheWriteFailureEscalated = true;
450
+ logger.error(
451
+ "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:",
452
+ cacheError
453
+ );
454
+ } else {
455
+ logger.warn("Cache write failed (result still returned):", cacheError);
456
+ }
436
457
  }
437
458
  }
438
- return { data: userinfo };
459
+ return verificationCacheDegraded ? { data: userinfo, meta: { verificationCacheDegraded: true } } : { data: userinfo };
439
460
  }
440
461
  async function buildCacheKey(token, suffix) {
441
- let key;
442
- if (typeof globalThis.crypto?.subtle?.digest === "function") {
443
- const encoder = new TextEncoder();
444
- const data = encoder.encode(token);
445
- const hashBuffer = await crypto.subtle.digest("SHA-256", data);
446
- const hashArray = Array.from(new Uint8Array(hashBuffer));
447
- const hashHex = hashArray.map((b) => b.toString(HEX_BASE).padStart(HEX_BYTE_WIDTH, "0")).join("");
448
- key = CACHE_KEY_PREFIX + hashHex;
449
- } else {
450
- const tokenPrefix = token.length > TOKEN_LOG_PREFIX_LENGTH ? token.substring(0, TOKEN_LOG_PREFIX_LENGTH) + "..." : "***";
451
- logger.warn(
452
- `crypto.subtle \u304C\u5229\u7528\u3067\u304D\u306A\u3044\u305F\u3081\u3001\u30AD\u30E3\u30C3\u30B7\u30E5\u30AD\u30FC\u306B\u30CF\u30C3\u30B7\u30E5\u5316\u3055\u308C\u3066\u3044\u306A\u3044\u30C8\u30FC\u30AF\u30F3\u3092\u4F7F\u7528\u3057\u307E\u3059 (token prefix: ${tokenPrefix})`
453
- );
454
- key = CACHE_KEY_PREFIX + token;
455
- }
462
+ if (typeof globalThis.crypto?.subtle?.digest !== "function") {
463
+ if (!missingSubtleCryptoLogged) {
464
+ missingSubtleCryptoLogged = true;
465
+ logger.error(
466
+ "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"
467
+ );
468
+ }
469
+ return null;
470
+ }
471
+ const encoder = new TextEncoder();
472
+ const data = encoder.encode(token);
473
+ const hashBuffer = await crypto.subtle.digest("SHA-256", data);
474
+ const hashArray = Array.from(new Uint8Array(hashBuffer));
475
+ const hashHex = hashArray.map((b) => b.toString(HEX_BASE).padStart(HEX_BYTE_WIDTH, "0")).join("");
476
+ const key = CACHE_KEY_PREFIX + hashHex;
456
477
  return suffix ? `${key}:${suffix}` : key;
457
478
  }
458
479
  return verifyAccessToken;
@@ -1201,7 +1222,7 @@ function createNonContractMethods(deps) {
1201
1222
  }
1202
1223
 
1203
1224
  // src/client/version-check.ts
1204
- var SDK_VERSION = "5.19.0";
1225
+ var SDK_VERSION = "5.22.0";
1205
1226
  var SDK_USER_AGENT = `FFID-SDK/${SDK_VERSION} (TypeScript)`;
1206
1227
  var SDK_VERSION_HEADER = "X-FFID-SDK-Version";
1207
1228
  function sdkHeaders() {
@@ -1443,33 +1464,48 @@ function createOAuthTokenMethods(deps) {
1443
1464
  async function signOutToken() {
1444
1465
  const tokens = tokenStore.getTokens();
1445
1466
  tokenStore.clearTokens();
1467
+ const allRevoked = {
1468
+ revoked: true,
1469
+ accessTokenRevoked: true,
1470
+ refreshTokenRevoked: true
1471
+ };
1446
1472
  if (!tokens) {
1447
1473
  logger.debug("No tokens to revoke");
1448
- return { data: void 0 };
1474
+ return { data: allRevoked };
1449
1475
  }
1450
- const url = `${baseUrl}${OAUTH_REVOKE_ENDPOINT}`;
1451
- logger.debug("Revoking token:", url);
1452
- try {
1453
- const response = await fetch(url, {
1454
- method: "POST",
1455
- credentials: "omit",
1456
- headers: { "Content-Type": "application/x-www-form-urlencoded", ...sdkHeaders() },
1457
- body: new URLSearchParams({
1458
- token: tokens.accessToken,
1459
- client_id: clientId
1460
- }).toString()
1461
- });
1462
- if (!response.ok) {
1463
- logger.warn(
1464
- "\u30C8\u30FC\u30AF\u30F3\u7121\u52B9\u5316\u30EA\u30AF\u30A8\u30B9\u30C8\u304C\u30B5\u30FC\u30D0\u30FC\u3067\u5931\u6557\u3057\u307E\u3057\u305F:",
1465
- `status=${response.status}`
1466
- );
1476
+ async function revokeOAuthToken(token, tokenTypeHint) {
1477
+ const url = `${baseUrl}${OAUTH_REVOKE_ENDPOINT}`;
1478
+ try {
1479
+ const response = await fetch(url, {
1480
+ method: "POST",
1481
+ credentials: "omit",
1482
+ headers: { "Content-Type": "application/x-www-form-urlencoded", ...sdkHeaders() },
1483
+ body: new URLSearchParams({
1484
+ token,
1485
+ client_id: clientId,
1486
+ token_type_hint: tokenTypeHint
1487
+ }).toString()
1488
+ });
1489
+ return response.ok;
1490
+ } catch {
1491
+ return false;
1467
1492
  }
1468
- } catch (error) {
1469
- logger.warn("\u30C8\u30FC\u30AF\u30F3\u7121\u52B9\u5316\u306E\u30CD\u30C3\u30C8\u30EF\u30FC\u30AF\u30EA\u30AF\u30A8\u30B9\u30C8\u306B\u5931\u6557\u3057\u307E\u3057\u305F:", error);
1493
+ }
1494
+ logger.debug("Revoking tokens:", `${baseUrl}${OAUTH_REVOKE_ENDPOINT}`);
1495
+ const accessTokenRevoked = await revokeOAuthToken(tokens.accessToken, "access_token");
1496
+ if (!accessTokenRevoked) {
1497
+ 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");
1498
+ }
1499
+ const refreshTokenRevoked = tokens.refreshToken ? await revokeOAuthToken(tokens.refreshToken, "refresh_token") : true;
1500
+ if (tokens.refreshToken && !refreshTokenRevoked) {
1501
+ 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");
1502
+ }
1503
+ const revoked = accessTokenRevoked && refreshTokenRevoked;
1504
+ if (!revoked) {
1505
+ 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");
1470
1506
  }
1471
1507
  logger.debug("Token sign-out completed");
1472
- return { data: void 0 };
1508
+ return { data: { revoked, accessTokenRevoked, refreshTokenRevoked } };
1473
1509
  }
1474
1510
  return { exchangeCodeForTokens, refreshAccessToken, signOutToken };
1475
1511
  }
@@ -1633,7 +1669,7 @@ function createSessionMethods(deps) {
1633
1669
  }
1634
1670
  if (response.status === NO_CONTENT_STATUS) {
1635
1671
  logger.debug("Response: 204 No Content");
1636
- return { data: void 0 };
1672
+ return { data: { revoked: true } };
1637
1673
  }
1638
1674
  if (!response.ok) {
1639
1675
  try {
@@ -1660,7 +1696,7 @@ function createSessionMethods(deps) {
1660
1696
  }
1661
1697
  }
1662
1698
  logger.debug("Response:", response.status);
1663
- return { data: void 0 };
1699
+ return { data: { revoked: true } };
1664
1700
  }
1665
1701
  return { getSession, signOutCookie };
1666
1702
  }
@@ -1783,6 +1819,9 @@ var REDIRECT_LOOP_WINDOW_MS = 6e4;
1783
1819
  var REDIRECT_LOOP_THRESHOLD = 3;
1784
1820
  var storageReadFailureLogged = false;
1785
1821
  var storageWriteFailureLogged = false;
1822
+ function isLoopDetectionDegraded() {
1823
+ return storageReadFailureLogged || storageWriteFailureLogged;
1824
+ }
1786
1825
  function logStorageReadFailure(logger, err) {
1787
1826
  if (storageReadFailureLogged) return;
1788
1827
  storageReadFailureLogged = true;
@@ -1927,9 +1966,16 @@ function createRedirectMethods(deps) {
1927
1966
  }
1928
1967
  const state = generateRandomState();
1929
1968
  if (!storeState(state, logger)) {
1969
+ cleanupVerifierStorage(logger);
1970
+ cleanupStateStorage(logger);
1930
1971
  logger.error(
1931
- "redirectToAuthorize: OAuth state \u3092\u4FDD\u5B58\u3067\u304D\u307E\u305B\u3093\u3067\u3057\u305F\uFF08storage \u5229\u7528\u4E0D\u53EF\uFF09\u3002\u30B3\u30FC\u30EB\u30D0\u30C3\u30AF\u3067\u306E CSRF \u691C\u8A3C\u304C\u5931\u6557\u3057\u307E\u3059"
1972
+ "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"
1932
1973
  );
1974
+ return {
1975
+ success: false,
1976
+ code: "state_persistence_failed",
1977
+ 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"
1978
+ };
1933
1979
  }
1934
1980
  const redirectUri = resolvedRedirectUri ?? window.location.origin + window.location.pathname;
1935
1981
  const params = new URLSearchParams({
@@ -1964,7 +2010,7 @@ function createRedirectMethods(deps) {
1964
2010
  rollbackLastRedirectAttempt(authorizeKey, logger);
1965
2011
  throw err;
1966
2012
  }
1967
- return { success: true };
2013
+ return isLoopDetectionDegraded() ? { success: true, loopDetectionDisabled: true } : { success: true };
1968
2014
  }
1969
2015
  async function redirectToLogin(options) {
1970
2016
  if (typeof window === "undefined") {
@@ -1,8 +1,8 @@
1
- import { F as FFIDLogger, a as FFIDError, b as FFIDCacheAdapter, c as FFIDVerifyAccessTokenOptions, d as FFIDApiResponse, e as FFIDOAuthUserInfo } from '../ffid-client-BuDM5tmq.cjs';
2
- export { f as FFIDAddMemberParams, g as FFIDAddMemberRequest, h as FFIDAddMemberResponse, i as FFIDAssignableMemberRole, j as FFIDCacheConfig, k as FFIDClient, l as FFIDConfig, m as FFIDListMembersResponse, n as FFIDMemberRole, o as FFIDOrganization, p as FFIDOrganizationMember, q as FFIDOtpSendResponse, r as FFIDOtpVerifyResponse, s as FFIDPasswordResetConfirmResponse, t as FFIDPasswordResetResponse, u as FFIDPasswordResetVerifyResponse, v as FFIDProfileCallOptions, w as FFIDProvisionMemberPlan, x as FFIDProvisionMemberPlanStatus, y as FFIDProvisionMemberResult, z as FFIDProvisionMemberStatus, A as FFIDProvisionOrganizationDryRun, B as FFIDProvisionOrganizationMemberInput, C as FFIDProvisionOrganizationOutcome, D as FFIDProvisionOrganizationParams, E as FFIDProvisionOrganizationResponse, G as FFIDProvisionUserDryRun, H as FFIDProvisionUserOutcome, I as FFIDProvisionUserParams, J as FFIDProvisionUserProfileInput, K as FFIDProvisionUserResponse, L as FFIDProvisionedOrganization, M as FFIDProvisionedUser, N as FFIDRemoveMemberResponse, O as FFIDResetSessionResponse, P as FFIDSubscription, Q as FFIDUpdateMemberRoleResponse, R as FFIDUpdateUserProfileRequest, S as FFIDUser, T as FFIDUserProfile, U as TokenData, V as TokenStore, W as createFFIDClient, X as createTokenStore } from '../ffid-client-BuDM5tmq.cjs';
1
+ import { F as FFIDLogger, a as FFIDError, b as FFIDCacheAdapter, c as FFIDVerifyAccessTokenOptions, d as FFIDApiResponse, e as FFIDOAuthUserInfo } from '../ffid-client-CEJMpqRZ.cjs';
2
+ export { f as FFIDAddMemberParams, g as FFIDAddMemberRequest, h as FFIDAddMemberResponse, i as FFIDAssignableMemberRole, j as FFIDCacheConfig, k as FFIDClient, l as FFIDConfig, m as FFIDListMembersResponse, n as FFIDMemberRole, o as FFIDOrganization, p as FFIDOrganizationMember, q as FFIDOtpSendResponse, r as FFIDOtpVerifyResponse, s as FFIDPasswordResetConfirmResponse, t as FFIDPasswordResetResponse, u as FFIDPasswordResetVerifyResponse, v as FFIDProfileCallOptions, w as FFIDProvisionMemberPlan, x as FFIDProvisionMemberPlanStatus, y as FFIDProvisionMemberResult, z as FFIDProvisionMemberStatus, A as FFIDProvisionOrganizationDryRun, B as FFIDProvisionOrganizationMemberInput, C as FFIDProvisionOrganizationOutcome, D as FFIDProvisionOrganizationParams, E as FFIDProvisionOrganizationResponse, G as FFIDProvisionUserDryRun, H as FFIDProvisionUserOutcome, I as FFIDProvisionUserParams, J as FFIDProvisionUserProfileInput, K as FFIDProvisionUserResponse, L as FFIDProvisionedOrganization, M as FFIDProvisionedUser, N as FFIDRemoveMemberResponse, O as FFIDResetSessionResponse, P as FFIDSubscription, Q as FFIDUpdateMemberRoleResponse, R as FFIDUpdateUserProfileRequest, S as FFIDUser, T as FFIDUserProfile, U as TokenData, V as TokenStore, W as createFFIDClient, X as createTokenStore } from '../ffid-client-CEJMpqRZ.cjs';
3
3
  export { D as DEFAULT_API_BASE_URL, a as DEFAULT_OAUTH_SCOPES } from '../constants-D61jqRIO.cjs';
4
4
  import { z } from 'zod';
5
- import '../types-BeVl-z12.cjs';
5
+ import '../types-Cjb9J0rL.cjs';
6
6
 
7
7
  /**
8
8
  * FFID SDK error codes
@@ -1,8 +1,8 @@
1
- import { F as FFIDLogger, a as FFIDError, b as FFIDCacheAdapter, c as FFIDVerifyAccessTokenOptions, d as FFIDApiResponse, e as FFIDOAuthUserInfo } from '../ffid-client-D0x8TBS5.js';
2
- export { f as FFIDAddMemberParams, g as FFIDAddMemberRequest, h as FFIDAddMemberResponse, i as FFIDAssignableMemberRole, j as FFIDCacheConfig, k as FFIDClient, l as FFIDConfig, m as FFIDListMembersResponse, n as FFIDMemberRole, o as FFIDOrganization, p as FFIDOrganizationMember, q as FFIDOtpSendResponse, r as FFIDOtpVerifyResponse, s as FFIDPasswordResetConfirmResponse, t as FFIDPasswordResetResponse, u as FFIDPasswordResetVerifyResponse, v as FFIDProfileCallOptions, w as FFIDProvisionMemberPlan, x as FFIDProvisionMemberPlanStatus, y as FFIDProvisionMemberResult, z as FFIDProvisionMemberStatus, A as FFIDProvisionOrganizationDryRun, B as FFIDProvisionOrganizationMemberInput, C as FFIDProvisionOrganizationOutcome, D as FFIDProvisionOrganizationParams, E as FFIDProvisionOrganizationResponse, G as FFIDProvisionUserDryRun, H as FFIDProvisionUserOutcome, I as FFIDProvisionUserParams, J as FFIDProvisionUserProfileInput, K as FFIDProvisionUserResponse, L as FFIDProvisionedOrganization, M as FFIDProvisionedUser, N as FFIDRemoveMemberResponse, O as FFIDResetSessionResponse, P as FFIDSubscription, Q as FFIDUpdateMemberRoleResponse, R as FFIDUpdateUserProfileRequest, S as FFIDUser, T as FFIDUserProfile, U as TokenData, V as TokenStore, W as createFFIDClient, X as createTokenStore } from '../ffid-client-D0x8TBS5.js';
1
+ import { F as FFIDLogger, a as FFIDError, b as FFIDCacheAdapter, c as FFIDVerifyAccessTokenOptions, d as FFIDApiResponse, e as FFIDOAuthUserInfo } from '../ffid-client-DM0ZhIDV.js';
2
+ export { f as FFIDAddMemberParams, g as FFIDAddMemberRequest, h as FFIDAddMemberResponse, i as FFIDAssignableMemberRole, j as FFIDCacheConfig, k as FFIDClient, l as FFIDConfig, m as FFIDListMembersResponse, n as FFIDMemberRole, o as FFIDOrganization, p as FFIDOrganizationMember, q as FFIDOtpSendResponse, r as FFIDOtpVerifyResponse, s as FFIDPasswordResetConfirmResponse, t as FFIDPasswordResetResponse, u as FFIDPasswordResetVerifyResponse, v as FFIDProfileCallOptions, w as FFIDProvisionMemberPlan, x as FFIDProvisionMemberPlanStatus, y as FFIDProvisionMemberResult, z as FFIDProvisionMemberStatus, A as FFIDProvisionOrganizationDryRun, B as FFIDProvisionOrganizationMemberInput, C as FFIDProvisionOrganizationOutcome, D as FFIDProvisionOrganizationParams, E as FFIDProvisionOrganizationResponse, G as FFIDProvisionUserDryRun, H as FFIDProvisionUserOutcome, I as FFIDProvisionUserParams, J as FFIDProvisionUserProfileInput, K as FFIDProvisionUserResponse, L as FFIDProvisionedOrganization, M as FFIDProvisionedUser, N as FFIDRemoveMemberResponse, O as FFIDResetSessionResponse, P as FFIDSubscription, Q as FFIDUpdateMemberRoleResponse, R as FFIDUpdateUserProfileRequest, S as FFIDUser, T as FFIDUserProfile, U as TokenData, V as TokenStore, W as createFFIDClient, X as createTokenStore } from '../ffid-client-DM0ZhIDV.js';
3
3
  export { D as DEFAULT_API_BASE_URL, a as DEFAULT_OAUTH_SCOPES } from '../constants-D61jqRIO.js';
4
4
  import { z } from 'zod';
5
- import '../types-BeVl-z12.js';
5
+ import '../types-Cjb9J0rL.js';
6
6
 
7
7
  /**
8
8
  * FFID SDK error codes
@@ -260,10 +260,12 @@ var OAUTH_INTROSPECT_ENDPOINT = "/api/v1/oauth/introspect";
260
260
  var CACHE_KEY_PREFIX = "ffid:introspect:";
261
261
  var HEX_BASE = 16;
262
262
  var HEX_BYTE_WIDTH = 2;
263
- var TOKEN_LOG_PREFIX_LENGTH = 8;
264
263
  function createVerifyAccessToken(deps) {
265
264
  const { authMode, baseUrl, serviceCode, serviceApiKey, verifyStrategy, logger, createError, errorCodes, cache, timeout } = deps;
266
265
  let jwtVerify2 = null;
266
+ let cacheReadFailureEscalated = false;
267
+ let cacheWriteFailureEscalated = false;
268
+ let missingSubtleCryptoLogged = false;
267
269
  function getJwtVerifier() {
268
270
  if (!jwtVerify2) {
269
271
  jwtVerify2 = createJwtVerifier({
@@ -323,8 +325,9 @@ function createVerifyAccessToken(deps) {
323
325
  }
324
326
  const url = `${baseUrl}${OAUTH_INTROSPECT_ENDPOINT}`;
325
327
  logger.debug("Verifying access token:", url);
326
- const cacheKey = cache ? await buildCacheKey(accessToken, cacheKeySuffix) : "";
327
- if (cache) {
328
+ const cacheKey = cache ? await buildCacheKey(accessToken, cacheKeySuffix) : null;
329
+ let verificationCacheDegraded = cache !== void 0 && cacheKey === null;
330
+ if (cache && cacheKey !== null) {
328
331
  try {
329
332
  const cached = await cache.adapter.get(cacheKey);
330
333
  if (cached && typeof cached === "object" && "sub" in cached) {
@@ -332,7 +335,16 @@ function createVerifyAccessToken(deps) {
332
335
  return { data: cached };
333
336
  }
334
337
  } catch (cacheError) {
335
- logger.warn("Cache read failed, falling back to API call:", cacheError);
338
+ verificationCacheDegraded = true;
339
+ if (!cacheReadFailureEscalated) {
340
+ cacheReadFailureEscalated = true;
341
+ logger.error(
342
+ "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:",
343
+ cacheError
344
+ );
345
+ } else {
346
+ logger.warn("Cache read failed, falling back to API call:", cacheError);
347
+ }
336
348
  }
337
349
  }
338
350
  const fetchOptions = {
@@ -427,31 +439,40 @@ function createVerifyAccessToken(deps) {
427
439
  }
428
440
  } : base;
429
441
  const userinfo = normalizeUserinfo(raw);
430
- if (cache) {
442
+ if (cache && cacheKey !== null) {
431
443
  try {
432
444
  await cache.adapter.set(cacheKey, userinfo, cache.ttl);
433
445
  } catch (cacheError) {
434
- logger.warn("Cache write failed (result still returned):", cacheError);
446
+ verificationCacheDegraded = true;
447
+ if (!cacheWriteFailureEscalated) {
448
+ cacheWriteFailureEscalated = true;
449
+ logger.error(
450
+ "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:",
451
+ cacheError
452
+ );
453
+ } else {
454
+ logger.warn("Cache write failed (result still returned):", cacheError);
455
+ }
435
456
  }
436
457
  }
437
- return { data: userinfo };
458
+ return verificationCacheDegraded ? { data: userinfo, meta: { verificationCacheDegraded: true } } : { data: userinfo };
438
459
  }
439
460
  async function buildCacheKey(token, suffix) {
440
- let key;
441
- if (typeof globalThis.crypto?.subtle?.digest === "function") {
442
- const encoder = new TextEncoder();
443
- const data = encoder.encode(token);
444
- const hashBuffer = await crypto.subtle.digest("SHA-256", data);
445
- const hashArray = Array.from(new Uint8Array(hashBuffer));
446
- const hashHex = hashArray.map((b) => b.toString(HEX_BASE).padStart(HEX_BYTE_WIDTH, "0")).join("");
447
- key = CACHE_KEY_PREFIX + hashHex;
448
- } else {
449
- const tokenPrefix = token.length > TOKEN_LOG_PREFIX_LENGTH ? token.substring(0, TOKEN_LOG_PREFIX_LENGTH) + "..." : "***";
450
- logger.warn(
451
- `crypto.subtle \u304C\u5229\u7528\u3067\u304D\u306A\u3044\u305F\u3081\u3001\u30AD\u30E3\u30C3\u30B7\u30E5\u30AD\u30FC\u306B\u30CF\u30C3\u30B7\u30E5\u5316\u3055\u308C\u3066\u3044\u306A\u3044\u30C8\u30FC\u30AF\u30F3\u3092\u4F7F\u7528\u3057\u307E\u3059 (token prefix: ${tokenPrefix})`
452
- );
453
- key = CACHE_KEY_PREFIX + token;
454
- }
461
+ if (typeof globalThis.crypto?.subtle?.digest !== "function") {
462
+ if (!missingSubtleCryptoLogged) {
463
+ missingSubtleCryptoLogged = true;
464
+ logger.error(
465
+ "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"
466
+ );
467
+ }
468
+ return null;
469
+ }
470
+ const encoder = new TextEncoder();
471
+ const data = encoder.encode(token);
472
+ const hashBuffer = await crypto.subtle.digest("SHA-256", data);
473
+ const hashArray = Array.from(new Uint8Array(hashBuffer));
474
+ const hashHex = hashArray.map((b) => b.toString(HEX_BASE).padStart(HEX_BYTE_WIDTH, "0")).join("");
475
+ const key = CACHE_KEY_PREFIX + hashHex;
455
476
  return suffix ? `${key}:${suffix}` : key;
456
477
  }
457
478
  return verifyAccessToken;
@@ -1200,7 +1221,7 @@ function createNonContractMethods(deps) {
1200
1221
  }
1201
1222
 
1202
1223
  // src/client/version-check.ts
1203
- var SDK_VERSION = "5.19.0";
1224
+ var SDK_VERSION = "5.22.0";
1204
1225
  var SDK_USER_AGENT = `FFID-SDK/${SDK_VERSION} (TypeScript)`;
1205
1226
  var SDK_VERSION_HEADER = "X-FFID-SDK-Version";
1206
1227
  function sdkHeaders() {
@@ -1442,33 +1463,48 @@ function createOAuthTokenMethods(deps) {
1442
1463
  async function signOutToken() {
1443
1464
  const tokens = tokenStore.getTokens();
1444
1465
  tokenStore.clearTokens();
1466
+ const allRevoked = {
1467
+ revoked: true,
1468
+ accessTokenRevoked: true,
1469
+ refreshTokenRevoked: true
1470
+ };
1445
1471
  if (!tokens) {
1446
1472
  logger.debug("No tokens to revoke");
1447
- return { data: void 0 };
1473
+ return { data: allRevoked };
1448
1474
  }
1449
- const url = `${baseUrl}${OAUTH_REVOKE_ENDPOINT}`;
1450
- logger.debug("Revoking token:", url);
1451
- try {
1452
- const response = await fetch(url, {
1453
- method: "POST",
1454
- credentials: "omit",
1455
- headers: { "Content-Type": "application/x-www-form-urlencoded", ...sdkHeaders() },
1456
- body: new URLSearchParams({
1457
- token: tokens.accessToken,
1458
- client_id: clientId
1459
- }).toString()
1460
- });
1461
- if (!response.ok) {
1462
- logger.warn(
1463
- "\u30C8\u30FC\u30AF\u30F3\u7121\u52B9\u5316\u30EA\u30AF\u30A8\u30B9\u30C8\u304C\u30B5\u30FC\u30D0\u30FC\u3067\u5931\u6557\u3057\u307E\u3057\u305F:",
1464
- `status=${response.status}`
1465
- );
1475
+ async function revokeOAuthToken(token, tokenTypeHint) {
1476
+ const url = `${baseUrl}${OAUTH_REVOKE_ENDPOINT}`;
1477
+ try {
1478
+ const response = await fetch(url, {
1479
+ method: "POST",
1480
+ credentials: "omit",
1481
+ headers: { "Content-Type": "application/x-www-form-urlencoded", ...sdkHeaders() },
1482
+ body: new URLSearchParams({
1483
+ token,
1484
+ client_id: clientId,
1485
+ token_type_hint: tokenTypeHint
1486
+ }).toString()
1487
+ });
1488
+ return response.ok;
1489
+ } catch {
1490
+ return false;
1466
1491
  }
1467
- } catch (error) {
1468
- logger.warn("\u30C8\u30FC\u30AF\u30F3\u7121\u52B9\u5316\u306E\u30CD\u30C3\u30C8\u30EF\u30FC\u30AF\u30EA\u30AF\u30A8\u30B9\u30C8\u306B\u5931\u6557\u3057\u307E\u3057\u305F:", error);
1492
+ }
1493
+ logger.debug("Revoking tokens:", `${baseUrl}${OAUTH_REVOKE_ENDPOINT}`);
1494
+ const accessTokenRevoked = await revokeOAuthToken(tokens.accessToken, "access_token");
1495
+ if (!accessTokenRevoked) {
1496
+ 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");
1497
+ }
1498
+ const refreshTokenRevoked = tokens.refreshToken ? await revokeOAuthToken(tokens.refreshToken, "refresh_token") : true;
1499
+ if (tokens.refreshToken && !refreshTokenRevoked) {
1500
+ 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");
1501
+ }
1502
+ const revoked = accessTokenRevoked && refreshTokenRevoked;
1503
+ if (!revoked) {
1504
+ 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");
1469
1505
  }
1470
1506
  logger.debug("Token sign-out completed");
1471
- return { data: void 0 };
1507
+ return { data: { revoked, accessTokenRevoked, refreshTokenRevoked } };
1472
1508
  }
1473
1509
  return { exchangeCodeForTokens, refreshAccessToken, signOutToken };
1474
1510
  }
@@ -1632,7 +1668,7 @@ function createSessionMethods(deps) {
1632
1668
  }
1633
1669
  if (response.status === NO_CONTENT_STATUS) {
1634
1670
  logger.debug("Response: 204 No Content");
1635
- return { data: void 0 };
1671
+ return { data: { revoked: true } };
1636
1672
  }
1637
1673
  if (!response.ok) {
1638
1674
  try {
@@ -1659,7 +1695,7 @@ function createSessionMethods(deps) {
1659
1695
  }
1660
1696
  }
1661
1697
  logger.debug("Response:", response.status);
1662
- return { data: void 0 };
1698
+ return { data: { revoked: true } };
1663
1699
  }
1664
1700
  return { getSession, signOutCookie };
1665
1701
  }
@@ -1782,6 +1818,9 @@ var REDIRECT_LOOP_WINDOW_MS = 6e4;
1782
1818
  var REDIRECT_LOOP_THRESHOLD = 3;
1783
1819
  var storageReadFailureLogged = false;
1784
1820
  var storageWriteFailureLogged = false;
1821
+ function isLoopDetectionDegraded() {
1822
+ return storageReadFailureLogged || storageWriteFailureLogged;
1823
+ }
1785
1824
  function logStorageReadFailure(logger, err) {
1786
1825
  if (storageReadFailureLogged) return;
1787
1826
  storageReadFailureLogged = true;
@@ -1926,9 +1965,16 @@ function createRedirectMethods(deps) {
1926
1965
  }
1927
1966
  const state = generateRandomState();
1928
1967
  if (!storeState(state, logger)) {
1968
+ cleanupVerifierStorage(logger);
1969
+ cleanupStateStorage(logger);
1929
1970
  logger.error(
1930
- "redirectToAuthorize: OAuth state \u3092\u4FDD\u5B58\u3067\u304D\u307E\u305B\u3093\u3067\u3057\u305F\uFF08storage \u5229\u7528\u4E0D\u53EF\uFF09\u3002\u30B3\u30FC\u30EB\u30D0\u30C3\u30AF\u3067\u306E CSRF \u691C\u8A3C\u304C\u5931\u6557\u3057\u307E\u3059"
1971
+ "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"
1931
1972
  );
1973
+ return {
1974
+ success: false,
1975
+ code: "state_persistence_failed",
1976
+ 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"
1977
+ };
1932
1978
  }
1933
1979
  const redirectUri = resolvedRedirectUri ?? window.location.origin + window.location.pathname;
1934
1980
  const params = new URLSearchParams({
@@ -1963,7 +2009,7 @@ function createRedirectMethods(deps) {
1963
2009
  rollbackLastRedirectAttempt(authorizeKey, logger);
1964
2010
  throw err;
1965
2011
  }
1966
- return { success: true };
2012
+ return isLoopDetectionDegraded() ? { success: true, loopDetectionDisabled: true } : { success: true };
1967
2013
  }
1968
2014
  async function redirectToLogin(options) {
1969
2015
  if (typeof window === "undefined") {
@@ -1,5 +1,5 @@
1
- import { k as FFIDClient, e as FFIDOAuthUserInfo } from '../../ffid-client-BuDM5tmq.cjs';
2
- import '../../types-BeVl-z12.cjs';
1
+ import { k as FFIDClient, e as FFIDOAuthUserInfo } from '../../ffid-client-CEJMpqRZ.cjs';
2
+ import '../../types-Cjb9J0rL.cjs';
3
3
 
4
4
  /**
5
5
  * FFID SDK - Test mode client (E2E / integration test bypass)
@@ -1,5 +1,5 @@
1
- import { k as FFIDClient, e as FFIDOAuthUserInfo } from '../../ffid-client-D0x8TBS5.js';
2
- import '../../types-BeVl-z12.js';
1
+ import { k as FFIDClient, e as FFIDOAuthUserInfo } from '../../ffid-client-DM0ZhIDV.js';
2
+ import '../../types-Cjb9J0rL.js';
3
3
 
4
4
  /**
5
5
  * FFID SDK - Test mode client (E2E / integration test bypass)
@@ -56,7 +56,8 @@ declare const EFFECTIVE_SUBSCRIPTION_STATUSES: readonly ["active", "active_cance
56
56
  * `src/lib/common/subscription-helpers.ts`, and a server-side unit test pins
57
57
  * element-equality between the two arrays (#3474) — convention-only sync
58
58
  * could drift silently in the server-extending direction (a new server
59
- * literal would fall through SDK consumers' `switch` defaults).
59
+ * literal would fall through SDK consumers' `switch` defaults). The PR gate also
60
+ * runs `npm run sdk:verify-sync` for SDK/server subscription changes.
60
61
  */
61
62
  type EffectiveSubscriptionStatus = (typeof EFFECTIVE_SUBSCRIPTION_STATUSES)[number];
62
63
 
@@ -56,7 +56,8 @@ declare const EFFECTIVE_SUBSCRIPTION_STATUSES: readonly ["active", "active_cance
56
56
  * `src/lib/common/subscription-helpers.ts`, and a server-side unit test pins
57
57
  * element-equality between the two arrays (#3474) — convention-only sync
58
58
  * could drift silently in the server-extending direction (a new server
59
- * literal would fall through SDK consumers' `switch` defaults).
59
+ * literal would fall through SDK consumers' `switch` defaults). The PR gate also
60
+ * runs `npm run sdk:verify-sync` for SDK/server subscription changes.
60
61
  */
61
62
  type EffectiveSubscriptionStatus = (typeof EFFECTIVE_SUBSCRIPTION_STATUSES)[number];
62
63
 
@@ -89,7 +89,6 @@ function verifyWebhookSignature(rawBody, signatureHeader, secret, toleranceSecon
89
89
  }
90
90
 
91
91
  // src/webhooks/handler.ts
92
- var SDK_LOG_PREFIX = "[FFID Webhook SDK]";
93
92
  var HTTP_OK = 200;
94
93
  var HTTP_BAD_REQUEST = 400;
95
94
  var HTTP_INTERNAL_SERVER_ERROR = 500;
@@ -100,7 +99,8 @@ var noopLogger = {
100
99
  },
101
100
  warn: () => {
102
101
  },
103
- error: (...args) => console.error(SDK_LOG_PREFIX, ...args)
102
+ error: () => {
103
+ }
104
104
  };
105
105
  function createFFIDWebhookHandler(config) {
106
106
  if (!config.secret) {
@@ -1,4 +1,4 @@
1
- import { E as EffectiveSubscriptionStatus } from '../types-BeVl-z12.cjs';
1
+ import { E as EffectiveSubscriptionStatus } from '../types-Cjb9J0rL.cjs';
2
2
 
3
3
  /**
4
4
  * FFID Webhook SDK Constants
@@ -1,4 +1,4 @@
1
- import { E as EffectiveSubscriptionStatus } from '../types-BeVl-z12.js';
1
+ import { E as EffectiveSubscriptionStatus } from '../types-Cjb9J0rL.js';
2
2
 
3
3
  /**
4
4
  * FFID Webhook SDK Constants
@@ -87,7 +87,6 @@ function verifyWebhookSignature(rawBody, signatureHeader, secret, toleranceSecon
87
87
  }
88
88
 
89
89
  // src/webhooks/handler.ts
90
- var SDK_LOG_PREFIX = "[FFID Webhook SDK]";
91
90
  var HTTP_OK = 200;
92
91
  var HTTP_BAD_REQUEST = 400;
93
92
  var HTTP_INTERNAL_SERVER_ERROR = 500;
@@ -98,7 +97,8 @@ var noopLogger = {
98
97
  },
99
98
  warn: () => {
100
99
  },
101
- error: (...args) => console.error(SDK_LOG_PREFIX, ...args)
100
+ error: () => {
101
+ }
102
102
  };
103
103
  function createFFIDWebhookHandler(config) {
104
104
  if (!config.secret) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@feelflow/ffid-sdk",
3
- "version": "5.19.0",
3
+ "version": "5.22.0",
4
4
  "description": "FeelFlow ID Platform SDK for React/Next.js applications",
5
5
  "keywords": [
6
6
  "feelflow",