@feelflow/ffid-sdk 5.12.0 → 5.14.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/dist/{chunk-KB2JG64Z.js → chunk-4II4R7NR.js} +392 -10
- package/dist/{chunk-AOJDV3UO.cjs → chunk-BVDUQQHP.cjs} +395 -9
- 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-5IprSUVR.d.ts → ffid-client-9-lh0G_h.d.ts} +384 -134
- package/dist/{ffid-client-Dx4oc4Cw.d.cts → ffid-client-CKFxY60u.d.cts} +384 -134
- package/dist/{index-DqsWKU16.d.cts → index-CsVJTuPv.d.cts} +14 -112
- package/dist/{index-DqsWKU16.d.ts → index-CsVJTuPv.d.ts} +14 -112
- package/dist/index.cjs +53 -37
- package/dist/index.d.cts +436 -4
- package/dist/index.d.ts +436 -4
- package/dist/index.js +2 -2
- package/dist/legal/index.cjs +14 -0
- package/dist/legal/index.d.cts +16 -1
- package/dist/legal/index.d.ts +16 -1
- package/dist/legal/index.js +14 -0
- package/dist/server/index.cjs +241 -3
- package/dist/server/index.d.cts +2 -2
- package/dist/server/index.d.ts +2 -2
- package/dist/server/index.js +241 -3
- package/dist/server/test/index.d.cts +1 -1
- package/dist/server/test/index.d.ts +1 -1
- package/dist/webhooks/index.cjs +5 -2
- package/dist/webhooks/index.js +5 -2
- package/package.json +1 -1
|
@@ -124,6 +124,9 @@ function normalizeUserinfo(raw) {
|
|
|
124
124
|
return {
|
|
125
125
|
sub: raw.sub,
|
|
126
126
|
email: raw.email,
|
|
127
|
+
// #3791: undefined → undefined を保つ(false に丸めない)。古い FFID は wire に
|
|
128
|
+
// 含めないため、consumer は undefined を「未確認」ではなく「不明」として扱える。
|
|
129
|
+
emailVerified: raw.email_verified,
|
|
127
130
|
name: raw.name,
|
|
128
131
|
picture: raw.picture,
|
|
129
132
|
companyName: raw.company_name ?? null,
|
|
@@ -379,6 +382,9 @@ function createVerifyAccessToken(deps) {
|
|
|
379
382
|
const base = {
|
|
380
383
|
sub: introspectResponse.sub,
|
|
381
384
|
email: introspectResponse.email ?? null,
|
|
385
|
+
// #3791: preserve undefined (do NOT coerce to false) so introspect-strategy
|
|
386
|
+
// callers can distinguish "verified" / "not verified" / "older server".
|
|
387
|
+
email_verified: introspectResponse.email_verified,
|
|
382
388
|
name: introspectResponse.name ?? null,
|
|
383
389
|
picture: introspectResponse.picture ?? null,
|
|
384
390
|
company_name: introspectResponse.company_name ?? null,
|
|
@@ -933,8 +939,163 @@ function createProfileMethods(deps) {
|
|
|
933
939
|
return { getProfile, updateProfile };
|
|
934
940
|
}
|
|
935
941
|
|
|
942
|
+
// src/client/login-history-methods.ts
|
|
943
|
+
var EXT_LOGIN_HISTORY_ENDPOINT = "/api/v1/users/ext/me/login-history";
|
|
944
|
+
function createLoginHistoryMethods(deps) {
|
|
945
|
+
const { fetchWithAuth, createError } = deps;
|
|
946
|
+
async function getLoginHistory(params) {
|
|
947
|
+
if (params?.limit !== void 0 && (!Number.isInteger(params.limit) || params.limit <= 0)) {
|
|
948
|
+
return {
|
|
949
|
+
error: createError(
|
|
950
|
+
"VALIDATION_ERROR",
|
|
951
|
+
"limit \u306F1\u4EE5\u4E0A\u306E\u6574\u6570\u3092\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044"
|
|
952
|
+
)
|
|
953
|
+
};
|
|
954
|
+
}
|
|
955
|
+
const query = new URLSearchParams();
|
|
956
|
+
if (params?.userId !== void 0) {
|
|
957
|
+
query.set("userId", params.userId);
|
|
958
|
+
}
|
|
959
|
+
if (params?.limit !== void 0) {
|
|
960
|
+
query.set("limit", String(params.limit));
|
|
961
|
+
}
|
|
962
|
+
const queryString = query.toString();
|
|
963
|
+
const endpoint = queryString ? `${EXT_LOGIN_HISTORY_ENDPOINT}?${queryString}` : EXT_LOGIN_HISTORY_ENDPOINT;
|
|
964
|
+
return fetchWithAuth(endpoint);
|
|
965
|
+
}
|
|
966
|
+
return { getLoginHistory };
|
|
967
|
+
}
|
|
968
|
+
|
|
969
|
+
// src/client/organization-domains-methods.ts
|
|
970
|
+
var EXT_DOMAINS_ENDPOINT = "/api/v1/organizations/ext/domains";
|
|
971
|
+
function createOrganizationDomainsMethods(deps) {
|
|
972
|
+
const { fetchWithAuth, createError, serviceCode } = deps;
|
|
973
|
+
function buildQuery(organizationId) {
|
|
974
|
+
return new URLSearchParams({ organizationId, serviceCode }).toString();
|
|
975
|
+
}
|
|
976
|
+
async function getOrganizationDomains(params) {
|
|
977
|
+
if (!params.organizationId) {
|
|
978
|
+
return {
|
|
979
|
+
error: createError("VALIDATION_ERROR", "organizationId \u306F\u5FC5\u9808\u3067\u3059")
|
|
980
|
+
};
|
|
981
|
+
}
|
|
982
|
+
return fetchWithAuth(
|
|
983
|
+
`${EXT_DOMAINS_ENDPOINT}?${buildQuery(params.organizationId)}`
|
|
984
|
+
);
|
|
985
|
+
}
|
|
986
|
+
return { getOrganizationDomains };
|
|
987
|
+
}
|
|
988
|
+
|
|
989
|
+
// src/client/notification-preferences-methods.ts
|
|
990
|
+
var EXT_NOTIFICATION_PREFERENCES_ENDPOINT = "/api/v1/organizations/ext/notification-preferences";
|
|
991
|
+
function createNotificationPreferencesMethods(deps) {
|
|
992
|
+
const { fetchWithAuth, createError, serviceCode } = deps;
|
|
993
|
+
function buildQuery(organizationId) {
|
|
994
|
+
return new URLSearchParams({ organizationId, serviceCode }).toString();
|
|
995
|
+
}
|
|
996
|
+
async function getNotificationPreferences(params) {
|
|
997
|
+
if (!params.organizationId) {
|
|
998
|
+
return {
|
|
999
|
+
error: createError("VALIDATION_ERROR", "organizationId \u306F\u5FC5\u9808\u3067\u3059")
|
|
1000
|
+
};
|
|
1001
|
+
}
|
|
1002
|
+
return fetchWithAuth(
|
|
1003
|
+
`${EXT_NOTIFICATION_PREFERENCES_ENDPOINT}?${buildQuery(params.organizationId)}`
|
|
1004
|
+
);
|
|
1005
|
+
}
|
|
1006
|
+
async function updateNotificationPreferences(params) {
|
|
1007
|
+
if (!params.organizationId) {
|
|
1008
|
+
return {
|
|
1009
|
+
error: createError("VALIDATION_ERROR", "organizationId \u306F\u5FC5\u9808\u3067\u3059")
|
|
1010
|
+
};
|
|
1011
|
+
}
|
|
1012
|
+
if (params.preferences === null || typeof params.preferences !== "object" || Array.isArray(params.preferences)) {
|
|
1013
|
+
return {
|
|
1014
|
+
error: createError("VALIDATION_ERROR", "preferences \u306F\u30AA\u30D6\u30B8\u30A7\u30AF\u30C8\u3067\u3042\u308B\u5FC5\u8981\u304C\u3042\u308A\u307E\u3059")
|
|
1015
|
+
};
|
|
1016
|
+
}
|
|
1017
|
+
if (Object.keys(params.preferences).length === 0) {
|
|
1018
|
+
return {
|
|
1019
|
+
error: createError("VALIDATION_ERROR", "\u66F4\u65B0\u3059\u308B\u30D5\u30A3\u30FC\u30EB\u30C9\u30921\u3064\u4EE5\u4E0A\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044")
|
|
1020
|
+
};
|
|
1021
|
+
}
|
|
1022
|
+
return fetchWithAuth(
|
|
1023
|
+
`${EXT_NOTIFICATION_PREFERENCES_ENDPOINT}?${buildQuery(params.organizationId)}`,
|
|
1024
|
+
{
|
|
1025
|
+
method: "PATCH",
|
|
1026
|
+
body: JSON.stringify({ preferences: params.preferences })
|
|
1027
|
+
}
|
|
1028
|
+
);
|
|
1029
|
+
}
|
|
1030
|
+
return { getNotificationPreferences, updateNotificationPreferences };
|
|
1031
|
+
}
|
|
1032
|
+
|
|
1033
|
+
// src/client/invite-methods.ts
|
|
1034
|
+
var EXT_INVITE_ENDPOINT = "/api/v1/organizations/ext/invite";
|
|
1035
|
+
function createInviteMethods(deps) {
|
|
1036
|
+
const { fetchWithAuth, createError, serviceCode } = deps;
|
|
1037
|
+
const assignableRoles = /* @__PURE__ */ new Set(["admin", "member", "viewer"]);
|
|
1038
|
+
async function inviteMember(params) {
|
|
1039
|
+
if (!params.organizationId || !params.email) {
|
|
1040
|
+
return {
|
|
1041
|
+
error: createError("VALIDATION_ERROR", "organizationId \u3068 email \u306F\u5FC5\u9808\u3067\u3059")
|
|
1042
|
+
};
|
|
1043
|
+
}
|
|
1044
|
+
if (params.role !== void 0 && !assignableRoles.has(params.role)) {
|
|
1045
|
+
return {
|
|
1046
|
+
error: createError(
|
|
1047
|
+
"VALIDATION_ERROR",
|
|
1048
|
+
"role \u306F admin\u3001member\u3001viewer \u306E\u3044\u305A\u308C\u304B\u3092\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044"
|
|
1049
|
+
)
|
|
1050
|
+
};
|
|
1051
|
+
}
|
|
1052
|
+
const query = new URLSearchParams({
|
|
1053
|
+
organizationId: params.organizationId,
|
|
1054
|
+
serviceCode
|
|
1055
|
+
}).toString();
|
|
1056
|
+
return fetchWithAuth(`${EXT_INVITE_ENDPOINT}?${query}`, {
|
|
1057
|
+
method: "POST",
|
|
1058
|
+
body: JSON.stringify({ email: params.email, role: params.role })
|
|
1059
|
+
});
|
|
1060
|
+
}
|
|
1061
|
+
return { inviteMember };
|
|
1062
|
+
}
|
|
1063
|
+
|
|
1064
|
+
// src/client/org-membership-methods.ts
|
|
1065
|
+
var EXT_USER_ORGANIZATIONS_ENDPOINT = "/api/v1/users/ext/me/organizations";
|
|
1066
|
+
function createOrgMembershipMethods(deps) {
|
|
1067
|
+
const { fetchWithAuth, createError } = deps;
|
|
1068
|
+
async function leaveOrganization(params) {
|
|
1069
|
+
if (!params || !params.organizationId) {
|
|
1070
|
+
return {
|
|
1071
|
+
error: createError("VALIDATION_ERROR", "organizationId \u306F\u5FC5\u9808\u3067\u3059")
|
|
1072
|
+
};
|
|
1073
|
+
}
|
|
1074
|
+
let endpoint = `${EXT_USER_ORGANIZATIONS_ENDPOINT}/${encodeURIComponent(params.organizationId)}`;
|
|
1075
|
+
if (params.userId) {
|
|
1076
|
+
endpoint += `?${new URLSearchParams({ userId: params.userId }).toString()}`;
|
|
1077
|
+
}
|
|
1078
|
+
return fetchWithAuth(endpoint, {
|
|
1079
|
+
method: "DELETE"
|
|
1080
|
+
});
|
|
1081
|
+
}
|
|
1082
|
+
return { leaveOrganization };
|
|
1083
|
+
}
|
|
1084
|
+
|
|
1085
|
+
// src/client/noncontract-methods.ts
|
|
1086
|
+
function createNonContractMethods(deps) {
|
|
1087
|
+
const { fetchWithAuth, createError, serviceCode } = deps;
|
|
1088
|
+
return {
|
|
1089
|
+
...createLoginHistoryMethods({ fetchWithAuth, createError }),
|
|
1090
|
+
...createOrganizationDomainsMethods({ fetchWithAuth, createError, serviceCode }),
|
|
1091
|
+
...createNotificationPreferencesMethods({ fetchWithAuth, createError, serviceCode }),
|
|
1092
|
+
...createInviteMethods({ fetchWithAuth, createError, serviceCode }),
|
|
1093
|
+
...createOrgMembershipMethods({ fetchWithAuth, createError })
|
|
1094
|
+
};
|
|
1095
|
+
}
|
|
1096
|
+
|
|
936
1097
|
// src/client/version-check.ts
|
|
937
|
-
var SDK_VERSION = "5.
|
|
1098
|
+
var SDK_VERSION = "5.14.0";
|
|
938
1099
|
var SDK_USER_AGENT = `FFID-SDK/${SDK_VERSION} (TypeScript)`;
|
|
939
1100
|
var SDK_VERSION_HEADER = "X-FFID-SDK-Version";
|
|
940
1101
|
function sdkHeaders() {
|
|
@@ -1006,9 +1167,20 @@ function createOAuthTokenMethods(deps) {
|
|
|
1006
1167
|
logger,
|
|
1007
1168
|
errorCodes
|
|
1008
1169
|
} = deps;
|
|
1009
|
-
async function exchangeCodeForTokens(code, codeVerifier) {
|
|
1170
|
+
async function exchangeCodeForTokens(code, codeVerifier, opts) {
|
|
1010
1171
|
const url = `${baseUrl}${OAUTH_TOKEN_ENDPOINT}`;
|
|
1011
1172
|
logger.debug("Exchanging code for tokens:", url);
|
|
1173
|
+
if (opts?.expectedState !== void 0 || opts?.actualState !== void 0) {
|
|
1174
|
+
if (opts?.expectedState !== opts?.actualState) {
|
|
1175
|
+
logger.error("Token exchange aborted: OAuth state mismatch or omitted (possible CSRF)");
|
|
1176
|
+
return {
|
|
1177
|
+
error: {
|
|
1178
|
+
code: errorCodes.STATE_MISMATCH_ERROR,
|
|
1179
|
+
message: "OAuth state \u304C\u4E00\u81F4\u3057\u307E\u305B\u3093\u3002CSRF \u306E\u53EF\u80FD\u6027\u304C\u3042\u308B\u305F\u3081\u8A8D\u8A3C\u3092\u4E2D\u6B62\u3057\u307E\u3057\u305F"
|
|
1180
|
+
}
|
|
1181
|
+
};
|
|
1182
|
+
}
|
|
1183
|
+
}
|
|
1012
1184
|
const effectiveRedirectUri = resolvedRedirectUri ?? (typeof window !== "undefined" ? window.location.origin + window.location.pathname : null);
|
|
1013
1185
|
if (!effectiveRedirectUri) {
|
|
1014
1186
|
logger.error("redirectUri is required for token exchange in SSR environments. Set config.redirectUri explicitly.");
|
|
@@ -1492,6 +1664,102 @@ function base64UrlEncode(buffer) {
|
|
|
1492
1664
|
return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
1493
1665
|
}
|
|
1494
1666
|
|
|
1667
|
+
// src/auth/state.ts
|
|
1668
|
+
var STATE_STORAGE_KEY = "ffid_oauth_state";
|
|
1669
|
+
var STATE_FALLBACK_STORAGE_KEY = "ffid_oauth_state_fb";
|
|
1670
|
+
var STATE_FALLBACK_TIMESTAMP_KEY = "ffid_oauth_state_fb_ts";
|
|
1671
|
+
var OAUTH_STATE_MAX_AGE_MS = 5 * 60 * 1e3;
|
|
1672
|
+
function storeState(state, logger) {
|
|
1673
|
+
if (typeof window === "undefined") {
|
|
1674
|
+
logger?.warn("storeState: storage is not available in SSR context");
|
|
1675
|
+
return false;
|
|
1676
|
+
}
|
|
1677
|
+
let sessionStored = false;
|
|
1678
|
+
try {
|
|
1679
|
+
window.sessionStorage.setItem(STATE_STORAGE_KEY, state);
|
|
1680
|
+
sessionStored = true;
|
|
1681
|
+
} catch (error) {
|
|
1682
|
+
logger?.warn("storeState: sessionStorage \u3078\u306E\u4FDD\u5B58\u306B\u5931\u6557\u3057\u307E\u3057\u305F:", error);
|
|
1683
|
+
}
|
|
1684
|
+
let localStored = false;
|
|
1685
|
+
try {
|
|
1686
|
+
window.localStorage.setItem(STATE_FALLBACK_STORAGE_KEY, state);
|
|
1687
|
+
window.localStorage.setItem(STATE_FALLBACK_TIMESTAMP_KEY, String(Date.now()));
|
|
1688
|
+
localStored = true;
|
|
1689
|
+
} catch (error) {
|
|
1690
|
+
logger?.warn("storeState: localStorage fallback \u3078\u306E\u4FDD\u5B58\u306B\u5931\u6557\u3057\u307E\u3057\u305F:", error);
|
|
1691
|
+
}
|
|
1692
|
+
return sessionStored || localStored;
|
|
1693
|
+
}
|
|
1694
|
+
function retrieveState(logger) {
|
|
1695
|
+
if (typeof window === "undefined") return null;
|
|
1696
|
+
let sessionState = null;
|
|
1697
|
+
try {
|
|
1698
|
+
sessionState = window.sessionStorage.getItem(STATE_STORAGE_KEY);
|
|
1699
|
+
} catch (error) {
|
|
1700
|
+
logger?.warn("retrieveState: sessionStorage \u304B\u3089\u306E\u53D6\u5F97\u306B\u5931\u6557\u3057\u307E\u3057\u305F:", error);
|
|
1701
|
+
}
|
|
1702
|
+
if (sessionState) {
|
|
1703
|
+
cleanupStateStorage(logger);
|
|
1704
|
+
return sessionState;
|
|
1705
|
+
}
|
|
1706
|
+
let fallbackState = null;
|
|
1707
|
+
let fallbackTimestamp = null;
|
|
1708
|
+
try {
|
|
1709
|
+
fallbackState = window.localStorage.getItem(STATE_FALLBACK_STORAGE_KEY);
|
|
1710
|
+
fallbackTimestamp = window.localStorage.getItem(STATE_FALLBACK_TIMESTAMP_KEY);
|
|
1711
|
+
} catch (error) {
|
|
1712
|
+
logger?.warn("retrieveState: localStorage fallback \u304B\u3089\u306E\u53D6\u5F97\u306B\u5931\u6557\u3057\u307E\u3057\u305F:", error);
|
|
1713
|
+
cleanupStateStorage(logger);
|
|
1714
|
+
return null;
|
|
1715
|
+
}
|
|
1716
|
+
if (!fallbackState || !fallbackTimestamp) {
|
|
1717
|
+
cleanupStateStorage(logger);
|
|
1718
|
+
return null;
|
|
1719
|
+
}
|
|
1720
|
+
const ts = Number(fallbackTimestamp);
|
|
1721
|
+
const age = Date.now() - ts;
|
|
1722
|
+
if (!Number.isFinite(ts) || age < 0 || age > OAUTH_STATE_MAX_AGE_MS) {
|
|
1723
|
+
cleanupStateStorage(logger);
|
|
1724
|
+
return null;
|
|
1725
|
+
}
|
|
1726
|
+
logger?.warn(
|
|
1727
|
+
"retrieveState: sessionStorage was empty, recovered OAuth state from localStorage fallback"
|
|
1728
|
+
);
|
|
1729
|
+
cleanupStateStorage(logger);
|
|
1730
|
+
return fallbackState;
|
|
1731
|
+
}
|
|
1732
|
+
function isStateStorageAvailable() {
|
|
1733
|
+
if (typeof window === "undefined") return false;
|
|
1734
|
+
const probeKey = "__ffid_oauth_state_probe__";
|
|
1735
|
+
try {
|
|
1736
|
+
window.sessionStorage.setItem(probeKey, "1");
|
|
1737
|
+
window.sessionStorage.removeItem(probeKey);
|
|
1738
|
+
return true;
|
|
1739
|
+
} catch {
|
|
1740
|
+
}
|
|
1741
|
+
try {
|
|
1742
|
+
window.localStorage.setItem(probeKey, "1");
|
|
1743
|
+
window.localStorage.removeItem(probeKey);
|
|
1744
|
+
return true;
|
|
1745
|
+
} catch {
|
|
1746
|
+
return false;
|
|
1747
|
+
}
|
|
1748
|
+
}
|
|
1749
|
+
function cleanupStateStorage(logger) {
|
|
1750
|
+
try {
|
|
1751
|
+
window.sessionStorage.removeItem(STATE_STORAGE_KEY);
|
|
1752
|
+
} catch (error) {
|
|
1753
|
+
logger?.warn("retrieveState: sessionStorage \u306E\u30AF\u30EA\u30FC\u30F3\u30A2\u30C3\u30D7\u306B\u5931\u6557\u3057\u307E\u3057\u305F:", error);
|
|
1754
|
+
}
|
|
1755
|
+
try {
|
|
1756
|
+
window.localStorage.removeItem(STATE_FALLBACK_STORAGE_KEY);
|
|
1757
|
+
window.localStorage.removeItem(STATE_FALLBACK_TIMESTAMP_KEY);
|
|
1758
|
+
} catch (error) {
|
|
1759
|
+
logger?.warn("retrieveState: localStorage \u306E\u30AF\u30EA\u30FC\u30F3\u30A2\u30C3\u30D7\u306B\u5931\u6557\u3057\u307E\u3057\u305F:", error);
|
|
1760
|
+
}
|
|
1761
|
+
}
|
|
1762
|
+
|
|
1495
1763
|
// src/client/redirect.ts
|
|
1496
1764
|
var OAUTH_AUTHORIZE_ENDPOINT = "/api/v1/oauth/authorize";
|
|
1497
1765
|
var AUTH_LOGOUT_ENDPOINT = "/api/v1/auth/logout";
|
|
@@ -1623,6 +1891,7 @@ function createRedirectMethods(deps) {
|
|
|
1623
1891
|
const recentCount = getRecentRedirectCount(authorizeKey, now, logger);
|
|
1624
1892
|
if (recentCount >= REDIRECT_LOOP_THRESHOLD) {
|
|
1625
1893
|
cleanupVerifierStorage(logger);
|
|
1894
|
+
cleanupStateStorage(logger);
|
|
1626
1895
|
logger.warn("[FFID SDK] redirect loop detected \u2014 \u81EA\u52D5\u30EA\u30C0\u30A4\u30EC\u30AF\u30C8\u3092\u505C\u6B62\u3057\u307E\u3057\u305F", {
|
|
1627
1896
|
authorizeKey,
|
|
1628
1897
|
recentCount,
|
|
@@ -1646,6 +1915,11 @@ function createRedirectMethods(deps) {
|
|
|
1646
1915
|
return { success: false, error: errorMessage };
|
|
1647
1916
|
}
|
|
1648
1917
|
const state = generateRandomState();
|
|
1918
|
+
if (!storeState(state, logger)) {
|
|
1919
|
+
logger.error(
|
|
1920
|
+
"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"
|
|
1921
|
+
);
|
|
1922
|
+
}
|
|
1649
1923
|
const redirectUri = resolvedRedirectUri ?? window.location.origin + window.location.pathname;
|
|
1650
1924
|
const params = new URLSearchParams({
|
|
1651
1925
|
response_type: "code",
|
|
@@ -2435,7 +2709,8 @@ var FFID_ERROR_CODES = {
|
|
|
2435
2709
|
TOKEN_EXCHANGE_ERROR: "TOKEN_EXCHANGE_ERROR",
|
|
2436
2710
|
TOKEN_REFRESH_ERROR: "TOKEN_REFRESH_ERROR",
|
|
2437
2711
|
NO_TOKENS: "NO_TOKENS",
|
|
2438
|
-
TOKEN_VERIFICATION_ERROR: "TOKEN_VERIFICATION_ERROR"
|
|
2712
|
+
TOKEN_VERIFICATION_ERROR: "TOKEN_VERIFICATION_ERROR",
|
|
2713
|
+
STATE_MISMATCH_ERROR: "STATE_MISMATCH_ERROR"
|
|
2439
2714
|
};
|
|
2440
2715
|
var EXT_CHECK_ENDPOINT = "/api/v1/subscriptions/ext/check";
|
|
2441
2716
|
var DEFAULT_ALLOW_GRACE = true;
|
|
@@ -2823,6 +3098,18 @@ function createFFIDClient(config) {
|
|
|
2823
3098
|
fetchWithAuth,
|
|
2824
3099
|
createError
|
|
2825
3100
|
});
|
|
3101
|
+
const {
|
|
3102
|
+
getLoginHistory,
|
|
3103
|
+
getOrganizationDomains,
|
|
3104
|
+
getNotificationPreferences,
|
|
3105
|
+
updateNotificationPreferences,
|
|
3106
|
+
inviteMember,
|
|
3107
|
+
leaveOrganization
|
|
3108
|
+
} = createNonContractMethods({
|
|
3109
|
+
fetchWithAuth,
|
|
3110
|
+
createError,
|
|
3111
|
+
serviceCode: config.serviceCode
|
|
3112
|
+
});
|
|
2826
3113
|
const { getAnalyticsConfig } = createAnalyticsMethods({
|
|
2827
3114
|
fetchWithAuth,
|
|
2828
3115
|
createError
|
|
@@ -2903,6 +3190,13 @@ function createFFIDClient(config) {
|
|
|
2903
3190
|
removeMember,
|
|
2904
3191
|
getProfile,
|
|
2905
3192
|
updateProfile,
|
|
3193
|
+
// Non-contract ext API coverage (#3783)
|
|
3194
|
+
getLoginHistory,
|
|
3195
|
+
getOrganizationDomains,
|
|
3196
|
+
getNotificationPreferences,
|
|
3197
|
+
updateNotificationPreferences,
|
|
3198
|
+
inviteMember,
|
|
3199
|
+
leaveOrganization,
|
|
2906
3200
|
getAnalyticsConfig,
|
|
2907
3201
|
createCheckoutSession,
|
|
2908
3202
|
createPortalSession,
|
|
@@ -2953,6 +3247,99 @@ function createFFIDClient(config) {
|
|
|
2953
3247
|
redirectUri: resolvedRedirectUri
|
|
2954
3248
|
};
|
|
2955
3249
|
}
|
|
3250
|
+
|
|
3251
|
+
// src/client/callback.ts
|
|
3252
|
+
var CALLBACK_ERROR_CODES = {
|
|
3253
|
+
/** Stored state missing or did not match the callback `state` (CSRF guard) */
|
|
3254
|
+
STATE_MISMATCH: "STATE_MISMATCH_ERROR",
|
|
3255
|
+
/**
|
|
3256
|
+
* Web storage is unavailable, so `state` could never be persisted across the
|
|
3257
|
+
* OAuth bounce. An environment issue (e.g. iOS private mode), not an attack —
|
|
3258
|
+
* surfaced distinctly so callers don't mistake it for a CSRF mismatch.
|
|
3259
|
+
*/
|
|
3260
|
+
STATE_UNAVAILABLE: "STATE_STORAGE_UNAVAILABLE",
|
|
3261
|
+
/** Callback URL had no `code` param */
|
|
3262
|
+
MISSING_CODE: "MISSING_AUTHORIZATION_CODE",
|
|
3263
|
+
/** SSR with no `options.url` — cannot resolve callback params */
|
|
3264
|
+
CALLBACK_URL_UNAVAILABLE: "CALLBACK_URL_UNAVAILABLE"
|
|
3265
|
+
};
|
|
3266
|
+
function extractSearchParams(source) {
|
|
3267
|
+
try {
|
|
3268
|
+
return new URL(source).searchParams;
|
|
3269
|
+
} catch {
|
|
3270
|
+
const queryStart = source.indexOf("?");
|
|
3271
|
+
const query = queryStart >= 0 ? source.slice(queryStart + 1) : source;
|
|
3272
|
+
return new URLSearchParams(query);
|
|
3273
|
+
}
|
|
3274
|
+
}
|
|
3275
|
+
function cleanCallbackUrlParams(logger) {
|
|
3276
|
+
try {
|
|
3277
|
+
const cleanUrl = new URL(window.location.href);
|
|
3278
|
+
cleanUrl.searchParams.delete("code");
|
|
3279
|
+
cleanUrl.searchParams.delete("state");
|
|
3280
|
+
window.history.replaceState({}, "", cleanUrl.toString());
|
|
3281
|
+
} catch (error) {
|
|
3282
|
+
logger.warn("handleRedirectCallback: URL \u306E\u30AF\u30EA\u30FC\u30F3\u30A2\u30C3\u30D7\u306B\u5931\u6557\u3057\u307E\u3057\u305F:", error);
|
|
3283
|
+
}
|
|
3284
|
+
}
|
|
3285
|
+
async function handleRedirectCallback(client, options) {
|
|
3286
|
+
const { logger } = client;
|
|
3287
|
+
const hasWindow = typeof window !== "undefined";
|
|
3288
|
+
const source = options?.url ?? (hasWindow ? window.location.search : null);
|
|
3289
|
+
if (source === null) {
|
|
3290
|
+
logger.warn("handleRedirectCallback: SSR \u74B0\u5883\u3067\u306F options.url \u304C\u5FC5\u8981\u3067\u3059");
|
|
3291
|
+
return {
|
|
3292
|
+
error: client.createError(
|
|
3293
|
+
CALLBACK_ERROR_CODES.CALLBACK_URL_UNAVAILABLE,
|
|
3294
|
+
"\u30B3\u30FC\u30EB\u30D0\u30C3\u30AF URL \u3092\u89E3\u6C7A\u3067\u304D\u307E\u305B\u3093\u3002SSR \u74B0\u5883\u3067\u306F options.url \u3092\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044"
|
|
3295
|
+
)
|
|
3296
|
+
};
|
|
3297
|
+
}
|
|
3298
|
+
const params = extractSearchParams(source);
|
|
3299
|
+
const code = params.get("code");
|
|
3300
|
+
const urlState = params.get("state");
|
|
3301
|
+
if (!code) {
|
|
3302
|
+
logger.warn("handleRedirectCallback: \u8A8D\u53EF\u30B3\u30FC\u30C9\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093");
|
|
3303
|
+
return {
|
|
3304
|
+
error: client.createError(
|
|
3305
|
+
CALLBACK_ERROR_CODES.MISSING_CODE,
|
|
3306
|
+
"\u30B3\u30FC\u30EB\u30D0\u30C3\u30AF URL \u306B\u8A8D\u53EF\u30B3\u30FC\u30C9\u304C\u542B\u307E\u308C\u3066\u3044\u307E\u305B\u3093"
|
|
3307
|
+
)
|
|
3308
|
+
};
|
|
3309
|
+
}
|
|
3310
|
+
const storedState = retrieveState(logger);
|
|
3311
|
+
if (storedState !== null || urlState !== null) {
|
|
3312
|
+
const stateMatches = storedState !== null && storedState === urlState;
|
|
3313
|
+
if (!stateMatches) {
|
|
3314
|
+
if (storedState === null && !isStateStorageAvailable()) {
|
|
3315
|
+
logger.error(
|
|
3316
|
+
"handleRedirectCallback: OAuth state \u3092\u4FDD\u5B58\u3067\u304D\u308B storage \u304C\u5229\u7528\u3067\u304D\u306A\u3044\u305F\u3081 CSRF \u691C\u8A3C\u304C\u3067\u304D\u307E\u305B\u3093\uFF08iOS private mode \u7B49\uFF09"
|
|
3317
|
+
);
|
|
3318
|
+
return {
|
|
3319
|
+
error: client.createError(
|
|
3320
|
+
CALLBACK_ERROR_CODES.STATE_UNAVAILABLE,
|
|
3321
|
+
"OAuth state \u3092\u4FDD\u5B58\u3067\u304D\u308B storage \u304C\u5229\u7528\u3067\u304D\u306A\u3044\u305F\u3081\u8A8D\u8A3C\u3092\u5B8C\u4E86\u3067\u304D\u307E\u305B\u3093\u3002\u30D6\u30E9\u30A6\u30B6\u306E\u30D7\u30E9\u30A4\u30D9\u30FC\u30C8\u30E2\u30FC\u30C9\u3084\u30B9\u30C8\u30EC\u30FC\u30B8\u8A2D\u5B9A\u3092\u3054\u78BA\u8A8D\u304F\u3060\u3055\u3044"
|
|
3322
|
+
)
|
|
3323
|
+
};
|
|
3324
|
+
}
|
|
3325
|
+
logger.error(
|
|
3326
|
+
"handleRedirectCallback: OAuth state mismatch or omitted (possible CSRF) \u2014 \u8A8D\u8A3C\u3092\u4E2D\u6B62\u3057\u307E\u3057\u305F"
|
|
3327
|
+
);
|
|
3328
|
+
return {
|
|
3329
|
+
error: client.createError(
|
|
3330
|
+
CALLBACK_ERROR_CODES.STATE_MISMATCH,
|
|
3331
|
+
"OAuth state \u304C\u4E00\u81F4\u3057\u307E\u305B\u3093\u3002CSRF \u306E\u53EF\u80FD\u6027\u304C\u3042\u308B\u305F\u3081\u8A8D\u8A3C\u3092\u4E2D\u6B62\u3057\u307E\u3057\u305F"
|
|
3332
|
+
)
|
|
3333
|
+
};
|
|
3334
|
+
}
|
|
3335
|
+
}
|
|
3336
|
+
const verifier = retrieveCodeVerifier(logger);
|
|
3337
|
+
const result = await client.exchangeCodeForTokens(code, verifier ?? void 0);
|
|
3338
|
+
if (!result.error && hasWindow) {
|
|
3339
|
+
cleanCallbackUrlParams(logger);
|
|
3340
|
+
}
|
|
3341
|
+
return result;
|
|
3342
|
+
}
|
|
2956
3343
|
var DEFAULT_REFRESH_INTERVAL_MS = 5 * 60 * 1e3;
|
|
2957
3344
|
var TOKEN_REFRESH_RATIO = 0.8;
|
|
2958
3345
|
var FFIDContext = react.createContext(null);
|
|
@@ -3127,8 +3514,7 @@ function FFIDProvider({
|
|
|
3127
3514
|
const code = urlParams.get("code");
|
|
3128
3515
|
if (!code) return;
|
|
3129
3516
|
client.logger.debug("Authorization code detected, exchanging for tokens");
|
|
3130
|
-
|
|
3131
|
-
client.exchangeCodeForTokens(code, codeVerifier ?? void 0).then((result) => {
|
|
3517
|
+
handleRedirectCallback(client).then((result) => {
|
|
3132
3518
|
if (result.error) {
|
|
3133
3519
|
client.logger.error("Token exchange failed:", result.error);
|
|
3134
3520
|
setError(result.error);
|
|
@@ -3136,10 +3522,6 @@ function FFIDProvider({
|
|
|
3136
3522
|
setIsLoading(false);
|
|
3137
3523
|
return;
|
|
3138
3524
|
}
|
|
3139
|
-
const cleanUrl = new URL(window.location.href);
|
|
3140
|
-
cleanUrl.searchParams.delete("code");
|
|
3141
|
-
cleanUrl.searchParams.delete("state");
|
|
3142
|
-
window.history.replaceState({}, "", cleanUrl.toString());
|
|
3143
3525
|
client.logger.debug("Token exchange successful, refreshing session");
|
|
3144
3526
|
return refresh();
|
|
3145
3527
|
}).catch((err) => {
|
|
@@ -4957,18 +5339,22 @@ exports.FFIDUserMenu = FFIDUserMenu;
|
|
|
4957
5339
|
exports.FFID_ANNOUNCEMENTS_ERROR_CODES = FFID_ANNOUNCEMENTS_ERROR_CODES;
|
|
4958
5340
|
exports.FFID_INQUIRY_CATEGORIES = FFID_INQUIRY_CATEGORIES;
|
|
4959
5341
|
exports.FFID_INQUIRY_CATEGORIES_SITE_2026 = FFID_INQUIRY_CATEGORIES_SITE_2026;
|
|
5342
|
+
exports.cleanupStateStorage = cleanupStateStorage;
|
|
4960
5343
|
exports.computeEffectiveStatusFromSession = computeEffectiveStatusFromSession;
|
|
4961
5344
|
exports.createFFIDAnnouncementsClient = createFFIDAnnouncementsClient;
|
|
4962
5345
|
exports.createFFIDClient = createFFIDClient;
|
|
4963
5346
|
exports.createTokenStore = createTokenStore;
|
|
4964
5347
|
exports.generateCodeChallenge = generateCodeChallenge;
|
|
4965
5348
|
exports.generateCodeVerifier = generateCodeVerifier;
|
|
5349
|
+
exports.handleRedirectCallback = handleRedirectCallback;
|
|
4966
5350
|
exports.hasAccessFromUserinfo = hasAccessFromUserinfo;
|
|
4967
5351
|
exports.isBlockedFromUserinfo = isBlockedFromUserinfo;
|
|
4968
5352
|
exports.isFFIDInquiryCategorySite2026 = isFFIDInquiryCategorySite2026;
|
|
4969
5353
|
exports.normalizeRedirectUri = normalizeRedirectUri;
|
|
4970
5354
|
exports.retrieveCodeVerifier = retrieveCodeVerifier;
|
|
5355
|
+
exports.retrieveState = retrieveState;
|
|
4971
5356
|
exports.storeCodeVerifier = storeCodeVerifier;
|
|
5357
|
+
exports.storeState = storeState;
|
|
4972
5358
|
exports.useFFID = useFFID;
|
|
4973
5359
|
exports.useFFIDAnnouncements = useFFIDAnnouncements;
|
|
4974
5360
|
exports.useFFIDContext = useFFIDContext;
|
|
@@ -1,34 +1,34 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var
|
|
3
|
+
var chunkBVDUQQHP_cjs = require('../chunk-BVDUQQHP.cjs');
|
|
4
4
|
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
Object.defineProperty(exports, "FFIDAnnouncementBadge", {
|
|
8
8
|
enumerable: true,
|
|
9
|
-
get: function () { return
|
|
9
|
+
get: function () { return chunkBVDUQQHP_cjs.FFIDAnnouncementBadge; }
|
|
10
10
|
});
|
|
11
11
|
Object.defineProperty(exports, "FFIDAnnouncementList", {
|
|
12
12
|
enumerable: true,
|
|
13
|
-
get: function () { return
|
|
13
|
+
get: function () { return chunkBVDUQQHP_cjs.FFIDAnnouncementList; }
|
|
14
14
|
});
|
|
15
15
|
Object.defineProperty(exports, "FFIDInquiryForm", {
|
|
16
16
|
enumerable: true,
|
|
17
|
-
get: function () { return
|
|
17
|
+
get: function () { return chunkBVDUQQHP_cjs.FFIDInquiryForm; }
|
|
18
18
|
});
|
|
19
19
|
Object.defineProperty(exports, "FFIDLoginButton", {
|
|
20
20
|
enumerable: true,
|
|
21
|
-
get: function () { return
|
|
21
|
+
get: function () { return chunkBVDUQQHP_cjs.FFIDLoginButton; }
|
|
22
22
|
});
|
|
23
23
|
Object.defineProperty(exports, "FFIDOrganizationSwitcher", {
|
|
24
24
|
enumerable: true,
|
|
25
|
-
get: function () { return
|
|
25
|
+
get: function () { return chunkBVDUQQHP_cjs.FFIDOrganizationSwitcher; }
|
|
26
26
|
});
|
|
27
27
|
Object.defineProperty(exports, "FFIDSubscriptionBadge", {
|
|
28
28
|
enumerable: true,
|
|
29
|
-
get: function () { return
|
|
29
|
+
get: function () { return chunkBVDUQQHP_cjs.FFIDSubscriptionBadge; }
|
|
30
30
|
});
|
|
31
31
|
Object.defineProperty(exports, "FFIDUserMenu", {
|
|
32
32
|
enumerable: true,
|
|
33
|
-
get: function () { return
|
|
33
|
+
get: function () { return chunkBVDUQQHP_cjs.FFIDUserMenu; }
|
|
34
34
|
});
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export {
|
|
1
|
+
export { D as FFIDAnnouncementBadge, af as FFIDAnnouncementBadgeClassNames, ag as FFIDAnnouncementBadgeProps, G as FFIDAnnouncementList, ah as FFIDAnnouncementListClassNames, ai as FFIDAnnouncementListProps, P as FFIDInquiryForm, Q as FFIDInquiryFormCategoryItem, R as FFIDInquiryFormClassNames, S as FFIDInquiryFormLegalLayout, T as FFIDInquiryFormOrganization, U as FFIDInquiryFormPlaceholderContext, V as FFIDInquiryFormPrefill, W as FFIDInquiryFormProps, X as FFIDInquiryFormSubmitData, Y as FFIDInquiryFormSubmitResult, _ as FFIDLoginButton, aj as FFIDLoginButtonProps, a1 as FFIDOrganizationSwitcher, ak as FFIDOrganizationSwitcherClassNames, al as FFIDOrganizationSwitcherProps, a6 as FFIDSubscriptionBadge, am as FFIDSubscriptionBadgeClassNames, an as FFIDSubscriptionBadgeProps, a8 as FFIDUserMenu, ao as FFIDUserMenuClassNames, ap as FFIDUserMenuProps } from '../index-CsVJTuPv.cjs';
|
|
2
2
|
import 'react/jsx-runtime';
|
|
3
3
|
import 'react';
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export {
|
|
1
|
+
export { D as FFIDAnnouncementBadge, af as FFIDAnnouncementBadgeClassNames, ag as FFIDAnnouncementBadgeProps, G as FFIDAnnouncementList, ah as FFIDAnnouncementListClassNames, ai as FFIDAnnouncementListProps, P as FFIDInquiryForm, Q as FFIDInquiryFormCategoryItem, R as FFIDInquiryFormClassNames, S as FFIDInquiryFormLegalLayout, T as FFIDInquiryFormOrganization, U as FFIDInquiryFormPlaceholderContext, V as FFIDInquiryFormPrefill, W as FFIDInquiryFormProps, X as FFIDInquiryFormSubmitData, Y as FFIDInquiryFormSubmitResult, _ as FFIDLoginButton, aj as FFIDLoginButtonProps, a1 as FFIDOrganizationSwitcher, ak as FFIDOrganizationSwitcherClassNames, al as FFIDOrganizationSwitcherProps, a6 as FFIDSubscriptionBadge, am as FFIDSubscriptionBadgeClassNames, an as FFIDSubscriptionBadgeProps, a8 as FFIDUserMenu, ao as FFIDUserMenuClassNames, ap as FFIDUserMenuProps } from '../index-CsVJTuPv.js';
|
|
2
2
|
import 'react/jsx-runtime';
|
|
3
3
|
import 'react';
|
package/dist/components/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export { FFIDAnnouncementBadge, FFIDAnnouncementList, FFIDInquiryForm, FFIDLoginButton, FFIDOrganizationSwitcher, FFIDSubscriptionBadge, FFIDUserMenu } from '../chunk-
|
|
1
|
+
export { FFIDAnnouncementBadge, FFIDAnnouncementList, FFIDInquiryForm, FFIDLoginButton, FFIDOrganizationSwitcher, FFIDSubscriptionBadge, FFIDUserMenu } from '../chunk-4II4R7NR.js';
|