@passflow/core 0.14.1 → 0.16.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.
Files changed (55) hide show
  1. package/dist/index.cjs +1 -1
  2. package/dist/index.cjs.map +1 -1
  3. package/dist/index.mjs +2751 -1196
  4. package/dist/index.mjs.map +1 -1
  5. package/dist/lib/api/auth.d.ts +17 -2
  6. package/dist/lib/api/auth.d.ts.map +1 -1
  7. package/dist/lib/api/axios-client.d.ts +47 -2
  8. package/dist/lib/api/axios-client.d.ts.map +1 -1
  9. package/dist/lib/api/index.d.ts +1 -0
  10. package/dist/lib/api/index.d.ts.map +1 -1
  11. package/dist/lib/api/model.d.ts +67 -2
  12. package/dist/lib/api/model.d.ts.map +1 -1
  13. package/dist/lib/api/upgrade.d.ts +39 -0
  14. package/dist/lib/api/upgrade.d.ts.map +1 -0
  15. package/dist/lib/index.d.ts +4 -2
  16. package/dist/lib/index.d.ts.map +1 -1
  17. package/dist/lib/interceptors/fetch-registry.d.ts +96 -0
  18. package/dist/lib/interceptors/fetch-registry.d.ts.map +1 -0
  19. package/dist/lib/passflow.d.ts +103 -1
  20. package/dist/lib/passflow.d.ts.map +1 -1
  21. package/dist/lib/services/auth-service.d.ts +51 -2
  22. package/dist/lib/services/auth-service.d.ts.map +1 -1
  23. package/dist/lib/upgrade/acr.d.ts +85 -0
  24. package/dist/lib/upgrade/acr.d.ts.map +1 -0
  25. package/dist/lib/upgrade/axios-adapter.d.ts +81 -0
  26. package/dist/lib/upgrade/axios-adapter.d.ts.map +1 -0
  27. package/dist/lib/upgrade/challenge.d.ts +158 -3
  28. package/dist/lib/upgrade/challenge.d.ts.map +1 -1
  29. package/dist/lib/upgrade/client.d.ts +29 -0
  30. package/dist/lib/upgrade/client.d.ts.map +1 -0
  31. package/dist/lib/upgrade/controller.d.ts +366 -25
  32. package/dist/lib/upgrade/controller.d.ts.map +1 -1
  33. package/dist/lib/upgrade/core.d.ts +353 -0
  34. package/dist/lib/upgrade/core.d.ts.map +1 -0
  35. package/dist/lib/upgrade/errors.d.ts +208 -0
  36. package/dist/lib/upgrade/errors.d.ts.map +1 -0
  37. package/dist/lib/upgrade/facade.d.ts +183 -0
  38. package/dist/lib/upgrade/facade.d.ts.map +1 -0
  39. package/dist/lib/upgrade/fetch-adapter.d.ts +30 -0
  40. package/dist/lib/upgrade/fetch-adapter.d.ts.map +1 -0
  41. package/dist/lib/upgrade/index.d.ts +15 -0
  42. package/dist/lib/upgrade/index.d.ts.map +1 -1
  43. package/dist/lib/upgrade/markers.d.ts +64 -0
  44. package/dist/lib/upgrade/markers.d.ts.map +1 -0
  45. package/dist/lib/upgrade/replay.d.ts +132 -0
  46. package/dist/lib/upgrade/replay.d.ts.map +1 -0
  47. package/dist/lib/upgrade/select.d.ts.map +1 -1
  48. package/dist/lib/upgrade/session.d.ts +72 -0
  49. package/dist/lib/upgrade/session.d.ts.map +1 -0
  50. package/dist/lib/upgrade/snapshot.d.ts +196 -0
  51. package/dist/lib/upgrade/snapshot.d.ts.map +1 -0
  52. package/dist/lib/upgrade/transport.d.ts +69 -0
  53. package/dist/lib/upgrade/transport.d.ts.map +1 -0
  54. package/dist/tsconfig.build.tsbuildinfo +1 -1
  55. package/package.json +1 -1
@@ -0,0 +1,183 @@
1
+ /**
2
+ * The Facade: one object that assembles the controller, the transport and the
3
+ * adapters, and the only construction surface a consumer touches.
4
+ *
5
+ * `UpgradeController`, the transport, `upgradeFetch` and the fetch registry stay
6
+ * internal. They are exported as types for testing, not as things to build.
7
+ *
8
+ * **Five members, and the rule is written down because `structural.md` names
9
+ * Facade-as-God-Object as this family's characteristic failure: adding a sixth
10
+ * requires deleting one.** `createClient` is deliberately NOT a member — it is
11
+ * `axios.create(config)` followed by `attachAuth`, so it lives on the `Passflow`
12
+ * instance where the constructor belongs, and it is not a fourth code path.
13
+ *
14
+ * ## The two gates
15
+ *
16
+ * Passflow's own API (domain 1) is derived from `PassflowConfig.url` and handled
17
+ * with zero configuration. The customer's backend (domain 2) is touched only
18
+ * when `backend` is declared AND an adapter is attached: declaring without
19
+ * attaching observes nothing, attaching without declaring matches nothing
20
+ * beyond domain 1. That double gate is why the elevated-credential replay
21
+ * surface is unreachable in the default configuration — there is no default
22
+ * interception surface at all.
23
+ *
24
+ * ## The session-change reset
25
+ *
26
+ * A challenge is server-bound to the session's `sid`, and an abandoned flight
27
+ * must never elevate a NEW session. `UpgradeController.inFlight` is private with
28
+ * no reset, so on `SignIn` / `SignOut` the controller is DISCARDED and a fresh
29
+ * one constructed. A waiter whose elevation was running across that boundary is
30
+ * settled with `UpgradeSessionChangedError` — never replayed — which §13.3's
31
+ * table maps to the original response on fetch and a tagged rejection on axios.
32
+ *
33
+ * SSR-safe: no global is read at module scope, and nothing is installed until
34
+ * `enable()` is called with `interceptFetch`.
35
+ */
36
+ import type { PlatformAdapter } from '../platform';
37
+ import { type AxiosReissue, type ResponseInterceptorHost } from './axios-adapter';
38
+ import type { AssuranceBar, UpgradeChallenge, UpgradeDiagnosticSink } from './challenge';
39
+ import { type UpgradeElevation, type UpgradeFlightSummary, type UpgradePromptHandler, type Waiter } from './controller';
40
+ import type { UpgradeFactor } from './factors';
41
+ import type { FetchLike, ReplayPolicy } from './replay';
42
+ import type { UpgradeSessionPin } from './session';
43
+ import { type UpgradeTransportDeps } from './transport';
44
+ /** Domain 1, as resolved once from `PassflowConfig.url`. `null` = unresolved. */
45
+ export type PassflowUpgradeDomain = {
46
+ origin: string;
47
+ pathPrefix?: string;
48
+ } | null;
49
+ /**
50
+ * What the facade needs from its host, as narrow function-shaped dependencies.
51
+ *
52
+ * Everything HTTP arrives pre-wired, which is what keeps this module free of
53
+ * endpoints and of any opinion about delivery mode.
54
+ */
55
+ export type UpgradeFacadeDeps = UpgradeTransportDeps & {
56
+ /** The platform adapter, for the WebAuthn ceremony. */
57
+ platform: PlatformAdapter;
58
+ /** Domain 1. Resolved by the host from `url`; the facade never derives it. */
59
+ passflowDomain: PassflowUpgradeDomain;
60
+ /** Construction-time factor override, from `PassflowConfig.upgrade`. */
61
+ factors?: readonly UpgradeFactor[];
62
+ /** The bar the elevation reached, read from the freshly stored access token. */
63
+ readObtainedBar?: () => AssuranceBar | undefined;
64
+ /** §5.5's credential swap. Snapshotted before the elevation, read again after. */
65
+ authorizationSwap?: () => {
66
+ from: string;
67
+ to: string;
68
+ } | undefined;
69
+ /**
70
+ * Installs (or clears, on `undefined`) the handler the SDK's OWN axios clients
71
+ * consult inside their existing error interceptor — §4.4's internal seam,
72
+ * which an ordinary customer-registered interceptor is structurally too late
73
+ * to be.
74
+ *
75
+ * It receives a FACTORY rather than a handler because each of the SDK's
76
+ * clients must replay through its own instance: a handler built around one
77
+ * client's `request` would re-issue another client's request on the wrong
78
+ * instance.
79
+ */
80
+ attachInternalSeam?: (factory: ((reissue: AxiosReissue) => (error: unknown) => Promise<unknown>) | undefined) => void;
81
+ /** Subscribes to session changes (`SignIn` / `SignOut`). Returns unsubscribe. */
82
+ onSessionChange?: (listener: () => void) => () => void;
83
+ /**
84
+ * The session and user identity the host can currently read, or `undefined`
85
+ * when this delivery mode exposes none.
86
+ *
87
+ * Read ONCE per flight, when the flight starts, and handed to `commitRefresh`
88
+ * so the token-applying path can refuse to write user A's tokens into user
89
+ * B's session (§13.4 revision 6 / H2).
90
+ */
91
+ readSessionPin?: () => UpgradeSessionPin | undefined;
92
+ };
93
+ /**
94
+ * Everything `enable()` accepts: the prompt, the interception scope and the
95
+ * interception policy.
96
+ *
97
+ * `requireChallengeChannel`, `backend` and `shouldIntercept` are interception
98
+ * policy and deliberately NOT on `PassflowConfig` — they are per-installation,
99
+ * not client-wide.
100
+ */
101
+ export type UpgradeEnableOptions = {
102
+ /** REQUIRED. The js-sdk ships no UI. */
103
+ prompt: UpgradePromptHandler;
104
+ /**
105
+ * Where the customer's own API is: an origin, an origin with a path prefix, or
106
+ * a same-origin path prefix (`'/api'`). Nothing outside this and domain 1 is
107
+ * ever observed, and there is no allow-all opt-in — a developer who wants
108
+ * every origin lists every origin, which is a sentence they can read in a diff.
109
+ */
110
+ backend?: string | string[];
111
+ /**
112
+ * Patches `globalThis.fetch` through the registry, at the upgrade priority.
113
+ * **Off by default**, and a different decision from enabling the UI: one prop
114
+ * for both would bundle "show me the 2FA prompt" with "patch the global", and
115
+ * only the second has the blast radius. It widens which call sites are
116
+ * observed, never the scope.
117
+ */
118
+ interceptFetch?: boolean;
119
+ /** Requires the standards channel. The body is then never read. */
120
+ requireChallengeChannel?: 'header';
121
+ /** NARROWING ONLY: `false` excludes an admitted URL; `true` cannot widen. */
122
+ shouldIntercept?: (url: string) => boolean;
123
+ replay?: ReplayPolicy;
124
+ /** The fetch surface's only option. Default `'resolve-original'`. */
125
+ onCancel?: 'resolve-original' | 'reject';
126
+ onUpgradeDiagnostic?: UpgradeDiagnosticSink;
127
+ factors?: readonly UpgradeFactor[];
128
+ maxAttempts?: number;
129
+ /** One terminal state per flight, after every waiter has reported. */
130
+ onFlightSettled?: (summary: UpgradeFlightSummary) => void;
131
+ /** Forwarded verbatim to the prepare round for passkey upgrades. */
132
+ relyingPartyId?: string;
133
+ };
134
+ /** What the imperative seam resolves with (§13.4). */
135
+ export type UpgradeResult = {
136
+ status: 'elevated';
137
+ acr?: string;
138
+ };
139
+ /**
140
+ * What the imperative seam accepts: a bar the CALLER states.
141
+ *
142
+ * Stating one is what makes `UpgradeInsufficientError` reachable on this
143
+ * surface; an options-less call states no bar, has no protected operation to
144
+ * replay, and resolves `{ status: 'elevated' }` (§13.3's `n/a` cell).
145
+ */
146
+ export type UpgradeOptions = {
147
+ acrValues?: string;
148
+ maxAge?: number;
149
+ };
150
+ export type UpgradeFacade = {
151
+ /**
152
+ * Drives an elevation directly — §13.4's imperative seam.
153
+ *
154
+ * The facade is CALLABLE, which is why this signature is here rather than on
155
+ * a method: the documented surface is `passflow.upgrade(options?)`, and the
156
+ * same object also carries `enable`, `withAuth` and the rest. Resolves on
157
+ * proof completion; rejects `UpgradeCancelledError`,
158
+ * `UpgradeUnsatisfiableError`, `UpgradeUnavailableError`,
159
+ * `UpgradeSessionChangedError`, `UpgradeTransportError` — and, when the
160
+ * caller stated a bar the proof did not reach, `UpgradeInsufficientError`.
161
+ */
162
+ (options?: UpgradeOptions): Promise<UpgradeResult>;
163
+ /**
164
+ * Installs the prompt handler and the selected interceptors. Returns an
165
+ * idempotent teardown. Calling `enable()` twice replaces the previous
166
+ * installation and tears the first one down.
167
+ */
168
+ enable(options: UpgradeEnableOptions): () => void;
169
+ /** True when a prompt handler is installed AND the delivery mode can commit. */
170
+ readonly enabled: boolean;
171
+ /**
172
+ * Drives an elevation directly — headless clients, a "verify now" button,
173
+ * tests. Single-flight with the interceptors: concurrent callers share one
174
+ * prompt, because they share one controller.
175
+ */
176
+ elevate(challenge: UpgradeChallenge, waiter?: Waiter): Promise<UpgradeElevation>;
177
+ /** Wraps a fetch and returns the wrapper. Touches no global. */
178
+ withAuth(next?: FetchLike): FetchLike;
179
+ /** Attaches to an axios instance the developer owns, MUTATING it. Returns `detach()`. */
180
+ attachAuth(instance: ResponseInterceptorHost): () => void;
181
+ };
182
+ export declare const createUpgradeFacade: (deps: UpgradeFacadeDeps) => UpgradeFacade;
183
+ //# sourceMappingURL=facade.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"facade.d.ts","sourceRoot":"","sources":["../../../lib/upgrade/facade.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,30 @@
1
+ /**
2
+ * The FETCH adapter over `handleChallenge`. One of four, and the thinnest.
3
+ *
4
+ * **NO LOGIC IN AN ADAPTER.** Everything here is translation: a `Response`
5
+ * becomes a `ChallengeContext` on the way in, and a `CoreOutcome` becomes a
6
+ * `Response` (or a throw) on the way out, through `settleFetchOutcome`. It
7
+ * parses no challenge, compares no bar, decides no scope, counts no retry and
8
+ * chooses no error class. If something in this file ever looks like a decision,
9
+ * it belongs in `core.ts` instead — a row that passes here and fails on the
10
+ * axios surface is a leak out of the core, not a difference between transports.
11
+ *
12
+ * The one genuinely transport-shaped obligation it carries is the body: a
13
+ * replay needs the request body while it is still unread, so `planReplay` runs
14
+ * BEFORE the first call and the core is told whether a replay is even possible
15
+ * before any prompt opens. The caller's own response is never consumed — every
16
+ * body read is taken from `clone()`, so `bodyUsed === false` holds on the object
17
+ * the caller receives.
18
+ *
19
+ * SSR-safe: no global is read at module scope. `withAuth()`'s default `next` is
20
+ * resolved inside the call, not here.
21
+ */
22
+ import { type UpgradeInterceptorOptionsSource } from './core';
23
+ import { type FetchLike } from './replay';
24
+ /**
25
+ * Wraps a `fetch`, returning a new function. Installs nothing and touches no
26
+ * global — this is the SSR- and React-Native-safe entry point, and the one the
27
+ * global patch itself is built from.
28
+ */
29
+ export declare const upgradeFetch: (next: FetchLike, source: UpgradeInterceptorOptionsSource) => FetchLike;
30
+ //# sourceMappingURL=fetch-adapter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fetch-adapter.d.ts","sourceRoot":"","sources":["../../../lib/upgrade/fetch-adapter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,EAGL,KAAK,+BAA+B,EAIrC,MAAM,QAAQ,CAAC;AAChB,OAAO,EAAE,KAAK,SAAS,EAAiD,MAAM,UAAU,CAAC;AA2HzF;;;;GAIG;AACH,eAAO,MAAM,YAAY,GAAI,MAAM,SAAS,EAAE,QAAQ,+BAA+B,KAAG,SA2CvF,CAAC"}
@@ -1,5 +1,20 @@
1
1
  export { INSUFFICIENT_USER_AUTHENTICATION, UPGRADE_REQUIRED, parseChallengeBody, parseChallengeHeader, parseUpgradeChallenge, type UpgradeChallenge, } from './challenge';
2
2
  export { DEFAULT_UPGRADE_FACTORS, factorRegistry, passkeyFactor, recoveryCodesFactor, UpgradeFactorError, totpFactor, type UpgradeAssertion, type UpgradeAssertContext, type UpgradeFactor, type UpgradeFactorInput, type UpgradeFactorType, type UpgradePrepared, } from './factors';
3
3
  export { UpgradeCancelledError, UpgradeController, UpgradeUnsatisfiableError, type UpgradeControllerOptions, type UpgradePromptHandler, type UpgradePromptRequest, type UpgradePromptResult, type UpgradeTransport, } from './controller';
4
+ export { RECOVERABLE_COMMIT_ERROR_IDS, SESSION_INVALIDATING_COMMIT_ERROR_IDS, UpgradeErrorIds, UpgradeTransportError, classifyUpgradeError, invalidatesSessionOnCommit, isRecoverableCommitFailure, isRetryableUpgradeKind, isUpgradeTransportError, type UpgradeEndpoint, type UpgradeErrorKind, type UpgradeTransportErrorInit, } from './errors';
5
+ export { UPGRADE_REPLAYED_MARKER, UPGRADE_TRANSACTION_MARKER, hasUpgradeReplayedMarker, hasUpgradeTransactionMarker, markUpgradeReplayed, markUpgradeTransaction, type UpgradeRequestMarkers, } from './markers';
4
6
  export { selectFactors } from './select';
7
+ export { RELATIVE_BASE, captureHeaders, createRequestSnapshot, headersToObject, normaliseUrl, snapshotHeader, swapAuthorization, type NormalisedUrl, type RequestSnapshot, type RequestSnapshotInit, type SnapshotHeader, } from './snapshot';
8
+ export { readSessionPin, sessionPinMatches, type UpgradeSessionClaims, type UpgradeSessionPin, } from './session';
9
+ export { createUpgradeTransport, toPrepared, type UpgradeTransportDeps } from './transport';
10
+ export { parseAcrToken, readAcrValues, requiredAcrLevel, type AcrReading } from './acr';
11
+ export { parseMaxAge, readUpgradeChallenge, type AssuranceBar, type ChallengeChannel, type ChallengeInput, type ChallengeReadOptions, type ChallengeReading, type MaxAgeReading, type UpgradeDiagnostic, type UpgradeDiagnosticReason, type UpgradeDiagnosticSink, } from './challenge';
12
+ export { barMeets, barOf, mergeChallenges, type UpgradeElevation, type UpgradeFlightState, type UpgradeFlightSummary, type UpgradeProof, type UpgradeWaiterSummary, type Waiter, type WaiterEntry, type WaiterOutcome, type WaiterReport, } from './controller';
13
+ export { UpgradeInsufficientError, UpgradeSessionChangedError, UpgradeUnavailableError, } from './errors';
14
+ export { coreOutcomeToError, handleChallenge, isInterceptionScope, isUpgradeCancelled, isUpgradeExhausted, isUpgradeInsufficient, isUpgradeSessionChanged, isUpgradeTransportFailure, isUpgradeUnavailable, isUpgradeUnsatisfiable, settleAxiosOutcome, settleFetchOutcome, tagUpgradeOutcome, type ChallengeContext, type CoreOutcome, type InterceptionScope, type Settlement, type UpgradeInterceptorOptions, } from './core';
15
+ export { methodOf, planReplay, policyPermits, urlOf, type BodyKind, type FetchLike, type ReplayHints, type ReplayPlan, type ReplayPolicy, type UpgradeReplayInfo, } from './replay';
16
+ export { attachUpgradeAuth, createAxiosUpgradeHandler, type AxiosConfigLike, type AxiosReissue, type ResponseInterceptorHost, } from './axios-adapter';
17
+ export { upgradeFetch } from './fetch-adapter';
18
+ export { createUpgradeClient, type CreateClientConfig } from './client';
19
+ export { createUpgradeFacade, type PassflowUpgradeDomain, type UpgradeEnableOptions, type UpgradeFacade, type UpgradeFacadeDeps, type UpgradeOptions, type UpgradeResult, } from './facade';
5
20
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../lib/upgrade/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,gCAAgC,EAChC,gBAAgB,EAChB,kBAAkB,EAClB,oBAAoB,EACpB,qBAAqB,EACrB,KAAK,gBAAgB,GACtB,MAAM,aAAa,CAAC;AAErB,OAAO,EACL,uBAAuB,EACvB,cAAc,EACd,aAAa,EACb,mBAAmB,EACnB,kBAAkB,EAClB,UAAU,EACV,KAAK,gBAAgB,EACrB,KAAK,oBAAoB,EACzB,KAAK,aAAa,EAClB,KAAK,kBAAkB,EACvB,KAAK,iBAAiB,EACtB,KAAK,eAAe,GACrB,MAAM,WAAW,CAAC;AAEnB,OAAO,EACL,qBAAqB,EACrB,iBAAiB,EACjB,yBAAyB,EACzB,KAAK,wBAAwB,EAC7B,KAAK,oBAAoB,EACzB,KAAK,oBAAoB,EACzB,KAAK,mBAAmB,EACxB,KAAK,gBAAgB,GACtB,MAAM,cAAc,CAAC;AAEtB,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../lib/upgrade/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,64 @@
1
+ /**
2
+ * Per-request markers for the upgrade transaction.
3
+ *
4
+ * Two 401s look identical on the wire and mean opposite things. A protected
5
+ * resource's 401 says "this session is not strong enough"; the elevation's own
6
+ * `POST /auth/refresh` 401 says "that TOTP code was wrong, try again". Neither
7
+ * carries RFC 9470 vocabulary on the commit path — `refreshErrorStatus` maps the
8
+ * four recoverable upgrade errors to a bare 401 with no `WWW-Authenticate` and no
9
+ * `upgrade_required` body — so the response alone cannot tell them apart from an
10
+ * expired session.
11
+ *
12
+ * The evidence that CAN tell them apart is the request: we know we issued it.
13
+ * Both rounds of the elevation (`POST /auth/upgrade/start` and the committing
14
+ * `POST /auth/refresh`) carry a marker on their axios config, set at the call
15
+ * site immediately before dispatch. The response interceptor reads it off
16
+ * `error.config`, which axios hands back unchanged, and suppresses
17
+ * `setSessionInvalid()` for that one request only. An unrelated background 401
18
+ * arriving while a prompt is open is unmarked and invalidates exactly as before.
19
+ *
20
+ * A second marker records that a request has already been replayed after an
21
+ * elevation, so an adapter that re-issues through the same interceptor cannot
22
+ * elevate twice for one challenge.
23
+ *
24
+ * ## Why these are enumerable, against the design's "non-enumerable"
25
+ *
26
+ * The design asks for non-enumerable properties, reasoning that they must never
27
+ * reach the wire. Non-enumerable does not survive the trip: `AxiosClient.send`
28
+ * spreads the caller's config into a fresh object (`{ method, url, ...options }`)
29
+ * and axios' own `mergeConfig` iterates `Object.keys`, so both drop a
30
+ * non-enumerable property before the request is dispatched — verified against
31
+ * `axios@1.13` with a stub adapter. An enumerable key reaches both the adapter
32
+ * and `error.config`.
33
+ *
34
+ * The property the design actually wanted is preserved regardless: axios
35
+ * serializes only `data` (and `params`/`headers`), never unknown config keys, so
36
+ * a marker is never transmitted. The same mechanism already carries
37
+ * `_retryCount` through the rate-limit retry path.
38
+ */
39
+ /** Config key marking a request as one of the upgrade transaction's two rounds. */
40
+ export declare const UPGRADE_TRANSACTION_MARKER = "__passflowUpgradeTransaction";
41
+ /** Config key marking a request as the single post-elevation replay (FR3). */
42
+ export declare const UPGRADE_REPLAYED_MARKER = "__passflowUpgradeReplayed";
43
+ /** The markers as they appear on a request config. Both are absent unless set. */
44
+ export type UpgradeRequestMarkers = {
45
+ /** Set by the upgrade transport on `prepare` and `commit`. */
46
+ __passflowUpgradeTransaction?: true;
47
+ /** Set by the replaying adapter before it re-issues the original request. */
48
+ __passflowUpgradeReplayed?: true;
49
+ };
50
+ /**
51
+ * Returns a copy of `config` marked as part of the upgrade transaction.
52
+ *
53
+ * Copies rather than mutates: the caller may be reusing a config object, and a
54
+ * marker that outlives its request would suppress invalidation for an unrelated
55
+ * 401 — the exact over-broad behaviour the per-request rule replaces.
56
+ */
57
+ export declare const markUpgradeTransaction: <T extends object>(config?: T) => T & UpgradeRequestMarkers;
58
+ /** Reports whether a request config carries the upgrade-transaction marker. */
59
+ export declare const hasUpgradeTransactionMarker: (config: unknown) => boolean;
60
+ /** Returns a copy of `config` marked as the one permitted post-elevation replay. */
61
+ export declare const markUpgradeReplayed: <T extends object>(config: T) => T & UpgradeRequestMarkers;
62
+ /** Reports whether a request config has already been replayed after an elevation. */
63
+ export declare const hasUpgradeReplayedMarker: (config: unknown) => boolean;
64
+ //# sourceMappingURL=markers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"markers.d.ts","sourceRoot":"","sources":["../../../lib/upgrade/markers.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AAEH,mFAAmF;AACnF,eAAO,MAAM,0BAA0B,iCAAiC,CAAC;AAEzE,8EAA8E;AAC9E,eAAO,MAAM,uBAAuB,8BAA8B,CAAC;AAEnE,kFAAkF;AAClF,MAAM,MAAM,qBAAqB,GAAG;IAClC,8DAA8D;IAC9D,4BAA4B,CAAC,EAAE,IAAI,CAAC;IACpC,6EAA6E;IAC7E,yBAAyB,CAAC,EAAE,IAAI,CAAC;CAClC,CAAC;AAKF;;;;;;GAMG;AACH,eAAO,MAAM,sBAAsB,GAAI,CAAC,SAAS,MAAM,EAAE,SAAS,CAAC,KAAG,CAAC,GAAG,qBAGxE,CAAC;AAEH,+EAA+E;AAC/E,eAAO,MAAM,2BAA2B,GAAI,QAAQ,OAAO,KAAG,OAAyD,CAAC;AAExH,oFAAoF;AACpF,eAAO,MAAM,mBAAmB,GAAI,CAAC,SAAS,MAAM,EAAE,QAAQ,CAAC,KAAG,CAAC,GAAG,qBAGpE,CAAC;AAEH,qFAAqF;AACrF,eAAO,MAAM,wBAAwB,GAAI,QAAQ,OAAO,KAAG,OAAsD,CAAC"}
@@ -0,0 +1,132 @@
1
+ /**
2
+ * The replay planner.
3
+ *
4
+ * Pure, no I/O, and it runs BEFORE the first call — a `Request` body must be
5
+ * cloned while it is still unread, so by the time a 401 has come back it is too
6
+ * late to decide whether a replay is possible. Planning first is also what lets
7
+ * the core fail BEFORE opening a prompt: prompting the user, elevating their
8
+ * session and only then discovering we cannot honour it is the worst possible
9
+ * ordering, because it spends a server challenge and a user interaction for
10
+ * nothing.
11
+ *
12
+ * ## Why replaying a challenged request is safe at all
13
+ *
14
+ * An RFC 9470 challenge is emitted by the authorization middleware —
15
+ * `RequireAuthLevel` / `RequireFreshAuth` — BEFORE the handler runs, so the
16
+ * request had no effect. That is categorically different from a 429 or a 502,
17
+ * where the request may have partially executed, which is why
18
+ * `AxiosClient.handleRateLimitError` correctly refuses to retry non-idempotent
19
+ * requests and why this correctly does the opposite.
20
+ *
21
+ * The guarantee depends on the CUSTOMER's middleware placement, though, so
22
+ * there is an escape hatch rather than an assumption: a backend that emits an
23
+ * `insufficient_user_authentication` challenge after a side effect is violating
24
+ * RFC 9470, and such a backend sets `'idempotent-only'`. Stating the
25
+ * precondition is the design; silently assuming it would not be.
26
+ *
27
+ * ## Per-waiter binding
28
+ *
29
+ * A plan is bound to the request that carried the challenge: its own URL, its
30
+ * own method and its own pre-captured body. No waiter's challenge, outcome or
31
+ * response ever changes what another waiter replays, or where — requirement
32
+ * AGGREGATION across waiters sharing a session is permitted and happens in the
33
+ * controller; replay SUBSTITUTION is prohibited, and that prohibition is this
34
+ * module's whole job.
35
+ */
36
+ import { type RequestSnapshot } from './snapshot';
37
+ /** Our own `fetch` shape. Declared here rather than imported so no DOM lib is required. */
38
+ export type FetchLike = (input: RequestInfo | URL, init?: RequestInit) => Promise<Response>;
39
+ /** How a body can be re-sent, which is the only thing that decides replayability. */
40
+ export type BodyKind =
41
+ /** No body at all. Re-issue the same input. */
42
+ 'none'
43
+ /** A value the caller still holds and `fetch` re-reads: string, Blob, buffer, FormData, params. */
44
+ | 'reusable'
45
+ /** A `Request` with a body, cloned before the first send. */
46
+ | 'request-clone'
47
+ /** Consumed by the first send and unrecoverable: a `ReadableStream`, or a failed clone. */
48
+ | 'one-shot';
49
+ /** What the policy predicate is told about a request. */
50
+ export type UpgradeReplayInfo = {
51
+ url: string;
52
+ method: string;
53
+ bodyKind: BodyKind;
54
+ };
55
+ /**
56
+ * Replay policy.
57
+ *
58
+ * The object form is §13.5's: size and streaming limits. The three string and
59
+ * function forms are §5.1's escape hatch, kept because `'idempotent-only'`
60
+ * expresses a constraint the object form cannot — a backend whose challenge may
61
+ * arrive after a side effect.
62
+ */
63
+ export type ReplayPolicy = {
64
+ /** Refuse to buffer a `Request` body larger than this many bytes, when the size is known. */
65
+ maxBodyBytes?: number;
66
+ /** A stream is never replayable; the field exists so the intent is written down. */
67
+ allowStreamingBody?: false;
68
+ } | 'always' | 'idempotent-only' | ((info: UpgradeReplayInfo) => boolean);
69
+ /** Hints the core passes at replay time. The planner applies them; it decides nothing. */
70
+ export type ReplayHints = {
71
+ /**
72
+ * The §5.5 credential swap, already resolved by the core.
73
+ *
74
+ * Applied if and only if the outgoing `Authorization` header value EXACTLY
75
+ * equals `from`: equality with the token we hold in storage is what proves the
76
+ * header was ours. Any other value is forwarded untouched, because rewriting a
77
+ * header we do not own would be a security bug.
78
+ *
79
+ * Without this the replay re-sends the stale `Authorization` header, every
80
+ * elevation ends in a surfaced second 401, and the feature silently never
81
+ * works. Cookie mode needs none of it: the browser attaches the current —
82
+ * now elevated — cookie by itself.
83
+ */
84
+ authorizationSwap?: {
85
+ from: string;
86
+ to: string;
87
+ };
88
+ };
89
+ export type ReplayPlan = {
90
+ /** False when the body cannot be re-sent, or the policy refused. */
91
+ replayable: boolean;
92
+ bodyKind: BodyKind;
93
+ /** Why it is not replayable, for the `'replay-unsupported'` diagnostic. */
94
+ reason?: string;
95
+ /** What to pass to `next()` on the FIRST call. */
96
+ forwardInput: RequestInfo | URL;
97
+ forwardInit?: RequestInit;
98
+ /**
99
+ * The normalised, immutable snapshot of this request — §13.4 revision 6.
100
+ *
101
+ * EXPOSED rather than hidden as a plan-time url, so the scope check, the
102
+ * challenge context, the diagnostics and the replay all read the same object
103
+ * and the request that is *authorized* cannot differ from the one that is
104
+ * *sent*.
105
+ */
106
+ snapshot: RequestSnapshot;
107
+ /** Re-issues the original request, once. Rejects if `!replayable`. */
108
+ replay: (next: FetchLike, hints?: ReplayHints) => Promise<Response>;
109
+ };
110
+ /** The URL of whatever shape the caller passed, without touching the network. */
111
+ export declare const urlOf: (input: RequestInfo | URL) => string;
112
+ /** The method of whatever shape the caller passed. `init` wins, as `fetch` itself has it. */
113
+ export declare const methodOf: (input: RequestInfo | URL, init?: RequestInit) => string;
114
+ /**
115
+ * Applies the policy. The planner asks; it never decides what the policy means.
116
+ *
117
+ * Exported because the axios adapter needs the same answer about an
118
+ * `AxiosRequestConfig`, which has no `Request` and no `BodyInit` to plan over.
119
+ * Two readings of `'idempotent-only'` — one here and one there — is exactly the
120
+ * per-surface drift the single core forbids, so there is one reading and both
121
+ * surfaces call it.
122
+ */
123
+ export declare const policyPermits: (policy: ReplayPolicy | undefined, info: UpgradeReplayInfo, bodyBytes?: number) => boolean;
124
+ /**
125
+ * Decides whether a request can be replayed, and produces the thunk that does it.
126
+ *
127
+ * Call it BEFORE the first send. The returned `forwardInput` / `forwardInit` are
128
+ * what the first send must use: for a `Request` with a body they are the
129
+ * original, with the spare clone held for the replay.
130
+ */
131
+ export declare const planReplay: (input: RequestInfo | URL, init?: RequestInit, policy?: ReplayPolicy) => ReplayPlan;
132
+ //# sourceMappingURL=replay.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"replay.d.ts","sourceRoot":"","sources":["../../../lib/upgrade/replay.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AAEH,OAAO,EAAE,KAAK,eAAe,EAA6D,MAAM,YAAY,CAAC;AAE7G,2FAA2F;AAC3F,MAAM,MAAM,SAAS,GAAG,CAAC,KAAK,EAAE,WAAW,GAAG,GAAG,EAAE,IAAI,CAAC,EAAE,WAAW,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAC;AAE5F,qFAAqF;AACrF,MAAM,MAAM,QAAQ;AAClB,+CAA+C;AAC7C,MAAM;AACR,mGAAmG;GACjG,UAAU;AACZ,6DAA6D;GAC3D,eAAe;AACjB,2FAA2F;GACzF,UAAU,CAAC;AAEf,yDAAyD;AACzD,MAAM,MAAM,iBAAiB,GAAG;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,QAAQ,CAAA;CAAE,CAAC;AAEpF;;;;;;;GAOG;AACH,MAAM,MAAM,YAAY,GACpB;IACE,6FAA6F;IAC7F,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,oFAAoF;IACpF,kBAAkB,CAAC,EAAE,KAAK,CAAC;CAC5B,GACD,QAAQ,GACR,iBAAiB,GACjB,CAAC,CAAC,IAAI,EAAE,iBAAiB,KAAK,OAAO,CAAC,CAAC;AAE3C,0FAA0F;AAC1F,MAAM,MAAM,WAAW,GAAG;IACxB;;;;;;;;;;;;OAYG;IACH,iBAAiB,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,MAAM,CAAA;KAAE,CAAC;CAClD,CAAC;AAEF,MAAM,MAAM,UAAU,GAAG;IACvB,oEAAoE;IACpE,UAAU,EAAE,OAAO,CAAC;IACpB,QAAQ,EAAE,QAAQ,CAAC;IACnB,2EAA2E;IAC3E,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,kDAAkD;IAClD,YAAY,EAAE,WAAW,GAAG,GAAG,CAAC;IAChC,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B;;;;;;;OAOG;IACH,QAAQ,EAAE,eAAe,CAAC;IAC1B,sEAAsE;IACtE,MAAM,EAAE,CAAC,IAAI,EAAE,SAAS,EAAE,KAAK,CAAC,EAAE,WAAW,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAC;CACrE,CAAC;AAYF,iFAAiF;AACjF,eAAO,MAAM,KAAK,GAAI,OAAO,WAAW,GAAG,GAAG,KAAG,MAQhD,CAAC;AAEF,6FAA6F;AAC7F,eAAO,MAAM,QAAQ,GAAI,OAAO,WAAW,GAAG,GAAG,EAAE,OAAO,WAAW,KAAG,MAGvE,CAAC;AAwCF;;;;;;;;GAQG;AACH,eAAO,MAAM,aAAa,GAAI,QAAQ,YAAY,GAAG,SAAS,EAAE,MAAM,iBAAiB,EAAE,YAAY,MAAM,KAAG,OAc7G,CAAC;AAoHF;;;;;;GAMG;AACH,eAAO,MAAM,UAAU,GAAI,OAAO,WAAW,GAAG,GAAG,EAAE,OAAO,WAAW,EAAE,SAAS,YAAY,KAAG,UAyGhG,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"select.d.ts","sourceRoot":"","sources":["../../../lib/upgrade/select.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAEH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AACpD,OAAO,KAAK,EAAE,aAAa,EAAqB,MAAM,WAAW,CAAC;AAoElE;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,aAAa,GAAI,WAAW,SAAS,aAAa,EAAE,EAAE,WAAW,gBAAgB,KAAG,SAAS,aAAa,EAKtH,CAAC"}
1
+ {"version":3,"file":"select.d.ts","sourceRoot":"","sources":["../../../lib/upgrade/select.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAGH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AACpD,OAAO,KAAK,EAAE,aAAa,EAAqB,MAAM,WAAW,CAAC;AAiElE;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,aAAa,GAAI,WAAW,SAAS,aAAa,EAAE,EAAE,WAAW,gBAAgB,KAAG,SAAS,aAAa,EAKtH,CAAC"}
@@ -0,0 +1,72 @@
1
+ /**
2
+ * Session and user identity pinning for an upgrade flight (§13.4 revision 6).
3
+ *
4
+ * ## The rule
5
+ *
6
+ * An upgrade flight is bound to the **session and user identity it began
7
+ * under**, and that identity is verified **at the moment tokens are applied** —
8
+ * not only around `prepare` and `commit`. On mismatch the rotated tokens are
9
+ * discarded, never applied, and the flight settles as `session changed
10
+ * mid-flight`.
11
+ *
12
+ * ## Why the epoch counter was not enough
13
+ *
14
+ * The round-1 epoch check wraps the two rounds that talk to the server. It does
15
+ * not wrap token APPLICATION, which happens inside `AuthService.refreshToken`
16
+ * after the commit returns. A logout and re-login during an in-flight upgrade
17
+ * therefore let user A's flight write its rotated tokens over user B's live
18
+ * session: the request was authorized as A, the storage belongs to B, and
19
+ * nothing in between compared the two principals.
20
+ *
21
+ * An epoch tells you something changed. An identity check tells you **whether
22
+ * it is still the same principal**, which is the property actually required.
23
+ * Both run, and they are complementary rather than redundant.
24
+ *
25
+ * ## Why this module is pure
26
+ *
27
+ * It imports nothing and reads no storage, so the token-applying path can be
28
+ * given a pin by value and the comparison can be tested with literals. Reading
29
+ * the identity is the host's job — it is the only code that knows where the
30
+ * session lives in each delivery mode.
31
+ */
32
+ /**
33
+ * The principal an upgrade flight is pinned to.
34
+ *
35
+ * `sub` is the user. `sid` is the session when the server issues one; it is
36
+ * absent in tokens that carry no session claim, and the field is then simply
37
+ * not part of the comparison — a missing claim cannot be made to prove
38
+ * anything, and inventing a value would make two different sessions compare
39
+ * equal or unequal arbitrarily.
40
+ */
41
+ export type UpgradeSessionPin = {
42
+ readonly sub: string;
43
+ readonly sid?: string;
44
+ };
45
+ /** Claims the pin is read from. Deliberately `unknown`-typed: it is a parsed JWT body. */
46
+ export type UpgradeSessionClaims = {
47
+ sub?: unknown;
48
+ sid?: unknown;
49
+ } | null | undefined;
50
+ /**
51
+ * Reads a pin from token claims, or `undefined` when there is no identity to
52
+ * pin to.
53
+ *
54
+ * `sub` is mandatory: without a user there is no principal, and a pin that
55
+ * compares nothing would report "unchanged" for every change.
56
+ */
57
+ export declare const readSessionPin: (claims: UpgradeSessionClaims) => UpgradeSessionPin | undefined;
58
+ /**
59
+ * Is `current` the same principal the flight was pinned to?
60
+ *
61
+ * `false` when `current` is absent: a pin exists, so the identity WAS readable
62
+ * when the flight began, and an identity that has since become unreadable is a
63
+ * change — the session was torn down. That is fail-closed on token
64
+ * APPLICATION (nothing is written) and still fail-open on session STATE
65
+ * (nothing is invalidated, nobody is logged out). §13.4 revision 6 keeps those
66
+ * two axes separate on purpose.
67
+ *
68
+ * When both sides carry a `sid` it must match as well, which is what catches a
69
+ * logout and re-login as the SAME user.
70
+ */
71
+ export declare const sessionPinMatches: (pin: UpgradeSessionPin, current: UpgradeSessionPin | undefined) => boolean;
72
+ //# sourceMappingURL=session.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../../../lib/upgrade/session.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAEH;;;;;;;;GAQG;AACH,MAAM,MAAM,iBAAiB,GAAG;IAC9B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC;CACvB,CAAC;AAEF,0FAA0F;AAC1F,MAAM,MAAM,oBAAoB,GAAG;IAAE,GAAG,CAAC,EAAE,OAAO,CAAC;IAAC,GAAG,CAAC,EAAE,OAAO,CAAA;CAAE,GAAG,IAAI,GAAG,SAAS,CAAC;AAEvF;;;;;;GAMG;AACH,eAAO,MAAM,cAAc,GAAI,QAAQ,oBAAoB,KAAG,iBAAiB,GAAG,SAKjF,CAAC;AAEF;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,iBAAiB,GAAI,KAAK,iBAAiB,EAAE,SAAS,iBAAiB,GAAG,SAAS,KAAG,OAQlG,CAAC"}