@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.
- package/dist/index.cjs +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +2751 -1196
- 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 -2
- 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 +1 -1
|
@@ -1,27 +1,107 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Upgrade orchestration
|
|
2
|
+
* Upgrade orchestration — ONE state machine, and it is the single authority for
|
|
3
|
+
* lifecycle.
|
|
3
4
|
*
|
|
4
5
|
* Turns a parsed RFC 9470 challenge into an elevated session: pick a factor,
|
|
5
6
|
* ask the user, run the prepare round if the factor needs one, assert, then
|
|
6
7
|
* commit through `POST /auth/refresh` with the `upgrade` field.
|
|
7
8
|
*
|
|
8
9
|
* The controller owns no UI. It calls out to a prompt handler, which the React
|
|
9
|
-
* layer (or any consumer) implements — that is what keeps the same logic
|
|
10
|
-
*
|
|
10
|
+
* layer (or any consumer) implements — that is what keeps the same logic usable
|
|
11
|
+
* from a headless client.
|
|
12
|
+
*
|
|
13
|
+
* ## Why a state machine rather than rules
|
|
14
|
+
*
|
|
15
|
+
* Three review rounds each found two independently-correct lifecycle fixes
|
|
16
|
+
* colliding: fold-versus-single-flight, replay-binding-versus-aggregation, and
|
|
17
|
+
* a prompt outliving the flight whose state had already been cleared. The cause
|
|
18
|
+
* was not any individual rule — it was that lifecycle was specified as rules
|
|
19
|
+
* scattered across four sections, so two of them could each be locally right
|
|
20
|
+
* and jointly impossible. Everything about when state is set, read and cleared
|
|
21
|
+
* lives here, and nothing else writes these fields.
|
|
22
|
+
*
|
|
23
|
+
* ## The states
|
|
24
|
+
*
|
|
25
|
+
* | state | meaning |
|
|
26
|
+
* |--------------|----------------------------------------------------------------|
|
|
27
|
+
* | `idle` | no flight; every field clear; `inFlight === null` |
|
|
28
|
+
* | `collecting` | prompt open, no factor selected yet |
|
|
29
|
+
* | `proving` | a factor is selected; `prepare`/commit on the wire |
|
|
30
|
+
* | `replaying` | proof accepted; waiters replaying their own bound requests |
|
|
31
|
+
* | `settling` | every waiter has reported; the summary is complete |
|
|
32
|
+
* | `done` | prompt settled and every waiter settled; one step from `idle` |
|
|
33
|
+
*
|
|
34
|
+
* ## The decision that shapes all of it
|
|
35
|
+
*
|
|
36
|
+
* **An elevation ends when every waiting request has been retried and settled.**
|
|
37
|
+
* Proof acceptance does NOT end the flight. The previous implementation cleared
|
|
38
|
+
* `inFlight` in a `.finally` on proof completion, and that `.finally` was a real
|
|
39
|
+
* window: a prompt on screen with `inFlight === null`, in which an unrelated
|
|
40
|
+
* challenged request opened a SECOND modal and a waiter could report into a
|
|
41
|
+
* controller that had already forgotten its flight. Clearing happens in one
|
|
42
|
+
* synchronous step, after the prompt has settled, and nowhere else.
|
|
43
|
+
*
|
|
44
|
+
* Two costs the design accepts for that: the prompt stays open for one extra
|
|
45
|
+
* round trip while the replays settle, and a genuinely new challenge arriving in
|
|
46
|
+
* that window queues behind the flight rather than opening a second prompt.
|
|
11
47
|
*/
|
|
12
|
-
import type { UpgradeChallenge } from './challenge';
|
|
48
|
+
import type { AssuranceBar, UpgradeChallenge } from './challenge';
|
|
13
49
|
import { type UpgradeAssertion, type UpgradeFactor, type UpgradeFactorType, type UpgradePrepared } from './factors';
|
|
14
50
|
import type { PlatformAdapter } from '../platform';
|
|
15
|
-
/** What the UI is shown when
|
|
51
|
+
/** What the UI is shown when an upgrade is required. */
|
|
16
52
|
export type UpgradePromptRequest = {
|
|
17
|
-
/** What the server demanded. */
|
|
53
|
+
/** What the server demanded — the MERGED requirement, when several challenges joined. */
|
|
18
54
|
challenge: UpgradeChallenge;
|
|
19
55
|
/** Usable factors, already ordered — the suggested one first. */
|
|
20
56
|
factors: readonly UpgradeFactor[];
|
|
21
57
|
/** Failure from the previous attempt, when the user is retrying. */
|
|
22
58
|
previousError?: Error;
|
|
59
|
+
/**
|
|
60
|
+
* Called the moment the user picks a factor and BEFORE any answer is
|
|
61
|
+
* submitted. REQUIRED of every prompt implementation — the SDK's own modal,
|
|
62
|
+
* the `useUpgrade` hook and a render-prop override alike.
|
|
63
|
+
*
|
|
64
|
+
* `selected` is defined off this call and off nothing else. Deriving it from
|
|
65
|
+
* `prompt` resolving would implement the SUBMISSION boundary instead: the
|
|
66
|
+
* shipped `UpgradePromptResult` resolves with the code already collected, so
|
|
67
|
+
* `selected` would stay false for the whole interval in which the user has
|
|
68
|
+
* picked TOTP and is typing six digits — and a stronger challenge arriving
|
|
69
|
+
* then would re-rank the list under their fingers, the exact UI jump the
|
|
70
|
+
* selection boundary exists to prevent.
|
|
71
|
+
*
|
|
72
|
+
* An override that never calls it leaves the controller in `collecting` for
|
|
73
|
+
* the whole ceremony, so a late join re-ranks mid-entry: degraded, not silent,
|
|
74
|
+
* because `onRequirementRaised` still fires and the re-rank is observable.
|
|
75
|
+
*/
|
|
76
|
+
onFactorSelected: (factor: UpgradeFactorType) => void;
|
|
77
|
+
/**
|
|
78
|
+
* The channel by which the controller pushes a raised bar and a re-ranked list
|
|
79
|
+
* into an OPEN prompt, which updates without resolving. Only ever used before
|
|
80
|
+
* a factor is selected (branch 2). The one-answer resolution contract is
|
|
81
|
+
* unchanged.
|
|
82
|
+
*
|
|
83
|
+
* A prompt SUBSCRIBES by assigning its own handler to this field on the
|
|
84
|
+
* request object it was handed, synchronously, before it awaits anything:
|
|
85
|
+
*
|
|
86
|
+
* ```ts
|
|
87
|
+
* const prompt: UpgradePromptHandler = (request) => {
|
|
88
|
+
* request.onRequirementRaised = (next) => setView(next);
|
|
89
|
+
* return collectAnswer(request);
|
|
90
|
+
* };
|
|
91
|
+
* ```
|
|
92
|
+
*
|
|
93
|
+
* It is a slot rather than a controller-supplied function because the
|
|
94
|
+
* direction is controller → prompt: only the prompt knows how to re-render
|
|
95
|
+
* itself. An override that leaves it unset shows the old bar, and the raised
|
|
96
|
+
* bar then surfaces as a re-challenged replay reporting `insufficient` —
|
|
97
|
+
* degraded, never silent.
|
|
98
|
+
*/
|
|
99
|
+
onRequirementRaised?: (next: {
|
|
100
|
+
challenge: UpgradeChallenge;
|
|
101
|
+
factors: readonly UpgradeFactor[];
|
|
102
|
+
}) => void;
|
|
23
103
|
};
|
|
24
|
-
/** The user's answer to
|
|
104
|
+
/** The user's answer to an upgrade prompt. */
|
|
25
105
|
export type UpgradePromptResult = {
|
|
26
106
|
type: 'submit';
|
|
27
107
|
factor: UpgradeFactorType;
|
|
@@ -33,28 +113,185 @@ export type UpgradePromptResult = {
|
|
|
33
113
|
export type UpgradePromptHandler = (request: UpgradePromptRequest) => Promise<UpgradePromptResult>;
|
|
34
114
|
/** Transport seam — the controller never touches axios directly, so it stays testable. */
|
|
35
115
|
export type UpgradeTransport = {
|
|
36
|
-
/**
|
|
37
|
-
|
|
116
|
+
/**
|
|
117
|
+
* `POST /auth/upgrade/start`. Only called for factors whose `requiresPrepare`
|
|
118
|
+
* is true.
|
|
119
|
+
*
|
|
120
|
+
* Resolves `undefined` when no prepare round was needed after all — a one-shot
|
|
121
|
+
* factor, or a server that answers `prepare_not_required` because its view of
|
|
122
|
+
* the factor differs from ours. Both are recoveries: the factor then asserts
|
|
123
|
+
* with no `prepared`, which is already the TOTP path.
|
|
124
|
+
*/
|
|
125
|
+
prepare(method: UpgradeFactorType): Promise<UpgradePrepared | undefined>;
|
|
38
126
|
/** `POST /auth/refresh` carrying `upgrade`. Resolves once elevated tokens are stored. */
|
|
39
127
|
commit(factor: UpgradeFactorType, assertion: UpgradeAssertion): Promise<void>;
|
|
40
128
|
};
|
|
129
|
+
/** The six states. The controller is always in exactly one. */
|
|
130
|
+
export type UpgradeFlightState = 'idle' | 'collecting' | 'proving' | 'replaying' | 'settling' | 'done';
|
|
131
|
+
/**
|
|
132
|
+
* Who is waiting on this elevation.
|
|
133
|
+
*
|
|
134
|
+
* A `request` waiter owns a replay bound to its own URL and method. An
|
|
135
|
+
* `imperative` waiter has nothing to replay, but when its caller STATED a bar
|
|
136
|
+
* it still has a locally checkable requirement — which is why `requested`
|
|
137
|
+
* exists, and why an AAL3 imperative call joining an AAL2 flight can no longer
|
|
138
|
+
* be reported as `elevated`.
|
|
139
|
+
*/
|
|
140
|
+
export type Waiter = {
|
|
141
|
+
kind: 'request';
|
|
142
|
+
url: string;
|
|
143
|
+
method: string;
|
|
144
|
+
} | {
|
|
145
|
+
kind: 'imperative';
|
|
146
|
+
requested?: AssuranceBar;
|
|
147
|
+
};
|
|
148
|
+
/** A waiter's terminal classification. `pending` until it reports. */
|
|
149
|
+
export type WaiterOutcome = 'pending' | 'replayed' | 'insufficient' | 'failed';
|
|
150
|
+
/** What a waiter reports once its own replay has settled. */
|
|
151
|
+
export type WaiterReport = {
|
|
152
|
+
outcome: 'replayed' | 'insufficient' | 'failed';
|
|
153
|
+
obtained?: AssuranceBar;
|
|
154
|
+
required?: AssuranceBar;
|
|
155
|
+
};
|
|
156
|
+
/**
|
|
157
|
+
* A registered waiter.
|
|
158
|
+
*
|
|
159
|
+
* `report()` is the declared inbound edge: classification happens in the
|
|
160
|
+
* adapter, after `elevate()` resolved, and this is the channel by which it
|
|
161
|
+
* reaches the controller. Because `waiters` is cleared in the final transition
|
|
162
|
+
* rather than on proof completion, the entries are still alive when the summary
|
|
163
|
+
* reads them.
|
|
164
|
+
*/
|
|
165
|
+
export type WaiterEntry = Waiter & {
|
|
166
|
+
outcome: WaiterOutcome;
|
|
167
|
+
obtained?: AssuranceBar;
|
|
168
|
+
required?: AssuranceBar;
|
|
169
|
+
/** Called exactly once. Idempotent: later calls are ignored. */
|
|
170
|
+
report(result: WaiterReport): void;
|
|
171
|
+
};
|
|
172
|
+
/** The proof's own outcome, which is NOT any operation's outcome. */
|
|
173
|
+
export type UpgradeProof = {
|
|
174
|
+
status: 'proven';
|
|
175
|
+
obtained: AssuranceBar;
|
|
176
|
+
} | {
|
|
177
|
+
status: 'failed';
|
|
178
|
+
reason: Error;
|
|
179
|
+
};
|
|
180
|
+
/** What a successful `elevate()` hands back to its caller. */
|
|
181
|
+
export type UpgradeElevation = {
|
|
182
|
+
/** This caller's own registered waiter. `report()` is its edge back. */
|
|
183
|
+
waiter: WaiterEntry;
|
|
184
|
+
/** The bar the proof reached, as far as the client can know it. */
|
|
185
|
+
obtained: AssuranceBar;
|
|
186
|
+
};
|
|
187
|
+
/** One row of the per-flight outcome summary. */
|
|
188
|
+
export type UpgradeWaiterSummary = {
|
|
189
|
+
url?: string;
|
|
190
|
+
method?: string;
|
|
191
|
+
outcome: WaiterOutcome;
|
|
192
|
+
obtained?: AssuranceBar;
|
|
193
|
+
required?: AssuranceBar;
|
|
194
|
+
};
|
|
195
|
+
/**
|
|
196
|
+
* The per-flight outcome summary: a pure function of `waiters` plus `proof`.
|
|
197
|
+
*
|
|
198
|
+
* `terminal` is what the prompt may report. The modal must NOT say "success"
|
|
199
|
+
* while any waiter is re-challenged — for AAL3 endpoints with TOTP-only users
|
|
200
|
+
* that is the NORMAL path, not an edge case, and a design cannot both claim the
|
|
201
|
+
* server is the authority and delete the path by which its second refusal
|
|
202
|
+
* reaches the user.
|
|
203
|
+
*/
|
|
204
|
+
export type UpgradeFlightSummary = {
|
|
205
|
+
proof: UpgradeProof;
|
|
206
|
+
waiters: UpgradeWaiterSummary[];
|
|
207
|
+
terminal: 'success' | 'insufficient' | 'failed';
|
|
208
|
+
/** The bars of the STRONGEST unmet waiter, so the message is honest. */
|
|
209
|
+
insufficient?: {
|
|
210
|
+
obtained: AssuranceBar;
|
|
211
|
+
required: AssuranceBar;
|
|
212
|
+
};
|
|
213
|
+
/** The proof failure, when the flight never produced a proof. */
|
|
214
|
+
error?: Error;
|
|
215
|
+
};
|
|
41
216
|
export type UpgradeControllerOptions = {
|
|
42
217
|
transport: UpgradeTransport;
|
|
43
218
|
prompt: UpgradePromptHandler;
|
|
44
219
|
platform: PlatformAdapter;
|
|
45
220
|
/** Defaults to the factors this SDK can complete. */
|
|
46
221
|
factors?: readonly UpgradeFactor[];
|
|
47
|
-
/** How many times the user may answer wrongly before the elevation gives up.
|
|
222
|
+
/** How many times the user may answer wrongly before the elevation gives up. */
|
|
48
223
|
maxAttempts?: number;
|
|
224
|
+
/**
|
|
225
|
+
* The bar the elevation actually reached, when the host can read it — from the
|
|
226
|
+
* `acr` claim of the freshly stored access token, for instance.
|
|
227
|
+
*
|
|
228
|
+
* Without it the controller reports the bar the ceremony was RANKED for, which
|
|
229
|
+
* is the honest client-side approximation: the server scores `acr` from the
|
|
230
|
+
* base session plus the verified factor and is the only authority, and a
|
|
231
|
+
* replay that is re-challenged corrects the approximation immediately.
|
|
232
|
+
*/
|
|
233
|
+
readObtainedBar?: () => AssuranceBar | undefined;
|
|
234
|
+
/**
|
|
235
|
+
* Called once per flight, in the settling transition, after every waiter has
|
|
236
|
+
* reported. This is how the open prompt is given its terminal state, and how
|
|
237
|
+
* the React layer gets exactly one terminal state per flight.
|
|
238
|
+
*/
|
|
239
|
+
onFlightSettled?: (summary: UpgradeFlightSummary) => void;
|
|
240
|
+
/**
|
|
241
|
+
* Liveness guard: how long to wait for waiters to report before settling the
|
|
242
|
+
* flight anyway. A waiter that never calls `report()` is an adapter bug, and
|
|
243
|
+
* without a bound it would hold `inFlight` open forever — so every later
|
|
244
|
+
* elevation would join a dead flight, which is a worse failure than settling
|
|
245
|
+
* early. Defaults to 30s.
|
|
246
|
+
*/
|
|
247
|
+
replaySettleTimeoutMs?: number;
|
|
49
248
|
};
|
|
50
|
-
/**
|
|
249
|
+
/**
|
|
250
|
+
* Raised when the user dismissed the upgrade rather than failing it.
|
|
251
|
+
*
|
|
252
|
+
* `cause` is carried for one case only: a prompt implementation that threw or
|
|
253
|
+
* rejected is treated as a failed COLLECTION and settles on the cancellation
|
|
254
|
+
* contract, because there is no answer and no proof — but the thrown error is
|
|
255
|
+
* the only evidence of an integration bug, so it travels rather than being
|
|
256
|
+
* swallowed.
|
|
257
|
+
*/
|
|
51
258
|
export declare class UpgradeCancelledError extends Error {
|
|
52
|
-
|
|
259
|
+
readonly code: "upgrade_cancelled";
|
|
260
|
+
/** Declared here rather than inherited: `Error.cause` is ES2022, this build targets ES2021 libs. */
|
|
261
|
+
readonly cause?: unknown;
|
|
262
|
+
constructor(cause?: unknown);
|
|
53
263
|
}
|
|
54
264
|
/** Raised when no registered factor can satisfy the challenge. */
|
|
55
265
|
export declare class UpgradeUnsatisfiableError extends Error {
|
|
266
|
+
readonly code: "upgrade_unsatisfiable";
|
|
56
267
|
constructor(message?: string);
|
|
57
268
|
}
|
|
269
|
+
/** The bar a challenge demands, in the shape the errors and the hook share. */
|
|
270
|
+
export declare const barOf: (challenge: UpgradeChallenge) => AssuranceBar;
|
|
271
|
+
/**
|
|
272
|
+
* Does `obtained` reach `required`?
|
|
273
|
+
*
|
|
274
|
+
* Only the `acr` axis is decidable client-side. A completed proof always
|
|
275
|
+
* satisfies a freshness demand, since it just happened, and a requirement with
|
|
276
|
+
* no level constrains nothing on this axis — so both of those are `true`.
|
|
277
|
+
*
|
|
278
|
+
* **An UNKNOWN obtained level does not meet a bar.** This is the one place in
|
|
279
|
+
* the feature where fail-open is wrong, and §13.4 revision 6 draws the line
|
|
280
|
+
* explicitly: fail-open governs *recovery* — availability, not logging the user
|
|
281
|
+
* out, not blocking the app — never the *granting of access*. Answering "meets"
|
|
282
|
+
* for a level we could not read is granting access on no evidence.
|
|
283
|
+
*
|
|
284
|
+
* It is NOT pre-judging satisfiability, and the difference is what §13.2 line
|
|
285
|
+
* 1599 and §13.4 line 2213 protect: nothing is refused outright, no elevation
|
|
286
|
+
* is declined, no factor is filtered. The SDK merely declines to ASSERT that a
|
|
287
|
+
* bar was reached; the server's replay re-challenge decides, and that surfaces
|
|
288
|
+
* as §13.3's `insufficient` with both bars attached. The only waiters this can
|
|
289
|
+
* settle locally are imperative ones that STATED a bar, which have no replay to
|
|
290
|
+
* ask the server about.
|
|
291
|
+
*/
|
|
292
|
+
export declare const barMeets: (obtained: AssuranceBar, required: AssuranceBar) => boolean;
|
|
293
|
+
/** Field-wise merge of two requirements: `acr` tightens up, freshness tightens down. */
|
|
294
|
+
export declare const mergeChallenges: (current: UpgradeChallenge, incoming: UpgradeChallenge) => UpgradeChallenge;
|
|
58
295
|
export declare class UpgradeController {
|
|
59
296
|
private readonly transport;
|
|
60
297
|
private readonly prompt;
|
|
@@ -62,33 +299,137 @@ export declare class UpgradeController {
|
|
|
62
299
|
private readonly registry;
|
|
63
300
|
private readonly available;
|
|
64
301
|
private readonly maxAttempts;
|
|
302
|
+
private readonly readObtainedBar?;
|
|
303
|
+
private readonly onFlightSettled?;
|
|
304
|
+
private readonly settleTimeoutMs;
|
|
305
|
+
/** The current state. Public for diagnostics and tests; nothing outside writes it. */
|
|
306
|
+
private state;
|
|
65
307
|
/**
|
|
66
308
|
* The elevation currently running.
|
|
67
309
|
*
|
|
68
|
-
*
|
|
69
|
-
*
|
|
70
|
-
*
|
|
71
|
-
*
|
|
310
|
+
* Single-flight is the STATE SET, not a flag: `elevate` returns the existing
|
|
311
|
+
* flight in every state but `idle`, and starts one only from `idle`. A screen
|
|
312
|
+
* that fires several protected requests at once gets several challenges, and
|
|
313
|
+
* every one of them must land on ONE prompt — otherwise the user sees a stack
|
|
314
|
+
* of modals and every extra elevation burns a challenge for nothing.
|
|
72
315
|
*/
|
|
73
316
|
private inFlight;
|
|
317
|
+
/** The merged requirement. Written at registration and at every join. */
|
|
318
|
+
private requirement;
|
|
319
|
+
/** Non-null from the moment the prompt reports a selection, and from nowhere else. */
|
|
320
|
+
private selected;
|
|
321
|
+
/** The requirement in force when the factor was selected — what the ceremony was ranked for. */
|
|
322
|
+
private rankedRequirement;
|
|
323
|
+
private registrations;
|
|
324
|
+
private proof;
|
|
325
|
+
/** Factors the SERVER refused for this user, dropped for the duration of this flight only. */
|
|
326
|
+
private pruned;
|
|
327
|
+
/** The request handed to the open prompt. Its `onRequirementRaised` slot is branch 2's channel. */
|
|
328
|
+
private openPrompt;
|
|
329
|
+
/** Set while `awaitReports` is waiting; called by the reporting path. See H7. */
|
|
330
|
+
private reportsSettled?;
|
|
74
331
|
constructor(options: UpgradeControllerOptions);
|
|
332
|
+
/** The current lifecycle state. Read-only. */
|
|
333
|
+
get currentState(): UpgradeFlightState;
|
|
334
|
+
/** True while a flight is open — from the first challenge until the last waiter settled. */
|
|
335
|
+
get active(): boolean;
|
|
75
336
|
/**
|
|
76
337
|
* Elevates the session so the challenged request can be retried.
|
|
77
338
|
*
|
|
78
|
-
*
|
|
79
|
-
*
|
|
80
|
-
*
|
|
339
|
+
* Every call registers a waiter, including the imperative one. Concurrent
|
|
340
|
+
* callers share one elevation and one prompt; the returned promise settles for
|
|
341
|
+
* THIS caller when the proof settles, and carries that caller's own waiter
|
|
342
|
+
* entry so it can report its replay's outcome back.
|
|
343
|
+
*
|
|
344
|
+
* It resolves when the server accepted a factor and recorded the elevation. It
|
|
345
|
+
* rejects for cancelled, exhausted, unsatisfiable, unavailable, session-changed
|
|
346
|
+
* and non-retryable transport failure — and, for a waiter whose own demanded
|
|
347
|
+
* bar the proof did not reach, with `UpgradeInsufficientError`.
|
|
348
|
+
*/
|
|
349
|
+
elevate(challenge: UpgradeChallenge, waiter?: Waiter): Promise<UpgradeElevation>;
|
|
350
|
+
/** Registers a waiter and the bar it arrived demanding. */
|
|
351
|
+
private register;
|
|
352
|
+
/**
|
|
353
|
+
* Folds a joining challenge into the running flight.
|
|
354
|
+
*
|
|
355
|
+
* The boundary is factor SELECTION. Before it, nothing is on the wire and
|
|
356
|
+
* there is no race, so a strictly stronger challenge folds in place: the
|
|
357
|
+
* requirement rises, the list is re-ranked for the higher bar, and the open
|
|
358
|
+
* prompt is told. After it, a ceremony is running — a `prepare` round trip, a
|
|
359
|
+
* TOTP commit, a WebAuthn ceremony holding the platform authenticator — and
|
|
360
|
+
* neither interrupting it nor starting a second concurrent flight is
|
|
361
|
+
* acceptable, so it completes at the bar it was ranked for and each waiter's
|
|
362
|
+
* own replay decides its own fate.
|
|
363
|
+
*/
|
|
364
|
+
private join;
|
|
365
|
+
/** The factors to offer right now: ranked for the merged bar, minus what the server refused. */
|
|
366
|
+
private offeredFactors;
|
|
367
|
+
/** The waiter's inbound edge. Idempotent — a second call is ignored. */
|
|
368
|
+
private receiveReport;
|
|
369
|
+
/** Resolves `awaitReports`' promise when the last outstanding report lands. */
|
|
370
|
+
private notifyReportsSettled;
|
|
371
|
+
/**
|
|
372
|
+
* The flight, start to finish.
|
|
373
|
+
*
|
|
374
|
+
* Nothing here clears state: clearing is one synchronous step at the very end,
|
|
375
|
+
* after the prompt has settled, so nothing can observe a half-cleared
|
|
376
|
+
* controller and no waiter can report into a flight the controller has
|
|
377
|
+
* forgotten.
|
|
378
|
+
*/
|
|
379
|
+
private runFlight;
|
|
380
|
+
/**
|
|
381
|
+
* Settles one waiter against the proof.
|
|
382
|
+
*
|
|
383
|
+
* An imperative waiter has no operation to replay, so it is classified here
|
|
384
|
+
* and nowhere else: with no stated bar it simply succeeded, and with a stated
|
|
385
|
+
* bar the proof is checked against it — which is what stops an AAL3 imperative
|
|
386
|
+
* call that joined an AAL2 flight from resolving as though it had been
|
|
387
|
+
* elevated to AAL3.
|
|
388
|
+
*
|
|
389
|
+
* A REQUEST waiter is never pre-judged here, and §13 is explicit about it
|
|
390
|
+
* twice: "a demand above 3 is never refused client-side — the SDK does not
|
|
391
|
+
* pre-judge satisfiability (§13.4) — it ranks factors for the highest bar it
|
|
392
|
+
* knows and lets the server's replay re-challenge decide, surfacing as
|
|
393
|
+
* §13.3's `insufficient`", and "never refuse outright on the default path".
|
|
394
|
+
* The reason is not conformance alone: the client-side comparison is the
|
|
395
|
+
* fragile half — ACR ordinals, the minimum-of-list rule — so refusing here
|
|
396
|
+
* can skip a replay the server would have accepted, and it loses the
|
|
397
|
+
* `elevated-insufficient` diagnostic the replay path raises. So the waiter is
|
|
398
|
+
* released whatever the bar comparison says; `handleChallenge` replays, reads
|
|
399
|
+
* the server's answer, and reports `insufficient` if that is what it is.
|
|
400
|
+
*/
|
|
401
|
+
private release;
|
|
402
|
+
/**
|
|
403
|
+
* Waits for every released waiter to report, under the liveness bound.
|
|
404
|
+
*
|
|
405
|
+
* Report-driven, not polled. The previous implementation re-armed a
|
|
406
|
+
* `setTimeout(…, 0)` until the last waiter reported, which burned a timer per
|
|
407
|
+
* event-loop turn for the whole replay window and made the state machine
|
|
408
|
+
* untestable under fake timers — `runAllTimers()` never terminates against a
|
|
409
|
+
* self-re-arming zero-delay timer. `receiveReport` is the one edge on which a
|
|
410
|
+
* waiter stops being pending, so it resolves this promise directly.
|
|
411
|
+
*
|
|
412
|
+
* The deadline stays: a waiter that never reports is an adapter bug, and
|
|
413
|
+
* settling it as `failed` is better than holding `inFlight` open forever,
|
|
414
|
+
* which would make every later elevation join a dead flight.
|
|
81
415
|
*/
|
|
82
|
-
|
|
83
|
-
|
|
416
|
+
private awaitReports;
|
|
417
|
+
/** The summary: a pure function of the waiters and the proof. */
|
|
418
|
+
private summarise;
|
|
84
419
|
/**
|
|
85
|
-
*
|
|
420
|
+
* Collects an answer and proves it, re-prompting on every recoverable failure.
|
|
86
421
|
*
|
|
87
|
-
* The prepare round is re-run on every attempt rather than cached: a
|
|
88
|
-
*
|
|
89
|
-
*
|
|
422
|
+
* The prepare round is re-run on every attempt rather than cached: a challenge
|
|
423
|
+
* is single-use and the server consumes it atomically, so a failed attempt has
|
|
424
|
+
* already spent it. Reusing the id would fail with
|
|
90
425
|
* `ErrorUpgradeChallengeInvalid` and look like a bad code to the user.
|
|
91
426
|
*/
|
|
427
|
+
private prove;
|
|
428
|
+
/** Opens (or re-opens) the prompt and wires the two prompt → controller edges. */
|
|
429
|
+
private collect;
|
|
430
|
+
/** Maps a proof failure to a terminal reason, or `undefined` when the user may retry. */
|
|
431
|
+
private classifyProofFailure;
|
|
432
|
+
/** Runs one full proof for a single factor. */
|
|
92
433
|
private proveAndCommit;
|
|
93
434
|
}
|
|
94
435
|
//# sourceMappingURL=controller.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"controller.d.ts","sourceRoot":"","sources":["../../../lib/upgrade/controller.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"controller.d.ts","sourceRoot":"","sources":["../../../lib/upgrade/controller.ts"],"names":[],"mappings":"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"}
|