@feelflow/ffid-sdk 10.0.0 → 11.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1062 -1010
- package/dist/agency/index.d.cts +23 -1
- package/dist/agency/index.d.ts +23 -1
- package/dist/{chunk-OXW6QSTO.js → chunk-QORWLPWG.js} +174 -46
- package/dist/{chunk-2LU2F3F2.cjs → chunk-VR3QR64A.cjs} +176 -45
- 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-5uxXRgIO.d.ts → ffid-client-By_vcKYu.d.ts} +32 -10
- package/dist/{ffid-client-CumygXXz.d.cts → ffid-client-C3A8L9uW.d.cts} +32 -10
- package/dist/{index-KjewcNn1.d.cts → index-BknVfXhZ.d.cts} +21 -9
- package/dist/{index-KjewcNn1.d.ts → index-BknVfXhZ.d.ts} +21 -9
- package/dist/index.cjs +100 -88
- package/dist/index.d.cts +117 -7
- package/dist/index.d.ts +117 -7
- package/dist/index.js +2 -2
- package/dist/server/index.cjs +101 -13
- package/dist/server/index.d.cts +74 -3
- package/dist/server/index.d.ts +74 -3
- package/dist/server/index.js +99 -14
- package/dist/server/test/index.d.cts +1 -1
- package/dist/server/test/index.d.ts +1 -1
- package/package.json +131 -131
package/dist/server/index.cjs
CHANGED
|
@@ -305,6 +305,9 @@ var OAUTH_INTROSPECT_ENDPOINT = "/api/v1/oauth/introspect";
|
|
|
305
305
|
var CACHE_KEY_PREFIX = "ffid:introspect:";
|
|
306
306
|
var HEX_BASE = 16;
|
|
307
307
|
var HEX_BYTE_WIDTH = 2;
|
|
308
|
+
var HTTP_TOO_MANY_REQUESTS = 429;
|
|
309
|
+
var HTTP_SERVER_ERROR_MIN = 500;
|
|
310
|
+
var HTTP_SERVER_ERROR_LIMIT = 600;
|
|
308
311
|
function createVerifyAccessToken(deps) {
|
|
309
312
|
const { authMode, baseUrl, serviceCode, serviceApiKey, verifyStrategy, logger, createError, errorCodes, cache, timeout } = deps;
|
|
310
313
|
let jwtVerify2 = null;
|
|
@@ -359,6 +362,16 @@ function createVerifyAccessToken(deps) {
|
|
|
359
362
|
}
|
|
360
363
|
return verifyViaIntrospect(accessToken);
|
|
361
364
|
}
|
|
365
|
+
function introspectionUnavailable(status) {
|
|
366
|
+
logger.error("Token introspection unavailable", { status });
|
|
367
|
+
return {
|
|
368
|
+
error: {
|
|
369
|
+
code: errorCodes.NETWORK_ERROR,
|
|
370
|
+
message: `\u30C8\u30FC\u30AF\u30F3\u691C\u8A3C\u30B5\u30FC\u30D3\u30B9\u304C\u4E00\u6642\u7684\u306B\u5229\u7528\u3067\u304D\u307E\u305B\u3093 (status: ${status})`,
|
|
371
|
+
details: { status }
|
|
372
|
+
}
|
|
373
|
+
};
|
|
374
|
+
}
|
|
362
375
|
async function verifyViaIntrospect(accessToken, cacheKeySuffix) {
|
|
363
376
|
if (!serviceApiKey) {
|
|
364
377
|
return {
|
|
@@ -416,10 +429,12 @@ function createVerifyAccessToken(deps) {
|
|
|
416
429
|
}
|
|
417
430
|
};
|
|
418
431
|
}
|
|
432
|
+
const upstreamUnavailable = response.status === HTTP_TOO_MANY_REQUESTS || response.status >= HTTP_SERVER_ERROR_MIN && response.status < HTTP_SERVER_ERROR_LIMIT;
|
|
419
433
|
let introspectResponse;
|
|
420
434
|
try {
|
|
421
435
|
introspectResponse = await response.json();
|
|
422
436
|
} catch (parseError) {
|
|
437
|
+
if (upstreamUnavailable) return introspectionUnavailable(response.status);
|
|
423
438
|
logger.error("Parse error during token verification:", parseError);
|
|
424
439
|
return {
|
|
425
440
|
error: {
|
|
@@ -428,6 +443,7 @@ function createVerifyAccessToken(deps) {
|
|
|
428
443
|
}
|
|
429
444
|
};
|
|
430
445
|
}
|
|
446
|
+
if (upstreamUnavailable) return introspectionUnavailable(response.status);
|
|
431
447
|
if (!response.ok) {
|
|
432
448
|
const errorBody = introspectResponse;
|
|
433
449
|
return {
|
|
@@ -1371,7 +1387,7 @@ var FFID_ORGANIZATION_PROFILE_ERROR_CODES = {
|
|
|
1371
1387
|
};
|
|
1372
1388
|
|
|
1373
1389
|
// src/client/version-check.ts
|
|
1374
|
-
var SDK_VERSION = "
|
|
1390
|
+
var SDK_VERSION = "11.0.0";
|
|
1375
1391
|
var SDK_USER_AGENT = `FFID-SDK/${SDK_VERSION} (TypeScript)`;
|
|
1376
1392
|
var SDK_VERSION_HEADER = "X-FFID-SDK-Version";
|
|
1377
1393
|
function sdkHeaders() {
|
|
@@ -1939,7 +1955,7 @@ function createInviteMethods(deps) {
|
|
|
1939
1955
|
}).toString();
|
|
1940
1956
|
return fetchWithAuth(`${EXT_INVITE_ENDPOINT}?${query}`, {
|
|
1941
1957
|
method: "POST",
|
|
1942
|
-
body: JSON.stringify({ email: params.email, role: params.role })
|
|
1958
|
+
body: JSON.stringify({ email: params.email, role: params.role, inviterUserId: params.inviterUserId })
|
|
1943
1959
|
});
|
|
1944
1960
|
}
|
|
1945
1961
|
return { inviteMember };
|
|
@@ -2242,6 +2258,25 @@ function createOAuthTokenMethods(deps) {
|
|
|
2242
2258
|
return { exchangeCodeForTokens, refreshAccessToken, signOutToken };
|
|
2243
2259
|
}
|
|
2244
2260
|
|
|
2261
|
+
// src/client/request-timeout.ts
|
|
2262
|
+
function withRequestTimeout(options, timeout) {
|
|
2263
|
+
if (timeout === void 0 || options.signal?.aborted) return options;
|
|
2264
|
+
const deadline = AbortSignal.timeout(timeout);
|
|
2265
|
+
if (!options.signal) return { ...options, signal: deadline };
|
|
2266
|
+
const callerSignal = options.signal;
|
|
2267
|
+
const controller = new AbortController();
|
|
2268
|
+
const abortFromCaller = () => abort(callerSignal.reason);
|
|
2269
|
+
const abortFromDeadline = () => abort(deadline.reason);
|
|
2270
|
+
function abort(reason) {
|
|
2271
|
+
callerSignal.removeEventListener("abort", abortFromCaller);
|
|
2272
|
+
deadline.removeEventListener("abort", abortFromDeadline);
|
|
2273
|
+
controller.abort(reason);
|
|
2274
|
+
}
|
|
2275
|
+
callerSignal.addEventListener("abort", abortFromCaller, { once: true });
|
|
2276
|
+
deadline.addEventListener("abort", abortFromDeadline, { once: true });
|
|
2277
|
+
return { ...options, signal: controller.signal };
|
|
2278
|
+
}
|
|
2279
|
+
|
|
2245
2280
|
// src/client/session.ts
|
|
2246
2281
|
var SESSION_ENDPOINT = "/api/v1/auth/session";
|
|
2247
2282
|
var LOGOUT_ENDPOINT = "/api/v1/auth/signout";
|
|
@@ -3760,6 +3795,7 @@ function toServiceAccessDecision(response, allowGrace) {
|
|
|
3760
3795
|
const isBlocked = response.isBlocked ?? (effectiveStatus === null || effectiveStatus !== null && BLOCKING_EFFECTIVE_STATUSES.includes(effectiveStatus));
|
|
3761
3796
|
return {
|
|
3762
3797
|
hasAccess,
|
|
3798
|
+
...response.seatAssigned !== void 0 ? { seatAssigned: response.seatAssigned } : {},
|
|
3763
3799
|
effectiveStatus,
|
|
3764
3800
|
isGrace,
|
|
3765
3801
|
isBlocked: isBlocked || !hasAccess,
|
|
@@ -3966,9 +4002,10 @@ function createFFIDClient(config) {
|
|
|
3966
4002
|
async function fetchWithAuth(endpoint, options = {}, authOverride) {
|
|
3967
4003
|
const url = `${baseUrl}${endpoint}`;
|
|
3968
4004
|
logger.debug("Fetching:", url);
|
|
3969
|
-
|
|
4005
|
+
let fetchOptions = buildFetchOptions(options, authOverride);
|
|
3970
4006
|
let response;
|
|
3971
4007
|
try {
|
|
4008
|
+
fetchOptions = withRequestTimeout(fetchOptions, timeout);
|
|
3972
4009
|
response = await fetch(url, fetchOptions);
|
|
3973
4010
|
} catch (error) {
|
|
3974
4011
|
logger.error("Network error:", error);
|
|
@@ -3983,9 +4020,9 @@ function createFFIDClient(config) {
|
|
|
3983
4020
|
const refreshResult = await refreshAccessToken();
|
|
3984
4021
|
if (!refreshResult.error) {
|
|
3985
4022
|
logger.debug("Token refreshed, retrying request");
|
|
3986
|
-
const retryOptions = buildFetchOptions(options);
|
|
3987
4023
|
try {
|
|
3988
|
-
|
|
4024
|
+
fetchOptions = withRequestTimeout(buildFetchOptions(options), timeout);
|
|
4025
|
+
response = await fetch(url, fetchOptions);
|
|
3989
4026
|
} catch (retryError) {
|
|
3990
4027
|
logger.error("Network error on retry:", retryError);
|
|
3991
4028
|
return {
|
|
@@ -4004,6 +4041,15 @@ function createFFIDClient(config) {
|
|
|
4004
4041
|
try {
|
|
4005
4042
|
raw = await response.json();
|
|
4006
4043
|
} catch (parseError) {
|
|
4044
|
+
if (fetchOptions.signal?.aborted) {
|
|
4045
|
+
logger.error("Response body aborted:", parseError);
|
|
4046
|
+
return {
|
|
4047
|
+
error: {
|
|
4048
|
+
code: FFID_ERROR_CODES.NETWORK_ERROR,
|
|
4049
|
+
message: "\u30EC\u30B9\u30DD\u30F3\u30B9\u306E\u53D7\u4FE1\u304C\u4E2D\u65AD\u3055\u308C\u307E\u3057\u305F"
|
|
4050
|
+
}
|
|
4051
|
+
};
|
|
4052
|
+
}
|
|
4007
4053
|
logger.error("Parse error:", parseError, "Status:", response.status);
|
|
4008
4054
|
return {
|
|
4009
4055
|
error: {
|
|
@@ -4365,6 +4411,50 @@ function createFFIDClient(config) {
|
|
|
4365
4411
|
};
|
|
4366
4412
|
}
|
|
4367
4413
|
|
|
4414
|
+
// src/client/authorization-error.ts
|
|
4415
|
+
var FFID_OAUTH_DENIAL_REASONS = [
|
|
4416
|
+
/** The user is not a member of the organization */
|
|
4417
|
+
"no_membership",
|
|
4418
|
+
/** The organization has no usable subscription for the service */
|
|
4419
|
+
"no_active_subscription",
|
|
4420
|
+
/** The organization has a subscription, but the user has no seat in it */
|
|
4421
|
+
"no_seat_assignment",
|
|
4422
|
+
/** The subscription has expired (canceled / past due beyond grace) */
|
|
4423
|
+
"subscription_expired",
|
|
4424
|
+
/** The flat plan's member limit is full, so no seat could be given */
|
|
4425
|
+
"member_limit_reached"
|
|
4426
|
+
];
|
|
4427
|
+
function isFFIDOAuthDenialReason(value) {
|
|
4428
|
+
return typeof value === "string" && FFID_OAUTH_DENIAL_REASONS.includes(value);
|
|
4429
|
+
}
|
|
4430
|
+
var DEFAULT_AUTHORIZATION_ERROR_MESSAGE = "\u8A8D\u53EF\u30B5\u30FC\u30D0\u30FC\u304C\u30A8\u30E9\u30FC\u3092\u8FD4\u3057\u307E\u3057\u305F";
|
|
4431
|
+
function buildAuthorizationError(params) {
|
|
4432
|
+
const oauthError = params.get("error");
|
|
4433
|
+
if (!oauthError) return null;
|
|
4434
|
+
const rawReason = params.get("denial_reason");
|
|
4435
|
+
const errorDescription = params.get("error_description");
|
|
4436
|
+
const details = {
|
|
4437
|
+
source: "authorization",
|
|
4438
|
+
denialReason: isFFIDOAuthDenialReason(rawReason) ? rawReason : null,
|
|
4439
|
+
errorDescription
|
|
4440
|
+
};
|
|
4441
|
+
return {
|
|
4442
|
+
code: oauthError,
|
|
4443
|
+
message: errorDescription ?? DEFAULT_AUTHORIZATION_ERROR_MESSAGE,
|
|
4444
|
+
details: { ...details }
|
|
4445
|
+
};
|
|
4446
|
+
}
|
|
4447
|
+
function getFFIDAuthorizationError(error) {
|
|
4448
|
+
const details = error?.details;
|
|
4449
|
+
if (!error || !details || details.source !== "authorization") return null;
|
|
4450
|
+
const description = details.errorDescription;
|
|
4451
|
+
return {
|
|
4452
|
+
oauthError: error.code,
|
|
4453
|
+
denialReason: isFFIDOAuthDenialReason(details.denialReason) ? details.denialReason : null,
|
|
4454
|
+
errorDescription: typeof description === "string" ? description : null
|
|
4455
|
+
};
|
|
4456
|
+
}
|
|
4457
|
+
|
|
4368
4458
|
// src/client/cache/memory-cache-adapter.ts
|
|
4369
4459
|
var MS_PER_SECOND2 = 1e3;
|
|
4370
4460
|
function createMemoryCacheAdapter() {
|
|
@@ -4655,14 +4745,9 @@ function createServerAuthClient(config) {
|
|
|
4655
4745
|
}
|
|
4656
4746
|
};
|
|
4657
4747
|
}
|
|
4658
|
-
const
|
|
4659
|
-
if (
|
|
4660
|
-
return {
|
|
4661
|
-
error: {
|
|
4662
|
-
code: errorParam,
|
|
4663
|
-
message: url.searchParams.get("error_description") ?? "\u8A8D\u53EF\u30B5\u30FC\u30D0\u30FC\u304C\u30A8\u30E9\u30FC\u3092\u8FD4\u3057\u307E\u3057\u305F"
|
|
4664
|
-
}
|
|
4665
|
-
};
|
|
4748
|
+
const authorizationError = buildAuthorizationError(url.searchParams);
|
|
4749
|
+
if (authorizationError) {
|
|
4750
|
+
return { error: authorizationError };
|
|
4666
4751
|
}
|
|
4667
4752
|
const code = url.searchParams.get("code");
|
|
4668
4753
|
if (!code) {
|
|
@@ -4774,6 +4859,7 @@ exports.FFID_ERROR_CODES = FFID_ERROR_CODES;
|
|
|
4774
4859
|
exports.FFID_INDUSTRY_CODES = FFID_INDUSTRY_CODES;
|
|
4775
4860
|
exports.FFID_MAX_ORGANIZATION_LOGO_FILE_SIZE_BYTES = FFID_MAX_ORGANIZATION_LOGO_FILE_SIZE_BYTES;
|
|
4776
4861
|
exports.FFID_MAX_ORGANIZATION_LOGO_FILE_SIZE_MB = FFID_MAX_ORGANIZATION_LOGO_FILE_SIZE_MB;
|
|
4862
|
+
exports.FFID_OAUTH_DENIAL_REASONS = FFID_OAUTH_DENIAL_REASONS;
|
|
4777
4863
|
exports.FFID_ORGANIZATION_PROFILE_ERROR_CODES = FFID_ORGANIZATION_PROFILE_ERROR_CODES;
|
|
4778
4864
|
exports.FFID_SESSION_COOKIE_NAME = FFID_SESSION_COOKIE_NAME;
|
|
4779
4865
|
exports.canonicalizeCatalogValue = canonicalizeCatalogValue;
|
|
@@ -4792,9 +4878,11 @@ exports.createVerifyAccessToken = createVerifyAccessToken;
|
|
|
4792
4878
|
exports.decodeConsentCookie = decodeConsentCookie;
|
|
4793
4879
|
exports.encodeConsentCookie = encodeConsentCookie;
|
|
4794
4880
|
exports.generateServerState = generateServerState;
|
|
4881
|
+
exports.getFFIDAuthorizationError = getFFIDAuthorizationError;
|
|
4795
4882
|
exports.getFFIDConsentFromCookieHeader = getFFIDConsentFromCookieHeader;
|
|
4796
4883
|
exports.getFFIDConsentFromCookies = getFFIDConsentFromCookies;
|
|
4797
4884
|
exports.hashCanonicalJson = hashCanonicalJson;
|
|
4885
|
+
exports.isFFIDOAuthDenialReason = isFFIDOAuthDenialReason;
|
|
4798
4886
|
exports.sessionCookies = sessionCookies;
|
|
4799
4887
|
exports.sha256Hex = sha256Hex;
|
|
4800
4888
|
exports.validatePublishedCatalog = validatePublishedCatalog;
|
package/dist/server/index.d.cts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { F as
|
|
2
|
-
export { j as FFIDAddMemberParams, k as FFIDAddMemberRequest, l as FFIDAddMemberResponse, m as FFIDAllowedLogoMimeType, n as FFIDAssignableMemberRole, o as FFIDCacheConfig, p as FFIDCatalogPublication, q as FFIDCatalogPublicationStatus, r as FFIDClient, s as FFIDCompanySizeCode, t as FFIDConfig, u as FFIDIndustryCode, v as FFIDListMembersResponse, w as FFIDListingConsentInput, x as FFIDLogoWallEntry, y as FFIDLogoWallResponse, z as FFIDMemberRole, A as
|
|
1
|
+
import { F as FFIDError, a as FFIDLogger, b as FFIDCacheAdapter, c as FFIDVerifyAccessTokenOptions, d as FFIDApiResponse, e as FFIDOAuthUserInfo, f as FFIDCatalogEnvironment, g as FFIDBillingInterval, h as FFIDTaxBehavior, i as FFIDPublishedCatalog } from '../ffid-client-C3A8L9uW.cjs';
|
|
2
|
+
export { j as FFIDAddMemberParams, k as FFIDAddMemberRequest, l as FFIDAddMemberResponse, m as FFIDAllowedLogoMimeType, n as FFIDAssignableMemberRole, o as FFIDCacheConfig, p as FFIDCatalogPublication, q as FFIDCatalogPublicationStatus, r as FFIDClient, s as FFIDCompanySizeCode, t as FFIDConfig, u as FFIDIndustryCode, v as FFIDListMembersResponse, w as FFIDListingConsentInput, x as FFIDLogoWallEntry, y as FFIDLogoWallResponse, z as FFIDMemberRole, A as FFIDMemberSeatAssignmentResult, B as FFIDOrganization, C as FFIDOrganizationLogoUploadResponse, D as FFIDOrganizationMember, E as FFIDOrganizationProfile, G as FFIDOrganizationProfileErrorCode, H as FFIDOrganizationProfileResponse, I as FFIDOtpSendResponse, J as FFIDOtpVerifyResponse, K as FFIDPasswordResetConfirmResponse, L as FFIDPasswordResetResponse, M as FFIDPasswordResetVerifyResponse, N as FFIDProfileCallOptions, O as FFIDProvisionMemberPlan, P as FFIDProvisionMemberPlanStatus, Q as FFIDProvisionMemberResult, R as FFIDProvisionMemberStatus, S as FFIDProvisionOrganizationDryRun, T as FFIDProvisionOrganizationMemberInput, U as FFIDProvisionOrganizationOutcome, V as FFIDProvisionOrganizationParams, W as FFIDProvisionOrganizationResponse, X as FFIDProvisionUserDryRun, Y as FFIDProvisionUserOutcome, Z as FFIDProvisionUserParams, _ as FFIDProvisionUserProfileInput, $ as FFIDProvisionUserResponse, a0 as FFIDProvisionedOrganization, a1 as FFIDProvisionedUser, a2 as FFIDPublishedPlan, a3 as FFIDRemoveMemberResponse, a4 as FFIDResetSessionResponse, a5 as FFIDSubscription, a6 as FFIDUpdateMemberRoleResponse, a7 as FFIDUpdateUserProfileRequest, a8 as FFIDUploadOrganizationLogoParams, a9 as FFIDUpsertOrganizationProfileParams, aa as FFIDUpsertOrganizationProfileRequest, ab as FFIDUser, ac as FFIDUserProfile, ad as FFID_ALLOWED_ORGANIZATION_LOGO_MIME_TYPES, ae as FFID_CATALOG_ENVIRONMENTS, af as FFID_CATALOG_PUBLICATION_STATUSES, ag as FFID_COMPANY_SIZE_CODES, ah as FFID_INDUSTRY_CODES, ai as FFID_MAX_ORGANIZATION_LOGO_FILE_SIZE_BYTES, aj as FFID_MAX_ORGANIZATION_LOGO_FILE_SIZE_MB, ak as FFID_ORGANIZATION_PROFILE_ERROR_CODES, al as TokenData, am as TokenStore, an as createFFIDClient, ao as createTokenStore } from '../ffid-client-C3A8L9uW.cjs';
|
|
3
3
|
export { D as DEFAULT_API_BASE_URL, a as DEFAULT_OAUTH_SCOPES } from '../constants-Ce2eXQtB.cjs';
|
|
4
4
|
import { z } from 'zod';
|
|
5
5
|
import '../types-Cjb9J0rL.cjs';
|
|
@@ -31,6 +31,77 @@ declare const FFID_ERROR_CODES: {
|
|
|
31
31
|
*/
|
|
32
32
|
type FFIDErrorCode = (typeof FFID_ERROR_CODES)[keyof typeof FFID_ERROR_CODES];
|
|
33
33
|
|
|
34
|
+
/**
|
|
35
|
+
* OAuth authorization errors returned to the service's `redirect_uri` (#6523)
|
|
36
|
+
*
|
|
37
|
+
* When FFID's `/oauth/authorize` refuses a user, it redirects back with
|
|
38
|
+
* `error=<OAuth error>&error_description=...&state=...` (RFC 6749 §4.1.2.1). For
|
|
39
|
+
* subscription / seat denials it also adds the FFID extension `denial_reason`
|
|
40
|
+
* (#6498), so a service can tell "no seat in this organization" from "no
|
|
41
|
+
* subscription" without parsing `error_description`.
|
|
42
|
+
*
|
|
43
|
+
* Both callback paths build the same {@link FFIDError} from those params:
|
|
44
|
+
* - `handleRedirectCallback` / token-mode `FFIDProvider` (browser, PKCE)
|
|
45
|
+
* - `createServerAuthClient().handleCallback` (confidential / BFF)
|
|
46
|
+
*
|
|
47
|
+
* `error.code` is the OAuth `error` value verbatim (e.g. `access_denied`), and
|
|
48
|
+
* `error.details` is {@link FFIDAuthorizationErrorDetails}. Read it with
|
|
49
|
+
* {@link getFFIDAuthorizationError}.
|
|
50
|
+
*
|
|
51
|
+
* This module has no browser dependency so the server entry can share it.
|
|
52
|
+
*/
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Values of FFID's `denial_reason` (the authorize error redirect and the
|
|
56
|
+
* `/oauth/userinfo` 403; `/oauth/introspect` only answers `active: false`). Kept
|
|
57
|
+
* identical to the FFID server's `DENIAL_REASONS` by a test in the FFID repository.
|
|
58
|
+
*
|
|
59
|
+
* This is a different vocabulary from `FFIDServiceAccessDenialReason`
|
|
60
|
+
* (`checkServiceAccess()`), which is derived from the subscription status.
|
|
61
|
+
*/
|
|
62
|
+
declare const FFID_OAUTH_DENIAL_REASONS: readonly ["no_membership", "no_active_subscription", "no_seat_assignment", "subscription_expired", "member_limit_reached"];
|
|
63
|
+
type FFIDOAuthDenialReason = (typeof FFID_OAUTH_DENIAL_REASONS)[number];
|
|
64
|
+
/** Runtime guard for {@link FFIDOAuthDenialReason}. Unknown values are `false`. */
|
|
65
|
+
declare function isFFIDOAuthDenialReason(value: unknown): value is FFIDOAuthDenialReason;
|
|
66
|
+
/** `FFIDError.details` of an authorization error */
|
|
67
|
+
interface FFIDAuthorizationErrorDetails {
|
|
68
|
+
/** Marks the error as an authorize redirect error (vs. token / network errors) */
|
|
69
|
+
source: 'authorization';
|
|
70
|
+
/**
|
|
71
|
+
* FFID's `denial_reason`. `null` when FFID did not send one (any OAuth error other
|
|
72
|
+
* than a subscription / seat denial) **or** sent a value this SDK version does not
|
|
73
|
+
* know — the error itself is still returned, only the reason is unknown.
|
|
74
|
+
*/
|
|
75
|
+
denialReason: FFIDOAuthDenialReason | null;
|
|
76
|
+
/**
|
|
77
|
+
* `error_description` as sent — short ASCII English meant for logs. The same text
|
|
78
|
+
* is `error.message` (when FFID sent one), so show end users guidance chosen by
|
|
79
|
+
* `denialReason` rather than the message itself.
|
|
80
|
+
*/
|
|
81
|
+
errorDescription: string | null;
|
|
82
|
+
}
|
|
83
|
+
/** An authorization error read back by {@link getFFIDAuthorizationError} */
|
|
84
|
+
interface FFIDAuthorizationError {
|
|
85
|
+
/** The OAuth `error` value, e.g. `access_denied`, `login_required` */
|
|
86
|
+
oauthError: string;
|
|
87
|
+
denialReason: FFIDOAuthDenialReason | null;
|
|
88
|
+
errorDescription: string | null;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Read an authorization error (and FFID's `denial_reason`) from an {@link FFIDError},
|
|
92
|
+
* e.g. `useFFID().error` or the result of `handleRedirectCallback` /
|
|
93
|
+
* `createServerAuthClient().handleCallback`. Returns `null` for any other error.
|
|
94
|
+
*
|
|
95
|
+
* ```ts
|
|
96
|
+
* const denial = getFFIDAuthorizationError(error)
|
|
97
|
+
* if (denial?.denialReason === 'no_seat_assignment') {
|
|
98
|
+
* // Ask an organization owner / admin to add the user. Do not call login() again:
|
|
99
|
+
* // FFID would refuse the same way.
|
|
100
|
+
* }
|
|
101
|
+
* ```
|
|
102
|
+
*/
|
|
103
|
+
declare function getFFIDAuthorizationError(error: FFIDError | null | undefined): FFIDAuthorizationError | null;
|
|
104
|
+
|
|
34
105
|
/** Token verification - verifyAccessToken() implementation */
|
|
35
106
|
|
|
36
107
|
/** Dependencies required by verifyAccessToken */
|
|
@@ -566,4 +637,4 @@ declare function computeScopeHash(input: FFIDScopeHashInput): Promise<string>;
|
|
|
566
637
|
*/
|
|
567
638
|
declare function validatePublishedCatalog(catalog: unknown): asserts catalog is FFIDPublishedCatalog;
|
|
568
639
|
|
|
569
|
-
export { ALL_DENIED_EXCEPT_NECESSARY, type BuildAuthorizeUrlOptions, type BuildAuthorizeUrlResult, CATALOG_HASH_PATTERN, CONSENT_COOKIE_NAME, COOKIE_VERSION, type CookieOptions, FFIDCacheAdapter, type FFIDCanonicalJsonValue, FFIDCatalogEnvironment, FFIDCatalogHashError, type FFIDConsentCategories, type FFIDConsentCategoryCode, type FFIDCookieStoreLike, type FFIDErrorCode, FFIDOAuthUserInfo, type FFIDPaymentConfiguration, FFIDPublishedCatalog, type FFIDScopeHashInput, type FFIDScopeHashPlan, FFIDTaxBehavior, type FFIDTaxConfiguration, FFIDVerifyAccessTokenOptions, FFID_ERROR_CODES, FFID_SESSION_COOKIE_NAME, type HandleCallbackInput, type KVNamespaceLike, type ServerAuthClient, type ServerAuthConfig, type ServerSessionTokens, type ServerTokenResult, canonicalizeCatalogValue, computeFFIDCheckoutCopyHash, computeLegalDisclosureHash, computePaymentConfigurationHash, computePraxisCopyHash, computeScopeHash, computeTaxConfigurationHash, createKVCacheAdapter, createMemoryCacheAdapter, createServerAuthClient, createVerifyAccessToken, decodeConsentCookie, encodeConsentCookie, generateServerState, getFFIDConsentFromCookieHeader, getFFIDConsentFromCookies, hashCanonicalJson, sessionCookies, sha256Hex, validatePublishedCatalog };
|
|
640
|
+
export { ALL_DENIED_EXCEPT_NECESSARY, type BuildAuthorizeUrlOptions, type BuildAuthorizeUrlResult, CATALOG_HASH_PATTERN, CONSENT_COOKIE_NAME, COOKIE_VERSION, type CookieOptions, type FFIDAuthorizationError, type FFIDAuthorizationErrorDetails, FFIDCacheAdapter, type FFIDCanonicalJsonValue, FFIDCatalogEnvironment, FFIDCatalogHashError, type FFIDConsentCategories, type FFIDConsentCategoryCode, type FFIDCookieStoreLike, type FFIDErrorCode, type FFIDOAuthDenialReason, FFIDOAuthUserInfo, type FFIDPaymentConfiguration, FFIDPublishedCatalog, type FFIDScopeHashInput, type FFIDScopeHashPlan, FFIDTaxBehavior, type FFIDTaxConfiguration, FFIDVerifyAccessTokenOptions, FFID_ERROR_CODES, FFID_OAUTH_DENIAL_REASONS, FFID_SESSION_COOKIE_NAME, type HandleCallbackInput, type KVNamespaceLike, type ServerAuthClient, type ServerAuthConfig, type ServerSessionTokens, type ServerTokenResult, canonicalizeCatalogValue, computeFFIDCheckoutCopyHash, computeLegalDisclosureHash, computePaymentConfigurationHash, computePraxisCopyHash, computeScopeHash, computeTaxConfigurationHash, createKVCacheAdapter, createMemoryCacheAdapter, createServerAuthClient, createVerifyAccessToken, decodeConsentCookie, encodeConsentCookie, generateServerState, getFFIDAuthorizationError, getFFIDConsentFromCookieHeader, getFFIDConsentFromCookies, hashCanonicalJson, isFFIDOAuthDenialReason, sessionCookies, sha256Hex, validatePublishedCatalog };
|
package/dist/server/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { F as
|
|
2
|
-
export { j as FFIDAddMemberParams, k as FFIDAddMemberRequest, l as FFIDAddMemberResponse, m as FFIDAllowedLogoMimeType, n as FFIDAssignableMemberRole, o as FFIDCacheConfig, p as FFIDCatalogPublication, q as FFIDCatalogPublicationStatus, r as FFIDClient, s as FFIDCompanySizeCode, t as FFIDConfig, u as FFIDIndustryCode, v as FFIDListMembersResponse, w as FFIDListingConsentInput, x as FFIDLogoWallEntry, y as FFIDLogoWallResponse, z as FFIDMemberRole, A as
|
|
1
|
+
import { F as FFIDError, a as FFIDLogger, b as FFIDCacheAdapter, c as FFIDVerifyAccessTokenOptions, d as FFIDApiResponse, e as FFIDOAuthUserInfo, f as FFIDCatalogEnvironment, g as FFIDBillingInterval, h as FFIDTaxBehavior, i as FFIDPublishedCatalog } from '../ffid-client-By_vcKYu.js';
|
|
2
|
+
export { j as FFIDAddMemberParams, k as FFIDAddMemberRequest, l as FFIDAddMemberResponse, m as FFIDAllowedLogoMimeType, n as FFIDAssignableMemberRole, o as FFIDCacheConfig, p as FFIDCatalogPublication, q as FFIDCatalogPublicationStatus, r as FFIDClient, s as FFIDCompanySizeCode, t as FFIDConfig, u as FFIDIndustryCode, v as FFIDListMembersResponse, w as FFIDListingConsentInput, x as FFIDLogoWallEntry, y as FFIDLogoWallResponse, z as FFIDMemberRole, A as FFIDMemberSeatAssignmentResult, B as FFIDOrganization, C as FFIDOrganizationLogoUploadResponse, D as FFIDOrganizationMember, E as FFIDOrganizationProfile, G as FFIDOrganizationProfileErrorCode, H as FFIDOrganizationProfileResponse, I as FFIDOtpSendResponse, J as FFIDOtpVerifyResponse, K as FFIDPasswordResetConfirmResponse, L as FFIDPasswordResetResponse, M as FFIDPasswordResetVerifyResponse, N as FFIDProfileCallOptions, O as FFIDProvisionMemberPlan, P as FFIDProvisionMemberPlanStatus, Q as FFIDProvisionMemberResult, R as FFIDProvisionMemberStatus, S as FFIDProvisionOrganizationDryRun, T as FFIDProvisionOrganizationMemberInput, U as FFIDProvisionOrganizationOutcome, V as FFIDProvisionOrganizationParams, W as FFIDProvisionOrganizationResponse, X as FFIDProvisionUserDryRun, Y as FFIDProvisionUserOutcome, Z as FFIDProvisionUserParams, _ as FFIDProvisionUserProfileInput, $ as FFIDProvisionUserResponse, a0 as FFIDProvisionedOrganization, a1 as FFIDProvisionedUser, a2 as FFIDPublishedPlan, a3 as FFIDRemoveMemberResponse, a4 as FFIDResetSessionResponse, a5 as FFIDSubscription, a6 as FFIDUpdateMemberRoleResponse, a7 as FFIDUpdateUserProfileRequest, a8 as FFIDUploadOrganizationLogoParams, a9 as FFIDUpsertOrganizationProfileParams, aa as FFIDUpsertOrganizationProfileRequest, ab as FFIDUser, ac as FFIDUserProfile, ad as FFID_ALLOWED_ORGANIZATION_LOGO_MIME_TYPES, ae as FFID_CATALOG_ENVIRONMENTS, af as FFID_CATALOG_PUBLICATION_STATUSES, ag as FFID_COMPANY_SIZE_CODES, ah as FFID_INDUSTRY_CODES, ai as FFID_MAX_ORGANIZATION_LOGO_FILE_SIZE_BYTES, aj as FFID_MAX_ORGANIZATION_LOGO_FILE_SIZE_MB, ak as FFID_ORGANIZATION_PROFILE_ERROR_CODES, al as TokenData, am as TokenStore, an as createFFIDClient, ao as createTokenStore } from '../ffid-client-By_vcKYu.js';
|
|
3
3
|
export { D as DEFAULT_API_BASE_URL, a as DEFAULT_OAUTH_SCOPES } from '../constants-Ce2eXQtB.js';
|
|
4
4
|
import { z } from 'zod';
|
|
5
5
|
import '../types-Cjb9J0rL.js';
|
|
@@ -31,6 +31,77 @@ declare const FFID_ERROR_CODES: {
|
|
|
31
31
|
*/
|
|
32
32
|
type FFIDErrorCode = (typeof FFID_ERROR_CODES)[keyof typeof FFID_ERROR_CODES];
|
|
33
33
|
|
|
34
|
+
/**
|
|
35
|
+
* OAuth authorization errors returned to the service's `redirect_uri` (#6523)
|
|
36
|
+
*
|
|
37
|
+
* When FFID's `/oauth/authorize` refuses a user, it redirects back with
|
|
38
|
+
* `error=<OAuth error>&error_description=...&state=...` (RFC 6749 §4.1.2.1). For
|
|
39
|
+
* subscription / seat denials it also adds the FFID extension `denial_reason`
|
|
40
|
+
* (#6498), so a service can tell "no seat in this organization" from "no
|
|
41
|
+
* subscription" without parsing `error_description`.
|
|
42
|
+
*
|
|
43
|
+
* Both callback paths build the same {@link FFIDError} from those params:
|
|
44
|
+
* - `handleRedirectCallback` / token-mode `FFIDProvider` (browser, PKCE)
|
|
45
|
+
* - `createServerAuthClient().handleCallback` (confidential / BFF)
|
|
46
|
+
*
|
|
47
|
+
* `error.code` is the OAuth `error` value verbatim (e.g. `access_denied`), and
|
|
48
|
+
* `error.details` is {@link FFIDAuthorizationErrorDetails}. Read it with
|
|
49
|
+
* {@link getFFIDAuthorizationError}.
|
|
50
|
+
*
|
|
51
|
+
* This module has no browser dependency so the server entry can share it.
|
|
52
|
+
*/
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Values of FFID's `denial_reason` (the authorize error redirect and the
|
|
56
|
+
* `/oauth/userinfo` 403; `/oauth/introspect` only answers `active: false`). Kept
|
|
57
|
+
* identical to the FFID server's `DENIAL_REASONS` by a test in the FFID repository.
|
|
58
|
+
*
|
|
59
|
+
* This is a different vocabulary from `FFIDServiceAccessDenialReason`
|
|
60
|
+
* (`checkServiceAccess()`), which is derived from the subscription status.
|
|
61
|
+
*/
|
|
62
|
+
declare const FFID_OAUTH_DENIAL_REASONS: readonly ["no_membership", "no_active_subscription", "no_seat_assignment", "subscription_expired", "member_limit_reached"];
|
|
63
|
+
type FFIDOAuthDenialReason = (typeof FFID_OAUTH_DENIAL_REASONS)[number];
|
|
64
|
+
/** Runtime guard for {@link FFIDOAuthDenialReason}. Unknown values are `false`. */
|
|
65
|
+
declare function isFFIDOAuthDenialReason(value: unknown): value is FFIDOAuthDenialReason;
|
|
66
|
+
/** `FFIDError.details` of an authorization error */
|
|
67
|
+
interface FFIDAuthorizationErrorDetails {
|
|
68
|
+
/** Marks the error as an authorize redirect error (vs. token / network errors) */
|
|
69
|
+
source: 'authorization';
|
|
70
|
+
/**
|
|
71
|
+
* FFID's `denial_reason`. `null` when FFID did not send one (any OAuth error other
|
|
72
|
+
* than a subscription / seat denial) **or** sent a value this SDK version does not
|
|
73
|
+
* know — the error itself is still returned, only the reason is unknown.
|
|
74
|
+
*/
|
|
75
|
+
denialReason: FFIDOAuthDenialReason | null;
|
|
76
|
+
/**
|
|
77
|
+
* `error_description` as sent — short ASCII English meant for logs. The same text
|
|
78
|
+
* is `error.message` (when FFID sent one), so show end users guidance chosen by
|
|
79
|
+
* `denialReason` rather than the message itself.
|
|
80
|
+
*/
|
|
81
|
+
errorDescription: string | null;
|
|
82
|
+
}
|
|
83
|
+
/** An authorization error read back by {@link getFFIDAuthorizationError} */
|
|
84
|
+
interface FFIDAuthorizationError {
|
|
85
|
+
/** The OAuth `error` value, e.g. `access_denied`, `login_required` */
|
|
86
|
+
oauthError: string;
|
|
87
|
+
denialReason: FFIDOAuthDenialReason | null;
|
|
88
|
+
errorDescription: string | null;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Read an authorization error (and FFID's `denial_reason`) from an {@link FFIDError},
|
|
92
|
+
* e.g. `useFFID().error` or the result of `handleRedirectCallback` /
|
|
93
|
+
* `createServerAuthClient().handleCallback`. Returns `null` for any other error.
|
|
94
|
+
*
|
|
95
|
+
* ```ts
|
|
96
|
+
* const denial = getFFIDAuthorizationError(error)
|
|
97
|
+
* if (denial?.denialReason === 'no_seat_assignment') {
|
|
98
|
+
* // Ask an organization owner / admin to add the user. Do not call login() again:
|
|
99
|
+
* // FFID would refuse the same way.
|
|
100
|
+
* }
|
|
101
|
+
* ```
|
|
102
|
+
*/
|
|
103
|
+
declare function getFFIDAuthorizationError(error: FFIDError | null | undefined): FFIDAuthorizationError | null;
|
|
104
|
+
|
|
34
105
|
/** Token verification - verifyAccessToken() implementation */
|
|
35
106
|
|
|
36
107
|
/** Dependencies required by verifyAccessToken */
|
|
@@ -566,4 +637,4 @@ declare function computeScopeHash(input: FFIDScopeHashInput): Promise<string>;
|
|
|
566
637
|
*/
|
|
567
638
|
declare function validatePublishedCatalog(catalog: unknown): asserts catalog is FFIDPublishedCatalog;
|
|
568
639
|
|
|
569
|
-
export { ALL_DENIED_EXCEPT_NECESSARY, type BuildAuthorizeUrlOptions, type BuildAuthorizeUrlResult, CATALOG_HASH_PATTERN, CONSENT_COOKIE_NAME, COOKIE_VERSION, type CookieOptions, FFIDCacheAdapter, type FFIDCanonicalJsonValue, FFIDCatalogEnvironment, FFIDCatalogHashError, type FFIDConsentCategories, type FFIDConsentCategoryCode, type FFIDCookieStoreLike, type FFIDErrorCode, FFIDOAuthUserInfo, type FFIDPaymentConfiguration, FFIDPublishedCatalog, type FFIDScopeHashInput, type FFIDScopeHashPlan, FFIDTaxBehavior, type FFIDTaxConfiguration, FFIDVerifyAccessTokenOptions, FFID_ERROR_CODES, FFID_SESSION_COOKIE_NAME, type HandleCallbackInput, type KVNamespaceLike, type ServerAuthClient, type ServerAuthConfig, type ServerSessionTokens, type ServerTokenResult, canonicalizeCatalogValue, computeFFIDCheckoutCopyHash, computeLegalDisclosureHash, computePaymentConfigurationHash, computePraxisCopyHash, computeScopeHash, computeTaxConfigurationHash, createKVCacheAdapter, createMemoryCacheAdapter, createServerAuthClient, createVerifyAccessToken, decodeConsentCookie, encodeConsentCookie, generateServerState, getFFIDConsentFromCookieHeader, getFFIDConsentFromCookies, hashCanonicalJson, sessionCookies, sha256Hex, validatePublishedCatalog };
|
|
640
|
+
export { ALL_DENIED_EXCEPT_NECESSARY, type BuildAuthorizeUrlOptions, type BuildAuthorizeUrlResult, CATALOG_HASH_PATTERN, CONSENT_COOKIE_NAME, COOKIE_VERSION, type CookieOptions, type FFIDAuthorizationError, type FFIDAuthorizationErrorDetails, FFIDCacheAdapter, type FFIDCanonicalJsonValue, FFIDCatalogEnvironment, FFIDCatalogHashError, type FFIDConsentCategories, type FFIDConsentCategoryCode, type FFIDCookieStoreLike, type FFIDErrorCode, type FFIDOAuthDenialReason, FFIDOAuthUserInfo, type FFIDPaymentConfiguration, FFIDPublishedCatalog, type FFIDScopeHashInput, type FFIDScopeHashPlan, FFIDTaxBehavior, type FFIDTaxConfiguration, FFIDVerifyAccessTokenOptions, FFID_ERROR_CODES, FFID_OAUTH_DENIAL_REASONS, FFID_SESSION_COOKIE_NAME, type HandleCallbackInput, type KVNamespaceLike, type ServerAuthClient, type ServerAuthConfig, type ServerSessionTokens, type ServerTokenResult, canonicalizeCatalogValue, computeFFIDCheckoutCopyHash, computeLegalDisclosureHash, computePaymentConfigurationHash, computePraxisCopyHash, computeScopeHash, computeTaxConfigurationHash, createKVCacheAdapter, createMemoryCacheAdapter, createServerAuthClient, createVerifyAccessToken, decodeConsentCookie, encodeConsentCookie, generateServerState, getFFIDAuthorizationError, getFFIDConsentFromCookieHeader, getFFIDConsentFromCookies, hashCanonicalJson, isFFIDOAuthDenialReason, sessionCookies, sha256Hex, validatePublishedCatalog };
|
package/dist/server/index.js
CHANGED
|
@@ -304,6 +304,9 @@ var OAUTH_INTROSPECT_ENDPOINT = "/api/v1/oauth/introspect";
|
|
|
304
304
|
var CACHE_KEY_PREFIX = "ffid:introspect:";
|
|
305
305
|
var HEX_BASE = 16;
|
|
306
306
|
var HEX_BYTE_WIDTH = 2;
|
|
307
|
+
var HTTP_TOO_MANY_REQUESTS = 429;
|
|
308
|
+
var HTTP_SERVER_ERROR_MIN = 500;
|
|
309
|
+
var HTTP_SERVER_ERROR_LIMIT = 600;
|
|
307
310
|
function createVerifyAccessToken(deps) {
|
|
308
311
|
const { authMode, baseUrl, serviceCode, serviceApiKey, verifyStrategy, logger, createError, errorCodes, cache, timeout } = deps;
|
|
309
312
|
let jwtVerify2 = null;
|
|
@@ -358,6 +361,16 @@ function createVerifyAccessToken(deps) {
|
|
|
358
361
|
}
|
|
359
362
|
return verifyViaIntrospect(accessToken);
|
|
360
363
|
}
|
|
364
|
+
function introspectionUnavailable(status) {
|
|
365
|
+
logger.error("Token introspection unavailable", { status });
|
|
366
|
+
return {
|
|
367
|
+
error: {
|
|
368
|
+
code: errorCodes.NETWORK_ERROR,
|
|
369
|
+
message: `\u30C8\u30FC\u30AF\u30F3\u691C\u8A3C\u30B5\u30FC\u30D3\u30B9\u304C\u4E00\u6642\u7684\u306B\u5229\u7528\u3067\u304D\u307E\u305B\u3093 (status: ${status})`,
|
|
370
|
+
details: { status }
|
|
371
|
+
}
|
|
372
|
+
};
|
|
373
|
+
}
|
|
361
374
|
async function verifyViaIntrospect(accessToken, cacheKeySuffix) {
|
|
362
375
|
if (!serviceApiKey) {
|
|
363
376
|
return {
|
|
@@ -415,10 +428,12 @@ function createVerifyAccessToken(deps) {
|
|
|
415
428
|
}
|
|
416
429
|
};
|
|
417
430
|
}
|
|
431
|
+
const upstreamUnavailable = response.status === HTTP_TOO_MANY_REQUESTS || response.status >= HTTP_SERVER_ERROR_MIN && response.status < HTTP_SERVER_ERROR_LIMIT;
|
|
418
432
|
let introspectResponse;
|
|
419
433
|
try {
|
|
420
434
|
introspectResponse = await response.json();
|
|
421
435
|
} catch (parseError) {
|
|
436
|
+
if (upstreamUnavailable) return introspectionUnavailable(response.status);
|
|
422
437
|
logger.error("Parse error during token verification:", parseError);
|
|
423
438
|
return {
|
|
424
439
|
error: {
|
|
@@ -427,6 +442,7 @@ function createVerifyAccessToken(deps) {
|
|
|
427
442
|
}
|
|
428
443
|
};
|
|
429
444
|
}
|
|
445
|
+
if (upstreamUnavailable) return introspectionUnavailable(response.status);
|
|
430
446
|
if (!response.ok) {
|
|
431
447
|
const errorBody = introspectResponse;
|
|
432
448
|
return {
|
|
@@ -1370,7 +1386,7 @@ var FFID_ORGANIZATION_PROFILE_ERROR_CODES = {
|
|
|
1370
1386
|
};
|
|
1371
1387
|
|
|
1372
1388
|
// src/client/version-check.ts
|
|
1373
|
-
var SDK_VERSION = "
|
|
1389
|
+
var SDK_VERSION = "11.0.0";
|
|
1374
1390
|
var SDK_USER_AGENT = `FFID-SDK/${SDK_VERSION} (TypeScript)`;
|
|
1375
1391
|
var SDK_VERSION_HEADER = "X-FFID-SDK-Version";
|
|
1376
1392
|
function sdkHeaders() {
|
|
@@ -1938,7 +1954,7 @@ function createInviteMethods(deps) {
|
|
|
1938
1954
|
}).toString();
|
|
1939
1955
|
return fetchWithAuth(`${EXT_INVITE_ENDPOINT}?${query}`, {
|
|
1940
1956
|
method: "POST",
|
|
1941
|
-
body: JSON.stringify({ email: params.email, role: params.role })
|
|
1957
|
+
body: JSON.stringify({ email: params.email, role: params.role, inviterUserId: params.inviterUserId })
|
|
1942
1958
|
});
|
|
1943
1959
|
}
|
|
1944
1960
|
return { inviteMember };
|
|
@@ -2241,6 +2257,25 @@ function createOAuthTokenMethods(deps) {
|
|
|
2241
2257
|
return { exchangeCodeForTokens, refreshAccessToken, signOutToken };
|
|
2242
2258
|
}
|
|
2243
2259
|
|
|
2260
|
+
// src/client/request-timeout.ts
|
|
2261
|
+
function withRequestTimeout(options, timeout) {
|
|
2262
|
+
if (timeout === void 0 || options.signal?.aborted) return options;
|
|
2263
|
+
const deadline = AbortSignal.timeout(timeout);
|
|
2264
|
+
if (!options.signal) return { ...options, signal: deadline };
|
|
2265
|
+
const callerSignal = options.signal;
|
|
2266
|
+
const controller = new AbortController();
|
|
2267
|
+
const abortFromCaller = () => abort(callerSignal.reason);
|
|
2268
|
+
const abortFromDeadline = () => abort(deadline.reason);
|
|
2269
|
+
function abort(reason) {
|
|
2270
|
+
callerSignal.removeEventListener("abort", abortFromCaller);
|
|
2271
|
+
deadline.removeEventListener("abort", abortFromDeadline);
|
|
2272
|
+
controller.abort(reason);
|
|
2273
|
+
}
|
|
2274
|
+
callerSignal.addEventListener("abort", abortFromCaller, { once: true });
|
|
2275
|
+
deadline.addEventListener("abort", abortFromDeadline, { once: true });
|
|
2276
|
+
return { ...options, signal: controller.signal };
|
|
2277
|
+
}
|
|
2278
|
+
|
|
2244
2279
|
// src/client/session.ts
|
|
2245
2280
|
var SESSION_ENDPOINT = "/api/v1/auth/session";
|
|
2246
2281
|
var LOGOUT_ENDPOINT = "/api/v1/auth/signout";
|
|
@@ -3759,6 +3794,7 @@ function toServiceAccessDecision(response, allowGrace) {
|
|
|
3759
3794
|
const isBlocked = response.isBlocked ?? (effectiveStatus === null || effectiveStatus !== null && BLOCKING_EFFECTIVE_STATUSES.includes(effectiveStatus));
|
|
3760
3795
|
return {
|
|
3761
3796
|
hasAccess,
|
|
3797
|
+
...response.seatAssigned !== void 0 ? { seatAssigned: response.seatAssigned } : {},
|
|
3762
3798
|
effectiveStatus,
|
|
3763
3799
|
isGrace,
|
|
3764
3800
|
isBlocked: isBlocked || !hasAccess,
|
|
@@ -3965,9 +4001,10 @@ function createFFIDClient(config) {
|
|
|
3965
4001
|
async function fetchWithAuth(endpoint, options = {}, authOverride) {
|
|
3966
4002
|
const url = `${baseUrl}${endpoint}`;
|
|
3967
4003
|
logger.debug("Fetching:", url);
|
|
3968
|
-
|
|
4004
|
+
let fetchOptions = buildFetchOptions(options, authOverride);
|
|
3969
4005
|
let response;
|
|
3970
4006
|
try {
|
|
4007
|
+
fetchOptions = withRequestTimeout(fetchOptions, timeout);
|
|
3971
4008
|
response = await fetch(url, fetchOptions);
|
|
3972
4009
|
} catch (error) {
|
|
3973
4010
|
logger.error("Network error:", error);
|
|
@@ -3982,9 +4019,9 @@ function createFFIDClient(config) {
|
|
|
3982
4019
|
const refreshResult = await refreshAccessToken();
|
|
3983
4020
|
if (!refreshResult.error) {
|
|
3984
4021
|
logger.debug("Token refreshed, retrying request");
|
|
3985
|
-
const retryOptions = buildFetchOptions(options);
|
|
3986
4022
|
try {
|
|
3987
|
-
|
|
4023
|
+
fetchOptions = withRequestTimeout(buildFetchOptions(options), timeout);
|
|
4024
|
+
response = await fetch(url, fetchOptions);
|
|
3988
4025
|
} catch (retryError) {
|
|
3989
4026
|
logger.error("Network error on retry:", retryError);
|
|
3990
4027
|
return {
|
|
@@ -4003,6 +4040,15 @@ function createFFIDClient(config) {
|
|
|
4003
4040
|
try {
|
|
4004
4041
|
raw = await response.json();
|
|
4005
4042
|
} catch (parseError) {
|
|
4043
|
+
if (fetchOptions.signal?.aborted) {
|
|
4044
|
+
logger.error("Response body aborted:", parseError);
|
|
4045
|
+
return {
|
|
4046
|
+
error: {
|
|
4047
|
+
code: FFID_ERROR_CODES.NETWORK_ERROR,
|
|
4048
|
+
message: "\u30EC\u30B9\u30DD\u30F3\u30B9\u306E\u53D7\u4FE1\u304C\u4E2D\u65AD\u3055\u308C\u307E\u3057\u305F"
|
|
4049
|
+
}
|
|
4050
|
+
};
|
|
4051
|
+
}
|
|
4006
4052
|
logger.error("Parse error:", parseError, "Status:", response.status);
|
|
4007
4053
|
return {
|
|
4008
4054
|
error: {
|
|
@@ -4364,6 +4410,50 @@ function createFFIDClient(config) {
|
|
|
4364
4410
|
};
|
|
4365
4411
|
}
|
|
4366
4412
|
|
|
4413
|
+
// src/client/authorization-error.ts
|
|
4414
|
+
var FFID_OAUTH_DENIAL_REASONS = [
|
|
4415
|
+
/** The user is not a member of the organization */
|
|
4416
|
+
"no_membership",
|
|
4417
|
+
/** The organization has no usable subscription for the service */
|
|
4418
|
+
"no_active_subscription",
|
|
4419
|
+
/** The organization has a subscription, but the user has no seat in it */
|
|
4420
|
+
"no_seat_assignment",
|
|
4421
|
+
/** The subscription has expired (canceled / past due beyond grace) */
|
|
4422
|
+
"subscription_expired",
|
|
4423
|
+
/** The flat plan's member limit is full, so no seat could be given */
|
|
4424
|
+
"member_limit_reached"
|
|
4425
|
+
];
|
|
4426
|
+
function isFFIDOAuthDenialReason(value) {
|
|
4427
|
+
return typeof value === "string" && FFID_OAUTH_DENIAL_REASONS.includes(value);
|
|
4428
|
+
}
|
|
4429
|
+
var DEFAULT_AUTHORIZATION_ERROR_MESSAGE = "\u8A8D\u53EF\u30B5\u30FC\u30D0\u30FC\u304C\u30A8\u30E9\u30FC\u3092\u8FD4\u3057\u307E\u3057\u305F";
|
|
4430
|
+
function buildAuthorizationError(params) {
|
|
4431
|
+
const oauthError = params.get("error");
|
|
4432
|
+
if (!oauthError) return null;
|
|
4433
|
+
const rawReason = params.get("denial_reason");
|
|
4434
|
+
const errorDescription = params.get("error_description");
|
|
4435
|
+
const details = {
|
|
4436
|
+
source: "authorization",
|
|
4437
|
+
denialReason: isFFIDOAuthDenialReason(rawReason) ? rawReason : null,
|
|
4438
|
+
errorDescription
|
|
4439
|
+
};
|
|
4440
|
+
return {
|
|
4441
|
+
code: oauthError,
|
|
4442
|
+
message: errorDescription ?? DEFAULT_AUTHORIZATION_ERROR_MESSAGE,
|
|
4443
|
+
details: { ...details }
|
|
4444
|
+
};
|
|
4445
|
+
}
|
|
4446
|
+
function getFFIDAuthorizationError(error) {
|
|
4447
|
+
const details = error?.details;
|
|
4448
|
+
if (!error || !details || details.source !== "authorization") return null;
|
|
4449
|
+
const description = details.errorDescription;
|
|
4450
|
+
return {
|
|
4451
|
+
oauthError: error.code,
|
|
4452
|
+
denialReason: isFFIDOAuthDenialReason(details.denialReason) ? details.denialReason : null,
|
|
4453
|
+
errorDescription: typeof description === "string" ? description : null
|
|
4454
|
+
};
|
|
4455
|
+
}
|
|
4456
|
+
|
|
4367
4457
|
// src/client/cache/memory-cache-adapter.ts
|
|
4368
4458
|
var MS_PER_SECOND2 = 1e3;
|
|
4369
4459
|
function createMemoryCacheAdapter() {
|
|
@@ -4654,14 +4744,9 @@ function createServerAuthClient(config) {
|
|
|
4654
4744
|
}
|
|
4655
4745
|
};
|
|
4656
4746
|
}
|
|
4657
|
-
const
|
|
4658
|
-
if (
|
|
4659
|
-
return {
|
|
4660
|
-
error: {
|
|
4661
|
-
code: errorParam,
|
|
4662
|
-
message: url.searchParams.get("error_description") ?? "\u8A8D\u53EF\u30B5\u30FC\u30D0\u30FC\u304C\u30A8\u30E9\u30FC\u3092\u8FD4\u3057\u307E\u3057\u305F"
|
|
4663
|
-
}
|
|
4664
|
-
};
|
|
4747
|
+
const authorizationError = buildAuthorizationError(url.searchParams);
|
|
4748
|
+
if (authorizationError) {
|
|
4749
|
+
return { error: authorizationError };
|
|
4665
4750
|
}
|
|
4666
4751
|
const code = url.searchParams.get("code");
|
|
4667
4752
|
if (!code) {
|
|
@@ -4752,4 +4837,4 @@ function clear(opts = {}) {
|
|
|
4752
4837
|
}
|
|
4753
4838
|
var sessionCookies = { serialize, read, clear };
|
|
4754
4839
|
|
|
4755
|
-
export { ALL_DENIED_EXCEPT_NECESSARY, CATALOG_HASH_PATTERN, CONSENT_COOKIE_NAME, COOKIE_VERSION, FFIDCatalogHashError, FFID_ALLOWED_ORGANIZATION_LOGO_MIME_TYPES, FFID_CATALOG_ENVIRONMENTS, FFID_CATALOG_PUBLICATION_STATUSES, FFID_COMPANY_SIZE_CODES, FFID_ERROR_CODES, FFID_INDUSTRY_CODES, FFID_MAX_ORGANIZATION_LOGO_FILE_SIZE_BYTES, FFID_MAX_ORGANIZATION_LOGO_FILE_SIZE_MB, FFID_ORGANIZATION_PROFILE_ERROR_CODES, FFID_SESSION_COOKIE_NAME, canonicalizeCatalogValue, computeFFIDCheckoutCopyHash, computeLegalDisclosureHash, computePaymentConfigurationHash, computePraxisCopyHash, computeScopeHash, computeTaxConfigurationHash, createFFIDClient, createKVCacheAdapter, createMemoryCacheAdapter, createServerAuthClient, createTokenStore, createVerifyAccessToken, decodeConsentCookie, encodeConsentCookie, generateServerState, getFFIDConsentFromCookieHeader, getFFIDConsentFromCookies, hashCanonicalJson, sessionCookies, sha256Hex, validatePublishedCatalog };
|
|
4840
|
+
export { ALL_DENIED_EXCEPT_NECESSARY, CATALOG_HASH_PATTERN, CONSENT_COOKIE_NAME, COOKIE_VERSION, FFIDCatalogHashError, FFID_ALLOWED_ORGANIZATION_LOGO_MIME_TYPES, FFID_CATALOG_ENVIRONMENTS, FFID_CATALOG_PUBLICATION_STATUSES, FFID_COMPANY_SIZE_CODES, FFID_ERROR_CODES, FFID_INDUSTRY_CODES, FFID_MAX_ORGANIZATION_LOGO_FILE_SIZE_BYTES, FFID_MAX_ORGANIZATION_LOGO_FILE_SIZE_MB, FFID_OAUTH_DENIAL_REASONS, FFID_ORGANIZATION_PROFILE_ERROR_CODES, FFID_SESSION_COOKIE_NAME, canonicalizeCatalogValue, computeFFIDCheckoutCopyHash, computeLegalDisclosureHash, computePaymentConfigurationHash, computePraxisCopyHash, computeScopeHash, computeTaxConfigurationHash, createFFIDClient, createKVCacheAdapter, createMemoryCacheAdapter, createServerAuthClient, createTokenStore, createVerifyAccessToken, decodeConsentCookie, encodeConsentCookie, generateServerState, getFFIDAuthorizationError, getFFIDConsentFromCookieHeader, getFFIDConsentFromCookies, hashCanonicalJson, isFFIDOAuthDenialReason, sessionCookies, sha256Hex, validatePublishedCatalog };
|