@feelflow/ffid-sdk 11.0.0 → 12.1.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.
@@ -310,13 +310,13 @@ var HTTP_SERVER_ERROR_MIN = 500;
310
310
  var HTTP_SERVER_ERROR_LIMIT = 600;
311
311
  function createVerifyAccessToken(deps) {
312
312
  const { authMode, baseUrl, serviceCode, serviceApiKey, verifyStrategy, logger, createError, errorCodes, cache, timeout } = deps;
313
- let jwtVerify2 = null;
313
+ let jwtVerify3 = null;
314
314
  let cacheReadFailureEscalated = false;
315
315
  let cacheWriteFailureEscalated = false;
316
316
  let missingSubtleCryptoLogged = false;
317
317
  function getJwtVerifier() {
318
- if (!jwtVerify2) {
319
- jwtVerify2 = createJwtVerifier({
318
+ if (!jwtVerify3) {
319
+ jwtVerify3 = createJwtVerifier({
320
320
  baseUrl,
321
321
  serviceCode,
322
322
  logger,
@@ -326,7 +326,7 @@ function createVerifyAccessToken(deps) {
326
326
  errorCodes
327
327
  });
328
328
  }
329
- return jwtVerify2;
329
+ return jwtVerify3;
330
330
  }
331
331
  async function verifyAccessToken(accessToken, options) {
332
332
  if (authMode !== "service-key") {
@@ -574,7 +574,8 @@ function createBillingMethods(deps) {
574
574
  subscriptionId: params.subscriptionId,
575
575
  successUrl: params.successUrl,
576
576
  cancelUrl: params.cancelUrl,
577
- ...params.planId ? { planId: params.planId } : {}
577
+ ...params.planId ? { planId: params.planId } : {},
578
+ ...params.quantity !== void 0 ? { quantity: params.quantity } : {}
578
579
  })
579
580
  }
580
581
  );
@@ -1387,7 +1388,7 @@ var FFID_ORGANIZATION_PROFILE_ERROR_CODES = {
1387
1388
  };
1388
1389
 
1389
1390
  // src/client/version-check.ts
1390
- var SDK_VERSION = "11.0.0";
1391
+ var SDK_VERSION = "12.1.0";
1391
1392
  var SDK_USER_AGENT = `FFID-SDK/${SDK_VERSION} (TypeScript)`;
1392
1393
  var SDK_VERSION_HEADER = "X-FFID-SDK-Version";
1393
1394
  function sdkHeaders() {
@@ -4595,6 +4596,19 @@ function generateServerState() {
4595
4596
  globalThis.crypto.getRandomValues(bytes);
4596
4597
  return Array.from(bytes, (b) => b.toString(HEX_RADIX2).padStart(2, "0")).join("");
4597
4598
  }
4599
+ var STORED_STATE_SEPARATOR = ".";
4600
+ function packStoredLoginHandle(handle) {
4601
+ return `${handle.state}${STORED_STATE_SEPARATOR}${handle.codeVerifier}`;
4602
+ }
4603
+ function unpackStoredLoginHandle(stored) {
4604
+ if (!stored) return null;
4605
+ const sep = stored.indexOf(STORED_STATE_SEPARATOR);
4606
+ if (sep <= 0) return null;
4607
+ const state = stored.slice(0, sep);
4608
+ const codeVerifier = stored.slice(sep + STORED_STATE_SEPARATOR.length);
4609
+ if (!codeVerifier) return null;
4610
+ return { state, codeVerifier };
4611
+ }
4598
4612
 
4599
4613
  // src/server/auth/server-oauth-token.ts
4600
4614
  var OAUTH_TOKEN_ENDPOINT2 = "/api/v1/oauth/token";
@@ -4656,7 +4670,7 @@ async function postToken(deps, body, fallbackErrorCode) {
4656
4670
  }
4657
4671
  return { tokens: toTokens(validation.tokens) };
4658
4672
  }
4659
- function exchangeAuthorizationCodeServer(deps, code) {
4673
+ function exchangeAuthorizationCodeServer(deps, code, codeVerifier) {
4660
4674
  return postToken(
4661
4675
  deps,
4662
4676
  {
@@ -4664,7 +4678,8 @@ function exchangeAuthorizationCodeServer(deps, code) {
4664
4678
  code,
4665
4679
  client_id: deps.clientId,
4666
4680
  client_secret: deps.clientSecret,
4667
- redirect_uri: deps.redirectUri
4681
+ redirect_uri: deps.redirectUri,
4682
+ code_verifier: codeVerifier
4668
4683
  },
4669
4684
  FFID_ERROR_CODES.TOKEN_EXCHANGE_ERROR
4670
4685
  );
@@ -4685,6 +4700,7 @@ function refreshTokensServer(deps, refreshToken) {
4685
4700
  // src/server/auth/server-auth-client.ts
4686
4701
  var OAUTH_AUTHORIZE_ENDPOINT2 = "/api/v1/oauth/authorize";
4687
4702
  var SCREEN_HINT_SIGNUP2 = "signup";
4703
+ var CODE_CHALLENGE_METHOD_S256 = "S256";
4688
4704
  function createServerAuthClient(config) {
4689
4705
  if (!config.serviceCode?.trim()) {
4690
4706
  throw new Error("FFID Server Auth: serviceCode \u304C\u672A\u8A2D\u5B9A\u3067\u3059");
@@ -4709,20 +4725,29 @@ function createServerAuthClient(config) {
4709
4725
  redirectUri: config.redirectUri,
4710
4726
  timeout: config.timeout
4711
4727
  };
4712
- function buildAuthorizeUrl(opts) {
4728
+ async function buildAuthorizeUrl(opts) {
4713
4729
  const state = generateServerState();
4730
+ const codeVerifier = generateCodeVerifier();
4731
+ const codeChallenge = await generateCodeChallenge(codeVerifier);
4714
4732
  const params = new URLSearchParams({
4715
4733
  response_type: "code",
4716
4734
  client_id: clientId,
4717
4735
  scope: config.scope,
4718
4736
  redirect_uri: config.redirectUri,
4719
- state
4737
+ state,
4738
+ code_challenge: codeChallenge,
4739
+ code_challenge_method: CODE_CHALLENGE_METHOD_S256
4720
4740
  });
4721
4741
  const organizationId = opts?.organizationId?.trim();
4722
4742
  if (organizationId) params.set("organization_id", organizationId);
4723
4743
  if (opts?.screenHint === SCREEN_HINT_SIGNUP2) params.set("screen_hint", SCREEN_HINT_SIGNUP2);
4724
4744
  if (opts?.prompt) params.set("prompt", opts.prompt);
4725
- return { url: `${apiBaseUrl}${OAUTH_AUTHORIZE_ENDPOINT2}?${params.toString()}`, state };
4745
+ return {
4746
+ url: `${apiBaseUrl}${OAUTH_AUTHORIZE_ENDPOINT2}?${params.toString()}`,
4747
+ // Opaque cookie value: state + verifier. Only `state` and the challenge
4748
+ // reach the URL; the verifier stays in the caller's httpOnly cookie.
4749
+ state: packStoredLoginHandle({ state, codeVerifier })
4750
+ };
4726
4751
  }
4727
4752
  async function handleCallback(input) {
4728
4753
  let url;
@@ -4736,12 +4761,15 @@ function createServerAuthClient(config) {
4736
4761
  }
4737
4762
  };
4738
4763
  }
4764
+ const stored = unpackStoredLoginHandle(input.storedState);
4739
4765
  const returnedState = url.searchParams.get("state") ?? void 0;
4740
- if (!input.storedState || !returnedState || input.storedState !== returnedState) {
4766
+ if (!stored || !returnedState || stored.state !== returnedState) {
4741
4767
  return {
4742
4768
  error: {
4743
4769
  code: FFID_ERROR_CODES.STATE_MISMATCH_ERROR,
4744
- 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"
4770
+ // Not "CSRF suspected": a state-only cookie written before 12.0.0 also
4771
+ // lands here, once per in-flight login across the upgrade (#6611).
4772
+ message: "OAuth state \u304C\u4E00\u81F4\u3057\u306A\u3044\u304B\u3001\u30ED\u30B0\u30A4\u30F3\u306E\u958B\u59CB\u304B\u3089\u6642\u9593\u304C\u7D4C\u3063\u3066\u3044\u307E\u3059\u3002\u3082\u3046\u4E00\u5EA6\u30ED\u30B0\u30A4\u30F3\u3057\u3066\u304F\u3060\u3055\u3044"
4745
4773
  }
4746
4774
  };
4747
4775
  }
@@ -4758,7 +4786,7 @@ function createServerAuthClient(config) {
4758
4786
  }
4759
4787
  };
4760
4788
  }
4761
- return exchangeAuthorizationCodeServer(deps, code);
4789
+ return exchangeAuthorizationCodeServer(deps, code, stored.codeVerifier);
4762
4790
  }
4763
4791
  function refresh(refreshToken) {
4764
4792
  return refreshTokensServer(deps, refreshToken);
@@ -4838,6 +4866,186 @@ function clear(opts = {}) {
4838
4866
  }
4839
4867
  var sessionCookies = { serialize, read, clear };
4840
4868
 
4869
+ // src/server/db-token/constants.ts
4870
+ var FFID_DB_TOKEN_ALGORITHM = "ES256";
4871
+ var FFID_DB_TOKEN_ROLE = "authenticated";
4872
+ var FFID_DB_TOKEN_AUDIENCE = "authenticated";
4873
+ var DEFAULT_DB_TOKEN_TTL_SECONDS = 300;
4874
+ var MAX_DB_TOKEN_TTL_SECONDS = 900;
4875
+ var FFID_DB_TOKEN_ORGANIZATION_CLAIM = "organization_id";
4876
+ var FFID_DB_TOKEN_RESERVED_CLAIMS = [
4877
+ "sub",
4878
+ "role",
4879
+ "aud",
4880
+ "iss",
4881
+ "iat",
4882
+ "exp",
4883
+ "nbf",
4884
+ "jti",
4885
+ FFID_DB_TOKEN_ORGANIZATION_CLAIM
4886
+ ];
4887
+ var MILLISECONDS_PER_SECOND = 1e3;
4888
+
4889
+ // src/server/db-token/errors.ts
4890
+ var FFID_DB_TOKEN_ERROR_CODES = {
4891
+ /** Signing key is not a private P-256 JWK with a `kid`, or cannot be imported. */
4892
+ INVALID_SIGNING_KEY: "INVALID_SIGNING_KEY",
4893
+ /** `ttlSeconds` is not a positive integer within `MAX_DB_TOKEN_TTL_SECONDS`. */
4894
+ INVALID_TTL: "INVALID_TTL",
4895
+ /** `userId` is not a UUID. */
4896
+ INVALID_USER_ID: "INVALID_USER_ID",
4897
+ /** `organizationId` is not a UUID. */
4898
+ INVALID_ORGANIZATION_ID: "INVALID_ORGANIZATION_ID",
4899
+ /** A product claim uses a name the minter reserves (`sub`, `role`, `exp`, ...). */
4900
+ RESERVED_CLAIM: "RESERVED_CLAIM",
4901
+ /** `now` is not a valid `Date` (an Invalid Date would sign `iat` / `exp` as null). */
4902
+ INVALID_CLOCK: "INVALID_CLOCK"
4903
+ };
4904
+ var FFIDDbTokenError = class extends Error {
4905
+ code;
4906
+ constructor(code, message, options) {
4907
+ super(message, options);
4908
+ this.name = "FFIDDbTokenError";
4909
+ this.code = code;
4910
+ }
4911
+ };
4912
+
4913
+ // src/server/db-token/mint.ts
4914
+ var UUID_PATTERN2 = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
4915
+ var RESERVED_CLAIM_SET = new Set(FFID_DB_TOKEN_RESERVED_CLAIMS);
4916
+ var isNonEmptyString = (value) => typeof value === "string" && value.length > 0;
4917
+ var invalidKey = (reason, cause) => new FFIDDbTokenError(
4918
+ FFID_DB_TOKEN_ERROR_CODES.INVALID_SIGNING_KEY,
4919
+ `Invalid DB token signing key: ${reason}`,
4920
+ cause === void 0 ? void 0 : { cause }
4921
+ );
4922
+ var describeCause = (cause) => cause instanceof Error ? `${cause.name}: ${cause.message}` : String(cause);
4923
+ function parseSigningKey(signingKey, kidOverride) {
4924
+ let candidate = signingKey;
4925
+ if (typeof signingKey === "string") {
4926
+ if (signingKey.trim().length === 0) {
4927
+ throw invalidKey("signingKey is empty (is the environment variable set?)");
4928
+ }
4929
+ try {
4930
+ candidate = JSON.parse(signingKey);
4931
+ } catch {
4932
+ throw invalidKey("not valid JSON");
4933
+ }
4934
+ }
4935
+ if (typeof candidate !== "object" || candidate === null || Array.isArray(candidate)) {
4936
+ throw invalidKey("expected a JWK object");
4937
+ }
4938
+ const jwk = candidate;
4939
+ if (jwk.kty !== "EC" || jwk.crv !== "P-256") {
4940
+ throw invalidKey(`expected kty=EC crv=P-256 for ${FFID_DB_TOKEN_ALGORITHM}`);
4941
+ }
4942
+ if (!isNonEmptyString(jwk.d) || !isNonEmptyString(jwk.x) || !isNonEmptyString(jwk.y)) {
4943
+ throw invalidKey("expected a private key (d, x, y)");
4944
+ }
4945
+ const kid = kidOverride ?? jwk.kid;
4946
+ if (!isNonEmptyString(kid)) {
4947
+ throw invalidKey("kid is required (Supabase matches the header kid against the imported key)");
4948
+ }
4949
+ return { jwk, kid };
4950
+ }
4951
+ function resolveTtlSeconds(ttlSeconds) {
4952
+ const ttl = ttlSeconds ?? DEFAULT_DB_TOKEN_TTL_SECONDS;
4953
+ if (!Number.isInteger(ttl) || ttl < 1 || ttl > MAX_DB_TOKEN_TTL_SECONDS) {
4954
+ throw new FFIDDbTokenError(
4955
+ FFID_DB_TOKEN_ERROR_CODES.INVALID_TTL,
4956
+ `ttlSeconds must be an integer between 1 and ${MAX_DB_TOKEN_TTL_SECONDS}`
4957
+ );
4958
+ }
4959
+ return ttl;
4960
+ }
4961
+ function requireUuid(value, code, label) {
4962
+ if (typeof value !== "string" || !UUID_PATTERN2.test(value)) {
4963
+ throw new FFIDDbTokenError(FFID_DB_TOKEN_ERROR_CODES[code], `${label} must be a UUID`);
4964
+ }
4965
+ return value.toLowerCase();
4966
+ }
4967
+ function resolveNow(now) {
4968
+ if (now === void 0) return /* @__PURE__ */ new Date();
4969
+ if (!(now instanceof Date) || Number.isNaN(now.getTime())) {
4970
+ throw new FFIDDbTokenError(FFID_DB_TOKEN_ERROR_CODES.INVALID_CLOCK, "now must be a valid Date");
4971
+ }
4972
+ return now;
4973
+ }
4974
+ function assertNoReservedClaims(claims) {
4975
+ if (!claims) return;
4976
+ for (const name of Object.keys(claims)) {
4977
+ if (RESERVED_CLAIM_SET.has(name)) {
4978
+ throw new FFIDDbTokenError(
4979
+ FFID_DB_TOKEN_ERROR_CODES.RESERVED_CLAIM,
4980
+ `Claim "${name}" is reserved by the DB token minter`
4981
+ );
4982
+ }
4983
+ }
4984
+ }
4985
+ async function importSigningKey(jwk, kid) {
4986
+ const ecParameters = { kty: jwk.kty, crv: jwk.crv, x: jwk.x, y: jwk.y };
4987
+ let privateKey;
4988
+ let publicKey;
4989
+ try {
4990
+ const imported = await Promise.all([
4991
+ jose.importJWK({ ...ecParameters, d: jwk.d, kid, alg: FFID_DB_TOKEN_ALGORITHM }, FFID_DB_TOKEN_ALGORITHM),
4992
+ jose.importJWK({ ...ecParameters, kid, alg: FFID_DB_TOKEN_ALGORITHM }, FFID_DB_TOKEN_ALGORITHM)
4993
+ ]);
4994
+ if (imported[0] instanceof Uint8Array || imported[1] instanceof Uint8Array) {
4995
+ throw new Error("expected an asymmetric key");
4996
+ }
4997
+ ;
4998
+ [privateKey, publicKey] = imported;
4999
+ } catch (cause) {
5000
+ throw invalidKey(`key material could not be imported (${describeCause(cause)})`, cause);
5001
+ }
5002
+ try {
5003
+ const probe = await new jose.SignJWT({}).setProtectedHeader({ alg: FFID_DB_TOKEN_ALGORITHM, kid }).sign(privateKey);
5004
+ await jose.jwtVerify(probe, publicKey);
5005
+ } catch (cause) {
5006
+ throw invalidKey("private part (d) does not match the public part (x, y)", cause);
5007
+ }
5008
+ return privateKey;
5009
+ }
5010
+ function createDbTokenMinter(config) {
5011
+ const { jwk, kid } = parseSigningKey(config.signingKey, config.kid);
5012
+ const ttlSeconds = resolveTtlSeconds(config.ttlSeconds);
5013
+ const issuer = config.issuer;
5014
+ let keyPromise;
5015
+ const loadKey = () => {
5016
+ keyPromise ??= importSigningKey(jwk, kid).catch((error) => {
5017
+ keyPromise = void 0;
5018
+ throw error;
5019
+ });
5020
+ return keyPromise;
5021
+ };
5022
+ return {
5023
+ kid,
5024
+ ttlSeconds,
5025
+ async mint(input) {
5026
+ const sub = requireUuid(input.userId, "INVALID_USER_ID", "userId");
5027
+ const organizationId = input.organizationId === void 0 || input.organizationId === null ? void 0 : requireUuid(input.organizationId, "INVALID_ORGANIZATION_ID", "organizationId");
5028
+ assertNoReservedClaims(input.claims);
5029
+ const now = resolveNow(input.now);
5030
+ const iat = Math.floor(now.getTime() / MILLISECONDS_PER_SECOND);
5031
+ const exp = iat + ttlSeconds;
5032
+ const claims = {
5033
+ ...input.claims,
5034
+ sub,
5035
+ role: FFID_DB_TOKEN_ROLE,
5036
+ aud: FFID_DB_TOKEN_AUDIENCE,
5037
+ iat,
5038
+ exp,
5039
+ ...issuer === void 0 ? {} : { iss: issuer },
5040
+ ...organizationId === void 0 ? {} : { [FFID_DB_TOKEN_ORGANIZATION_CLAIM]: organizationId }
5041
+ };
5042
+ const key = await loadKey();
5043
+ const token = await new jose.SignJWT(claims).setProtectedHeader({ alg: FFID_DB_TOKEN_ALGORITHM, kid, typ: "JWT" }).sign(key);
5044
+ return { token, expiresAt: new Date(exp * MILLISECONDS_PER_SECOND), claims };
5045
+ }
5046
+ };
5047
+ }
5048
+
4841
5049
  Object.defineProperty(exports, "DEFAULT_API_BASE_URL", {
4842
5050
  enumerable: true,
4843
5051
  get: function () { return chunkMDHKSVLP_cjs.DEFAULT_API_BASE_URL; }
@@ -4850,11 +5058,19 @@ exports.ALL_DENIED_EXCEPT_NECESSARY = ALL_DENIED_EXCEPT_NECESSARY;
4850
5058
  exports.CATALOG_HASH_PATTERN = CATALOG_HASH_PATTERN;
4851
5059
  exports.CONSENT_COOKIE_NAME = CONSENT_COOKIE_NAME;
4852
5060
  exports.COOKIE_VERSION = COOKIE_VERSION;
5061
+ exports.DEFAULT_DB_TOKEN_TTL_SECONDS = DEFAULT_DB_TOKEN_TTL_SECONDS;
4853
5062
  exports.FFIDCatalogHashError = FFIDCatalogHashError;
5063
+ exports.FFIDDbTokenError = FFIDDbTokenError;
4854
5064
  exports.FFID_ALLOWED_ORGANIZATION_LOGO_MIME_TYPES = FFID_ALLOWED_ORGANIZATION_LOGO_MIME_TYPES;
4855
5065
  exports.FFID_CATALOG_ENVIRONMENTS = FFID_CATALOG_ENVIRONMENTS;
4856
5066
  exports.FFID_CATALOG_PUBLICATION_STATUSES = FFID_CATALOG_PUBLICATION_STATUSES;
4857
5067
  exports.FFID_COMPANY_SIZE_CODES = FFID_COMPANY_SIZE_CODES;
5068
+ exports.FFID_DB_TOKEN_ALGORITHM = FFID_DB_TOKEN_ALGORITHM;
5069
+ exports.FFID_DB_TOKEN_AUDIENCE = FFID_DB_TOKEN_AUDIENCE;
5070
+ exports.FFID_DB_TOKEN_ERROR_CODES = FFID_DB_TOKEN_ERROR_CODES;
5071
+ exports.FFID_DB_TOKEN_ORGANIZATION_CLAIM = FFID_DB_TOKEN_ORGANIZATION_CLAIM;
5072
+ exports.FFID_DB_TOKEN_RESERVED_CLAIMS = FFID_DB_TOKEN_RESERVED_CLAIMS;
5073
+ exports.FFID_DB_TOKEN_ROLE = FFID_DB_TOKEN_ROLE;
4858
5074
  exports.FFID_ERROR_CODES = FFID_ERROR_CODES;
4859
5075
  exports.FFID_INDUSTRY_CODES = FFID_INDUSTRY_CODES;
4860
5076
  exports.FFID_MAX_ORGANIZATION_LOGO_FILE_SIZE_BYTES = FFID_MAX_ORGANIZATION_LOGO_FILE_SIZE_BYTES;
@@ -4862,6 +5078,7 @@ exports.FFID_MAX_ORGANIZATION_LOGO_FILE_SIZE_MB = FFID_MAX_ORGANIZATION_LOGO_FIL
4862
5078
  exports.FFID_OAUTH_DENIAL_REASONS = FFID_OAUTH_DENIAL_REASONS;
4863
5079
  exports.FFID_ORGANIZATION_PROFILE_ERROR_CODES = FFID_ORGANIZATION_PROFILE_ERROR_CODES;
4864
5080
  exports.FFID_SESSION_COOKIE_NAME = FFID_SESSION_COOKIE_NAME;
5081
+ exports.MAX_DB_TOKEN_TTL_SECONDS = MAX_DB_TOKEN_TTL_SECONDS;
4865
5082
  exports.canonicalizeCatalogValue = canonicalizeCatalogValue;
4866
5083
  exports.computeFFIDCheckoutCopyHash = computeFFIDCheckoutCopyHash;
4867
5084
  exports.computeLegalDisclosureHash = computeLegalDisclosureHash;
@@ -4869,6 +5086,7 @@ exports.computePaymentConfigurationHash = computePaymentConfigurationHash;
4869
5086
  exports.computePraxisCopyHash = computePraxisCopyHash;
4870
5087
  exports.computeScopeHash = computeScopeHash;
4871
5088
  exports.computeTaxConfigurationHash = computeTaxConfigurationHash;
5089
+ exports.createDbTokenMinter = createDbTokenMinter;
4872
5090
  exports.createFFIDClient = createFFIDClient;
4873
5091
  exports.createKVCacheAdapter = createKVCacheAdapter;
4874
5092
  exports.createMemoryCacheAdapter = createMemoryCacheAdapter;
@@ -1,5 +1,5 @@
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';
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-aUNBn5SW.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-aUNBn5SW.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';
@@ -356,14 +356,22 @@ interface BuildAuthorizeUrlOptions {
356
356
  interface BuildAuthorizeUrlResult {
357
357
  /** Authorize URL to 302 the browser to. */
358
358
  url: string;
359
- /** Random `state` — persist in an httpOnly cookie and pass back as `storedState`. */
359
+ /**
360
+ * Opaque value to persist in an httpOnly cookie and pass back as
361
+ * `storedState`. Since 12.0.0 (#6611) it is `<state>.<code_verifier>`, not the
362
+ * raw OAuth `state` — do not compare it with the callback's `state` query
363
+ * yourself, and do not parse it; {@link ServerAuthClient.handleCallback} does.
364
+ */
360
365
  state: string;
361
366
  }
362
367
  /** Input to {@link ServerAuthClient.handleCallback}. */
363
368
  interface HandleCallbackInput {
364
369
  /** The callback request URL (absolute). `code` / `state` are read from its query. */
365
370
  url: string | URL;
366
- /** The `state` previously stored in the httpOnly cookie (undefined if absent). */
371
+ /**
372
+ * The value previously stored in the httpOnly cookie — `result.state` from
373
+ * {@link ServerAuthClient.buildAuthorizeUrl}, verbatim (undefined if absent).
374
+ */
367
375
  storedState: string | undefined;
368
376
  }
369
377
  /**
@@ -380,14 +388,18 @@ type ServerTokenResult = {
380
388
  /** Framework-agnostic confidential / BFF auth client. */
381
389
  interface ServerAuthClient {
382
390
  /**
383
- * Build the authorize URL and a fresh `state`. The caller stores `state` in an
384
- * httpOnly cookie, then 302s to `url`. No `code_challenge` is sent (v1 uses
385
- * `client_secret`, not PKCE).
391
+ * Build the authorize URL with a fresh `state` and a PKCE S256
392
+ * `code_challenge` (#6611). The caller stores the returned `state` (which also
393
+ * carries the `code_verifier`) in an httpOnly cookie, then 302s to `url`.
394
+ * Async since 12.0.0 — the S256 challenge uses Web Crypto `subtle.digest`.
386
395
  */
387
- buildAuthorizeUrl(opts?: BuildAuthorizeUrlOptions): BuildAuthorizeUrlResult;
396
+ buildAuthorizeUrl(opts?: BuildAuthorizeUrlOptions): Promise<BuildAuthorizeUrlResult>;
388
397
  /**
389
- * Validate `state` (CSRF, fail-closed) then exchange `code` for tokens using
390
- * the `client_secret` (server→server, browser never sees the secret or tokens).
398
+ * Validate `state` (CSRF, fail-closed; a stored value without the PKCE
399
+ * verifier is also a mismatch) then exchange `code` for tokens using the
400
+ * `client_secret` and `code_verifier` (server→server; browser JS never reads
401
+ * the secret, the verifier, or the tokens — the verifier and tokens travel only
402
+ * in httpOnly cookies, never in a URL).
391
403
  */
392
404
  handleCallback(input: HandleCallbackInput): Promise<ServerTokenResult>;
393
405
  /** Refresh tokens (`refresh_token` grant, `client_secret`-authenticated). */
@@ -637,4 +649,147 @@ declare function computeScopeHash(input: FFIDScopeHashInput): Promise<string>;
637
649
  */
638
650
  declare function validatePublishedCatalog(catalog: unknown): asserts catalog is FFIDPublishedCatalog;
639
651
 
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 };
652
+ /**
653
+ * Constants for the product-side DB token minter (#6318).
654
+ *
655
+ * A product that reaches its own Supabase through supabase-js (PostgREST)
656
+ * mints a short-lived token for the FFID user on its server and sends it as
657
+ * the `Authorization: Bearer` header. Supabase validates it against the
658
+ * product's JWT Signing Keys and RLS reads the user from `auth.jwt()`.
659
+ */
660
+ /** Only asymmetric signing is allowed; a shared secret (HS256) is rejected by policy. */
661
+ declare const FFID_DB_TOKEN_ALGORITHM: "ES256";
662
+ /** Supabase maps this claim to the Postgres role the request runs as. */
663
+ declare const FFID_DB_TOKEN_ROLE: "authenticated";
664
+ /** Same shape as the user tokens Supabase Auth issues (`aud` = `authenticated`). */
665
+ declare const FFID_DB_TOKEN_AUDIENCE: "authenticated";
666
+ /** Default lifetime: 5 minutes. The token is minted per request and never sent to the browser. */
667
+ declare const DEFAULT_DB_TOKEN_TTL_SECONDS = 300;
668
+ /** Hard ceiling: 15 minutes. Longer tokens defeat the "mint per request" model. */
669
+ declare const MAX_DB_TOKEN_TTL_SECONDS = 900;
670
+ /** Claim that carries the FFID organization id when the product scopes rows by organization. */
671
+ declare const FFID_DB_TOKEN_ORGANIZATION_CLAIM: "organization_id";
672
+ /**
673
+ * Claims the minter owns. Product-specific `claims` must not use these names,
674
+ * so a caller cannot forge a different user, role, or lifetime.
675
+ */
676
+ declare const FFID_DB_TOKEN_RESERVED_CLAIMS: readonly ["sub", "role", "aud", "iss", "iat", "exp", "nbf", "jti", "organization_id"];
677
+
678
+ /**
679
+ * Types for the product-side DB token minter (#6318).
680
+ */
681
+
682
+ /**
683
+ * Private P-256 JWK used to sign DB tokens. Generate it with
684
+ * `supabase gen signing-key --algorithm ES256`, import that private JWK into the
685
+ * product's Supabase JWT Signing Keys (Supabase keeps it non-extractable and
686
+ * publishes only the public half via JWKS), and keep the same JWK in a server
687
+ * environment variable. Nothing else may hold it.
688
+ *
689
+ * The CLI output also carries `use`, `key_ops` and `ext`; the minter strips
690
+ * those before import (WebCrypto rejects an ECDSA private key whose `key_ops`
691
+ * includes `verify`).
692
+ */
693
+ interface FFIDDbTokenSigningJwk {
694
+ kty: 'EC';
695
+ crv: 'P-256';
696
+ /** Private scalar. Its presence is what makes this a signing key. */
697
+ d: string;
698
+ x: string;
699
+ y: string;
700
+ /** Key id. Supabase matches the token header `kid` against the imported key. */
701
+ kid?: string;
702
+ alg?: string;
703
+ use?: string;
704
+ [parameter: string]: unknown;
705
+ }
706
+ interface FFIDDbTokenMinterConfig {
707
+ /**
708
+ * The private JWK, either as an object or as the JSON string stored in an
709
+ * environment variable (for example `SUPABASE_JWT_SIGNING_KEY`).
710
+ */
711
+ signingKey: string | FFIDDbTokenSigningJwk;
712
+ /** Overrides the JWK's `kid`. Required when the JWK has none. */
713
+ kid?: string;
714
+ /** Token lifetime. Default `DEFAULT_DB_TOKEN_TTL_SECONDS`, max `MAX_DB_TOKEN_TTL_SECONDS`. */
715
+ ttlSeconds?: number;
716
+ /** Optional `iss` claim. Supabase does not require it; set it when the product's policies read it. */
717
+ issuer?: string;
718
+ }
719
+ interface FFIDMintDbTokenInput {
720
+ /** FFID user id (UUID). Becomes `sub`, the value `private.current_ffid_user_id()` returns. */
721
+ userId: string;
722
+ /** FFID organization id (UUID). Becomes the `organization_id` claim. `null` means "none", like `FFIDUser.organizationId`. */
723
+ organizationId?: string | null;
724
+ /** Product-specific claims. Reserved names (`FFID_DB_TOKEN_RESERVED_CLAIMS`) are rejected. */
725
+ claims?: Record<string, unknown>;
726
+ /** Clock override for tests. Defaults to `new Date()`. An Invalid Date is rejected (`INVALID_CLOCK`). */
727
+ now?: Date;
728
+ }
729
+ interface FFIDDbTokenClaims {
730
+ sub: string;
731
+ role: typeof FFID_DB_TOKEN_ROLE;
732
+ aud: typeof FFID_DB_TOKEN_AUDIENCE;
733
+ iat: number;
734
+ exp: number;
735
+ iss?: string;
736
+ organization_id?: string;
737
+ [claim: string]: unknown;
738
+ }
739
+ interface FFIDDbToken {
740
+ /** Compact JWS to send as `Authorization: Bearer <token>` to the product's Supabase. */
741
+ token: string;
742
+ /** `exp` as a `Date`. For logging / diagnostics; the client is still created per request. */
743
+ expiresAt: Date;
744
+ /** The signed payload, for logging claim names (never log the token itself). */
745
+ claims: FFIDDbTokenClaims;
746
+ }
747
+ interface FFIDDbTokenMinter {
748
+ /** Key id written to every token header. */
749
+ readonly kid: string;
750
+ /** Effective lifetime in seconds. */
751
+ readonly ttlSeconds: number;
752
+ mint(input: FFIDMintDbTokenInput): Promise<FFIDDbToken>;
753
+ }
754
+
755
+ /**
756
+ * Creates a minter bound to one signing key. Create it once per process and
757
+ * call `mint()` per request.
758
+ *
759
+ * The key shape is checked synchronously here. The key material is imported on
760
+ * the first `mint()`; a successful import is kept for the minter's lifetime,
761
+ * a failed one is retried on the next `mint()`.
762
+ *
763
+ * @throws FFIDDbTokenError when the key shape or TTL is invalid.
764
+ */
765
+ declare function createDbTokenMinter(config: FFIDDbTokenMinterConfig): FFIDDbTokenMinter;
766
+
767
+ /**
768
+ * Errors thrown by the product-side DB token minter (#6318).
769
+ *
770
+ * These are programming / configuration errors on the product's server, never
771
+ * end-user input errors. Messages never include key material.
772
+ */
773
+ declare const FFID_DB_TOKEN_ERROR_CODES: {
774
+ /** Signing key is not a private P-256 JWK with a `kid`, or cannot be imported. */
775
+ readonly INVALID_SIGNING_KEY: "INVALID_SIGNING_KEY";
776
+ /** `ttlSeconds` is not a positive integer within `MAX_DB_TOKEN_TTL_SECONDS`. */
777
+ readonly INVALID_TTL: "INVALID_TTL";
778
+ /** `userId` is not a UUID. */
779
+ readonly INVALID_USER_ID: "INVALID_USER_ID";
780
+ /** `organizationId` is not a UUID. */
781
+ readonly INVALID_ORGANIZATION_ID: "INVALID_ORGANIZATION_ID";
782
+ /** A product claim uses a name the minter reserves (`sub`, `role`, `exp`, ...). */
783
+ readonly RESERVED_CLAIM: "RESERVED_CLAIM";
784
+ /** `now` is not a valid `Date` (an Invalid Date would sign `iat` / `exp` as null). */
785
+ readonly INVALID_CLOCK: "INVALID_CLOCK";
786
+ };
787
+ type FFIDDbTokenErrorCode = (typeof FFID_DB_TOKEN_ERROR_CODES)[keyof typeof FFID_DB_TOKEN_ERROR_CODES];
788
+ declare class FFIDDbTokenError extends Error {
789
+ readonly code: FFIDDbTokenErrorCode;
790
+ constructor(code: FFIDDbTokenErrorCode, message: string, options?: {
791
+ cause?: unknown;
792
+ });
793
+ }
794
+
795
+ export { ALL_DENIED_EXCEPT_NECESSARY, type BuildAuthorizeUrlOptions, type BuildAuthorizeUrlResult, CATALOG_HASH_PATTERN, CONSENT_COOKIE_NAME, COOKIE_VERSION, type CookieOptions, DEFAULT_DB_TOKEN_TTL_SECONDS, type FFIDAuthorizationError, type FFIDAuthorizationErrorDetails, FFIDCacheAdapter, type FFIDCanonicalJsonValue, FFIDCatalogEnvironment, FFIDCatalogHashError, type FFIDConsentCategories, type FFIDConsentCategoryCode, type FFIDCookieStoreLike, type FFIDDbToken, type FFIDDbTokenClaims, FFIDDbTokenError, type FFIDDbTokenErrorCode, type FFIDDbTokenMinter, type FFIDDbTokenMinterConfig, type FFIDDbTokenSigningJwk, type FFIDErrorCode, type FFIDMintDbTokenInput, type FFIDOAuthDenialReason, FFIDOAuthUserInfo, type FFIDPaymentConfiguration, FFIDPublishedCatalog, type FFIDScopeHashInput, type FFIDScopeHashPlan, FFIDTaxBehavior, type FFIDTaxConfiguration, FFIDVerifyAccessTokenOptions, FFID_DB_TOKEN_ALGORITHM, FFID_DB_TOKEN_AUDIENCE, FFID_DB_TOKEN_ERROR_CODES, FFID_DB_TOKEN_ORGANIZATION_CLAIM, FFID_DB_TOKEN_RESERVED_CLAIMS, FFID_DB_TOKEN_ROLE, FFID_ERROR_CODES, FFID_OAUTH_DENIAL_REASONS, FFID_SESSION_COOKIE_NAME, type HandleCallbackInput, type KVNamespaceLike, MAX_DB_TOKEN_TTL_SECONDS, type ServerAuthClient, type ServerAuthConfig, type ServerSessionTokens, type ServerTokenResult, canonicalizeCatalogValue, computeFFIDCheckoutCopyHash, computeLegalDisclosureHash, computePaymentConfigurationHash, computePraxisCopyHash, computeScopeHash, computeTaxConfigurationHash, createDbTokenMinter, createKVCacheAdapter, createMemoryCacheAdapter, createServerAuthClient, createVerifyAccessToken, decodeConsentCookie, encodeConsentCookie, generateServerState, getFFIDAuthorizationError, getFFIDConsentFromCookieHeader, getFFIDConsentFromCookies, hashCanonicalJson, isFFIDOAuthDenialReason, sessionCookies, sha256Hex, validatePublishedCatalog };