@rakomi/react-native 0.4.0 → 0.5.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.
@@ -1,6 +1,6 @@
1
1
  THIRD-PARTY SOFTWARE NOTICES AND INFORMATION
2
2
 
3
- For the npm package: @rakomi/react-native@0.4.0
3
+ For the npm package: @rakomi/react-native@0.5.0
4
4
 
5
5
  This package's published bundle (`dist/`) INLINES the third-party components listed below.
6
6
  Their copyright and permission notices are reproduced verbatim, as required by their licenses
package/dist/index.cjs CHANGED
@@ -299,8 +299,19 @@ function deriveDefaultJwksUri(tokenEndpoint) {
299
299
  }
300
300
  var RAKOMI_PLATFORM_ISSUER_DEFAULT = "https://api.rakomi.com";
301
301
  var RAKOMI_PLATFORM_AUDIENCE_DEFAULT = "https://api.rakomi.com";
302
- function deriveDefaultIssuer(_tokenEndpoint) {
303
- return RAKOMI_PLATFORM_ISSUER_DEFAULT;
302
+ function deriveDefaultIssuer(options) {
303
+ if (options.baseUrl) {
304
+ try {
305
+ new URL(options.baseUrl);
306
+ return sdkCore.resolveExpectedIssuer(options.baseUrl);
307
+ } catch {
308
+ }
309
+ }
310
+ try {
311
+ return new URL(options.tokenEndpoint).origin;
312
+ } catch {
313
+ return RAKOMI_PLATFORM_ISSUER_DEFAULT;
314
+ }
304
315
  }
305
316
  var REFRESH_TOKEN_PREFIX = "v1:";
306
317
  var REFRESH_BUFFER_SECONDS = 60;
@@ -360,7 +371,7 @@ var TokenRuntime = class _TokenRuntime {
360
371
  this.clearTimeoutFn = options.clearTimeout ?? globalThis.clearTimeout.bind(globalThis);
361
372
  this.jwksUri = options.jwksUri ?? deriveDefaultJwksUri(options.tokenEndpoint);
362
373
  this.jwksTtlMs = options.jwksTtlMs ?? 24 * 60 * 60 * 1e3;
363
- this.expectedIssuer = options.expectedIssuer ?? deriveDefaultIssuer(options.tokenEndpoint);
374
+ this.expectedIssuer = options.expectedIssuer ?? deriveDefaultIssuer({ baseUrl: options.baseUrl, tokenEndpoint: options.tokenEndpoint });
364
375
  this.expectedAudience = options.expectedAudience ?? RAKOMI_PLATFORM_AUDIENCE_DEFAULT;
365
376
  }
366
377
  /**
@@ -545,11 +556,32 @@ var TokenRuntime = class _TokenRuntime {
545
556
  this.storageKeyResolved = await sdkCore.deriveTenantStorageKey(this.crypto, this.tenantId, "refresh_token");
546
557
  return this.storageKeyResolved;
547
558
  }
559
+ /**
560
+ * The persisted JWKS-cache slot, scoped per-host in addition to per-tenant.
561
+ *
562
+ * `deriveTenantStorageKey`'s own domain separation is tenant-only, which was correct while every
563
+ * tenant verified against ONE platform-wide JWKS document. Now that a tenant's issuer host can
564
+ * change across the app's lifetime (a custom domain bound after this SDK version first shipped, or
565
+ * simply a dev pointed at a different host between builds), a slot keyed by tenant alone would
566
+ * silently reuse a PREVIOUS host's persisted document as this instance's "fresh" cache on cold
567
+ * start — cross-host reuse this offline-verification path must never do. The host segment is not
568
+ * secret (it is the caller's own `jwksUri` configuration), so a plain suffix is enough; this only
569
+ * needs to be a stable, DIFFERENT key per host, not a cryptographically-opaque one.
570
+ */
548
571
  async resolveJwksKey() {
549
572
  if (this.jwksKeyResolved) return this.jwksKeyResolved;
550
- this.jwksKeyResolved = await sdkCore.deriveTenantStorageKey(this.crypto, this.tenantId, "jwks_cache");
573
+ const base = await sdkCore.deriveTenantStorageKey(this.crypto, this.tenantId, "jwks_cache");
574
+ const host = this.jwksHost();
575
+ this.jwksKeyResolved = host ? `${base}.h.${host}` : base;
551
576
  return this.jwksKeyResolved;
552
577
  }
578
+ jwksHost() {
579
+ try {
580
+ return new URL(this.jwksUri).hostname;
581
+ } catch {
582
+ return null;
583
+ }
584
+ }
553
585
  async getOrCreateJwksCache() {
554
586
  if (this.jwksCache) return this.jwksCache;
555
587
  const key = await this.resolveJwksKey();
@@ -1051,6 +1083,7 @@ function RakomiProvider(props) {
1051
1083
  clientId: props.publishableKey,
1052
1084
  tenantId: props.tenantId ?? props.publishableKey,
1053
1085
  tokenEndpoint,
1086
+ baseUrl: props.baseUrl,
1054
1087
  storage: adapter.storage,
1055
1088
  http,
1056
1089
  crypto: adapter.crypto,
package/dist/index.d.cts CHANGED
@@ -495,7 +495,7 @@ declare const MAX_WEBHOOK_TOLERANCE = 600;
495
495
  /** Portable webhook-verify error class — the value IS the cross-SDK conformance `error_class`. */
496
496
  type WebhookVerifyErrorClass = 'invalid_signature' | 'invalid_secret' | 'missing_header' | 'timestamp_too_old' | 'timestamp_too_new' | 'invalid_body';
497
497
  /** Open-set publisher event type (the known catalog widened with the base string for forward-compat). */
498
- type PublisherEventType = 'app.installed' | 'app.uninstalled' | 'app.install.scope_bump' | 'app.install.receipts_revoked' | 'publisher.created' | 'publisher.domain_verified' | 'publisher.dpa_accepted' | 'app.created' | 'app.version_published' | 'app.state_changed' | 'publisher.review_requested' | 'publisher.review_denied' | 'publisher.review_stale' | 'publisher.verified' | 'publisher.deverified' | 'publisher.subscription_activated' | 'publisher.subscription_lapsed';
498
+ type PublisherEventType = 'app.installed' | 'app.uninstalled' | 'app.install.scope_bump' | 'app.install.receipts_revoked' | 'publisher.created' | 'publisher.domain_verified' | 'publisher.dpa_accepted' | 'app.created' | 'app.version_published' | 'app.state_changed' | 'publisher.review_requested' | 'publisher.review_denied' | 'publisher.review_stale' | 'publisher.verified' | 'publisher.deverified' | 'publisher.subscription_activated' | 'publisher.subscription_lapsed' | 'app.version_pending_review' | 'app.pending_version_approved' | 'app.pending_version_rejected';
499
499
  type PublisherWebhookEventType = PublisherEventType | (string & {});
500
500
  /** The flat publisher-webhook delivery body. Unknown fields tolerated (forward-compat). No end-user PII. */
501
501
  interface PublisherWebhookEvent {
package/dist/index.d.ts CHANGED
@@ -495,7 +495,7 @@ declare const MAX_WEBHOOK_TOLERANCE = 600;
495
495
  /** Portable webhook-verify error class — the value IS the cross-SDK conformance `error_class`. */
496
496
  type WebhookVerifyErrorClass = 'invalid_signature' | 'invalid_secret' | 'missing_header' | 'timestamp_too_old' | 'timestamp_too_new' | 'invalid_body';
497
497
  /** Open-set publisher event type (the known catalog widened with the base string for forward-compat). */
498
- type PublisherEventType = 'app.installed' | 'app.uninstalled' | 'app.install.scope_bump' | 'app.install.receipts_revoked' | 'publisher.created' | 'publisher.domain_verified' | 'publisher.dpa_accepted' | 'app.created' | 'app.version_published' | 'app.state_changed' | 'publisher.review_requested' | 'publisher.review_denied' | 'publisher.review_stale' | 'publisher.verified' | 'publisher.deverified' | 'publisher.subscription_activated' | 'publisher.subscription_lapsed';
498
+ type PublisherEventType = 'app.installed' | 'app.uninstalled' | 'app.install.scope_bump' | 'app.install.receipts_revoked' | 'publisher.created' | 'publisher.domain_verified' | 'publisher.dpa_accepted' | 'app.created' | 'app.version_published' | 'app.state_changed' | 'publisher.review_requested' | 'publisher.review_denied' | 'publisher.review_stale' | 'publisher.verified' | 'publisher.deverified' | 'publisher.subscription_activated' | 'publisher.subscription_lapsed' | 'app.version_pending_review' | 'app.pending_version_approved' | 'app.pending_version_rejected';
499
499
  type PublisherWebhookEventType = PublisherEventType | (string & {});
500
500
  /** The flat publisher-webhook delivery body. Unknown fields tolerated (forward-compat). No end-user PII. */
501
501
  interface PublisherWebhookEvent {
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { createContext, useMemo, useReducer, useRef, useEffect, useState, useCallback, useContext } from 'react';
2
- import { createAuthorizationEndpointCache, EventLog, reduce, INITIAL_SNAPSHOT, createTranslator, isSignedIn, hasPermission, hasRole, extractOrgClaims, parseAuthConfigResponse, generatePkce, issueState, buildAuthorizationUrl, consumeState, exchangeAuthCode, parseOAuthCallbackError, scorePassword, isSafeUrl, passkeyError, decodeUser, assertPasskey, stepUpWithPasskey, registerPasskey, listPasskeys, renamePasskey, deletePasskey, verifyMagicLink, requestMagicLink, verifyEmailOtp, requestEmailOtp, register, verifyTotp, MfaStepUpUnavailableError, extractRequestId, decodeSession, deriveTenantStorageKey, createJwksCache, refreshAccessToken, decodeJwtPayload, PasskeyCeremonyError } from '@rakomi/sdk-core';
2
+ import { createAuthorizationEndpointCache, EventLog, reduce, INITIAL_SNAPSHOT, createTranslator, isSignedIn, hasPermission, hasRole, extractOrgClaims, parseAuthConfigResponse, generatePkce, issueState, buildAuthorizationUrl, consumeState, exchangeAuthCode, parseOAuthCallbackError, scorePassword, isSafeUrl, passkeyError, decodeUser, assertPasskey, stepUpWithPasskey, registerPasskey, listPasskeys, renamePasskey, deletePasskey, verifyMagicLink, requestMagicLink, verifyEmailOtp, requestEmailOtp, register, verifyTotp, MfaStepUpUnavailableError, extractRequestId, decodeSession, deriveTenantStorageKey, createJwksCache, refreshAccessToken, decodeJwtPayload, PasskeyCeremonyError, resolveExpectedIssuer } from '@rakomi/sdk-core';
3
3
  export { MfaStepUpRequiredError, MfaStepUpUnavailableError, base64url, createAuthorizationEndpointCache, createTranslator, decodeJwtPayload, decodeSession, decodeUser, deriveAuthorizationEndpointFallback, generatePkce, getErrorMessage, hasPermission, hasRole, isSafeUrl, networkError, parseOAuthCallbackError, parseTokenEndpointError, scorePassword, selectPluralForm } from '@rakomi/sdk-core';
4
4
  import { jwtVerify } from 'jose';
5
5
  import { jsx, Fragment, jsxs } from 'react/jsx-runtime';
@@ -298,8 +298,19 @@ function deriveDefaultJwksUri(tokenEndpoint) {
298
298
  }
299
299
  var RAKOMI_PLATFORM_ISSUER_DEFAULT = "https://api.rakomi.com";
300
300
  var RAKOMI_PLATFORM_AUDIENCE_DEFAULT = "https://api.rakomi.com";
301
- function deriveDefaultIssuer(_tokenEndpoint) {
302
- return RAKOMI_PLATFORM_ISSUER_DEFAULT;
301
+ function deriveDefaultIssuer(options) {
302
+ if (options.baseUrl) {
303
+ try {
304
+ new URL(options.baseUrl);
305
+ return resolveExpectedIssuer(options.baseUrl);
306
+ } catch {
307
+ }
308
+ }
309
+ try {
310
+ return new URL(options.tokenEndpoint).origin;
311
+ } catch {
312
+ return RAKOMI_PLATFORM_ISSUER_DEFAULT;
313
+ }
303
314
  }
304
315
  var REFRESH_TOKEN_PREFIX = "v1:";
305
316
  var REFRESH_BUFFER_SECONDS = 60;
@@ -359,7 +370,7 @@ var TokenRuntime = class _TokenRuntime {
359
370
  this.clearTimeoutFn = options.clearTimeout ?? globalThis.clearTimeout.bind(globalThis);
360
371
  this.jwksUri = options.jwksUri ?? deriveDefaultJwksUri(options.tokenEndpoint);
361
372
  this.jwksTtlMs = options.jwksTtlMs ?? 24 * 60 * 60 * 1e3;
362
- this.expectedIssuer = options.expectedIssuer ?? deriveDefaultIssuer(options.tokenEndpoint);
373
+ this.expectedIssuer = options.expectedIssuer ?? deriveDefaultIssuer({ baseUrl: options.baseUrl, tokenEndpoint: options.tokenEndpoint });
363
374
  this.expectedAudience = options.expectedAudience ?? RAKOMI_PLATFORM_AUDIENCE_DEFAULT;
364
375
  }
365
376
  /**
@@ -544,11 +555,32 @@ var TokenRuntime = class _TokenRuntime {
544
555
  this.storageKeyResolved = await deriveTenantStorageKey(this.crypto, this.tenantId, "refresh_token");
545
556
  return this.storageKeyResolved;
546
557
  }
558
+ /**
559
+ * The persisted JWKS-cache slot, scoped per-host in addition to per-tenant.
560
+ *
561
+ * `deriveTenantStorageKey`'s own domain separation is tenant-only, which was correct while every
562
+ * tenant verified against ONE platform-wide JWKS document. Now that a tenant's issuer host can
563
+ * change across the app's lifetime (a custom domain bound after this SDK version first shipped, or
564
+ * simply a dev pointed at a different host between builds), a slot keyed by tenant alone would
565
+ * silently reuse a PREVIOUS host's persisted document as this instance's "fresh" cache on cold
566
+ * start — cross-host reuse this offline-verification path must never do. The host segment is not
567
+ * secret (it is the caller's own `jwksUri` configuration), so a plain suffix is enough; this only
568
+ * needs to be a stable, DIFFERENT key per host, not a cryptographically-opaque one.
569
+ */
547
570
  async resolveJwksKey() {
548
571
  if (this.jwksKeyResolved) return this.jwksKeyResolved;
549
- this.jwksKeyResolved = await deriveTenantStorageKey(this.crypto, this.tenantId, "jwks_cache");
572
+ const base = await deriveTenantStorageKey(this.crypto, this.tenantId, "jwks_cache");
573
+ const host = this.jwksHost();
574
+ this.jwksKeyResolved = host ? `${base}.h.${host}` : base;
550
575
  return this.jwksKeyResolved;
551
576
  }
577
+ jwksHost() {
578
+ try {
579
+ return new URL(this.jwksUri).hostname;
580
+ } catch {
581
+ return null;
582
+ }
583
+ }
552
584
  async getOrCreateJwksCache() {
553
585
  if (this.jwksCache) return this.jwksCache;
554
586
  const key = await this.resolveJwksKey();
@@ -1050,6 +1082,7 @@ function RakomiProvider(props) {
1050
1082
  clientId: props.publishableKey,
1051
1083
  tenantId: props.tenantId ?? props.publishableKey,
1052
1084
  tokenEndpoint,
1085
+ baseUrl: props.baseUrl,
1053
1086
  storage: adapter.storage,
1054
1087
  http,
1055
1088
  crypto: adapter.crypto,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rakomi/react-native",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "description": "React Native / Expo SDK for Rakomi authentication.",
5
5
  "keywords": [
6
6
  "rakomi",
@@ -83,16 +83,16 @@
83
83
  }
84
84
  },
85
85
  "devDependencies": {
86
- "@rakomi/react": "0.4.0"
86
+ "@rakomi/react": "0.5.0"
87
87
  },
88
88
  "dependencies": {
89
89
  "jose": "^6.2.3",
90
- "@rakomi/sdk-core": "^0.4.0"
90
+ "@rakomi/sdk-core": "^0.5.0"
91
91
  },
92
92
  "scripts": {
93
93
  "build": "NODE_OPTIONS=--max-old-space-size=4096 tsup",
94
94
  "typecheck": "tsc --noEmit && tsc -p tsconfig.typetest.json",
95
95
  "lint": "eslint src/ test/ --max-warnings=0",
96
- "test": "node ../../scripts/ci/vitest-fork-crash-retry.mjs --results ./test-results.json -- vitest run"
96
+ "test": "node ../../scripts/assert-not-story-agent-worktree.mjs test && node ../../scripts/ci/vitest-fork-crash-retry.mjs --results ./test-results.json -- vitest run"
97
97
  }
98
98
  }
package/sbom.cdx.json CHANGED
@@ -26,10 +26,10 @@
26
26
  },
27
27
  "component": {
28
28
  "type": "library",
29
- "bom-ref": "pkg:npm/%40rakomi%2Freact-native@0.4.0",
29
+ "bom-ref": "pkg:npm/%40rakomi%2Freact-native@0.5.0",
30
30
  "name": "@rakomi/react-native",
31
- "version": "0.4.0",
32
- "purl": "pkg:npm/%40rakomi%2Freact-native@0.4.0",
31
+ "version": "0.5.0",
32
+ "purl": "pkg:npm/%40rakomi%2Freact-native@0.5.0",
33
33
  "licenses": [
34
34
  {
35
35
  "license": {
@@ -42,10 +42,10 @@
42
42
  "components": [
43
43
  {
44
44
  "type": "library",
45
- "bom-ref": "pkg:npm/@rakomi/sdk-core@0.4.0",
45
+ "bom-ref": "pkg:npm/@rakomi/sdk-core@0.5.0",
46
46
  "name": "@rakomi/sdk-core",
47
- "version": "0.4.0",
48
- "purl": "pkg:npm/@rakomi/sdk-core@0.4.0",
47
+ "version": "0.5.0",
48
+ "purl": "pkg:npm/@rakomi/sdk-core@0.5.0",
49
49
  "licenses": [
50
50
  {
51
51
  "license": {