@passflow/core 0.14.0 → 0.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +2 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.mjs +2767 -1054
- package/dist/index.mjs.map +1 -1
- package/dist/lib/api/auth.d.ts +17 -2
- package/dist/lib/api/auth.d.ts.map +1 -1
- package/dist/lib/api/axios-client.d.ts +47 -2
- package/dist/lib/api/axios-client.d.ts.map +1 -1
- package/dist/lib/api/index.d.ts +1 -0
- package/dist/lib/api/index.d.ts.map +1 -1
- package/dist/lib/api/model.d.ts +67 -2
- package/dist/lib/api/model.d.ts.map +1 -1
- package/dist/lib/api/upgrade.d.ts +39 -0
- package/dist/lib/api/upgrade.d.ts.map +1 -0
- package/dist/lib/index.d.ts +4 -0
- package/dist/lib/index.d.ts.map +1 -1
- package/dist/lib/interceptors/fetch-registry.d.ts +96 -0
- package/dist/lib/interceptors/fetch-registry.d.ts.map +1 -0
- package/dist/lib/passflow.d.ts +103 -1
- package/dist/lib/passflow.d.ts.map +1 -1
- package/dist/lib/services/auth-service.d.ts +51 -2
- package/dist/lib/services/auth-service.d.ts.map +1 -1
- package/dist/lib/upgrade/acr.d.ts +85 -0
- package/dist/lib/upgrade/acr.d.ts.map +1 -0
- package/dist/lib/upgrade/axios-adapter.d.ts +81 -0
- package/dist/lib/upgrade/axios-adapter.d.ts.map +1 -0
- package/dist/lib/upgrade/challenge.d.ts +158 -3
- package/dist/lib/upgrade/challenge.d.ts.map +1 -1
- package/dist/lib/upgrade/client.d.ts +29 -0
- package/dist/lib/upgrade/client.d.ts.map +1 -0
- package/dist/lib/upgrade/controller.d.ts +366 -25
- package/dist/lib/upgrade/controller.d.ts.map +1 -1
- package/dist/lib/upgrade/core.d.ts +353 -0
- package/dist/lib/upgrade/core.d.ts.map +1 -0
- package/dist/lib/upgrade/errors.d.ts +208 -0
- package/dist/lib/upgrade/errors.d.ts.map +1 -0
- package/dist/lib/upgrade/facade.d.ts +183 -0
- package/dist/lib/upgrade/facade.d.ts.map +1 -0
- package/dist/lib/upgrade/fetch-adapter.d.ts +30 -0
- package/dist/lib/upgrade/fetch-adapter.d.ts.map +1 -0
- package/dist/lib/upgrade/index.d.ts +15 -0
- package/dist/lib/upgrade/index.d.ts.map +1 -1
- package/dist/lib/upgrade/markers.d.ts +64 -0
- package/dist/lib/upgrade/markers.d.ts.map +1 -0
- package/dist/lib/upgrade/replay.d.ts +132 -0
- package/dist/lib/upgrade/replay.d.ts.map +1 -0
- package/dist/lib/upgrade/select.d.ts.map +1 -1
- package/dist/lib/upgrade/session.d.ts +72 -0
- package/dist/lib/upgrade/session.d.ts.map +1 -0
- package/dist/lib/upgrade/snapshot.d.ts +196 -0
- package/dist/lib/upgrade/snapshot.d.ts.map +1 -0
- package/dist/lib/upgrade/transport.d.ts +69 -0
- package/dist/lib/upgrade/transport.d.ts.map +1 -0
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/package.json +3 -3
- package/dist/index.js +0 -2
- package/dist/index.js.map +0 -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;
|
|
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"}
|