@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
|
@@ -122,6 +122,9 @@ function normalizeUserinfo(raw) {
|
|
|
122
122
|
return {
|
|
123
123
|
sub: raw.sub,
|
|
124
124
|
email: raw.email,
|
|
125
|
+
// #3791: undefined → undefined を保つ(false に丸めない)。古い FFID は wire に
|
|
126
|
+
// 含めないため、consumer は undefined を「未確認」ではなく「不明」として扱える。
|
|
127
|
+
emailVerified: raw.email_verified,
|
|
125
128
|
name: raw.name,
|
|
126
129
|
picture: raw.picture,
|
|
127
130
|
companyName: raw.company_name ?? null,
|
|
@@ -377,6 +380,9 @@ function createVerifyAccessToken(deps) {
|
|
|
377
380
|
const base = {
|
|
378
381
|
sub: introspectResponse.sub,
|
|
379
382
|
email: introspectResponse.email ?? null,
|
|
383
|
+
// #3791: preserve undefined (do NOT coerce to false) so introspect-strategy
|
|
384
|
+
// callers can distinguish "verified" / "not verified" / "older server".
|
|
385
|
+
email_verified: introspectResponse.email_verified,
|
|
380
386
|
name: introspectResponse.name ?? null,
|
|
381
387
|
picture: introspectResponse.picture ?? null,
|
|
382
388
|
company_name: introspectResponse.company_name ?? null,
|
|
@@ -931,8 +937,163 @@ function createProfileMethods(deps) {
|
|
|
931
937
|
return { getProfile, updateProfile };
|
|
932
938
|
}
|
|
933
939
|
|
|
940
|
+
// src/client/login-history-methods.ts
|
|
941
|
+
var EXT_LOGIN_HISTORY_ENDPOINT = "/api/v1/users/ext/me/login-history";
|
|
942
|
+
function createLoginHistoryMethods(deps) {
|
|
943
|
+
const { fetchWithAuth, createError } = deps;
|
|
944
|
+
async function getLoginHistory(params) {
|
|
945
|
+
if (params?.limit !== void 0 && (!Number.isInteger(params.limit) || params.limit <= 0)) {
|
|
946
|
+
return {
|
|
947
|
+
error: createError(
|
|
948
|
+
"VALIDATION_ERROR",
|
|
949
|
+
"limit \u306F1\u4EE5\u4E0A\u306E\u6574\u6570\u3092\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044"
|
|
950
|
+
)
|
|
951
|
+
};
|
|
952
|
+
}
|
|
953
|
+
const query = new URLSearchParams();
|
|
954
|
+
if (params?.userId !== void 0) {
|
|
955
|
+
query.set("userId", params.userId);
|
|
956
|
+
}
|
|
957
|
+
if (params?.limit !== void 0) {
|
|
958
|
+
query.set("limit", String(params.limit));
|
|
959
|
+
}
|
|
960
|
+
const queryString = query.toString();
|
|
961
|
+
const endpoint = queryString ? `${EXT_LOGIN_HISTORY_ENDPOINT}?${queryString}` : EXT_LOGIN_HISTORY_ENDPOINT;
|
|
962
|
+
return fetchWithAuth(endpoint);
|
|
963
|
+
}
|
|
964
|
+
return { getLoginHistory };
|
|
965
|
+
}
|
|
966
|
+
|
|
967
|
+
// src/client/organization-domains-methods.ts
|
|
968
|
+
var EXT_DOMAINS_ENDPOINT = "/api/v1/organizations/ext/domains";
|
|
969
|
+
function createOrganizationDomainsMethods(deps) {
|
|
970
|
+
const { fetchWithAuth, createError, serviceCode } = deps;
|
|
971
|
+
function buildQuery(organizationId) {
|
|
972
|
+
return new URLSearchParams({ organizationId, serviceCode }).toString();
|
|
973
|
+
}
|
|
974
|
+
async function getOrganizationDomains(params) {
|
|
975
|
+
if (!params.organizationId) {
|
|
976
|
+
return {
|
|
977
|
+
error: createError("VALIDATION_ERROR", "organizationId \u306F\u5FC5\u9808\u3067\u3059")
|
|
978
|
+
};
|
|
979
|
+
}
|
|
980
|
+
return fetchWithAuth(
|
|
981
|
+
`${EXT_DOMAINS_ENDPOINT}?${buildQuery(params.organizationId)}`
|
|
982
|
+
);
|
|
983
|
+
}
|
|
984
|
+
return { getOrganizationDomains };
|
|
985
|
+
}
|
|
986
|
+
|
|
987
|
+
// src/client/notification-preferences-methods.ts
|
|
988
|
+
var EXT_NOTIFICATION_PREFERENCES_ENDPOINT = "/api/v1/organizations/ext/notification-preferences";
|
|
989
|
+
function createNotificationPreferencesMethods(deps) {
|
|
990
|
+
const { fetchWithAuth, createError, serviceCode } = deps;
|
|
991
|
+
function buildQuery(organizationId) {
|
|
992
|
+
return new URLSearchParams({ organizationId, serviceCode }).toString();
|
|
993
|
+
}
|
|
994
|
+
async function getNotificationPreferences(params) {
|
|
995
|
+
if (!params.organizationId) {
|
|
996
|
+
return {
|
|
997
|
+
error: createError("VALIDATION_ERROR", "organizationId \u306F\u5FC5\u9808\u3067\u3059")
|
|
998
|
+
};
|
|
999
|
+
}
|
|
1000
|
+
return fetchWithAuth(
|
|
1001
|
+
`${EXT_NOTIFICATION_PREFERENCES_ENDPOINT}?${buildQuery(params.organizationId)}`
|
|
1002
|
+
);
|
|
1003
|
+
}
|
|
1004
|
+
async function updateNotificationPreferences(params) {
|
|
1005
|
+
if (!params.organizationId) {
|
|
1006
|
+
return {
|
|
1007
|
+
error: createError("VALIDATION_ERROR", "organizationId \u306F\u5FC5\u9808\u3067\u3059")
|
|
1008
|
+
};
|
|
1009
|
+
}
|
|
1010
|
+
if (params.preferences === null || typeof params.preferences !== "object" || Array.isArray(params.preferences)) {
|
|
1011
|
+
return {
|
|
1012
|
+
error: createError("VALIDATION_ERROR", "preferences \u306F\u30AA\u30D6\u30B8\u30A7\u30AF\u30C8\u3067\u3042\u308B\u5FC5\u8981\u304C\u3042\u308A\u307E\u3059")
|
|
1013
|
+
};
|
|
1014
|
+
}
|
|
1015
|
+
if (Object.keys(params.preferences).length === 0) {
|
|
1016
|
+
return {
|
|
1017
|
+
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")
|
|
1018
|
+
};
|
|
1019
|
+
}
|
|
1020
|
+
return fetchWithAuth(
|
|
1021
|
+
`${EXT_NOTIFICATION_PREFERENCES_ENDPOINT}?${buildQuery(params.organizationId)}`,
|
|
1022
|
+
{
|
|
1023
|
+
method: "PATCH",
|
|
1024
|
+
body: JSON.stringify({ preferences: params.preferences })
|
|
1025
|
+
}
|
|
1026
|
+
);
|
|
1027
|
+
}
|
|
1028
|
+
return { getNotificationPreferences, updateNotificationPreferences };
|
|
1029
|
+
}
|
|
1030
|
+
|
|
1031
|
+
// src/client/invite-methods.ts
|
|
1032
|
+
var EXT_INVITE_ENDPOINT = "/api/v1/organizations/ext/invite";
|
|
1033
|
+
function createInviteMethods(deps) {
|
|
1034
|
+
const { fetchWithAuth, createError, serviceCode } = deps;
|
|
1035
|
+
const assignableRoles = /* @__PURE__ */ new Set(["admin", "member", "viewer"]);
|
|
1036
|
+
async function inviteMember(params) {
|
|
1037
|
+
if (!params.organizationId || !params.email) {
|
|
1038
|
+
return {
|
|
1039
|
+
error: createError("VALIDATION_ERROR", "organizationId \u3068 email \u306F\u5FC5\u9808\u3067\u3059")
|
|
1040
|
+
};
|
|
1041
|
+
}
|
|
1042
|
+
if (params.role !== void 0 && !assignableRoles.has(params.role)) {
|
|
1043
|
+
return {
|
|
1044
|
+
error: createError(
|
|
1045
|
+
"VALIDATION_ERROR",
|
|
1046
|
+
"role \u306F admin\u3001member\u3001viewer \u306E\u3044\u305A\u308C\u304B\u3092\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044"
|
|
1047
|
+
)
|
|
1048
|
+
};
|
|
1049
|
+
}
|
|
1050
|
+
const query = new URLSearchParams({
|
|
1051
|
+
organizationId: params.organizationId,
|
|
1052
|
+
serviceCode
|
|
1053
|
+
}).toString();
|
|
1054
|
+
return fetchWithAuth(`${EXT_INVITE_ENDPOINT}?${query}`, {
|
|
1055
|
+
method: "POST",
|
|
1056
|
+
body: JSON.stringify({ email: params.email, role: params.role })
|
|
1057
|
+
});
|
|
1058
|
+
}
|
|
1059
|
+
return { inviteMember };
|
|
1060
|
+
}
|
|
1061
|
+
|
|
1062
|
+
// src/client/org-membership-methods.ts
|
|
1063
|
+
var EXT_USER_ORGANIZATIONS_ENDPOINT = "/api/v1/users/ext/me/organizations";
|
|
1064
|
+
function createOrgMembershipMethods(deps) {
|
|
1065
|
+
const { fetchWithAuth, createError } = deps;
|
|
1066
|
+
async function leaveOrganization(params) {
|
|
1067
|
+
if (!params || !params.organizationId) {
|
|
1068
|
+
return {
|
|
1069
|
+
error: createError("VALIDATION_ERROR", "organizationId \u306F\u5FC5\u9808\u3067\u3059")
|
|
1070
|
+
};
|
|
1071
|
+
}
|
|
1072
|
+
let endpoint = `${EXT_USER_ORGANIZATIONS_ENDPOINT}/${encodeURIComponent(params.organizationId)}`;
|
|
1073
|
+
if (params.userId) {
|
|
1074
|
+
endpoint += `?${new URLSearchParams({ userId: params.userId }).toString()}`;
|
|
1075
|
+
}
|
|
1076
|
+
return fetchWithAuth(endpoint, {
|
|
1077
|
+
method: "DELETE"
|
|
1078
|
+
});
|
|
1079
|
+
}
|
|
1080
|
+
return { leaveOrganization };
|
|
1081
|
+
}
|
|
1082
|
+
|
|
1083
|
+
// src/client/noncontract-methods.ts
|
|
1084
|
+
function createNonContractMethods(deps) {
|
|
1085
|
+
const { fetchWithAuth, createError, serviceCode } = deps;
|
|
1086
|
+
return {
|
|
1087
|
+
...createLoginHistoryMethods({ fetchWithAuth, createError }),
|
|
1088
|
+
...createOrganizationDomainsMethods({ fetchWithAuth, createError, serviceCode }),
|
|
1089
|
+
...createNotificationPreferencesMethods({ fetchWithAuth, createError, serviceCode }),
|
|
1090
|
+
...createInviteMethods({ fetchWithAuth, createError, serviceCode }),
|
|
1091
|
+
...createOrgMembershipMethods({ fetchWithAuth, createError })
|
|
1092
|
+
};
|
|
1093
|
+
}
|
|
1094
|
+
|
|
934
1095
|
// src/client/version-check.ts
|
|
935
|
-
var SDK_VERSION = "5.
|
|
1096
|
+
var SDK_VERSION = "5.14.0";
|
|
936
1097
|
var SDK_USER_AGENT = `FFID-SDK/${SDK_VERSION} (TypeScript)`;
|
|
937
1098
|
var SDK_VERSION_HEADER = "X-FFID-SDK-Version";
|
|
938
1099
|
function sdkHeaders() {
|
|
@@ -1004,9 +1165,20 @@ function createOAuthTokenMethods(deps) {
|
|
|
1004
1165
|
logger,
|
|
1005
1166
|
errorCodes
|
|
1006
1167
|
} = deps;
|
|
1007
|
-
async function exchangeCodeForTokens(code, codeVerifier) {
|
|
1168
|
+
async function exchangeCodeForTokens(code, codeVerifier, opts) {
|
|
1008
1169
|
const url = `${baseUrl}${OAUTH_TOKEN_ENDPOINT}`;
|
|
1009
1170
|
logger.debug("Exchanging code for tokens:", url);
|
|
1171
|
+
if (opts?.expectedState !== void 0 || opts?.actualState !== void 0) {
|
|
1172
|
+
if (opts?.expectedState !== opts?.actualState) {
|
|
1173
|
+
logger.error("Token exchange aborted: OAuth state mismatch or omitted (possible CSRF)");
|
|
1174
|
+
return {
|
|
1175
|
+
error: {
|
|
1176
|
+
code: errorCodes.STATE_MISMATCH_ERROR,
|
|
1177
|
+
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"
|
|
1178
|
+
}
|
|
1179
|
+
};
|
|
1180
|
+
}
|
|
1181
|
+
}
|
|
1010
1182
|
const effectiveRedirectUri = resolvedRedirectUri ?? (typeof window !== "undefined" ? window.location.origin + window.location.pathname : null);
|
|
1011
1183
|
if (!effectiveRedirectUri) {
|
|
1012
1184
|
logger.error("redirectUri is required for token exchange in SSR environments. Set config.redirectUri explicitly.");
|
|
@@ -1490,6 +1662,102 @@ function base64UrlEncode(buffer) {
|
|
|
1490
1662
|
return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
1491
1663
|
}
|
|
1492
1664
|
|
|
1665
|
+
// src/auth/state.ts
|
|
1666
|
+
var STATE_STORAGE_KEY = "ffid_oauth_state";
|
|
1667
|
+
var STATE_FALLBACK_STORAGE_KEY = "ffid_oauth_state_fb";
|
|
1668
|
+
var STATE_FALLBACK_TIMESTAMP_KEY = "ffid_oauth_state_fb_ts";
|
|
1669
|
+
var OAUTH_STATE_MAX_AGE_MS = 5 * 60 * 1e3;
|
|
1670
|
+
function storeState(state, logger) {
|
|
1671
|
+
if (typeof window === "undefined") {
|
|
1672
|
+
logger?.warn("storeState: storage is not available in SSR context");
|
|
1673
|
+
return false;
|
|
1674
|
+
}
|
|
1675
|
+
let sessionStored = false;
|
|
1676
|
+
try {
|
|
1677
|
+
window.sessionStorage.setItem(STATE_STORAGE_KEY, state);
|
|
1678
|
+
sessionStored = true;
|
|
1679
|
+
} catch (error) {
|
|
1680
|
+
logger?.warn("storeState: sessionStorage \u3078\u306E\u4FDD\u5B58\u306B\u5931\u6557\u3057\u307E\u3057\u305F:", error);
|
|
1681
|
+
}
|
|
1682
|
+
let localStored = false;
|
|
1683
|
+
try {
|
|
1684
|
+
window.localStorage.setItem(STATE_FALLBACK_STORAGE_KEY, state);
|
|
1685
|
+
window.localStorage.setItem(STATE_FALLBACK_TIMESTAMP_KEY, String(Date.now()));
|
|
1686
|
+
localStored = true;
|
|
1687
|
+
} catch (error) {
|
|
1688
|
+
logger?.warn("storeState: localStorage fallback \u3078\u306E\u4FDD\u5B58\u306B\u5931\u6557\u3057\u307E\u3057\u305F:", error);
|
|
1689
|
+
}
|
|
1690
|
+
return sessionStored || localStored;
|
|
1691
|
+
}
|
|
1692
|
+
function retrieveState(logger) {
|
|
1693
|
+
if (typeof window === "undefined") return null;
|
|
1694
|
+
let sessionState = null;
|
|
1695
|
+
try {
|
|
1696
|
+
sessionState = window.sessionStorage.getItem(STATE_STORAGE_KEY);
|
|
1697
|
+
} catch (error) {
|
|
1698
|
+
logger?.warn("retrieveState: sessionStorage \u304B\u3089\u306E\u53D6\u5F97\u306B\u5931\u6557\u3057\u307E\u3057\u305F:", error);
|
|
1699
|
+
}
|
|
1700
|
+
if (sessionState) {
|
|
1701
|
+
cleanupStateStorage(logger);
|
|
1702
|
+
return sessionState;
|
|
1703
|
+
}
|
|
1704
|
+
let fallbackState = null;
|
|
1705
|
+
let fallbackTimestamp = null;
|
|
1706
|
+
try {
|
|
1707
|
+
fallbackState = window.localStorage.getItem(STATE_FALLBACK_STORAGE_KEY);
|
|
1708
|
+
fallbackTimestamp = window.localStorage.getItem(STATE_FALLBACK_TIMESTAMP_KEY);
|
|
1709
|
+
} catch (error) {
|
|
1710
|
+
logger?.warn("retrieveState: localStorage fallback \u304B\u3089\u306E\u53D6\u5F97\u306B\u5931\u6557\u3057\u307E\u3057\u305F:", error);
|
|
1711
|
+
cleanupStateStorage(logger);
|
|
1712
|
+
return null;
|
|
1713
|
+
}
|
|
1714
|
+
if (!fallbackState || !fallbackTimestamp) {
|
|
1715
|
+
cleanupStateStorage(logger);
|
|
1716
|
+
return null;
|
|
1717
|
+
}
|
|
1718
|
+
const ts = Number(fallbackTimestamp);
|
|
1719
|
+
const age = Date.now() - ts;
|
|
1720
|
+
if (!Number.isFinite(ts) || age < 0 || age > OAUTH_STATE_MAX_AGE_MS) {
|
|
1721
|
+
cleanupStateStorage(logger);
|
|
1722
|
+
return null;
|
|
1723
|
+
}
|
|
1724
|
+
logger?.warn(
|
|
1725
|
+
"retrieveState: sessionStorage was empty, recovered OAuth state from localStorage fallback"
|
|
1726
|
+
);
|
|
1727
|
+
cleanupStateStorage(logger);
|
|
1728
|
+
return fallbackState;
|
|
1729
|
+
}
|
|
1730
|
+
function isStateStorageAvailable() {
|
|
1731
|
+
if (typeof window === "undefined") return false;
|
|
1732
|
+
const probeKey = "__ffid_oauth_state_probe__";
|
|
1733
|
+
try {
|
|
1734
|
+
window.sessionStorage.setItem(probeKey, "1");
|
|
1735
|
+
window.sessionStorage.removeItem(probeKey);
|
|
1736
|
+
return true;
|
|
1737
|
+
} catch {
|
|
1738
|
+
}
|
|
1739
|
+
try {
|
|
1740
|
+
window.localStorage.setItem(probeKey, "1");
|
|
1741
|
+
window.localStorage.removeItem(probeKey);
|
|
1742
|
+
return true;
|
|
1743
|
+
} catch {
|
|
1744
|
+
return false;
|
|
1745
|
+
}
|
|
1746
|
+
}
|
|
1747
|
+
function cleanupStateStorage(logger) {
|
|
1748
|
+
try {
|
|
1749
|
+
window.sessionStorage.removeItem(STATE_STORAGE_KEY);
|
|
1750
|
+
} catch (error) {
|
|
1751
|
+
logger?.warn("retrieveState: sessionStorage \u306E\u30AF\u30EA\u30FC\u30F3\u30A2\u30C3\u30D7\u306B\u5931\u6557\u3057\u307E\u3057\u305F:", error);
|
|
1752
|
+
}
|
|
1753
|
+
try {
|
|
1754
|
+
window.localStorage.removeItem(STATE_FALLBACK_STORAGE_KEY);
|
|
1755
|
+
window.localStorage.removeItem(STATE_FALLBACK_TIMESTAMP_KEY);
|
|
1756
|
+
} catch (error) {
|
|
1757
|
+
logger?.warn("retrieveState: localStorage \u306E\u30AF\u30EA\u30FC\u30F3\u30A2\u30C3\u30D7\u306B\u5931\u6557\u3057\u307E\u3057\u305F:", error);
|
|
1758
|
+
}
|
|
1759
|
+
}
|
|
1760
|
+
|
|
1493
1761
|
// src/client/redirect.ts
|
|
1494
1762
|
var OAUTH_AUTHORIZE_ENDPOINT = "/api/v1/oauth/authorize";
|
|
1495
1763
|
var AUTH_LOGOUT_ENDPOINT = "/api/v1/auth/logout";
|
|
@@ -1621,6 +1889,7 @@ function createRedirectMethods(deps) {
|
|
|
1621
1889
|
const recentCount = getRecentRedirectCount(authorizeKey, now, logger);
|
|
1622
1890
|
if (recentCount >= REDIRECT_LOOP_THRESHOLD) {
|
|
1623
1891
|
cleanupVerifierStorage(logger);
|
|
1892
|
+
cleanupStateStorage(logger);
|
|
1624
1893
|
logger.warn("[FFID SDK] redirect loop detected \u2014 \u81EA\u52D5\u30EA\u30C0\u30A4\u30EC\u30AF\u30C8\u3092\u505C\u6B62\u3057\u307E\u3057\u305F", {
|
|
1625
1894
|
authorizeKey,
|
|
1626
1895
|
recentCount,
|
|
@@ -1644,6 +1913,11 @@ function createRedirectMethods(deps) {
|
|
|
1644
1913
|
return { success: false, error: errorMessage };
|
|
1645
1914
|
}
|
|
1646
1915
|
const state = generateRandomState();
|
|
1916
|
+
if (!storeState(state, logger)) {
|
|
1917
|
+
logger.error(
|
|
1918
|
+
"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"
|
|
1919
|
+
);
|
|
1920
|
+
}
|
|
1647
1921
|
const redirectUri = resolvedRedirectUri ?? window.location.origin + window.location.pathname;
|
|
1648
1922
|
const params = new URLSearchParams({
|
|
1649
1923
|
response_type: "code",
|
|
@@ -2433,7 +2707,8 @@ var FFID_ERROR_CODES = {
|
|
|
2433
2707
|
TOKEN_EXCHANGE_ERROR: "TOKEN_EXCHANGE_ERROR",
|
|
2434
2708
|
TOKEN_REFRESH_ERROR: "TOKEN_REFRESH_ERROR",
|
|
2435
2709
|
NO_TOKENS: "NO_TOKENS",
|
|
2436
|
-
TOKEN_VERIFICATION_ERROR: "TOKEN_VERIFICATION_ERROR"
|
|
2710
|
+
TOKEN_VERIFICATION_ERROR: "TOKEN_VERIFICATION_ERROR",
|
|
2711
|
+
STATE_MISMATCH_ERROR: "STATE_MISMATCH_ERROR"
|
|
2437
2712
|
};
|
|
2438
2713
|
var EXT_CHECK_ENDPOINT = "/api/v1/subscriptions/ext/check";
|
|
2439
2714
|
var DEFAULT_ALLOW_GRACE = true;
|
|
@@ -2821,6 +3096,18 @@ function createFFIDClient(config) {
|
|
|
2821
3096
|
fetchWithAuth,
|
|
2822
3097
|
createError
|
|
2823
3098
|
});
|
|
3099
|
+
const {
|
|
3100
|
+
getLoginHistory,
|
|
3101
|
+
getOrganizationDomains,
|
|
3102
|
+
getNotificationPreferences,
|
|
3103
|
+
updateNotificationPreferences,
|
|
3104
|
+
inviteMember,
|
|
3105
|
+
leaveOrganization
|
|
3106
|
+
} = createNonContractMethods({
|
|
3107
|
+
fetchWithAuth,
|
|
3108
|
+
createError,
|
|
3109
|
+
serviceCode: config.serviceCode
|
|
3110
|
+
});
|
|
2824
3111
|
const { getAnalyticsConfig } = createAnalyticsMethods({
|
|
2825
3112
|
fetchWithAuth,
|
|
2826
3113
|
createError
|
|
@@ -2901,6 +3188,13 @@ function createFFIDClient(config) {
|
|
|
2901
3188
|
removeMember,
|
|
2902
3189
|
getProfile,
|
|
2903
3190
|
updateProfile,
|
|
3191
|
+
// Non-contract ext API coverage (#3783)
|
|
3192
|
+
getLoginHistory,
|
|
3193
|
+
getOrganizationDomains,
|
|
3194
|
+
getNotificationPreferences,
|
|
3195
|
+
updateNotificationPreferences,
|
|
3196
|
+
inviteMember,
|
|
3197
|
+
leaveOrganization,
|
|
2904
3198
|
getAnalyticsConfig,
|
|
2905
3199
|
createCheckoutSession,
|
|
2906
3200
|
createPortalSession,
|
|
@@ -2951,6 +3245,99 @@ function createFFIDClient(config) {
|
|
|
2951
3245
|
redirectUri: resolvedRedirectUri
|
|
2952
3246
|
};
|
|
2953
3247
|
}
|
|
3248
|
+
|
|
3249
|
+
// src/client/callback.ts
|
|
3250
|
+
var CALLBACK_ERROR_CODES = {
|
|
3251
|
+
/** Stored state missing or did not match the callback `state` (CSRF guard) */
|
|
3252
|
+
STATE_MISMATCH: "STATE_MISMATCH_ERROR",
|
|
3253
|
+
/**
|
|
3254
|
+
* Web storage is unavailable, so `state` could never be persisted across the
|
|
3255
|
+
* OAuth bounce. An environment issue (e.g. iOS private mode), not an attack —
|
|
3256
|
+
* surfaced distinctly so callers don't mistake it for a CSRF mismatch.
|
|
3257
|
+
*/
|
|
3258
|
+
STATE_UNAVAILABLE: "STATE_STORAGE_UNAVAILABLE",
|
|
3259
|
+
/** Callback URL had no `code` param */
|
|
3260
|
+
MISSING_CODE: "MISSING_AUTHORIZATION_CODE",
|
|
3261
|
+
/** SSR with no `options.url` — cannot resolve callback params */
|
|
3262
|
+
CALLBACK_URL_UNAVAILABLE: "CALLBACK_URL_UNAVAILABLE"
|
|
3263
|
+
};
|
|
3264
|
+
function extractSearchParams(source) {
|
|
3265
|
+
try {
|
|
3266
|
+
return new URL(source).searchParams;
|
|
3267
|
+
} catch {
|
|
3268
|
+
const queryStart = source.indexOf("?");
|
|
3269
|
+
const query = queryStart >= 0 ? source.slice(queryStart + 1) : source;
|
|
3270
|
+
return new URLSearchParams(query);
|
|
3271
|
+
}
|
|
3272
|
+
}
|
|
3273
|
+
function cleanCallbackUrlParams(logger) {
|
|
3274
|
+
try {
|
|
3275
|
+
const cleanUrl = new URL(window.location.href);
|
|
3276
|
+
cleanUrl.searchParams.delete("code");
|
|
3277
|
+
cleanUrl.searchParams.delete("state");
|
|
3278
|
+
window.history.replaceState({}, "", cleanUrl.toString());
|
|
3279
|
+
} catch (error) {
|
|
3280
|
+
logger.warn("handleRedirectCallback: URL \u306E\u30AF\u30EA\u30FC\u30F3\u30A2\u30C3\u30D7\u306B\u5931\u6557\u3057\u307E\u3057\u305F:", error);
|
|
3281
|
+
}
|
|
3282
|
+
}
|
|
3283
|
+
async function handleRedirectCallback(client, options) {
|
|
3284
|
+
const { logger } = client;
|
|
3285
|
+
const hasWindow = typeof window !== "undefined";
|
|
3286
|
+
const source = options?.url ?? (hasWindow ? window.location.search : null);
|
|
3287
|
+
if (source === null) {
|
|
3288
|
+
logger.warn("handleRedirectCallback: SSR \u74B0\u5883\u3067\u306F options.url \u304C\u5FC5\u8981\u3067\u3059");
|
|
3289
|
+
return {
|
|
3290
|
+
error: client.createError(
|
|
3291
|
+
CALLBACK_ERROR_CODES.CALLBACK_URL_UNAVAILABLE,
|
|
3292
|
+
"\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"
|
|
3293
|
+
)
|
|
3294
|
+
};
|
|
3295
|
+
}
|
|
3296
|
+
const params = extractSearchParams(source);
|
|
3297
|
+
const code = params.get("code");
|
|
3298
|
+
const urlState = params.get("state");
|
|
3299
|
+
if (!code) {
|
|
3300
|
+
logger.warn("handleRedirectCallback: \u8A8D\u53EF\u30B3\u30FC\u30C9\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093");
|
|
3301
|
+
return {
|
|
3302
|
+
error: client.createError(
|
|
3303
|
+
CALLBACK_ERROR_CODES.MISSING_CODE,
|
|
3304
|
+
"\u30B3\u30FC\u30EB\u30D0\u30C3\u30AF URL \u306B\u8A8D\u53EF\u30B3\u30FC\u30C9\u304C\u542B\u307E\u308C\u3066\u3044\u307E\u305B\u3093"
|
|
3305
|
+
)
|
|
3306
|
+
};
|
|
3307
|
+
}
|
|
3308
|
+
const storedState = retrieveState(logger);
|
|
3309
|
+
if (storedState !== null || urlState !== null) {
|
|
3310
|
+
const stateMatches = storedState !== null && storedState === urlState;
|
|
3311
|
+
if (!stateMatches) {
|
|
3312
|
+
if (storedState === null && !isStateStorageAvailable()) {
|
|
3313
|
+
logger.error(
|
|
3314
|
+
"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"
|
|
3315
|
+
);
|
|
3316
|
+
return {
|
|
3317
|
+
error: client.createError(
|
|
3318
|
+
CALLBACK_ERROR_CODES.STATE_UNAVAILABLE,
|
|
3319
|
+
"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"
|
|
3320
|
+
)
|
|
3321
|
+
};
|
|
3322
|
+
}
|
|
3323
|
+
logger.error(
|
|
3324
|
+
"handleRedirectCallback: OAuth state mismatch or omitted (possible CSRF) \u2014 \u8A8D\u8A3C\u3092\u4E2D\u6B62\u3057\u307E\u3057\u305F"
|
|
3325
|
+
);
|
|
3326
|
+
return {
|
|
3327
|
+
error: client.createError(
|
|
3328
|
+
CALLBACK_ERROR_CODES.STATE_MISMATCH,
|
|
3329
|
+
"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"
|
|
3330
|
+
)
|
|
3331
|
+
};
|
|
3332
|
+
}
|
|
3333
|
+
}
|
|
3334
|
+
const verifier = retrieveCodeVerifier(logger);
|
|
3335
|
+
const result = await client.exchangeCodeForTokens(code, verifier ?? void 0);
|
|
3336
|
+
if (!result.error && hasWindow) {
|
|
3337
|
+
cleanCallbackUrlParams(logger);
|
|
3338
|
+
}
|
|
3339
|
+
return result;
|
|
3340
|
+
}
|
|
2954
3341
|
var DEFAULT_REFRESH_INTERVAL_MS = 5 * 60 * 1e3;
|
|
2955
3342
|
var TOKEN_REFRESH_RATIO = 0.8;
|
|
2956
3343
|
var FFIDContext = createContext(null);
|
|
@@ -3125,8 +3512,7 @@ function FFIDProvider({
|
|
|
3125
3512
|
const code = urlParams.get("code");
|
|
3126
3513
|
if (!code) return;
|
|
3127
3514
|
client.logger.debug("Authorization code detected, exchanging for tokens");
|
|
3128
|
-
|
|
3129
|
-
client.exchangeCodeForTokens(code, codeVerifier ?? void 0).then((result) => {
|
|
3515
|
+
handleRedirectCallback(client).then((result) => {
|
|
3130
3516
|
if (result.error) {
|
|
3131
3517
|
client.logger.error("Token exchange failed:", result.error);
|
|
3132
3518
|
setError(result.error);
|
|
@@ -3134,10 +3520,6 @@ function FFIDProvider({
|
|
|
3134
3520
|
setIsLoading(false);
|
|
3135
3521
|
return;
|
|
3136
3522
|
}
|
|
3137
|
-
const cleanUrl = new URL(window.location.href);
|
|
3138
|
-
cleanUrl.searchParams.delete("code");
|
|
3139
|
-
cleanUrl.searchParams.delete("state");
|
|
3140
|
-
window.history.replaceState({}, "", cleanUrl.toString());
|
|
3141
3523
|
client.logger.debug("Token exchange successful, refreshing session");
|
|
3142
3524
|
return refresh();
|
|
3143
3525
|
}).catch((err) => {
|
|
@@ -4938,4 +5320,4 @@ function FFIDInquiryForm({
|
|
|
4938
5320
|
);
|
|
4939
5321
|
}
|
|
4940
5322
|
|
|
4941
|
-
export { ACCESS_GRANTING_EFFECTIVE_STATUSES, BLOCKING_EFFECTIVE_STATUSES, DEFAULT_API_BASE_URL, DEFAULT_OAUTH_SCOPES, EFFECTIVE_SUBSCRIPTION_STATUSES, FFIDAnnouncementBadge, FFIDAnnouncementList, FFIDInquiryForm, FFIDLoginButton, FFIDOrganizationSwitcher, FFIDProvider, FFIDSDKError, FFIDSubscriptionBadge, FFIDUserMenu, FFID_ANNOUNCEMENTS_ERROR_CODES, FFID_INQUIRY_CATEGORIES, FFID_INQUIRY_CATEGORIES_SITE_2026, computeEffectiveStatusFromSession, createFFIDAnnouncementsClient, createFFIDClient, createTokenStore, generateCodeChallenge, generateCodeVerifier, hasAccessFromUserinfo, isBlockedFromUserinfo, isFFIDInquiryCategorySite2026, normalizeRedirectUri, retrieveCodeVerifier, storeCodeVerifier, useFFID, useFFIDAnnouncements, useFFIDContext, useSubscription, withSubscription };
|
|
5323
|
+
export { ACCESS_GRANTING_EFFECTIVE_STATUSES, BLOCKING_EFFECTIVE_STATUSES, DEFAULT_API_BASE_URL, DEFAULT_OAUTH_SCOPES, EFFECTIVE_SUBSCRIPTION_STATUSES, FFIDAnnouncementBadge, FFIDAnnouncementList, FFIDInquiryForm, FFIDLoginButton, FFIDOrganizationSwitcher, FFIDProvider, FFIDSDKError, FFIDSubscriptionBadge, FFIDUserMenu, FFID_ANNOUNCEMENTS_ERROR_CODES, FFID_INQUIRY_CATEGORIES, FFID_INQUIRY_CATEGORIES_SITE_2026, cleanupStateStorage, computeEffectiveStatusFromSession, createFFIDAnnouncementsClient, createFFIDClient, createTokenStore, generateCodeChallenge, generateCodeVerifier, handleRedirectCallback, hasAccessFromUserinfo, isBlockedFromUserinfo, isFFIDInquiryCategorySite2026, normalizeRedirectUri, retrieveCodeVerifier, retrieveState, storeCodeVerifier, storeState, useFFID, useFFIDAnnouncements, useFFIDContext, useSubscription, withSubscription };
|