@alxia/react-router 0.14.0 → 0.15.1
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/README.md +2 -2
- package/dist/session/flow-types.d.ts +24 -8
- package/dist/session/flow-types.d.ts.map +1 -1
- package/dist/session/index.js +34 -23
- package/dist/session/index.js.map +10 -10
- package/dist/session/land.d.ts +5 -3
- package/dist/session/land.d.ts.map +1 -1
- package/dist/session/link-land.d.ts +4 -2
- package/dist/session/link-land.d.ts.map +1 -1
- package/dist/session/link-token.d.ts +1 -1
- package/dist/session/link-token.d.ts.map +1 -1
- package/dist/session/link-types.d.ts +9 -5
- package/dist/session/link-types.d.ts.map +1 -1
- package/dist/session/links.d.ts +1 -1
- package/dist/session/links.d.ts.map +1 -1
- package/dist/session/second-factor.d.ts +1 -1
- package/dist/session/second-factor.d.ts.map +1 -1
- package/dist/session/sign-in.d.ts +3 -2
- package/dist/session/sign-in.d.ts.map +1 -1
- package/dist/session/step.d.ts +1 -1
- package/dist/session/step.d.ts.map +1 -1
- package/dist/session/viewer.d.ts +5 -4
- package/dist/session/viewer.d.ts.map +1 -1
- package/docs/guide/session.md +49 -2
- package/docs/troubleshooting.md +28 -0
- package/package.json +5 -5
package/README.md
CHANGED
|
@@ -1129,11 +1129,11 @@ From `@alxia/react-router/session` (`react-router` and `/errors`' files are its
|
|
|
1129
1129
|
| --- | --- |
|
|
1130
1130
|
| `sessionCookies({ session, challenge, secure, challengePath?, challengeSeconds? })`, `SessionCookies`, `SessionCookiesOptions`, `CookieSession` | the session and second-factor challenge cookies, `HttpOnly; SameSite=Lax`, `__Host-` and `__Secure-` when `secure` (required, never read from the environment): `commit(token, expiresAt)`, `read(request)`, `token(request)`, `destroy()`, `refreshed(session, expiresAt)` (a `Set-Cookie` only when the expiry moved), `commitChallenge`, `challengeOf`, `destroyChallenge`, `signInPageHeaders(deadSession)` and `names` (the cookie names as sent, prefixes included); a cookie written by an app's own code in `<expiry ms>.<token>` is read as it is |
|
|
1131
1131
|
| `serializeCookie(name, value, { maxAge, path?, secure })`, `readCookie(request, name)`, `secondsUntil(expiresAt, now?)`, `SerializeOptions` | the bytes `sessionCookies` writes, for the cookies of your own; a name that is not an RFC 6265 token throws a `TypeError` |
|
|
1132
|
-
| `signInFlow({ cookies, errors, json, revoke, attemptsLeft, paths?, clear?, unavailable?, codes?, endedParam? })`, `SignInFlow`, `SignInFlowOptions` | the server side of the sign-in pages, which also exposes what it was built with (`cookies`, `errors`, `json`, `attemptsLeft`, `unavailable(language)`, `clear()`) and `guard(request)`, the check an app's own POST step runs first: `nextParam`, `signInRedirect`, `landing`, `landReplacing` (revokes the session the browser held), `signInStep` (a same-site JSON post only; an API refusal as the form's message and status, an unreachable API as a 503), `readCodeInput`, `codeStep`, `codeRefusal` (with the tries left), `revokeSession`, `signOut` (JSON and same-origin only; it clears the challenge cookie too), `viewer` (`null` for a 401, `undefined` when unknown) and `requireViewer` (throws the sign-in redirect) |
|
|
1132
|
+
| `signInFlow({ cookies, errors, json, revoke, attemptsLeft, paths?, clear?, unavailable?, codes?, endedParam? })`, `SignInFlow`, `SignInFlowOptions` | the server side of the sign-in pages, which also exposes what it was built with (`cookies`, `errors`, `json`, `attemptsLeft`, `unavailable(language)`, `clear()`) and `guard(request)`, the check an app's own POST step runs first: `nextParam`, `signInRedirect`, `landing`, `landReplacing` (revokes the session the browser held), `signInStep` (a same-site JSON post only; an API refusal as the form's message and status, an unreachable API as a 503), `readCodeInput`, `codeStep`, `codeRefusal` (with the tries left), `revokeSession`, `signOut` (JSON and same-origin only; it clears the challenge cookie too), `viewer` (`null` for a 401, `undefined` when unknown) and `requireViewer` (throws the sign-in redirect); `revoke(token, request, context)` gets the caller context (`C`, inferred from the annotation of `revoke`'s third parameter) that `signOut`, `revokeSession`, `landReplacing` and `signInStep` take as `context` |
|
|
1133
1133
|
| `SignInAnswer`, `SignInFormError`, `SignInStayed`, `SignInCodes` | what a step answers (`signedIn`, `secondFactor` or `other`), a form step's answer when it stays on the page, and the `codes` option (`invalidCode`, `challengeOver`) |
|
|
1134
1134
|
| `stepUpSteps({ signIn, spent, locked?, wording?, codes? })`, `StepUpSteps`, `StepUpStepsOptions`, `StepUpCall` | a step-up for a route's action, reading the flow's `errors`, `json` and `attemptsLeft`, each step taking `{ request, language }` and refusing a post that is not a same-site JSON one: `request(run, { request, language })` answers the challenge and how it is confirmed (a throttled mail is `error: 'throttled'` with `retryAfter`), `confirm(run, { request, language, via? })` answers `error: 'invalid'` with the tries left (and `locked: true` for an app or recovery code with none left), `expired` or `failed`; `request` may answer `throttled` with `retryAfter`; and `isStepUpRequired(error)` |
|
|
1135
1135
|
| `StepUpCodes`, `StepUpStayed`, `INVALID_CODE`, `CHALLENGE_OVER`, `MAIL_THROTTLED`, `STEP_UP_REQUIRED`, `STEP_UP_OVER` | the `codes` option (`invalidCode`, `challengeOver`, `stepUpRequired`, `mailThrottled`), a step's answer that stays on the page, and janus's default codes, which an app can extend |
|
|
1136
|
-
| `authLinks({ signIn, codes?, wording?, start?, resetDone?, invalidState? })`, `AuthLinks`, `AuthLinksOptions`, `AuthLinksCodes` | the pages a mail's link opens, over the flow's cookies, errors and json: `linkArgs`, `spendToken(run, { request, language, path, states, fields? })`, `answerOf`, `landByLink
|
|
1136
|
+
| `authLinks({ signIn, codes?, wording?, start?, resetDone?, invalidState? })`, `AuthLinks`, `AuthLinksOptions`, `AuthLinksCodes` | the pages a mail's link opens, over the flow's cookies, errors and json: `linkArgs`, `spendToken(run, { request, language, path, states, fields? })`, `answerOf`, `landByLink` (`context?`), `afterReset(request, { context? })` (clears the session, the challenge and the flow's cookies), `requestLink(run, { request, language })` (a wait answers `throttled: true` and `retryAfter` when the API gives the seconds; `wording` words every refusal that is not a field's), `statePath` and `ended` |
|
|
1137
1137
|
| `Spent`, `SpendOptions`, `LinkStayed`, `RequestLinkAnswer` | their types |
|
|
1138
1138
|
| `TOKEN_PAGE_HEADERS` | `Referrer-Policy: no-referrer` and `X-Robots-Tag: noindex, nofollow`, for a page whose URL carries a token |
|
|
1139
1139
|
|
|
@@ -32,14 +32,19 @@ export interface SignInCodes {
|
|
|
32
32
|
/** The codes that end the challenge (`TOKEN_UNKNOWN`, `TOKEN_SPENT`, `TOKEN_EXPIRED`). */
|
|
33
33
|
readonly challengeOver?: readonly string[] | undefined;
|
|
34
34
|
}
|
|
35
|
-
export interface SignInFlowOptions<L extends string> {
|
|
35
|
+
export interface SignInFlowOptions<L extends string, C = undefined> {
|
|
36
36
|
readonly cookies: SessionCookies;
|
|
37
37
|
/** The app's `createApiErrors` instance. */
|
|
38
38
|
readonly errors: ApiErrors<L>;
|
|
39
39
|
/** The app's `jsonActions({ origin })`, so a body is read with the same CSRF check. */
|
|
40
40
|
readonly json: JsonActions;
|
|
41
|
-
/**
|
|
42
|
-
|
|
41
|
+
/**
|
|
42
|
+
* Ends a session at the API (the app's sign-out mutation); a failure is
|
|
43
|
+
* ignored. `context` is the caller context the step was given (`C`): the
|
|
44
|
+
* visitor's resolved address and language, say, which a bare `Request`
|
|
45
|
+
* cannot carry. `undefined` when the step got none.
|
|
46
|
+
*/
|
|
47
|
+
revoke(token: string, request: Request, context: C | undefined): Promise<unknown>;
|
|
43
48
|
/** A refused code's tries left, worded ("3 tries left"). */
|
|
44
49
|
attemptsLeft(count: number, language: L): string;
|
|
45
50
|
/** The sign-in pages' paths; `SIGN_IN_PATHS` by default. */
|
|
@@ -60,7 +65,7 @@ export interface SignInFlowOptions<L extends string> {
|
|
|
60
65
|
}
|
|
61
66
|
/** A form step's answer when it stays on the page: the form's message, with a status. */
|
|
62
67
|
export type SignInStayed<T extends object> = ReturnType<typeof data<SignInFormError<T>>>;
|
|
63
|
-
export interface SignInFlow<L extends string> {
|
|
68
|
+
export interface SignInFlow<L extends string, C = undefined> {
|
|
64
69
|
/** The cookies the flow was built with. */
|
|
65
70
|
readonly cookies: SessionCookies;
|
|
66
71
|
/** The `createApiErrors` instance the flow was built with. */
|
|
@@ -99,6 +104,8 @@ export interface SignInFlow<L extends string> {
|
|
|
99
104
|
landReplacing(answer: SignInAnswer, next: string, options: {
|
|
100
105
|
readonly request: Request;
|
|
101
106
|
readonly revokeOn?: readonly SignInStatus[] | undefined;
|
|
107
|
+
/** The caller context handed to `revoke`. */
|
|
108
|
+
readonly context?: C | undefined;
|
|
102
109
|
}): Promise<Response | null>;
|
|
103
110
|
/**
|
|
104
111
|
* Runs a sign-in step and redirects where its answer leads. An API
|
|
@@ -113,6 +120,8 @@ export interface SignInFlow<L extends string> {
|
|
|
113
120
|
readonly extra: T;
|
|
114
121
|
/** Answers an error before the default message: a redirect, a richer message. */
|
|
115
122
|
readonly refused?: ((error: ApiErrorLike) => Response | string | undefined) | undefined;
|
|
123
|
+
/** The caller context handed to `revoke` when the answer replaces a session. */
|
|
124
|
+
readonly context?: C | undefined;
|
|
116
125
|
}): Promise<Response | SignInStayed<T>>;
|
|
117
126
|
/**
|
|
118
127
|
* A refused code's message with the tries left; `undefined` when the
|
|
@@ -133,8 +142,13 @@ export interface SignInFlow<L extends string> {
|
|
|
133
142
|
confirm(challenge: string, code: string): Promise<SignInAnswer>;
|
|
134
143
|
recover(challenge: string, code: string): Promise<SignInAnswer>;
|
|
135
144
|
}): Promise<SignInAnswer>;
|
|
136
|
-
/**
|
|
137
|
-
|
|
145
|
+
/**
|
|
146
|
+
* Ends the request's session at the API; true when it carried one.
|
|
147
|
+
* `context` is handed to `revoke`.
|
|
148
|
+
*/
|
|
149
|
+
revokeSession(request: Request, options?: {
|
|
150
|
+
readonly context?: C | undefined;
|
|
151
|
+
}): Promise<boolean>;
|
|
138
152
|
/**
|
|
139
153
|
* Revokes the session, clears its cookies and redirects (to the sign-in
|
|
140
154
|
* page with `?signed-out=1` by default; `to` goes through `safeNext`). It
|
|
@@ -142,6 +156,8 @@ export interface SignInFlow<L extends string> {
|
|
|
142
156
|
*/
|
|
143
157
|
signOut(request: Request, options?: {
|
|
144
158
|
readonly to?: string | undefined;
|
|
159
|
+
/** The caller context handed to `revoke`. */
|
|
160
|
+
readonly context?: C | undefined;
|
|
145
161
|
}): Promise<Response>;
|
|
146
162
|
/**
|
|
147
163
|
* The viewer `read` finds for the session cookie's token (or `token`):
|
|
@@ -160,8 +176,8 @@ export interface SignInFlow<L extends string> {
|
|
|
160
176
|
}>;
|
|
161
177
|
}
|
|
162
178
|
/** The options once resolved, as the flow's functions take them. */
|
|
163
|
-
export interface FlowContext<L extends string> {
|
|
164
|
-
readonly options: SignInFlowOptions<L>;
|
|
179
|
+
export interface FlowContext<L extends string, C = undefined> {
|
|
180
|
+
readonly options: SignInFlowOptions<L, C>;
|
|
165
181
|
readonly paths: SignInPaths;
|
|
166
182
|
readonly invalidCode: string;
|
|
167
183
|
readonly challengeOver: readonly string[];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"flow-types.d.ts","sourceRoot":"","sources":["../../src/session/flow-types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,cAAc,CAAC;AACzC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AAC1D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAChD,OAAO,KAAK,EAAE,gBAAgB,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAE3E;;;;GAIG;AACH,MAAM,MAAM,YAAY,GACrB;IACA,QAAQ,CAAC,MAAM,EAAE,UAAU,CAAC;IAC5B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;CACjC,GACD;IACA,QAAQ,CAAC,MAAM,EAAE,cAAc,CAAC;IAChC,iEAAiE;IACjE,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI,GAAG,SAAS,CAAC;CACrD,GACD;IAAE,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAA;CAAE,CAAC;AAEhC,sEAAsE;AACtE,MAAM,MAAM,eAAe,CAAC,CAAC,GAAG,MAAM,IAAI,CAAC,GAAG;IAAE,SAAS,EAAE,MAAM,CAAA;CAAE,CAAC;AAEpE,kEAAkE;AAClE,MAAM,WAAW,WAAW;IAC3B,4DAA4D;IAC5D,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1C,0FAA0F;IAC1F,QAAQ,CAAC,aAAa,CAAC,EAAE,SAAS,MAAM,EAAE,GAAG,SAAS,CAAC;CACvD;AAED,MAAM,WAAW,iBAAiB,CAAC,CAAC,SAAS,MAAM;
|
|
1
|
+
{"version":3,"file":"flow-types.d.ts","sourceRoot":"","sources":["../../src/session/flow-types.ts"],"names":[],"mappings":"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"}
|
package/dist/session/index.js
CHANGED
|
@@ -212,13 +212,13 @@ import { data as data2, replace as replace2 } from "react-router";
|
|
|
212
212
|
|
|
213
213
|
// src/session/land.ts
|
|
214
214
|
import { redirect } from "react-router";
|
|
215
|
-
async function revokeSession({ options }, request) {
|
|
215
|
+
async function revokeSession({ options }, request, { context } = {}) {
|
|
216
216
|
samesitePost(options.json, request);
|
|
217
217
|
const token = options.cookies.token(request);
|
|
218
218
|
if (!token)
|
|
219
219
|
return false;
|
|
220
220
|
try {
|
|
221
|
-
await options.revoke(token, request);
|
|
221
|
+
await options.revoke(token, request, context);
|
|
222
222
|
} catch {}
|
|
223
223
|
return true;
|
|
224
224
|
}
|
|
@@ -243,19 +243,20 @@ function landing({ options, paths }, answer, next) {
|
|
|
243
243
|
}
|
|
244
244
|
return null;
|
|
245
245
|
}
|
|
246
|
-
async function landReplacing(
|
|
246
|
+
async function landReplacing(flow, answer, next, {
|
|
247
247
|
request,
|
|
248
|
-
revokeOn = ["signedIn"]
|
|
248
|
+
revokeOn = ["signedIn"],
|
|
249
|
+
context
|
|
249
250
|
}) {
|
|
250
|
-
samesitePost(
|
|
251
|
-
const landed = landing(
|
|
251
|
+
samesitePost(flow.options.json, request);
|
|
252
|
+
const landed = landing(flow, answer, next);
|
|
252
253
|
if (!landed || answer.status === "other")
|
|
253
254
|
return landed;
|
|
254
255
|
if (!revokeOn.includes(answer.status))
|
|
255
256
|
return landed;
|
|
256
|
-
const hadSession = await revokeSession(
|
|
257
|
+
const hadSession = await revokeSession(flow, request, { context });
|
|
257
258
|
if (hadSession && answer.status !== "signedIn") {
|
|
258
|
-
landed.headers.append("Set-Cookie",
|
|
259
|
+
landed.headers.append("Set-Cookie", flow.options.cookies.destroy());
|
|
259
260
|
}
|
|
260
261
|
return landed;
|
|
261
262
|
}
|
|
@@ -270,7 +271,12 @@ async function startOf({ start }, request, token) {
|
|
|
270
271
|
return HOME2;
|
|
271
272
|
}
|
|
272
273
|
}
|
|
273
|
-
async function landByLink(options, answer, {
|
|
274
|
+
async function landByLink(options, answer, {
|
|
275
|
+
request,
|
|
276
|
+
language,
|
|
277
|
+
other,
|
|
278
|
+
context
|
|
279
|
+
}) {
|
|
274
280
|
samesitePost(options.json, request);
|
|
275
281
|
if (answer.status === "other" && other) {
|
|
276
282
|
return stateRedirect(other.path, other.state);
|
|
@@ -278,7 +284,8 @@ async function landByLink(options, answer, { request, language, other }) {
|
|
|
278
284
|
const next = isLive(answer) ? await startOf(options, request, answer.token) : HOME2;
|
|
279
285
|
const landed = await options.signIn.landReplacing(answer, next, {
|
|
280
286
|
request,
|
|
281
|
-
revokeOn: ["signedIn", "secondFactor"]
|
|
287
|
+
revokeOn: ["signedIn", "secondFactor"],
|
|
288
|
+
context
|
|
282
289
|
});
|
|
283
290
|
if (!landed) {
|
|
284
291
|
return data2({ formError: options.signIn.unavailable(language) }, { status: 503 });
|
|
@@ -286,9 +293,9 @@ async function landByLink(options, answer, { request, language, other }) {
|
|
|
286
293
|
const location = landed.headers.get("Location") ?? HOME2;
|
|
287
294
|
return replace2(location, { headers: withTokenHeaders(landed.headers) });
|
|
288
295
|
}
|
|
289
|
-
async function afterReset(options, resetDone, request) {
|
|
296
|
+
async function afterReset(options, resetDone, request, { context } = {}) {
|
|
290
297
|
samesitePost(options.json, request);
|
|
291
|
-
await options.signIn.revokeSession(request);
|
|
298
|
+
await options.signIn.revokeSession(request, { context });
|
|
292
299
|
const headers = withTokenHeaders();
|
|
293
300
|
headers.append("Set-Cookie", options.cookies.destroy());
|
|
294
301
|
headers.append("Set-Cookie", options.cookies.destroyChallenge());
|
|
@@ -347,7 +354,7 @@ function authLinks(links) {
|
|
|
347
354
|
spendToken: (run, o) => spendToken(options, run, o),
|
|
348
355
|
answerOf,
|
|
349
356
|
landByLink: (answer, o) => landByLink(options, answer, o),
|
|
350
|
-
afterReset: (request) => afterReset(options, resetDone, request),
|
|
357
|
+
afterReset: (request, o) => afterReset(options, resetDone, request, o),
|
|
351
358
|
requestLink: (run, o) => requestLink(options, run, o)
|
|
352
359
|
};
|
|
353
360
|
}
|
|
@@ -386,17 +393,18 @@ function codeRefusal({ options, invalidCode, challengeOver }, error, language, w
|
|
|
386
393
|
return `${message} ${options.attemptsLeft(count, language)}`;
|
|
387
394
|
}
|
|
388
395
|
var unavailableMessage = ({ unavailable, errors }, language) => unavailable?.(language) ?? errors.unavailableMessage(language);
|
|
389
|
-
async function signInStep(
|
|
396
|
+
async function signInStep(flow, run, {
|
|
390
397
|
request,
|
|
391
398
|
language,
|
|
392
399
|
next,
|
|
393
400
|
extra,
|
|
394
|
-
refused
|
|
401
|
+
refused,
|
|
402
|
+
context
|
|
395
403
|
}) {
|
|
396
|
-
const { errors, json } =
|
|
404
|
+
const { errors, json } = flow.options;
|
|
397
405
|
samesitePost(json, request);
|
|
398
406
|
const stay = (formError, status) => data5({ ...extra, formError }, { status });
|
|
399
|
-
const unreachable = () => stay(unavailableMessage(
|
|
407
|
+
const unreachable = () => stay(unavailableMessage(flow.options, language), 503);
|
|
400
408
|
let answer;
|
|
401
409
|
try {
|
|
402
410
|
answer = await run();
|
|
@@ -411,7 +419,7 @@ async function signInStep(context, run, {
|
|
|
411
419
|
return own;
|
|
412
420
|
return stay(own ?? refusal.message, refusal.status);
|
|
413
421
|
}
|
|
414
|
-
return await landReplacing(
|
|
422
|
+
return await landReplacing(flow, answer, next, { request, context }) ?? unreachable();
|
|
415
423
|
}
|
|
416
424
|
|
|
417
425
|
// src/session/viewer.ts
|
|
@@ -454,10 +462,13 @@ async function requireViewer(context, request, read, expiresAt) {
|
|
|
454
462
|
headers: context.options.cookies.refreshed(session, expiresAt(found))
|
|
455
463
|
};
|
|
456
464
|
}
|
|
457
|
-
async function signOut(
|
|
458
|
-
|
|
465
|
+
async function signOut(flow, request, {
|
|
466
|
+
to,
|
|
467
|
+
context
|
|
468
|
+
} = {}) {
|
|
469
|
+
const { options, paths } = flow;
|
|
459
470
|
await options.json.readJson(request);
|
|
460
|
-
await revokeSession(
|
|
471
|
+
await revokeSession(flow, request, { context });
|
|
461
472
|
const headers = new Headers;
|
|
462
473
|
headers.append("Set-Cookie", options.cookies.destroy());
|
|
463
474
|
headers.append("Set-Cookie", options.cookies.destroyChallenge());
|
|
@@ -502,7 +513,7 @@ function signInFlow(options) {
|
|
|
502
513
|
codeRefusal: (error, language, whenNone) => codeRefusal(context, error, language, whenNone),
|
|
503
514
|
readCodeInput: (request) => readCodeInput(context, request),
|
|
504
515
|
codeStep,
|
|
505
|
-
revokeSession: (request) => revokeSession(context, request),
|
|
516
|
+
revokeSession: (request, o) => revokeSession(context, request, o),
|
|
506
517
|
signOut: (request, o) => signOut(context, request, o),
|
|
507
518
|
viewer: (request, read, token) => viewer(context, request, read, token),
|
|
508
519
|
requireViewer: (request, read, expiresAt) => requireViewer(context, request, read, expiresAt)
|
|
@@ -633,5 +644,5 @@ export {
|
|
|
633
644
|
stepUpSteps
|
|
634
645
|
};
|
|
635
646
|
|
|
636
|
-
//# debugId=
|
|
647
|
+
//# debugId=A957A7CA74458AAE64756E2164756E21
|
|
637
648
|
//# sourceMappingURL=index.js.map
|
|
@@ -5,19 +5,19 @@
|
|
|
5
5
|
"/** `@nxgt/janus`'s code for a wrong second-factor or step-up code. */\nexport const INVALID_CODE = 'CODE_INVALID';\n\n/** `@nxgt/janus`'s codes for a challenge that is gone: unknown, spent or expired. */\nexport const CHALLENGE_OVER = [\n\t'TOKEN_UNKNOWN',\n\t'TOKEN_SPENT',\n\t'TOKEN_EXPIRED',\n] as const;\n\n/** `@nxgt/janus`'s code for a mail the API holds back: a link or a code asked for too often. */\nexport const MAIL_THROTTLED = 'MAIL_THROTTLED';\n",
|
|
6
6
|
"/**\n * The bytes of a cookie, and the reading of one: `Name=v; Max-Age=n; Path=/;\n * HttpOnly[; Secure]; SameSite=Lax`, in this order, so a session set by an\n * app before it adopted this package is still read after, and the other way\n * round.\n */\n\nexport interface SerializeOptions {\n\t/** Seconds; floored, and never below 0 (0 removes the cookie, and so does a value that is not finite). */\n\treadonly maxAge: number;\n\t/** `/` by default. */\n\treadonly path?: string | undefined;\n\t/** Adds `Secure`. Required: it is never read from the environment. */\n\treadonly secure: boolean;\n}\n\n// RFC 6265 `token`: what a cookie name may be made of.\nconst TOKEN = /^[!#$%&'*+\\-.^_`|~0-9A-Za-z]+$/;\n// A path of the site: no `;` to open another attribute, no control character.\n// biome-ignore lint/suspicious/noControlCharactersInRegex: that is the point\nconst PATH = /^\\/[^;\\x00-\\x1f\\x7f]*$/;\n\n/** Whether `name` can name a cookie (an RFC 6265 token). */\nexport const isCookieName = (name: string): boolean => TOKEN.test(name);\n/** Whether `path` can be a cookie's `Path`: absolute, no `;`, no control character. */\nexport const isCookiePath = (path: string): boolean => PATH.test(path);\n\n/**\n * A `Set-Cookie` value: always `HttpOnly` and `SameSite=Lax`, `Secure` when\n * asked. A name or a path that could open another attribute throws a\n * `TypeError`; a `maxAge` that is not finite is 0.\n */\nexport function serializeCookie(\n\tname: string,\n\tvalue: string,\n\t{ maxAge, path = '/', secure }: SerializeOptions,\n): string {\n\tif (!isCookieName(name)) {\n\t\tthrow new TypeError(\n\t\t\t`serializeCookie(): the name ${JSON.stringify(name)} is not a cookie name (an RFC 6265 token).`,\n\t\t);\n\t}\n\tif (!isCookiePath(path)) {\n\t\tthrow new TypeError(\n\t\t\t`serializeCookie(): the path ${JSON.stringify(path)} is not a cookie path: it must start with \"/\" and hold no \";\" or control character.`,\n\t\t);\n\t}\n\treturn [\n\t\t`${name}=${encodeURIComponent(value)}`,\n\t\t`Max-Age=${Number.isFinite(maxAge) ? Math.max(0, Math.floor(maxAge)) : 0}`,\n\t\t`Path=${path}`,\n\t\t'HttpOnly',\n\t\t...(secure ? ['Secure'] : []),\n\t\t'SameSite=Lax',\n\t].join('; ');\n}\n\n/**\n * A cookie of the request: the first one of that name that is not empty,\n * decoded; `null` when there is none, or when it is not valid percent-encoding.\n */\nexport function readCookie(request: Request, name: string): string | null {\n\tfor (const pair of request.headers.get('cookie')?.split(';') ?? []) {\n\t\tconst index = pair.indexOf('=');\n\t\tif (index < 0 || pair.slice(0, index).trim() !== name) continue;\n\t\tconst raw = pair.slice(index + 1).trim();\n\t\tif (!raw) continue;\n\t\ttry {\n\t\t\treturn decodeURIComponent(raw);\n\t\t} catch {\n\t\t\treturn null;\n\t\t}\n\t}\n\treturn null;\n}\n\n/** An instant as milliseconds; 0 when it is not a date. */\nexport function millisOf(at: string | Date): number {\n\tconst time = typeof at === 'string' ? Date.parse(at) : at.getTime();\n\treturn Number.isNaN(time) ? 0 : time;\n}\n\n/** Seconds from `now` until `expiresAt`: 0 when past or unreadable. */\nexport function secondsUntil(\n\texpiresAt: string | Date,\n\tnow: number = Date.now(),\n): number {\n\tconst end = millisOf(expiresAt);\n\treturn end === 0 ? 0 : Math.max(0, (end - now) / 1000);\n}\n",
|
|
7
7
|
"import {\n\tisCookieName,\n\tisCookiePath,\n\tmillisOf,\n\treadCookie,\n\tsecondsUntil,\n\tserializeCookie,\n} from './serialize';\n\nexport interface SessionCookiesOptions {\n\t/**\n\t * The base name of the session cookie, e.g. `'my-app_session'`. With\n\t * `secure`, the cookie is named `__Host-my-app_session`.\n\t */\n\treadonly session: string;\n\t/**\n\t * The base name of the second-factor challenge's cookie. With `secure`, it\n\t * is named `__Secure-<name>`.\n\t */\n\treadonly challenge: string;\n\t/**\n\t * The app is served over HTTPS. Required, and never read from the\n\t * environment: pass `!import.meta.env.DEV`. `false` drops `Secure` and the\n\t * prefixes (Safari drops a `Secure` cookie over `http://localhost`).\n\t */\n\treadonly secure: boolean;\n\t/** Where the browser sends the challenge. `'/sign-in'` by default. */\n\treadonly challengePath?: string | undefined;\n\t/** The most the challenge lives. 300, `@nxgt/janus`'s lifetime, by default. */\n\treadonly challengeSeconds?: number | undefined;\n}\n\n/** What the session cookie holds: the token, and the expiry (ms) it was last set to. */\nexport interface CookieSession {\n\treadonly token: string;\n\treadonly expiresAt: number;\n}\n\nexport interface SessionCookies {\n\t/** The names as sent, prefixes included. */\n\treadonly names: { readonly session: string; readonly challenge: string };\n\t/** The session cookie of the request; `null` when absent or not `<ms>.<token>`. */\n\tread(request: Request): CookieSession | null;\n\t/** The session token of the request, or `null`. */\n\ttoken(request: Request): string | null;\n\t/** A `Set-Cookie` value keeping `token` until `expiresAt`. */\n\tcommit(token: string, expiresAt: string | Date, now?: number): string;\n\t/** A `Set-Cookie` value removing the session cookie. */\n\tdestroy(): string;\n\t/** The `Set-Cookie` header that keeps the cookie as long as the session lives; empty when the expiry did not move, or is absent or not a date. */\n\trefreshed(\n\t\tsession: CookieSession,\n\t\texpiresAt: string | Date | null,\n\t): HeadersInit;\n\t/** The challenge cookie of the request, or `null`. */\n\tchallengeOf(request: Request): string | null;\n\t/** A `Set-Cookie` value keeping the challenge for the code page. */\n\tcommitChallenge(\n\t\tchallenge: string,\n\t\texpiresAt?: string | Date | null,\n\t\tnow?: number,\n\t): string;\n\t/** A `Set-Cookie` value removing the challenge. */\n\tdestroyChallenge(): string;\n\t/**\n\t * What a signed-out visit of the sign-in page sets: the challenge cleared\n\t * blind (a client navigation fetches `/sign-in.data`, which the cookie's\n\t * path does not match, so the request never shows it), and a dead\n\t * session's cookie when `deadSession`.\n\t */\n\tsignInPageHeaders(deadSession: boolean): Headers;\n}\n\nconst PREFIX = /^__(?:Host|Secure)-/;\n\nfunction checkName(label: string, name: string): void {\n\tif (!isCookieName(name)) {\n\t\tthrow new TypeError(\n\t\t\t`sessionCookies(): the ${label} name ${JSON.stringify(name)} is not a cookie name (an RFC 6265 token: letters, digits and ! # $ % & ' * + - . ^ _ \\` | ~). Pass a name such as \"my-app_${label}\".`,\n\t\t);\n\t}\n\tif (PREFIX.test(name)) {\n\t\tthrow new TypeError(\n\t\t\t`sessionCookies(): the ${label} name ${JSON.stringify(name)} already carries a prefix. Pass the base name: the package adds __Host- (session) or __Secure- (challenge) when secure is true.`,\n\t\t);\n\t}\n}\n\n/** The cookies of a BFF's session and of its second-factor challenge. */\nexport function sessionCookies(options: SessionCookiesOptions): SessionCookies {\n\tconst {\n\t\tsecure,\n\t\tchallengePath = '/sign-in',\n\t\tchallengeSeconds = 5 * 60,\n\t} = options;\n\tcheckName('session', options.session);\n\tcheckName('challenge', options.challenge);\n\tif (!Number.isInteger(challengeSeconds) || challengeSeconds <= 0) {\n\t\tthrow new TypeError(\n\t\t\t`sessionCookies(): challengeSeconds must be a positive integer; got ${String(challengeSeconds)}.`,\n\t\t);\n\t}\n\tif (!isCookiePath(challengePath)) {\n\t\tthrow new TypeError(\n\t\t\t`sessionCookies(): challengePath must start with \"/\" and hold no \";\" or control character; got ${JSON.stringify(challengePath)}.`,\n\t\t);\n\t}\n\t// `__Host-` holds only with `Path=/` and no `Domain`: the session cookie\n\t// sets neither option.\n\tconst session = secure ? `__Host-${options.session}` : options.session;\n\tconst challenge = secure\n\t\t? `__Secure-${options.challenge}`\n\t\t: options.challenge;\n\tconst clear = (name: string, path?: string) =>\n\t\tserializeCookie(name, '', { maxAge: 0, secure, ...(path && { path }) });\n\n\tconst read = (request: Request): CookieSession | null => {\n\t\tconst value = readCookie(request, session);\n\t\tconst dot = value?.indexOf('.') ?? -1;\n\t\tif (!value || dot < 1) return null;\n\t\tconst digits = value.slice(0, dot);\n\t\tconst expiresAt = Number(digits);\n\t\tconst token = value.slice(dot + 1);\n\t\treturn token && /^\\d+$/.test(digits) && Number.isSafeInteger(expiresAt)\n\t\t\t? { token, expiresAt }\n\t\t\t: null;\n\t};\n\t// The value is `<expiry ms>.<token>`: the expiry only tells a later request\n\t// whether the API moved it, so the cookie is set again then. It is never\n\t// trusted: the API alone decides whether a session is valid.\n\tconst commit = (token: string, expiresAt: string | Date, now?: number) =>\n\t\tserializeCookie(session, `${millisOf(expiresAt)}.${token}`, {\n\t\t\tmaxAge: secondsUntil(expiresAt, now),\n\t\t\tsecure,\n\t\t});\n\n\treturn {\n\t\tnames: { session, challenge },\n\t\tread,\n\t\ttoken: (request) => read(request)?.token ?? null,\n\t\tcommit,\n\t\tdestroy: () => clear(session),\n\t\trefreshed(current, expiresAt) {\n\t\t\t// An expiry that is not a date is garbage, not an end: keep the cookie.\n\t\t\tconst end = expiresAt ? millisOf(expiresAt) : 0;\n\t\t\tif (!expiresAt || end === 0 || end === current.expiresAt) return {};\n\t\t\treturn { 'Set-Cookie': commit(current.token, expiresAt) };\n\t\t},\n\t\tchallengeOf: (request) => readCookie(request, challenge),\n\t\tcommitChallenge: (value, expiresAt, now) =>\n\t\t\tserializeCookie(challenge, value, {\n\t\t\t\tmaxAge: Math.min(\n\t\t\t\t\tchallengeSeconds,\n\t\t\t\t\texpiresAt ? secondsUntil(expiresAt, now) : challengeSeconds,\n\t\t\t\t),\n\t\t\t\tpath: challengePath,\n\t\t\t\tsecure,\n\t\t\t}),\n\t\tdestroyChallenge: () => clear(challenge, challengePath),\n\t\tsignInPageHeaders(deadSession) {\n\t\t\tconst headers = new Headers();\n\t\t\theaders.append('Set-Cookie', clear(challenge, challengePath));\n\t\t\tif (deadSession) headers.append('Set-Cookie', clear(session));\n\t\t\treturn headers;\n\t\t},\n\t};\n}\n",
|
|
8
|
-
"import { data, replace } from 'react-router';\nimport { isApiErrorLike } from '../errors/api-error';\nimport type { FormErrors } from '../errors/form-errors';\nimport type { InferOutput, StandardSchemaV1 } from '../errors/standard-schema';\nimport { samesitePost } from './guard';\nimport type {\n\tLinkStayed,\n\tLinksContext,\n\tSpendOptions,\n\tSpent,\n} from './link-types';\nimport { sitePath } from './model/site-path';\n\n/**\n * The response headers of a page a mail's link opens, whose URL carries a\n * token: no `Referer` sends it to another site (its fonts included), and\n * no search engine keeps it.\n */\nexport const TOKEN_PAGE_HEADERS = {\n\t'Referrer-Policy': 'no-referrer',\n\t'X-Robots-Tag': 'noindex, nofollow',\n} as const;\n\n/** A token page's path with no query or fragment, or a `TypeError` naming `call`. */\nconst pathOf = (path: string, call: string): string =>\n\tsitePath(path, { label: `${call}: a token page's path` });\n\nexport const statePath = (path: string, state: string): string =>\n\t`${pathOf(path, 'statePath()')}?${new URLSearchParams({ state })}`;\n\n/** `headers`, with the token page's own on top. */\nexport function withTokenHeaders(headers?: HeadersInit): Headers {\n\tconst merged = new Headers(headers);\n\tfor (const [name, value] of Object.entries(TOKEN_PAGE_HEADERS)) {\n\t\tmerged.set(name, value);\n\t}\n\treturn merged;\n}\n\n/** A redirect replacing the current history entry: the token's, so Back never opens it again. */\nexport const stateRedirect = (\n\tpath: string,\n\tstate: string,\n\theaders?: HeadersInit,\n): Response =>\n\treplace(statePath(path, state), { headers: withTokenHeaders(headers) });\n\nconst isTokenIssue = (issue: {\n\treadonly path?:\n\t\t| readonly (PropertyKey | { readonly key: PropertyKey })[]\n\t\t| undefined;\n}) => {\n\tconst first = issue.path?.[0];\n\treturn (typeof first === 'object' ? first.key : first) === 'token';\n};\n\nexport async function linkArgs<S extends StandardSchemaV1>(\n\t{ json }: Pick<LinksContext<string>, 'json'>,\n\tinvalidState: string,\n\tschema: S,\n\trequest: Request,\n\tpath: string,\n): Promise<InferOutput<S>> {\n\tsamesitePost(json, request);\n\tpathOf(path, 'linkArgs()');\n\tconst body = await json.readJson(request);\n\tconst result = await schema['~standard'].validate(body);\n\tif (!result.issues) return result.value as InferOutput<S>;\n\t// A token the API would refuse anyway (a link cut or padded on its way)\n\t// ends as an unknown one does; anything else is a bad request.\n\tif (result.issues.some(isTokenIssue)) {\n\t\tthrow stateRedirect(path, invalidState);\n\t}\n\tthrow data('Bad link request', { status: 400 });\n}\n\nexport async function spendToken<L extends string, T>(\n\toptions: LinksContext<L>,\n\trun: () => Promise<T>,\n\t{ request, language, path, states, fields: shown = [] }: SpendOptions<L>,\n): Promise<Spent<T>> {\n\tconst { errors } = options;\n\tsamesitePost(options.json, request);\n\tpathOf(path, 'spendToken()');\n\ttry {\n\t\treturn { ok: true, value: await run() };\n\t} catch (error) {\n\t\tconst refusal = errors.apiRefusal(error, language);\n\t\tif (!refusal) throw error;\n\t\tconst { message, status } = refusal;\n\t\tif (isApiErrorLike(error)) {\n\t\t\tconst state = error.code ? states[error.code] : undefined;\n\t\t\tif (state) return { ok: false, response: stateRedirect(path, state) };\n\t\t\tconst fields = errors.fieldErrors(error, language);\n\t\t\tconst named = Object.keys(fields);\n\t\t\t// A refusal under a field the page does not show would say nothing.\n\t\t\tif (named.length && named.every((field) => shown.includes(field))) {\n\t\t\t\treturn { ok: false, refusal: { fieldErrors: fields }, status };\n\t\t\t}\n\t\t}\n\t\treturn { ok: false, refusal: { formError: message }, status };\n\t}\n}\n\nexport const answerOf = (\n\tspent: Exclude<Spent<unknown>, { ok: true }>,\n): Response | LinkStayed =>\n\t'response' in spent\n\t\t? spent.response\n\t\t: data<FormErrors>(spent.refusal, { status: spent.status });\n",
|
|
8
|
+
"import { data, replace } from 'react-router';\nimport { isApiErrorLike } from '../errors/api-error';\nimport type { FormErrors } from '../errors/form-errors';\nimport type { InferOutput, StandardSchemaV1 } from '../errors/standard-schema';\nimport { samesitePost } from './guard';\nimport type {\n\tLinkStayed,\n\tLinksContext,\n\tSpendOptions,\n\tSpent,\n} from './link-types';\nimport { sitePath } from './model/site-path';\n\n/**\n * The response headers of a page a mail's link opens, whose URL carries a\n * token: no `Referer` sends it to another site (its fonts included), and\n * no search engine keeps it.\n */\nexport const TOKEN_PAGE_HEADERS = {\n\t'Referrer-Policy': 'no-referrer',\n\t'X-Robots-Tag': 'noindex, nofollow',\n} as const;\n\n/** A token page's path with no query or fragment, or a `TypeError` naming `call`. */\nconst pathOf = (path: string, call: string): string =>\n\tsitePath(path, { label: `${call}: a token page's path` });\n\nexport const statePath = (path: string, state: string): string =>\n\t`${pathOf(path, 'statePath()')}?${new URLSearchParams({ state })}`;\n\n/** `headers`, with the token page's own on top. */\nexport function withTokenHeaders(headers?: HeadersInit): Headers {\n\tconst merged = new Headers(headers);\n\tfor (const [name, value] of Object.entries(TOKEN_PAGE_HEADERS)) {\n\t\tmerged.set(name, value);\n\t}\n\treturn merged;\n}\n\n/** A redirect replacing the current history entry: the token's, so Back never opens it again. */\nexport const stateRedirect = (\n\tpath: string,\n\tstate: string,\n\theaders?: HeadersInit,\n): Response =>\n\treplace(statePath(path, state), { headers: withTokenHeaders(headers) });\n\nconst isTokenIssue = (issue: {\n\treadonly path?:\n\t\t| readonly (PropertyKey | { readonly key: PropertyKey })[]\n\t\t| undefined;\n}) => {\n\tconst first = issue.path?.[0];\n\treturn (typeof first === 'object' ? first.key : first) === 'token';\n};\n\nexport async function linkArgs<S extends StandardSchemaV1>(\n\t{ json }: Pick<LinksContext<string>, 'json'>,\n\tinvalidState: string,\n\tschema: S,\n\trequest: Request,\n\tpath: string,\n): Promise<InferOutput<S>> {\n\tsamesitePost(json, request);\n\tpathOf(path, 'linkArgs()');\n\tconst body = await json.readJson(request);\n\tconst result = await schema['~standard'].validate(body);\n\tif (!result.issues) return result.value as InferOutput<S>;\n\t// A token the API would refuse anyway (a link cut or padded on its way)\n\t// ends as an unknown one does; anything else is a bad request.\n\tif (result.issues.some(isTokenIssue)) {\n\t\tthrow stateRedirect(path, invalidState);\n\t}\n\tthrow data('Bad link request', { status: 400 });\n}\n\nexport async function spendToken<L extends string, C, T>(\n\toptions: LinksContext<L, C>,\n\trun: () => Promise<T>,\n\t{ request, language, path, states, fields: shown = [] }: SpendOptions<L>,\n): Promise<Spent<T>> {\n\tconst { errors } = options;\n\tsamesitePost(options.json, request);\n\tpathOf(path, 'spendToken()');\n\ttry {\n\t\treturn { ok: true, value: await run() };\n\t} catch (error) {\n\t\tconst refusal = errors.apiRefusal(error, language);\n\t\tif (!refusal) throw error;\n\t\tconst { message, status } = refusal;\n\t\tif (isApiErrorLike(error)) {\n\t\t\tconst state = error.code ? states[error.code] : undefined;\n\t\t\tif (state) return { ok: false, response: stateRedirect(path, state) };\n\t\t\tconst fields = errors.fieldErrors(error, language);\n\t\t\tconst named = Object.keys(fields);\n\t\t\t// A refusal under a field the page does not show would say nothing.\n\t\t\tif (named.length && named.every((field) => shown.includes(field))) {\n\t\t\t\treturn { ok: false, refusal: { fieldErrors: fields }, status };\n\t\t\t}\n\t\t}\n\t\treturn { ok: false, refusal: { formError: message }, status };\n\t}\n}\n\nexport const answerOf = (\n\tspent: Exclude<Spent<unknown>, { ok: true }>,\n): Response | LinkStayed =>\n\t'response' in spent\n\t\t? spent.response\n\t\t: data<FormErrors>(spent.refusal, { status: spent.status });\n",
|
|
9
9
|
"import type { JsonActions } from '../errors/json-actions';\nimport { refuseCrossSite } from '../errors/same-site';\n\n/**\n * What every step that signs in, out, spends or mails runs first: a native\n * form post (before hydration) goes back to its page, which then posts JSON\n * (`json.requireJsonPost`), and a request from another site is a 403 even\n * when it is JSON (the `Origin` and `Sec-Fetch-Site` rule of `readJson`). It\n * reads headers only, never the body.\n */\nexport function samesitePost(json: JsonActions, request: Request): void {\n\tjson.requireJsonPost(request);\n\trefuseCrossSite(json, request);\n}\n",
|
|
10
|
-
"import { data } from 'react-router';\nimport {\n\ttype ApiErrorLike,\n\tisApiErrorLike,\n\tretryAfterOf,\n} from '../errors/api-error';\nimport { MAIL_THROTTLED } from './codes';\nimport { samesitePost } from './guard';\nimport { afterReset, landByLink } from './link-land';\nimport {\n\tanswerOf,\n\tlinkArgs,\n\tspendToken,\n\tstatePath,\n\tstateRedirect,\n} from './link-token';\nimport type {\n\tAuthLinks,\n\tAuthLinksOptions,\n\tLinksContext,\n\tRequestLinkAnswer,\n} from './link-types';\nimport { sitePath } from './model/site-path';\n\nconst RESET_DONE = '/sign-in?password-reset=1';\n\n/**\n * Requests a mail's link with `run`. The API answers true whether or not\n * the e-mail holds an account, and so does the page: the same \"sent\" for\n * everyone. A wait or an unreachable API is the form's message; a malformed\n * address is the `email` field's.\n */\n/** The refusal's message: the app's own wording, else `errors`'. */\nconst worded = <L extends string>(\n\t{ wording, errors }: LinksContext<L>,\n\terror: ApiErrorLike,\n\tlanguage: L,\n): string =>\n\twording?.(error, language) ?? errors.apiErrorMessage(error, language);\n\nasync function requestLink<L extends string>(\n\toptions: LinksContext<L>,\n\trun: () => Promise<unknown>,\n\t{ request, language }: Parameters<AuthLinks<L>['requestLink']>[1],\n) {\n\tconst { errors } = options;\n\tsamesitePost(options.json, request);\n\ttry {\n\t\tawait run();\n\t\treturn data<RequestLinkAnswer>({ sent: true });\n\t} catch (error) {\n\t\tconst api = isApiErrorLike(error);\n\t\tif (\n\t\t\tapi &&\n\t\t\terror.code === (options.codes?.mailThrottled ?? MAIL_THROTTLED)\n\t\t) {\n\t\t\tconst retryAfter = retryAfterOf(error);\n\t\t\treturn data<RequestLinkAnswer>(\n\t\t\t\t{\n\t\t\t\t\tformError: worded(options, error, language),\n\t\t\t\t\tthrottled: true,\n\t\t\t\t\t...(retryAfter === undefined ? {} : { retryAfter }),\n\t\t\t\t},\n\t\t\t\t{ status: 429 },\n\t\t\t);\n\t\t}\n\t\tif (api) {\n\t\t\tconst email = errors.fieldErrors(error, language)['email'];\n\t\t\tif (email) {\n\t\t\t\treturn data<RequestLinkAnswer>(\n\t\t\t\t\t{ fieldErrors: { email } },\n\t\t\t\t\t{ status: error.status },\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\t\tconst refusal = errors.apiRefusal(error, language);\n\t\tif (!refusal) throw error;\n\t\treturn data<RequestLinkAnswer>(\n\t\t\t{ formError: api ? worded(options, error, language) : refusal.message },\n\t\t\t{ status: refusal.status },\n\t\t);\n\t}\n}\n\n/**\n * The pages a mail's link opens (a magic link, a password reset, an\n * invitation): the token page's end states, the arguments it posts, the\n * token spent against the API, the sign-in it leads to (replacing the\n * token's history entry), and the request for a link. Built once, beside the\n * app's `signInFlow`.\n *\n * @example\n * export const links = authLinks({ signIn });\n * // loader: return data({ ... }, { headers: TOKEN_PAGE_HEADERS });\n */\nexport function authLinks<L extends string>(\n\tlinks: AuthLinksOptions<L>,\n): AuthLinks<L> {\n\tconst { signIn } = links;\n\tconst options: LinksContext<L> = {\n\t\t...links,\n\t\tcookies: signIn.cookies,\n\t\terrors: signIn.errors,\n\t\tjson: signIn.json,\n\t};\n\tconst resetDone = options.resetDone ?? RESET_DONE;\n\tsitePath(resetDone, { label: 'authLinks(): resetDone', query: true });\n\tconst invalid = options.invalidState ?? 'invalid';\n\treturn {\n\t\tstatePath,\n\t\tended: stateRedirect,\n\t\tlinkArgs: (schema, request, path) =>\n\t\t\tlinkArgs(options, invalid, schema, request, path),\n\t\tspendToken: (run, o) => spendToken(options, run, o),\n\t\tanswerOf,\n\t\tlandByLink: (answer, o) => landByLink(options, answer, o),\n\t\tafterReset: (request) => afterReset(options, resetDone, request),\n\t\trequestLink: (run, o) => requestLink(options, run, o),\n\t};\n}\n",
|
|
11
|
-
"import { data, replace } from 'react-router';\nimport type { FormErrors } from '../errors/form-errors';\nimport type { SignInAnswer } from './flow-types';\nimport { samesitePost } from './guard';\nimport { isLive } from './land';\nimport { stateRedirect, withTokenHeaders } from './link-token';\nimport type { AuthLinks, LinkStayed, LinksContext } from './link-types';\nimport { HOME, safeNext } from './model';\n\n/** Where a new session starts: the app's start page, else `/`; never an unsafe path. */\nasync function startOf<L extends string>(\n\t{ start }: LinksContext<L>,\n\trequest: Request,\n\ttoken: string,\n): Promise<string> {\n\tif (!start) return HOME;\n\ttry {\n\t\treturn safeNext(await start(token, request));\n\t} catch {\n\t\t// The token is spent: a failed lookup must not fail the sign-in.\n\t\treturn HOME;\n\t}\n}\n\n/**\n * Where a link's sign-in leads, replacing the token's history entry. Signed\n * in: the start page, with the new session's cookie; a second factor: its\n * page. Either way the session the browser held, whoever's, is revoked\n * first: one browser holds one session. Only a JSON post goes on, before\n * anything is revoked or landed.\n */\nexport async function landByLink<L extends string>(\n\toptions: LinksContext<L>,\n\tanswer: SignInAnswer,\n\t{
|
|
12
|
-
"import { redirect } from 'react-router';\nimport type { FlowContext, SignInAnswer, SignInFlow } from './flow-types';\nimport { samesitePost } from './guard';\nimport { safeNext, secondFactorPath } from './model';\nimport { millisOf } from './serialize';\n\n/**\n * Ends the request's session at the API, when it carries one; a failure is\n * ignored, the caller clears the cookie anyway. True when there was one.\n */\nexport async function revokeSession<L extends string>(\n\t{ options }: FlowContext<L>,\n\trequest: Request,\n): Promise<boolean> {\n\tsamesitePost(options.json, request);\n\tconst token = options.cookies.token(request);\n\tif (!token) return false;\n\ttry {\n\t\tawait options.revoke(token, request);\n\t} catch {\n\t\t// The old session ends when it expires.\n\t}\n\treturn true;\n}\n\n/** The condition a session opens on: a signed-in answer with a token and an end still to come. */\nexport const isLive = (\n\tanswer: SignInAnswer,\n): answer is Extract<SignInAnswer, { status: 'signedIn' }> =>\n\tanswer.status === 'signedIn' &&\n\tBoolean(answer.token) &&\n\tBoolean(answer.expiresAt) &&\n\tmillisOf(answer.expiresAt) > Date.now();\n\n/**\n * Where a sign-in's answer leads: signed in, to `next` with the session\n * cookie (and the challenge cleared); a second factor, to its page with the\n * challenge in its own cookie, never in the URL. Any other answer, or a\n * signed-in one with no token or an end that is missing, unreadable or past,\n * opens no session: `null`, the caller shows its own state.\n */\nexport function landing<L extends string>(\n\t{ options, paths }: FlowContext<L>,\n\tanswer: SignInAnswer,\n\tnext: string,\n): Response | null {\n\tconst { cookies } = options;\n\t// A session with no readable or past end would be set and dead at once.\n\tif (isLive(answer)) {\n\t\tconst headers = new Headers();\n\t\theaders.append(\n\t\t\t'Set-Cookie',\n\t\t\tcookies.commit(answer.token, answer.expiresAt),\n\t\t);\n\t\theaders.append('Set-Cookie', cookies.destroyChallenge());\n\t\tfor (const cookie of options.clear?.() ?? []) {\n\t\t\theaders.append('Set-Cookie', cookie);\n\t\t}\n\t\treturn redirect(safeNext(next), { headers });\n\t}\n\tif (answer.status === 'secondFactor' && answer.challenge) {\n\t\treturn redirect(secondFactorPath(next, paths.secondFactor), {\n\t\t\theaders: {\n\t\t\t\t'Set-Cookie': cookies.commitChallenge(\n\t\t\t\t\tanswer.challenge,\n\t\t\t\t\tanswer.expiresAt,\n\t\t\t\t),\n\t\t\t},\n\t\t});\n\t}\n\treturn null;\n}\n\n/**\n * `landing`, ending the session the browser held first: one browser holds\n * one session, so the one its cookie names, whoever's, is revoked when an\n * answer in `revokeOn` lands. A signed-in answer's own cookie replaces the\n * old one; any other answer clears it. A failed revoke is ignored.\n */\nexport async function landReplacing<L extends string>(\n\
|
|
13
|
-
"import { data } from 'react-router';\nimport type { FlowContext, SignInAnswer, SignInFlow } from './flow-types';\nimport { samesitePost } from './guard';\nimport type { SecondFactorKind } from './model';\n\nconst MAX_CODE = 40;\n\nfunction isMethod(value: unknown): value is SecondFactorKind {\n\treturn value === 'code' || value === 'recovery';\n}\n\n/**\n * The code a second-factor page posted, with how it is given\n * (`{ kind: 'code' | 'recovery', code }`, the code at most 40 characters). A\n * native form post goes back to its page, which then posts JSON; a\n * cross-site post is a 403; a body of another shape is a 400.\n */\nexport async function readCodeInput<L extends string>(\n\t{ options }: FlowContext<L>,\n\trequest: Request,\n): Promise<{ kind: SecondFactorKind; code: string }> {\n\tsamesitePost(options.json, request);\n\tconst body = (await options.json.readJson(request)) as {\n\t\tkind?: unknown;\n\t\tcode?: unknown;\n\t} | null;\n\tconst { kind, code } = body ?? {};\n\tif (!isMethod(kind) || typeof code !== 'string' || code.length > MAX_CODE) {\n\t\tthrow data('Bad code request', { status: 400 });\n\t}\n\treturn { kind, code };\n}\n\n/**\n * The code against the sign-in's challenge: an issued code (an app's, or\n * the one mailed) is confirmed, a recovery code is recovered. Either\n * answers like a sign-in.\n */\nexport function codeStep(\n\tinput: { readonly kind: SecondFactorKind; readonly code: string },\n\tchallenge: string,\n\tsteps: Parameters<SignInFlow<string>['codeStep']>[2],\n): Promise<SignInAnswer> {\n\treturn input.kind === 'code'\n\t\t? steps.confirm(challenge, input.code)\n\t\t: steps.recover(challenge, input.code);\n}\n",
|
|
14
|
-
"import { data } from 'react-router';\nimport {\n\ttype ApiErrorLike,\n\tattemptsLeftOf,\n\tisApiErrorLike,\n} from '../errors/api-error';\nimport type {\n\tCodeWording,\n\tFlowContext,\n\tSignInAnswer,\n\tSignInFlow,\n\tSignInFlowOptions,\n\tSignInFormError,\n\tSignInStayed,\n} from './flow-types';\nimport { samesitePost } from './guard';\nimport { landReplacing } from './land';\n\n/**\n * A refused code's message, with the tries left when the API counts them;\n * `undefined` when the challenge is over (gone, or no try left and no\n * `whenNone` to say instead) and the user must sign in again.\n */\nexport function codeRefusal<L extends string>(\n\t{ options, invalidCode, challengeOver }: CodeWording<L>,\n\terror: ApiErrorLike,\n\tlanguage: L,\n\twhenNone?: string,\n): string | undefined {\n\tif (error.code !== invalidCode) {\n\t\treturn error.code && challengeOver.includes(error.code)\n\t\t\t? undefined\n\t\t\t: options.errors.apiErrorMessage(error, language);\n\t}\n\tconst message = options.errors.apiErrorMessage(error, language);\n\tconst count = attemptsLeftOf(error);\n\tif (count === undefined) return message;\n\tif (count === 0) {\n\t\treturn whenNone === undefined ? undefined : `${message} ${whenNone}`;\n\t}\n\treturn `${message} ${options.attemptsLeft(count, language)}`;\n}\n\n/** What a sign-in says when the API cannot be reached. */\nexport const unavailableMessage = <L extends string>(\n\t{ unavailable, errors }: Pick<SignInFlowOptions<L>, 'unavailable' | 'errors'>,\n\tlanguage: L,\n): string => unavailable?.(language) ?? errors.unavailableMessage(language);\n\n/**\n * Runs a sign-in step and redirects where its answer leads, a new session\n * replacing the browser's (`landReplacing`). A refusal is the form's\n * message (a wrong password, a lock with its delay); an unreachable API\n * says `unavailable`, with a 503.\n */\nexport async function signInStep<L extends string, T extends object>(\n\
|
|
15
|
-
"import { redirect } from 'react-router';\nimport { isApiErrorLike } from '../errors/api-error';\nimport { nextOf } from '../errors/page-path';\nimport { routeError } from '../errors/route-error';\nimport type { FlowContext } from './flow-types';\nimport { revokeSession } from './land';\nimport { safeNext, signInPath } from './model';\n\n/** The `next` a sign-in page was opened with, safe to redirect to. */\nexport const nextParam = (request: Request): string =>\n\tsafeNext(new URL(request.url).searchParams.get('next'));\n\n/**\n * The sign-in page, coming back to this request's page afterwards, with the\n * session cookie cleared: what a page answers without a (live) session. A\n * request that carried a session cookie says it ended when `endedParam` is\n * set.\n */\nexport function signInRedirect<L extends string>(\n\t{ options, paths }: FlowContext<L>,\n\trequest: Request,\n): Response {\n\tconst { endedParam, cookies } = options;\n\tconst query =\n\t\tendedParam && cookies.read(request) ? { [endedParam]: '1' } : undefined;\n\treturn redirect(\n\t\tsignInPath(nextOf(request.url), {\n\t\t\tpath: paths.signIn,\n\t\t\t...(query && { query }),\n\t\t}),\n\t\t{ headers: { 'Set-Cookie': cookies.destroy() } },\n\t);\n}\n\n/**\n * For the sign-in pages: the viewer when the cookie holds a live session,\n * `null` when the API refuses it (a 401), `undefined` without a cookie or\n * when the API cannot tell.\n */\nexport async function viewer<L extends string, V>(\n\t{ options }: FlowContext<L>,\n\trequest: Request,\n\tread: (token: string) => Promise<V | null>,\n\ttoken: string | null | undefined = options.cookies.token(request),\n): Promise<V | null | undefined> {\n\tif (!token) return undefined;\n\ttry {\n\t\treturn await read(token);\n\t} catch (error) {\n\t\treturn isApiErrorLike(error) && error.status === 401 ? null : undefined;\n\t}\n}\n\n/**\n * The signed-in viewer, else the sign-in redirect. Its `headers` move the\n * cookie's expiry to the session's when the API moved it.\n */\nexport async function requireViewer<L extends string, V>(\n\tcontext: FlowContext<L>,\n\trequest: Request,\n\tread: (token: string) => Promise<V | null>,\n\texpiresAt: (viewer: V) => string | null,\n): Promise<{ viewer: V; headers: HeadersInit }> {\n\tconst session = context.options.cookies.read(request);\n\tif (!session) throw signInRedirect(context, request);\n\tlet found: V | null;\n\ttry {\n\t\tfound = await read(session.token);\n\t} catch (error) {\n\t\tif (isApiErrorLike(error) && error.status === 401) {\n\t\t\tthrow signInRedirect(context, request);\n\t\t}\n\t\tthrow routeError(error);\n\t}\n\tif (!found) throw signInRedirect(context, request);\n\treturn {\n\t\tviewer: found,\n\t\theaders: context.options.cookies.refreshed(session, expiresAt(found)),\n\t};\n}\n\n/**\n * Ends the session and goes to the sign-in page (`?signed-out=1`), or to\n * `to`. The body is read as JSON first: JSON and same-origin only, so\n * another site cannot sign the user out.\n */\nexport async function signOut<L extends string>(\n\
|
|
16
|
-
"import { hasSiteCheck } from '../errors/same-site';\nimport { CHALLENGE_OVER, INVALID_CODE } from './codes';\nimport type { FlowContext, SignInFlow, SignInFlowOptions } from './flow-types';\nimport { samesitePost } from './guard';\nimport { landing, landReplacing, revokeSession } from './land';\nimport { SIGN_IN_PATHS, type SignInPaths } from './model';\nimport { sitePath } from './model/site-path';\nimport { codeStep, readCodeInput } from './second-factor';\nimport { codeRefusal, signInStep, unavailableMessage } from './step';\nimport {\n\tnextParam,\n\trequireViewer,\n\tsignInRedirect,\n\tsignOut,\n\tviewer,\n} from './viewer';\n\nfunction resolve<L extends string>(\n\toptions: SignInFlowOptions<L>,\n): FlowContext<L> {\n\tconst paths: SignInPaths = { ...SIGN_IN_PATHS, ...options.paths };\n\tfor (const key of ['signIn', 'secondFactor'] as const) {\n\t\tsitePath(paths[key], { label: `signInFlow(): paths.${key}` });\n\t}\n\tif (!hasSiteCheck(options.json)) {\n\t\tthrow new TypeError(\n\t\t\t\"signInFlow(): json must come from jsonActions(), which carries the site check every step runs; this object has none (a hand-made double?). Pass the app's jsonActions({ origin }) instance, or a spread copy of it.\",\n\t\t);\n\t}\n\tconst invalidCode = options.codes?.invalidCode ?? INVALID_CODE;\n\tconst challengeOver = options.codes?.challengeOver ?? CHALLENGE_OVER;\n\tif (challengeOver.includes(invalidCode)) {\n\t\tthrow new TypeError(\n\t\t\t`signInFlow(): the code ${JSON.stringify(invalidCode)} is both the wrong-code code (codes.invalidCode) and in codes.challengeOver. A wrong code that leaves tries cannot end the challenge.`,\n\t\t);\n\t}\n\treturn { options, paths, invalidCode, challengeOver };\n}\n\n/**\n * The server side of a BFF's sign-in, over the cookies, the app's\n * `createApiErrors` and its `jsonActions`: where an answer lands, the form\n * steps with their refusals, the second factor, the viewer and sign-out.\n * Built once, beside the app's cookies; it speaks `@nxgt/janus`'s words\n * (`signedIn`, `secondFactor`, `CODE_INVALID`) and the app maps its API's.\n *\n * @example\n * export const signIn = signInFlow({\n * \tcookies, errors, json,\n * \trevoke: (token, request)
|
|
10
|
+
"import { data } from 'react-router';\nimport {\n\ttype ApiErrorLike,\n\tisApiErrorLike,\n\tretryAfterOf,\n} from '../errors/api-error';\nimport { MAIL_THROTTLED } from './codes';\nimport { samesitePost } from './guard';\nimport { afterReset, landByLink } from './link-land';\nimport {\n\tanswerOf,\n\tlinkArgs,\n\tspendToken,\n\tstatePath,\n\tstateRedirect,\n} from './link-token';\nimport type {\n\tAuthLinks,\n\tAuthLinksOptions,\n\tLinksContext,\n\tRequestLinkAnswer,\n} from './link-types';\nimport { sitePath } from './model/site-path';\n\nconst RESET_DONE = '/sign-in?password-reset=1';\n\n/**\n * Requests a mail's link with `run`. The API answers true whether or not\n * the e-mail holds an account, and so does the page: the same \"sent\" for\n * everyone. A wait or an unreachable API is the form's message; a malformed\n * address is the `email` field's.\n */\n/** The refusal's message: the app's own wording, else `errors`'. */\nconst worded = <L extends string, C>(\n\t{ wording, errors }: LinksContext<L, C>,\n\terror: ApiErrorLike,\n\tlanguage: L,\n): string =>\n\twording?.(error, language) ?? errors.apiErrorMessage(error, language);\n\nasync function requestLink<L extends string, C>(\n\toptions: LinksContext<L, C>,\n\trun: () => Promise<unknown>,\n\t{ request, language }: Parameters<AuthLinks<L, C>['requestLink']>[1],\n) {\n\tconst { errors } = options;\n\tsamesitePost(options.json, request);\n\ttry {\n\t\tawait run();\n\t\treturn data<RequestLinkAnswer>({ sent: true });\n\t} catch (error) {\n\t\tconst api = isApiErrorLike(error);\n\t\tif (\n\t\t\tapi &&\n\t\t\terror.code === (options.codes?.mailThrottled ?? MAIL_THROTTLED)\n\t\t) {\n\t\t\tconst retryAfter = retryAfterOf(error);\n\t\t\treturn data<RequestLinkAnswer>(\n\t\t\t\t{\n\t\t\t\t\tformError: worded(options, error, language),\n\t\t\t\t\tthrottled: true,\n\t\t\t\t\t...(retryAfter === undefined ? {} : { retryAfter }),\n\t\t\t\t},\n\t\t\t\t{ status: 429 },\n\t\t\t);\n\t\t}\n\t\tif (api) {\n\t\t\tconst email = errors.fieldErrors(error, language)['email'];\n\t\t\tif (email) {\n\t\t\t\treturn data<RequestLinkAnswer>(\n\t\t\t\t\t{ fieldErrors: { email } },\n\t\t\t\t\t{ status: error.status },\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\t\tconst refusal = errors.apiRefusal(error, language);\n\t\tif (!refusal) throw error;\n\t\treturn data<RequestLinkAnswer>(\n\t\t\t{ formError: api ? worded(options, error, language) : refusal.message },\n\t\t\t{ status: refusal.status },\n\t\t);\n\t}\n}\n\n/**\n * The pages a mail's link opens (a magic link, a password reset, an\n * invitation): the token page's end states, the arguments it posts, the\n * token spent against the API, the sign-in it leads to (replacing the\n * token's history entry), and the request for a link. Built once, beside the\n * app's `signInFlow`.\n *\n * @example\n * export const links = authLinks({ signIn });\n * // loader: return data({ ... }, { headers: TOKEN_PAGE_HEADERS });\n */\nexport function authLinks<L extends string, C = undefined>(\n\tlinks: AuthLinksOptions<L, C>,\n): AuthLinks<L, C> {\n\tconst { signIn } = links;\n\tconst options: LinksContext<L, C> = {\n\t\t...links,\n\t\tcookies: signIn.cookies,\n\t\terrors: signIn.errors,\n\t\tjson: signIn.json,\n\t};\n\tconst resetDone = options.resetDone ?? RESET_DONE;\n\tsitePath(resetDone, { label: 'authLinks(): resetDone', query: true });\n\tconst invalid = options.invalidState ?? 'invalid';\n\treturn {\n\t\tstatePath,\n\t\tended: stateRedirect,\n\t\tlinkArgs: (schema, request, path) =>\n\t\t\tlinkArgs(options, invalid, schema, request, path),\n\t\tspendToken: (run, o) => spendToken(options, run, o),\n\t\tanswerOf,\n\t\tlandByLink: (answer, o) => landByLink(options, answer, o),\n\t\tafterReset: (request, o) => afterReset(options, resetDone, request, o),\n\t\trequestLink: (run, o) => requestLink(options, run, o),\n\t};\n}\n",
|
|
11
|
+
"import { data, replace } from 'react-router';\nimport type { FormErrors } from '../errors/form-errors';\nimport type { SignInAnswer } from './flow-types';\nimport { samesitePost } from './guard';\nimport { isLive } from './land';\nimport { stateRedirect, withTokenHeaders } from './link-token';\nimport type { AuthLinks, LinkStayed, LinksContext } from './link-types';\nimport { HOME, safeNext } from './model';\n\n/** Where a new session starts: the app's start page, else `/`; never an unsafe path. */\nasync function startOf<L extends string, C>(\n\t{ start }: LinksContext<L, C>,\n\trequest: Request,\n\ttoken: string,\n): Promise<string> {\n\tif (!start) return HOME;\n\ttry {\n\t\treturn safeNext(await start(token, request));\n\t} catch {\n\t\t// The token is spent: a failed lookup must not fail the sign-in.\n\t\treturn HOME;\n\t}\n}\n\n/**\n * Where a link's sign-in leads, replacing the token's history entry. Signed\n * in: the start page, with the new session's cookie; a second factor: its\n * page. Either way the session the browser held, whoever's, is revoked\n * first: one browser holds one session. Only a JSON post goes on, before\n * anything is revoked or landed.\n */\nexport async function landByLink<L extends string, C>(\n\toptions: LinksContext<L, C>,\n\tanswer: SignInAnswer,\n\t{\n\t\trequest,\n\t\tlanguage,\n\t\tother,\n\t\tcontext,\n\t}: Parameters<AuthLinks<L, C>['landByLink']>[1],\n): Promise<Response | LinkStayed> {\n\tsamesitePost(options.json, request);\n\tif (answer.status === 'other' && other) {\n\t\treturn stateRedirect(other.path, other.state);\n\t}\n\t// A dead session would be looked up with a token the API refuses.\n\tconst next = isLive(answer)\n\t\t? await startOf(options, request, answer.token)\n\t\t: HOME;\n\tconst landed = await options.signIn.landReplacing(answer, next, {\n\t\trequest,\n\t\trevokeOn: ['signedIn', 'secondFactor'],\n\t\tcontext,\n\t});\n\tif (!landed) {\n\t\treturn data<FormErrors>(\n\t\t\t{ formError: options.signIn.unavailable(language) },\n\t\t\t{ status: 503 },\n\t\t);\n\t}\n\tconst location = landed.headers.get('Location') ?? HOME;\n\treturn replace(location, { headers: withTokenHeaders(landed.headers) });\n}\n\n/**\n * After a password reset, which signs the account out everywhere: the\n * browser's session is revoked (it may be another account's) and its cookies\n * cleared (the session's, the challenge's, the flow's `clear()`), then the sign-in page says the password changed. Only a JSON post\n * goes on.\n */\nexport async function afterReset<L extends string, C>(\n\toptions: LinksContext<L, C>,\n\tresetDone: string,\n\trequest: Request,\n\t{ context }: { readonly context?: C | undefined } = {},\n): Promise<Response> {\n\tsamesitePost(options.json, request);\n\tawait options.signIn.revokeSession(request, { context });\n\tconst headers = withTokenHeaders();\n\theaders.append('Set-Cookie', options.cookies.destroy());\n\theaders.append('Set-Cookie', options.cookies.destroyChallenge());\n\tfor (const cookie of options.signIn.clear()) {\n\t\theaders.append('Set-Cookie', cookie);\n\t}\n\treturn replace(resetDone, { headers });\n}\n",
|
|
12
|
+
"import { redirect } from 'react-router';\nimport type { FlowContext, SignInAnswer, SignInFlow } from './flow-types';\nimport { samesitePost } from './guard';\nimport { safeNext, secondFactorPath } from './model';\nimport { millisOf } from './serialize';\n\n/**\n * Ends the request's session at the API, when it carries one; a failure is\n * ignored, the caller clears the cookie anyway. True when there was one.\n */\nexport async function revokeSession<L extends string, C>(\n\t{ options }: FlowContext<L, C>,\n\trequest: Request,\n\t{ context }: { readonly context?: C | undefined } = {},\n): Promise<boolean> {\n\tsamesitePost(options.json, request);\n\tconst token = options.cookies.token(request);\n\tif (!token) return false;\n\ttry {\n\t\tawait options.revoke(token, request, context);\n\t} catch {\n\t\t// The old session ends when it expires.\n\t}\n\treturn true;\n}\n\n/** The condition a session opens on: a signed-in answer with a token and an end still to come. */\nexport const isLive = (\n\tanswer: SignInAnswer,\n): answer is Extract<SignInAnswer, { status: 'signedIn' }> =>\n\tanswer.status === 'signedIn' &&\n\tBoolean(answer.token) &&\n\tBoolean(answer.expiresAt) &&\n\tmillisOf(answer.expiresAt) > Date.now();\n\n/**\n * Where a sign-in's answer leads: signed in, to `next` with the session\n * cookie (and the challenge cleared); a second factor, to its page with the\n * challenge in its own cookie, never in the URL. Any other answer, or a\n * signed-in one with no token or an end that is missing, unreadable or past,\n * opens no session: `null`, the caller shows its own state.\n */\nexport function landing<L extends string, C>(\n\t{ options, paths }: FlowContext<L, C>,\n\tanswer: SignInAnswer,\n\tnext: string,\n): Response | null {\n\tconst { cookies } = options;\n\t// A session with no readable or past end would be set and dead at once.\n\tif (isLive(answer)) {\n\t\tconst headers = new Headers();\n\t\theaders.append(\n\t\t\t'Set-Cookie',\n\t\t\tcookies.commit(answer.token, answer.expiresAt),\n\t\t);\n\t\theaders.append('Set-Cookie', cookies.destroyChallenge());\n\t\tfor (const cookie of options.clear?.() ?? []) {\n\t\t\theaders.append('Set-Cookie', cookie);\n\t\t}\n\t\treturn redirect(safeNext(next), { headers });\n\t}\n\tif (answer.status === 'secondFactor' && answer.challenge) {\n\t\treturn redirect(secondFactorPath(next, paths.secondFactor), {\n\t\t\theaders: {\n\t\t\t\t'Set-Cookie': cookies.commitChallenge(\n\t\t\t\t\tanswer.challenge,\n\t\t\t\t\tanswer.expiresAt,\n\t\t\t\t),\n\t\t\t},\n\t\t});\n\t}\n\treturn null;\n}\n\n/**\n * `landing`, ending the session the browser held first: one browser holds\n * one session, so the one its cookie names, whoever's, is revoked when an\n * answer in `revokeOn` lands. A signed-in answer's own cookie replaces the\n * old one; any other answer clears it. A failed revoke is ignored.\n */\nexport async function landReplacing<L extends string, C>(\n\tflow: FlowContext<L, C>,\n\tanswer: SignInAnswer,\n\tnext: string,\n\t{\n\t\trequest,\n\t\trevokeOn = ['signedIn'],\n\t\tcontext,\n\t}: Parameters<SignInFlow<L, C>['landReplacing']>[2],\n): Promise<Response | null> {\n\tsamesitePost(flow.options.json, request);\n\tconst landed = landing(flow, answer, next);\n\tif (!landed || answer.status === 'other') return landed;\n\tif (!revokeOn.includes(answer.status)) return landed;\n\tconst hadSession = await revokeSession(flow, request, { context });\n\tif (hadSession && answer.status !== 'signedIn') {\n\t\tlanded.headers.append('Set-Cookie', flow.options.cookies.destroy());\n\t}\n\treturn landed;\n}\n",
|
|
13
|
+
"import { data } from 'react-router';\nimport type { FlowContext, SignInAnswer, SignInFlow } from './flow-types';\nimport { samesitePost } from './guard';\nimport type { SecondFactorKind } from './model';\n\nconst MAX_CODE = 40;\n\nfunction isMethod(value: unknown): value is SecondFactorKind {\n\treturn value === 'code' || value === 'recovery';\n}\n\n/**\n * The code a second-factor page posted, with how it is given\n * (`{ kind: 'code' | 'recovery', code }`, the code at most 40 characters). A\n * native form post goes back to its page, which then posts JSON; a\n * cross-site post is a 403; a body of another shape is a 400.\n */\nexport async function readCodeInput<L extends string, C>(\n\t{ options }: FlowContext<L, C>,\n\trequest: Request,\n): Promise<{ kind: SecondFactorKind; code: string }> {\n\tsamesitePost(options.json, request);\n\tconst body = (await options.json.readJson(request)) as {\n\t\tkind?: unknown;\n\t\tcode?: unknown;\n\t} | null;\n\tconst { kind, code } = body ?? {};\n\tif (!isMethod(kind) || typeof code !== 'string' || code.length > MAX_CODE) {\n\t\tthrow data('Bad code request', { status: 400 });\n\t}\n\treturn { kind, code };\n}\n\n/**\n * The code against the sign-in's challenge: an issued code (an app's, or\n * the one mailed) is confirmed, a recovery code is recovered. Either\n * answers like a sign-in.\n */\nexport function codeStep(\n\tinput: { readonly kind: SecondFactorKind; readonly code: string },\n\tchallenge: string,\n\tsteps: Parameters<SignInFlow<string>['codeStep']>[2],\n): Promise<SignInAnswer> {\n\treturn input.kind === 'code'\n\t\t? steps.confirm(challenge, input.code)\n\t\t: steps.recover(challenge, input.code);\n}\n",
|
|
14
|
+
"import { data } from 'react-router';\nimport {\n\ttype ApiErrorLike,\n\tattemptsLeftOf,\n\tisApiErrorLike,\n} from '../errors/api-error';\nimport type {\n\tCodeWording,\n\tFlowContext,\n\tSignInAnswer,\n\tSignInFlow,\n\tSignInFlowOptions,\n\tSignInFormError,\n\tSignInStayed,\n} from './flow-types';\nimport { samesitePost } from './guard';\nimport { landReplacing } from './land';\n\n/**\n * A refused code's message, with the tries left when the API counts them;\n * `undefined` when the challenge is over (gone, or no try left and no\n * `whenNone` to say instead) and the user must sign in again.\n */\nexport function codeRefusal<L extends string>(\n\t{ options, invalidCode, challengeOver }: CodeWording<L>,\n\terror: ApiErrorLike,\n\tlanguage: L,\n\twhenNone?: string,\n): string | undefined {\n\tif (error.code !== invalidCode) {\n\t\treturn error.code && challengeOver.includes(error.code)\n\t\t\t? undefined\n\t\t\t: options.errors.apiErrorMessage(error, language);\n\t}\n\tconst message = options.errors.apiErrorMessage(error, language);\n\tconst count = attemptsLeftOf(error);\n\tif (count === undefined) return message;\n\tif (count === 0) {\n\t\treturn whenNone === undefined ? undefined : `${message} ${whenNone}`;\n\t}\n\treturn `${message} ${options.attemptsLeft(count, language)}`;\n}\n\n/** What a sign-in says when the API cannot be reached. */\nexport const unavailableMessage = <L extends string>(\n\t{ unavailable, errors }: Pick<SignInFlowOptions<L>, 'unavailable' | 'errors'>,\n\tlanguage: L,\n): string => unavailable?.(language) ?? errors.unavailableMessage(language);\n\n/**\n * Runs a sign-in step and redirects where its answer leads, a new session\n * replacing the browser's (`landReplacing`). A refusal is the form's\n * message (a wrong password, a lock with its delay); an unreachable API\n * says `unavailable`, with a 503.\n */\nexport async function signInStep<L extends string, C, T extends object>(\n\tflow: FlowContext<L, C>,\n\trun: () => Promise<SignInAnswer>,\n\t{\n\t\trequest,\n\t\tlanguage,\n\t\tnext,\n\t\textra,\n\t\trefused,\n\t\tcontext,\n\t}: Parameters<SignInFlow<L, C>['signInStep']>[1] & { extra: T },\n): Promise<Response | SignInStayed<T>> {\n\tconst { errors, json } = flow.options;\n\t// A cross-site post must never sign in or revoke (headers only).\n\tsamesitePost(json, request);\n\tconst stay = (formError: string, status: number) =>\n\t\tdata<SignInFormError<T>>({ ...extra, formError }, { status });\n\tconst unreachable = () =>\n\t\tstay(unavailableMessage(flow.options, language), 503);\n\tlet answer: SignInAnswer;\n\ttry {\n\t\tanswer = await run();\n\t} catch (error) {\n\t\tconst refusal = errors.apiRefusal(error, language);\n\t\tif (!refusal) throw error;\n\t\t// An API it cannot reach: sign-in's own words.\n\t\tif (!isApiErrorLike(error)) return unreachable();\n\t\tconst own = refused?.(error);\n\t\tif (own instanceof Response) return own;\n\t\treturn stay(own ?? refusal.message, refusal.status);\n\t}\n\treturn (\n\t\t(await landReplacing(flow, answer, next, { request, context })) ??\n\t\tunreachable()\n\t);\n}\n",
|
|
15
|
+
"import { redirect } from 'react-router';\nimport { isApiErrorLike } from '../errors/api-error';\nimport { nextOf } from '../errors/page-path';\nimport { routeError } from '../errors/route-error';\nimport type { FlowContext } from './flow-types';\nimport { revokeSession } from './land';\nimport { safeNext, signInPath } from './model';\n\n/** The `next` a sign-in page was opened with, safe to redirect to. */\nexport const nextParam = (request: Request): string =>\n\tsafeNext(new URL(request.url).searchParams.get('next'));\n\n/**\n * The sign-in page, coming back to this request's page afterwards, with the\n * session cookie cleared: what a page answers without a (live) session. A\n * request that carried a session cookie says it ended when `endedParam` is\n * set.\n */\nexport function signInRedirect<L extends string, C>(\n\t{ options, paths }: FlowContext<L, C>,\n\trequest: Request,\n): Response {\n\tconst { endedParam, cookies } = options;\n\tconst query =\n\t\tendedParam && cookies.read(request) ? { [endedParam]: '1' } : undefined;\n\treturn redirect(\n\t\tsignInPath(nextOf(request.url), {\n\t\t\tpath: paths.signIn,\n\t\t\t...(query && { query }),\n\t\t}),\n\t\t{ headers: { 'Set-Cookie': cookies.destroy() } },\n\t);\n}\n\n/**\n * For the sign-in pages: the viewer when the cookie holds a live session,\n * `null` when the API refuses it (a 401), `undefined` without a cookie or\n * when the API cannot tell.\n */\nexport async function viewer<L extends string, C, V>(\n\t{ options }: FlowContext<L, C>,\n\trequest: Request,\n\tread: (token: string) => Promise<V | null>,\n\ttoken: string | null | undefined = options.cookies.token(request),\n): Promise<V | null | undefined> {\n\tif (!token) return undefined;\n\ttry {\n\t\treturn await read(token);\n\t} catch (error) {\n\t\treturn isApiErrorLike(error) && error.status === 401 ? null : undefined;\n\t}\n}\n\n/**\n * The signed-in viewer, else the sign-in redirect. Its `headers` move the\n * cookie's expiry to the session's when the API moved it.\n */\nexport async function requireViewer<L extends string, C, V>(\n\tcontext: FlowContext<L, C>,\n\trequest: Request,\n\tread: (token: string) => Promise<V | null>,\n\texpiresAt: (viewer: V) => string | null,\n): Promise<{ viewer: V; headers: HeadersInit }> {\n\tconst session = context.options.cookies.read(request);\n\tif (!session) throw signInRedirect(context, request);\n\tlet found: V | null;\n\ttry {\n\t\tfound = await read(session.token);\n\t} catch (error) {\n\t\tif (isApiErrorLike(error) && error.status === 401) {\n\t\t\tthrow signInRedirect(context, request);\n\t\t}\n\t\tthrow routeError(error);\n\t}\n\tif (!found) throw signInRedirect(context, request);\n\treturn {\n\t\tviewer: found,\n\t\theaders: context.options.cookies.refreshed(session, expiresAt(found)),\n\t};\n}\n\n/**\n * Ends the session and goes to the sign-in page (`?signed-out=1`), or to\n * `to`. The body is read as JSON first: JSON and same-origin only, so\n * another site cannot sign the user out.\n */\nexport async function signOut<L extends string, C>(\n\tflow: FlowContext<L, C>,\n\trequest: Request,\n\t{\n\t\tto,\n\t\tcontext,\n\t}: {\n\t\treadonly to?: string | undefined;\n\t\treadonly context?: C | undefined;\n\t} = {},\n): Promise<Response> {\n\tconst { options, paths } = flow;\n\tawait options.json.readJson(request);\n\tawait revokeSession(flow, request, { context });\n\tconst headers = new Headers();\n\theaders.append('Set-Cookie', options.cookies.destroy());\n\theaders.append('Set-Cookie', options.cookies.destroyChallenge());\n\tfor (const cookie of options.clear?.() ?? []) {\n\t\theaders.append('Set-Cookie', cookie);\n\t}\n\treturn redirect(\n\t\tto === undefined ? `${paths.signIn}?signed-out=1` : safeNext(to),\n\t\t{ headers },\n\t);\n}\n",
|
|
16
|
+
"import { hasSiteCheck } from '../errors/same-site';\nimport { CHALLENGE_OVER, INVALID_CODE } from './codes';\nimport type { FlowContext, SignInFlow, SignInFlowOptions } from './flow-types';\nimport { samesitePost } from './guard';\nimport { landing, landReplacing, revokeSession } from './land';\nimport { SIGN_IN_PATHS, type SignInPaths } from './model';\nimport { sitePath } from './model/site-path';\nimport { codeStep, readCodeInput } from './second-factor';\nimport { codeRefusal, signInStep, unavailableMessage } from './step';\nimport {\n\tnextParam,\n\trequireViewer,\n\tsignInRedirect,\n\tsignOut,\n\tviewer,\n} from './viewer';\n\nfunction resolve<L extends string, C>(\n\toptions: SignInFlowOptions<L, C>,\n): FlowContext<L, C> {\n\tconst paths: SignInPaths = { ...SIGN_IN_PATHS, ...options.paths };\n\tfor (const key of ['signIn', 'secondFactor'] as const) {\n\t\tsitePath(paths[key], { label: `signInFlow(): paths.${key}` });\n\t}\n\tif (!hasSiteCheck(options.json)) {\n\t\tthrow new TypeError(\n\t\t\t\"signInFlow(): json must come from jsonActions(), which carries the site check every step runs; this object has none (a hand-made double?). Pass the app's jsonActions({ origin }) instance, or a spread copy of it.\",\n\t\t);\n\t}\n\tconst invalidCode = options.codes?.invalidCode ?? INVALID_CODE;\n\tconst challengeOver = options.codes?.challengeOver ?? CHALLENGE_OVER;\n\tif (challengeOver.includes(invalidCode)) {\n\t\tthrow new TypeError(\n\t\t\t`signInFlow(): the code ${JSON.stringify(invalidCode)} is both the wrong-code code (codes.invalidCode) and in codes.challengeOver. A wrong code that leaves tries cannot end the challenge.`,\n\t\t);\n\t}\n\treturn { options, paths, invalidCode, challengeOver };\n}\n\n/**\n * The server side of a BFF's sign-in, over the cookies, the app's\n * `createApiErrors` and its `jsonActions`: where an answer lands, the form\n * steps with their refusals, the second factor, the viewer and sign-out.\n * Built once, beside the app's cookies; it speaks `@nxgt/janus`'s words\n * (`signedIn`, `secondFactor`, `CODE_INVALID`) and the app maps its API's.\n *\n * @example\n * export const signIn = signInFlow({\n * \tcookies, errors, json,\n * \trevoke: (token, request, caller?: Caller) =>\n * \t\texecute(SignOutMutation, {}, { token, ...caller }),\n * \tattemptsLeft: (count, language) => t('auth.attempts-left', { count }, language),\n * });\n */\nexport function signInFlow<L extends string, C = undefined>(\n\toptions: SignInFlowOptions<L, C>,\n): SignInFlow<L, C> {\n\tconst context = resolve(options);\n\tconst { options: o } = context;\n\treturn {\n\t\tcookies: o.cookies,\n\t\terrors: o.errors,\n\t\tjson: o.json,\n\t\tunavailable: (language) => unavailableMessage(o, language),\n\t\tclear: () => o.clear?.() ?? [],\n\t\tattemptsLeft: o.attemptsLeft,\n\t\tguard: (request) => samesitePost(o.json, request),\n\t\tnextParam,\n\t\tsignInRedirect: (request) => signInRedirect(context, request),\n\t\tlanding: (answer, next) => landing(context, answer, next),\n\t\tlandReplacing: (answer, next, o) => landReplacing(context, answer, next, o),\n\t\tsignInStep: (run, o) => signInStep(context, run, o),\n\t\tcodeRefusal: (error, language, whenNone) =>\n\t\t\tcodeRefusal(context, error, language, whenNone),\n\t\treadCodeInput: (request) => readCodeInput(context, request),\n\t\tcodeStep,\n\t\trevokeSession: (request, o) => revokeSession(context, request, o),\n\t\tsignOut: (request, o) => signOut(context, request, o),\n\t\tviewer: (request, read, token) => viewer(context, request, read, token),\n\t\trequireViewer: (request, read, expiresAt) =>\n\t\t\trequireViewer(context, request, read, expiresAt),\n\t};\n}\n",
|
|
17
17
|
"import { data } from 'react-router';\nimport {\n\ttype ApiErrorLike,\n\tattemptsLeftOf,\n\tisApiErrorLike,\n\tretryAfterOf,\n} from '../errors/api-error';\nimport { INVALID_CODE, MAIL_THROTTLED } from './codes';\nimport type { CodeWording } from './flow-types';\nimport { samesitePost } from './guard';\nimport type {\n\tStepUpConfirmAnswer,\n\tStepUpError,\n\tStepUpRequestAnswer,\n\tStepUpVia,\n} from './model';\nimport { codeRefusal } from './step';\nimport { STEP_UP_OVER, STEP_UP_REQUIRED } from './step-up-codes';\nimport type {\n\tStepUpStayed,\n\tStepUpSteps,\n\tStepUpStepsOptions,\n} from './step-up-types';\n\ntype Refused = Extract<StepUpConfirmAnswer, { ok: false }>;\n\ninterface StepUpContext<L extends string> extends CodeWording<L> {\n\treadonly steps: StepUpStepsOptions<L>;\n\treadonly stepUpRequired: string;\n\treadonly mailThrottled: string;\n}\n\nfunction resolve<L extends string>(\n\tsteps: StepUpStepsOptions<L>,\n): StepUpContext<L> {\n\tconst invalidCode = steps.codes?.invalidCode ?? INVALID_CODE;\n\tconst challengeOver = steps.codes?.challengeOver ?? STEP_UP_OVER;\n\tif (challengeOver.includes(invalidCode)) {\n\t\tthrow new TypeError(\n\t\t\t`stepUpSteps(): the code ${JSON.stringify(invalidCode)} is both the wrong-code code (codes.invalidCode) and in codes.challengeOver. A wrong code that leaves tries cannot end the challenge.`,\n\t\t);\n\t}\n\treturn {\n\t\toptions: steps.signIn,\n\t\tsteps,\n\t\tinvalidCode,\n\t\tchallengeOver,\n\t\tstepUpRequired: steps.codes?.stepUpRequired ?? STEP_UP_REQUIRED,\n\t\tmailThrottled: steps.codes?.mailThrottled ?? MAIL_THROTTLED,\n\t};\n}\n\n/** The refusal's message: the app's own wording, else `errors`'. */\nfunction worded<L extends string>(\n\t{ steps }: StepUpContext<L>,\n\terror: ApiErrorLike,\n\tlanguage: L,\n): string {\n\treturn (\n\t\tsteps.wording?.(error, language) ??\n\t\tsteps.signIn.errors.apiErrorMessage(error, language)\n\t);\n}\n\n/** A code the API refused. With none left, a mailed one is over and an app's is locked. */\nfunction refusedCode<L extends string>(\n\tcontext: StepUpContext<L>,\n\terror: ApiErrorLike,\n\tvia: StepUpVia | undefined,\n\tlanguage: L,\n): Omit<Refused, 'ok' | 'intent'> {\n\tconst { steps } = context;\n\tconst attemptsLeft = attemptsLeftOf(error);\n\tif (attemptsLeft === 0 && via !== 'secondFactor') {\n\t\treturn {\n\t\t\terror: 'expired',\n\t\t\tattemptsLeft,\n\t\t\tlocked: false,\n\t\t\tmessage: steps.spent(language),\n\t\t};\n\t}\n\tconst locked = attemptsLeft === 0;\n\tconst message =\n\t\tcodeRefusal(\n\t\t\tcontext,\n\t\t\terror,\n\t\t\tlanguage,\n\t\t\tlocked ? steps.locked?.(language) : undefined,\n\t\t) ?? steps.signIn.errors.apiErrorMessage(error, language);\n\treturn {\n\t\terror: 'invalid',\n\t\t...(attemptsLeft === undefined ? {} : { attemptsLeft }),\n\t\tlocked,\n\t\tmessage,\n\t};\n}\n\nasync function requestStep<L extends string>(\n\tcontext: StepUpContext<L>,\n\trun: Parameters<StepUpSteps<L>['request']>[0],\n\t{ request, language }: Parameters<StepUpSteps<L>['request']>[1],\n): Promise<StepUpRequestAnswer | StepUpStayed<StepUpRequestAnswer>> {\n\tsamesitePost(context.steps.signIn.json, request);\n\tconst intent = 'request-step-up';\n\ttry {\n\t\tconst { challenge, via } = await run();\n\t\treturn { ok: true, intent, challenge, via };\n\t} catch (error) {\n\t\tconst refusal = context.steps.signIn.errors.apiRefusal(error, language);\n\t\tif (!refusal) throw error;\n\t\tconst api = isApiErrorLike(error);\n\t\tconst message = api ? worded(context, error, language) : refusal.message;\n\t\tconst throttled = api && error.code === context.mailThrottled;\n\t\tconst retryAfter = throttled ? retryAfterOf(error) : undefined;\n\t\treturn data<StepUpRequestAnswer>(\n\t\t\t{\n\t\t\t\tok: false,\n\t\t\t\tintent,\n\t\t\t\terror: throttled ? 'throttled' : 'failed',\n\t\t\t\tmessage,\n\t\t\t\t...(retryAfter === undefined ? {} : { retryAfter }),\n\t\t\t},\n\t\t\t{ status: refusal.status },\n\t\t);\n\t}\n}\n\nasync function confirmStep<L extends string>(\n\tcontext: StepUpContext<L>,\n\trun: () => Promise<unknown>,\n\t{ request, language, via }: Parameters<StepUpSteps<L>['confirm']>[1],\n): Promise<StepUpConfirmAnswer | StepUpStayed<StepUpConfirmAnswer>> {\n\tsamesitePost(context.steps.signIn.json, request);\n\tconst intent = 'confirm-step-up';\n\ttry {\n\t\tawait run();\n\t\treturn { ok: true, intent };\n\t} catch (error) {\n\t\tconst refusal = context.steps.signIn.errors.apiRefusal(error, language);\n\t\tif (!refusal) throw error;\n\t\tconst stay = (\n\t\t\tfailure: Omit<Refused, 'ok' | 'intent'>,\n\t\t): StepUpStayed<StepUpConfirmAnswer> =>\n\t\t\tdata<StepUpConfirmAnswer>(\n\t\t\t\t{ ok: false, intent, ...failure },\n\t\t\t\t{ status: refusal.status },\n\t\t\t);\n\t\tconst failed = (message: string) =>\n\t\t\tstay({ error: 'failed' satisfies StepUpError, locked: false, message });\n\t\tif (!isApiErrorLike(error)) return failed(refusal.message);\n\t\tif (error.code === context.invalidCode) {\n\t\t\treturn stay(refusedCode(context, error, via, language));\n\t\t}\n\t\tif (error.code && context.challengeOver.includes(error.code)) {\n\t\t\treturn stay({\n\t\t\t\terror: 'expired',\n\t\t\t\tlocked: false,\n\t\t\t\tmessage: context.steps.spent(language),\n\t\t\t});\n\t\t}\n\t\treturn failed(worded(context, error, language));\n\t}\n}\n\n/**\n * The server side of a BFF's step-up: `request` and `confirm` answer the\n * page's fetcher with `StepUpRequestAnswer` and `StepUpConfirmAnswer`, which\n * `stepUpReducer` takes as they are. It speaks `@nxgt/janus`'s words\n * (`CODE_INVALID`, `TOKEN_*`, `STEP_UP_REQUIRED`); the app maps the rest.\n *\n * @example\n * const stepUp = stepUpSteps({\n * \terrors,\n * \tattemptsLeft: (count, language) => t('code.attempts-left', { count }, language),\n * \tspent: (language) => t('step-up.spent', {}, language),\n * \tlocked: (language) => t('step-up.locked', {}, language),\n * });\n */\nexport function stepUpSteps<L extends string>(\n\toptions: StepUpStepsOptions<L>,\n): StepUpSteps<L> {\n\tconst context = resolve(options);\n\treturn {\n\t\trequest: (run, o) => requestStep(context, run, o),\n\t\tconfirm: (run, o) => confirmStep(context, run, o),\n\t\tisStepUpRequired: (error) =>\n\t\t\tisApiErrorLike(error) && error.code === context.stepUpRequired,\n\t};\n}\n",
|
|
18
18
|
"import { CHALLENGE_OVER } from './codes';\n\n/** `@nxgt/janus`'s code for a mutation that needs a recent step-up. */\nexport const STEP_UP_REQUIRED = 'STEP_UP_REQUIRED';\n\n/**\n * The codes that end a step-up's challenge: unknown, spent, expired or stale,\n * and a second factor that changed since the request (`SECOND_FACTOR_ACTIVE`,\n * `SECOND_FACTOR_NOT_ENROLLED`). The user asks again.\n */\nexport const STEP_UP_OVER = [\n\t...CHALLENGE_OVER,\n\t'TOKEN_STALE',\n\t'SECOND_FACTOR_ACTIVE',\n\t'SECOND_FACTOR_NOT_ENROLLED',\n] as const;\n"
|
|
19
19
|
],
|
|
20
|
-
"mappings": "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",
|
|
21
|
-
"debugId": "
|
|
20
|
+
"mappings": ";;;;;;;;;;;;;;;;;;;AACO,IAAM,eAAe;AAGrB,IAAM,iBAAiB;AAAA,EAC7B;AAAA,EACA;AAAA,EACA;AACD;AAGO,IAAM,iBAAiB;;ACM9B,IAAM,QAAQ;AAGd,IAAM,OAAO;AAGN,IAAM,eAAe,CAAC,SAA0B,MAAM,KAAK,IAAI;AAE/D,IAAM,eAAe,CAAC,SAA0B,KAAK,KAAK,IAAI;AAO9D,SAAS,eAAe,CAC9B,MACA,SACE,QAAQ,OAAO,KAAK,UACb;AAAA,EACT,IAAI,CAAC,aAAa,IAAI,GAAG;AAAA,IACxB,MAAM,IAAI,UACT,+BAA+B,KAAK,UAAU,IAAI,6CACnD;AAAA,EACD;AAAA,EACA,IAAI,CAAC,aAAa,IAAI,GAAG;AAAA,IACxB,MAAM,IAAI,UACT,+BAA+B,KAAK,UAAU,IAAI,sFACnD;AAAA,EACD;AAAA,EACA,OAAO;AAAA,IACN,GAAG,QAAQ,mBAAmB,KAAK;AAAA,IACnC,WAAW,OAAO,SAAS,MAAM,IAAI,KAAK,IAAI,GAAG,KAAK,MAAM,MAAM,CAAC,IAAI;AAAA,IACvE,QAAQ;AAAA,IACR;AAAA,IACA,GAAI,SAAS,CAAC,QAAQ,IAAI,CAAC;AAAA,IAC3B;AAAA,EACD,EAAE,KAAK,IAAI;AAAA;AAOL,SAAS,UAAU,CAAC,SAAkB,MAA6B;AAAA,EACzE,WAAW,QAAQ,QAAQ,QAAQ,IAAI,QAAQ,GAAG,MAAM,GAAG,KAAK,CAAC,GAAG;AAAA,IACnE,MAAM,QAAQ,KAAK,QAAQ,GAAG;AAAA,IAC9B,IAAI,QAAQ,KAAK,KAAK,MAAM,GAAG,KAAK,EAAE,KAAK,MAAM;AAAA,MAAM;AAAA,IACvD,MAAM,MAAM,KAAK,MAAM,QAAQ,CAAC,EAAE,KAAK;AAAA,IACvC,IAAI,CAAC;AAAA,MAAK;AAAA,IACV,IAAI;AAAA,MACH,OAAO,mBAAmB,GAAG;AAAA,MAC5B,MAAM;AAAA,MACP,OAAO;AAAA;AAAA,EAET;AAAA,EACA,OAAO;AAAA;AAID,SAAS,QAAQ,CAAC,IAA2B;AAAA,EACnD,MAAM,OAAO,OAAO,OAAO,WAAW,KAAK,MAAM,EAAE,IAAI,GAAG,QAAQ;AAAA,EAClE,OAAO,OAAO,MAAM,IAAI,IAAI,IAAI;AAAA;AAI1B,SAAS,YAAY,CAC3B,WACA,MAAc,KAAK,IAAI,GACd;AAAA,EACT,MAAM,MAAM,SAAS,SAAS;AAAA,EAC9B,OAAO,QAAQ,IAAI,IAAI,KAAK,IAAI,IAAI,MAAM,OAAO,IAAI;AAAA;;;ACftD,IAAM,SAAS;AAEf,SAAS,SAAS,CAAC,OAAe,MAAoB;AAAA,EACrD,IAAI,CAAC,aAAa,IAAI,GAAG;AAAA,IACxB,MAAM,IAAI,UACT,yBAAyB,cAAc,KAAK,UAAU,IAAI,+HAA+H,SAC1L;AAAA,EACD;AAAA,EACA,IAAI,OAAO,KAAK,IAAI,GAAG;AAAA,IACtB,MAAM,IAAI,UACT,yBAAyB,cAAc,KAAK,UAAU,IAAI,kIAC3D;AAAA,EACD;AAAA;AAIM,SAAS,cAAc,CAAC,SAAgD;AAAA,EAC9E;AAAA,IACC;AAAA,IACA,gBAAgB;AAAA,IAChB,mBAAmB,IAAI;AAAA,MACpB;AAAA,EACJ,UAAU,WAAW,QAAQ,OAAO;AAAA,EACpC,UAAU,aAAa,QAAQ,SAAS;AAAA,EACxC,IAAI,CAAC,OAAO,UAAU,gBAAgB,KAAK,oBAAoB,GAAG;AAAA,IACjE,MAAM,IAAI,UACT,sEAAsE,OAAO,gBAAgB,IAC9F;AAAA,EACD;AAAA,EACA,IAAI,CAAC,aAAa,aAAa,GAAG;AAAA,IACjC,MAAM,IAAI,UACT,iGAAiG,KAAK,UAAU,aAAa,IAC9H;AAAA,EACD;AAAA,EAGA,MAAM,UAAU,SAAS,UAAU,QAAQ,YAAY,QAAQ;AAAA,EAC/D,MAAM,YAAY,SACf,YAAY,QAAQ,cACpB,QAAQ;AAAA,EACX,MAAM,QAAQ,CAAC,MAAc,SAC5B,gBAAgB,MAAM,IAAI,EAAE,QAAQ,GAAG,WAAY,QAAQ,EAAE,KAAK,EAAG,CAAC;AAAA,EAEvE,MAAM,OAAO,CAAC,YAA2C;AAAA,IACxD,MAAM,QAAQ,WAAW,SAAS,OAAO;AAAA,IACzC,MAAM,MAAM,OAAO,QAAQ,GAAG,KAAK;AAAA,IACnC,IAAI,CAAC,SAAS,MAAM;AAAA,MAAG,OAAO;AAAA,IAC9B,MAAM,SAAS,MAAM,MAAM,GAAG,GAAG;AAAA,IACjC,MAAM,YAAY,OAAO,MAAM;AAAA,IAC/B,MAAM,QAAQ,MAAM,MAAM,MAAM,CAAC;AAAA,IACjC,OAAO,SAAS,QAAQ,KAAK,MAAM,KAAK,OAAO,cAAc,SAAS,IACnE,EAAE,OAAO,UAAU,IACnB;AAAA;AAAA,EAKJ,MAAM,SAAS,CAAC,OAAe,WAA0B,QACxD,gBAAgB,SAAS,GAAG,SAAS,SAAS,KAAK,SAAS;AAAA,IAC3D,QAAQ,aAAa,WAAW,GAAG;AAAA,IACnC;AAAA,EACD,CAAC;AAAA,EAEF,OAAO;AAAA,IACN,OAAO,EAAE,SAAS,UAAU;AAAA,IAC5B;AAAA,IACA,OAAO,CAAC,YAAY,KAAK,OAAO,GAAG,SAAS;AAAA,IAC5C;AAAA,IACA,SAAS,MAAM,MAAM,OAAO;AAAA,IAC5B,SAAS,CAAC,SAAS,WAAW;AAAA,MAE7B,MAAM,MAAM,YAAY,SAAS,SAAS,IAAI;AAAA,MAC9C,IAAI,CAAC,aAAa,QAAQ,KAAK,QAAQ,QAAQ;AAAA,QAAW,OAAO,CAAC;AAAA,MAClE,OAAO,EAAE,cAAc,OAAO,QAAQ,OAAO,SAAS,EAAE;AAAA;AAAA,IAEzD,aAAa,CAAC,YAAY,WAAW,SAAS,SAAS;AAAA,IACvD,iBAAiB,CAAC,OAAO,WAAW,QACnC,gBAAgB,WAAW,OAAO;AAAA,MACjC,QAAQ,KAAK,IACZ,kBACA,YAAY,aAAa,WAAW,GAAG,IAAI,gBAC5C;AAAA,MACA,MAAM;AAAA,MACN;AAAA,IACD,CAAC;AAAA,IACF,kBAAkB,MAAM,MAAM,WAAW,aAAa;AAAA,IACtD,iBAAiB,CAAC,aAAa;AAAA,MAC9B,MAAM,UAAU,IAAI;AAAA,MACpB,QAAQ,OAAO,cAAc,MAAM,WAAW,aAAa,CAAC;AAAA,MAC5D,IAAI;AAAA,QAAa,QAAQ,OAAO,cAAc,MAAM,OAAO,CAAC;AAAA,MAC5D,OAAO;AAAA;AAAA,EAET;AAAA;;ACrKD;;;ACUO,SAAS,YAAY,CAAC,MAAmB,SAAwB;AAAA,EACvE,KAAK,gBAAgB,OAAO;AAAA,EAC5B,gBAAgB,MAAM,OAAO;AAAA;;;ADMvB,IAAM,qBAAqB;AAAA,EACjC,mBAAmB;AAAA,EACnB,gBAAgB;AACjB;AAGA,IAAM,SAAS,CAAC,MAAc,SAC7B,SAAS,MAAM,EAAE,OAAO,GAAG,4BAA4B,CAAC;AAElD,IAAM,YAAY,CAAC,MAAc,UACvC,GAAG,OAAO,MAAM,aAAa,KAAK,IAAI,gBAAgB,EAAE,MAAM,CAAC;AAGzD,SAAS,gBAAgB,CAAC,SAAgC;AAAA,EAChE,MAAM,SAAS,IAAI,QAAQ,OAAO;AAAA,EAClC,YAAY,MAAM,UAAU,OAAO,QAAQ,kBAAkB,GAAG;AAAA,IAC/D,OAAO,IAAI,MAAM,KAAK;AAAA,EACvB;AAAA,EACA,OAAO;AAAA;AAID,IAAM,gBAAgB,CAC5B,MACA,OACA,YAEA,QAAQ,UAAU,MAAM,KAAK,GAAG,EAAE,SAAS,iBAAiB,OAAO,EAAE,CAAC;AAEvE,IAAM,eAAe,CAAC,UAIhB;AAAA,EACL,MAAM,QAAQ,MAAM,OAAO;AAAA,EAC3B,QAAQ,OAAO,UAAU,WAAW,MAAM,MAAM,WAAW;AAAA;AAG5D,eAAsB,QAAoC,GACvD,QACF,cACA,QACA,SACA,MAC0B;AAAA,EAC1B,aAAa,MAAM,OAAO;AAAA,EAC1B,OAAO,MAAM,YAAY;AAAA,EACzB,MAAM,OAAO,MAAM,KAAK,SAAS,OAAO;AAAA,EACxC,MAAM,SAAS,MAAM,OAAO,aAAa,SAAS,IAAI;AAAA,EACtD,IAAI,CAAC,OAAO;AAAA,IAAQ,OAAO,OAAO;AAAA,EAGlC,IAAI,OAAO,OAAO,KAAK,YAAY,GAAG;AAAA,IACrC,MAAM,cAAc,MAAM,YAAY;AAAA,EACvC;AAAA,EACA,MAAM,KAAK,oBAAoB,EAAE,QAAQ,IAAI,CAAC;AAAA;AAG/C,eAAsB,UAAkC,CACvD,SACA,OACE,SAAS,UAAU,MAAM,QAAQ,QAAQ,QAAQ,CAAC,KAChC;AAAA,EACpB,QAAQ,WAAW;AAAA,EACnB,aAAa,QAAQ,MAAM,OAAO;AAAA,EAClC,OAAO,MAAM,cAAc;AAAA,EAC3B,IAAI;AAAA,IACH,OAAO,EAAE,IAAI,MAAM,OAAO,MAAM,IAAI,EAAE;AAAA,IACrC,OAAO,OAAO;AAAA,IACf,MAAM,UAAU,OAAO,WAAW,OAAO,QAAQ;AAAA,IACjD,IAAI,CAAC;AAAA,MAAS,MAAM;AAAA,IACpB,QAAQ,SAAS,WAAW;AAAA,IAC5B,IAAI,gBAAe,KAAK,GAAG;AAAA,MAC1B,MAAM,QAAQ,MAAM,OAAO,OAAO,MAAM,QAAQ;AAAA,MAChD,IAAI;AAAA,QAAO,OAAO,EAAE,IAAI,OAAO,UAAU,cAAc,MAAM,KAAK,EAAE;AAAA,MACpE,MAAM,SAAS,OAAO,YAAY,OAAO,QAAQ;AAAA,MACjD,MAAM,QAAQ,OAAO,KAAK,MAAM;AAAA,MAEhC,IAAI,MAAM,UAAU,MAAM,MAAM,CAAC,UAAU,MAAM,SAAS,KAAK,CAAC,GAAG;AAAA,QAClE,OAAO,EAAE,IAAI,OAAO,SAAS,EAAE,aAAa,OAAO,GAAG,OAAO;AAAA,MAC9D;AAAA,IACD;AAAA,IACA,OAAO,EAAE,IAAI,OAAO,SAAS,EAAE,WAAW,QAAQ,GAAG,OAAO;AAAA;AAAA;AAIvD,IAAM,WAAW,CACvB,WAEA,cAAc,SACX,MAAM,WACN,KAAiB,MAAM,SAAS,EAAE,QAAQ,MAAM,OAAO,CAAC;;AE7G5D,iBAAS;;;ACAT,iBAAS,kBAAM;;;ACAf;AAUA,eAAsB,aAAkC,GACrD,WACF,WACE,YAAkD,CAAC,GAClC;AAAA,EACnB,aAAa,QAAQ,MAAM,OAAO;AAAA,EAClC,MAAM,QAAQ,QAAQ,QAAQ,MAAM,OAAO;AAAA,EAC3C,IAAI,CAAC;AAAA,IAAO,OAAO;AAAA,EACnB,IAAI;AAAA,IACH,MAAM,QAAQ,OAAO,OAAO,SAAS,OAAO;AAAA,IAC3C,MAAM;AAAA,EAGR,OAAO;AAAA;AAID,IAAM,SAAS,CACrB,WAEA,OAAO,WAAW,cAClB,QAAQ,OAAO,KAAK,KACpB,QAAQ,OAAO,SAAS,KACxB,SAAS,OAAO,SAAS,IAAI,KAAK,IAAI;AAShC,SAAS,OAA4B,GACzC,SAAS,SACX,QACA,MACkB;AAAA,EAClB,QAAQ,YAAY;AAAA,EAEpB,IAAI,OAAO,MAAM,GAAG;AAAA,IACnB,MAAM,UAAU,IAAI;AAAA,IACpB,QAAQ,OACP,cACA,QAAQ,OAAO,OAAO,OAAO,OAAO,SAAS,CAC9C;AAAA,IACA,QAAQ,OAAO,cAAc,QAAQ,iBAAiB,CAAC;AAAA,IACvD,WAAW,UAAU,QAAQ,QAAQ,KAAK,CAAC,GAAG;AAAA,MAC7C,QAAQ,OAAO,cAAc,MAAM;AAAA,IACpC;AAAA,IACA,OAAO,SAAS,UAAS,IAAI,GAAG,EAAE,QAAQ,CAAC;AAAA,EAC5C;AAAA,EACA,IAAI,OAAO,WAAW,kBAAkB,OAAO,WAAW;AAAA,IACzD,OAAO,SAAS,kBAAiB,MAAM,MAAM,YAAY,GAAG;AAAA,MAC3D,SAAS;AAAA,QACR,cAAc,QAAQ,gBACrB,OAAO,WACP,OAAO,SACR;AAAA,MACD;AAAA,IACD,CAAC;AAAA,EACF;AAAA,EACA,OAAO;AAAA;AASR,eAAsB,aAAkC,CACvD,MACA,QACA;AAAA,EAEC;AAAA,EACA,WAAW,CAAC,UAAU;AAAA,EACtB;AAAA,GAE0B;AAAA,EAC3B,aAAa,KAAK,QAAQ,MAAM,OAAO;AAAA,EACvC,MAAM,SAAS,QAAQ,MAAM,QAAQ,IAAI;AAAA,EACzC,IAAI,CAAC,UAAU,OAAO,WAAW;AAAA,IAAS,OAAO;AAAA,EACjD,IAAI,CAAC,SAAS,SAAS,OAAO,MAAM;AAAA,IAAG,OAAO;AAAA,EAC9C,MAAM,aAAa,MAAM,cAAc,MAAM,SAAS,EAAE,QAAQ,CAAC;AAAA,EACjE,IAAI,cAAc,OAAO,WAAW,YAAY;AAAA,IAC/C,OAAO,QAAQ,OAAO,cAAc,KAAK,QAAQ,QAAQ,QAAQ,CAAC;AAAA,EACnE;AAAA,EACA,OAAO;AAAA;;;ADxFR,eAAe,OAA4B,GACxC,SACF,SACA,OACkB;AAAA,EAClB,IAAI,CAAC;AAAA,IAAO,OAAO;AAAA,EACnB,IAAI;AAAA,IACH,OAAO,UAAS,MAAM,MAAM,OAAO,OAAO,CAAC;AAAA,IAC1C,MAAM;AAAA,IAEP,OAAO;AAAA;AAAA;AAWT,eAAsB,UAA+B,CACpD,SACA;AAAA,EAEC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,GAEgC;AAAA,EACjC,aAAa,QAAQ,MAAM,OAAO;AAAA,EAClC,IAAI,OAAO,WAAW,WAAW,OAAO;AAAA,IACvC,OAAO,cAAc,MAAM,MAAM,MAAM,KAAK;AAAA,EAC7C;AAAA,EAEA,MAAM,OAAO,OAAO,MAAM,IACvB,MAAM,QAAQ,SAAS,SAAS,OAAO,KAAK,IAC5C;AAAA,EACH,MAAM,SAAS,MAAM,QAAQ,OAAO,cAAc,QAAQ,MAAM;AAAA,IAC/D;AAAA,IACA,UAAU,CAAC,YAAY,cAAc;AAAA,IACrC;AAAA,EACD,CAAC;AAAA,EACD,IAAI,CAAC,QAAQ;AAAA,IACZ,OAAO,MACN,EAAE,WAAW,QAAQ,OAAO,YAAY,QAAQ,EAAE,GAClD,EAAE,QAAQ,IAAI,CACf;AAAA,EACD;AAAA,EACA,MAAM,WAAW,OAAO,QAAQ,IAAI,UAAU,KAAK;AAAA,EACnD,OAAO,SAAQ,UAAU,EAAE,SAAS,iBAAiB,OAAO,OAAO,EAAE,CAAC;AAAA;AASvE,eAAsB,UAA+B,CACpD,SACA,WACA,WACE,YAAkD,CAAC,GACjC;AAAA,EACpB,aAAa,QAAQ,MAAM,OAAO;AAAA,EAClC,MAAM,QAAQ,OAAO,cAAc,SAAS,EAAE,QAAQ,CAAC;AAAA,EACvD,MAAM,UAAU,iBAAiB;AAAA,EACjC,QAAQ,OAAO,cAAc,QAAQ,QAAQ,QAAQ,CAAC;AAAA,EACtD,QAAQ,OAAO,cAAc,QAAQ,QAAQ,iBAAiB,CAAC;AAAA,EAC/D,WAAW,UAAU,QAAQ,OAAO,MAAM,GAAG;AAAA,IAC5C,QAAQ,OAAO,cAAc,MAAM;AAAA,EACpC;AAAA,EACA,OAAO,SAAQ,WAAW,EAAE,QAAQ,CAAC;AAAA;;;AD5DtC,IAAM,aAAa;AASnB,IAAM,SAAS,GACZ,SAAS,UACX,OACA,aAEA,UAAU,OAAO,QAAQ,KAAK,OAAO,gBAAgB,OAAO,QAAQ;AAErE,eAAe,WAAgC,CAC9C,SACA,OACE,SAAS,YACV;AAAA,EACD,QAAQ,WAAW;AAAA,EACnB,aAAa,QAAQ,MAAM,OAAO;AAAA,EAClC,IAAI;AAAA,IACH,MAAM,IAAI;AAAA,IACV,OAAO,MAAwB,EAAE,MAAM,KAAK,CAAC;AAAA,IAC5C,OAAO,OAAO;AAAA,IACf,MAAM,MAAM,gBAAe,KAAK;AAAA,IAChC,IACC,OACA,MAAM,UAAU,QAAQ,OAAO,iBAAiB,iBAC/C;AAAA,MACD,MAAM,aAAa,aAAa,KAAK;AAAA,MACrC,OAAO,MACN;AAAA,QACC,WAAW,OAAO,SAAS,OAAO,QAAQ;AAAA,QAC1C,WAAW;AAAA,WACP,eAAe,YAAY,CAAC,IAAI,EAAE,WAAW;AAAA,MAClD,GACA,EAAE,QAAQ,IAAI,CACf;AAAA,IACD;AAAA,IACA,IAAI,KAAK;AAAA,MACR,MAAM,QAAQ,OAAO,YAAY,OAAO,QAAQ,EAAE;AAAA,MAClD,IAAI,OAAO;AAAA,QACV,OAAO,MACN,EAAE,aAAa,EAAE,MAAM,EAAE,GACzB,EAAE,QAAQ,MAAM,OAAO,CACxB;AAAA,MACD;AAAA,IACD;AAAA,IACA,MAAM,UAAU,OAAO,WAAW,OAAO,QAAQ;AAAA,IACjD,IAAI,CAAC;AAAA,MAAS,MAAM;AAAA,IACpB,OAAO,MACN,EAAE,WAAW,MAAM,OAAO,SAAS,OAAO,QAAQ,IAAI,QAAQ,QAAQ,GACtE,EAAE,QAAQ,QAAQ,OAAO,CAC1B;AAAA;AAAA;AAeK,SAAS,SAA0C,CACzD,OACkB;AAAA,EAClB,QAAQ,WAAW;AAAA,EACnB,MAAM,UAA8B;AAAA,OAChC;AAAA,IACH,SAAS,OAAO;AAAA,IAChB,QAAQ,OAAO;AAAA,IACf,MAAM,OAAO;AAAA,EACd;AAAA,EACA,MAAM,YAAY,QAAQ,aAAa;AAAA,EACvC,SAAS,WAAW,EAAE,OAAO,0BAA0B,OAAO,KAAK,CAAC;AAAA,EACpE,MAAM,UAAU,QAAQ,gBAAgB;AAAA,EACxC,OAAO;AAAA,IACN;AAAA,IACA,OAAO;AAAA,IACP,UAAU,CAAC,QAAQ,SAAS,SAC3B,SAAS,SAAS,SAAS,QAAQ,SAAS,IAAI;AAAA,IACjD,YAAY,CAAC,KAAK,MAAM,WAAW,SAAS,KAAK,CAAC;AAAA,IAClD;AAAA,IACA,YAAY,CAAC,QAAQ,MAAM,WAAW,SAAS,QAAQ,CAAC;AAAA,IACxD,YAAY,CAAC,SAAS,MAAM,WAAW,SAAS,WAAW,SAAS,CAAC;AAAA,IACrE,aAAa,CAAC,KAAK,MAAM,YAAY,SAAS,KAAK,CAAC;AAAA,EACrD;AAAA;;AGtHD,iBAAS;AAKT,IAAM,WAAW;AAEjB,SAAS,QAAQ,CAAC,OAA2C;AAAA,EAC5D,OAAO,UAAU,UAAU,UAAU;AAAA;AAStC,eAAsB,aAAkC,GACrD,WACF,SACoD;AAAA,EACpD,aAAa,QAAQ,MAAM,OAAO;AAAA,EAClC,MAAM,OAAQ,MAAM,QAAQ,KAAK,SAAS,OAAO;AAAA,EAIjD,QAAQ,MAAM,SAAS,QAAQ,CAAC;AAAA,EAChC,IAAI,CAAC,SAAS,IAAI,KAAK,OAAO,SAAS,YAAY,KAAK,SAAS,UAAU;AAAA,IAC1E,MAAM,MAAK,oBAAoB,EAAE,QAAQ,IAAI,CAAC;AAAA,EAC/C;AAAA,EACA,OAAO,EAAE,MAAM,KAAK;AAAA;AAQd,SAAS,QAAQ,CACvB,OACA,WACA,OACwB;AAAA,EACxB,OAAO,MAAM,SAAS,SACnB,MAAM,QAAQ,WAAW,MAAM,IAAI,IACnC,MAAM,QAAQ,WAAW,MAAM,IAAI;AAAA;;;AC7CvC,iBAAS;AAuBF,SAAS,WAA6B,GAC1C,SAAS,aAAa,iBACxB,OACA,UACA,UACqB;AAAA,EACrB,IAAI,MAAM,SAAS,aAAa;AAAA,IAC/B,OAAO,MAAM,QAAQ,cAAc,SAAS,MAAM,IAAI,IACnD,YACA,QAAQ,OAAO,gBAAgB,OAAO,QAAQ;AAAA,EAClD;AAAA,EACA,MAAM,UAAU,QAAQ,OAAO,gBAAgB,OAAO,QAAQ;AAAA,EAC9D,MAAM,QAAQ,gBAAe,KAAK;AAAA,EAClC,IAAI,UAAU;AAAA,IAAW,OAAO;AAAA,EAChC,IAAI,UAAU,GAAG;AAAA,IAChB,OAAO,aAAa,YAAY,YAAY,GAAG,WAAW;AAAA,EAC3D;AAAA,EACA,OAAO,GAAG,WAAW,QAAQ,aAAa,OAAO,QAAQ;AAAA;AAInD,IAAM,qBAAqB,GAC/B,aAAa,UACf,aACY,cAAc,QAAQ,KAAK,OAAO,mBAAmB,QAAQ;AAQ1E,eAAsB,UAAiD,CACtE,MACA;AAAA,EAEC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,GAEqC;AAAA,EACtC,QAAQ,QAAQ,SAAS,KAAK;AAAA,EAE9B,aAAa,MAAM,OAAO;AAAA,EAC1B,MAAM,OAAO,CAAC,WAAmB,WAChC,MAAyB,KAAK,OAAO,UAAU,GAAG,EAAE,OAAO,CAAC;AAAA,EAC7D,MAAM,cAAc,MACnB,KAAK,mBAAmB,KAAK,SAAS,QAAQ,GAAG,GAAG;AAAA,EACrD,IAAI;AAAA,EACJ,IAAI;AAAA,IACH,SAAS,MAAM,IAAI;AAAA,IAClB,OAAO,OAAO;AAAA,IACf,MAAM,UAAU,OAAO,WAAW,OAAO,QAAQ;AAAA,IACjD,IAAI,CAAC;AAAA,MAAS,MAAM;AAAA,IAEpB,IAAI,CAAC,gBAAe,KAAK;AAAA,MAAG,OAAO,YAAY;AAAA,IAC/C,MAAM,MAAM,UAAU,KAAK;AAAA,IAC3B,IAAI,eAAe;AAAA,MAAU,OAAO;AAAA,IACpC,OAAO,KAAK,OAAO,QAAQ,SAAS,QAAQ,MAAM;AAAA;AAAA,EAEnD,OACE,MAAM,cAAc,MAAM,QAAQ,MAAM,EAAE,SAAS,QAAQ,CAAC,KAC7D,YAAY;AAAA;;;ACxFd,qBAAS;AASF,IAAM,YAAY,CAAC,YACzB,UAAS,IAAI,IAAI,QAAQ,GAAG,EAAE,aAAa,IAAI,MAAM,CAAC;AAQhD,SAAS,cAAmC,GAChD,SAAS,SACX,SACW;AAAA,EACX,QAAQ,YAAY,YAAY;AAAA,EAChC,MAAM,QACL,cAAc,QAAQ,KAAK,OAAO,IAAI,GAAG,aAAa,IAAI,IAAI;AAAA,EAC/D,OAAO,UACN,YAAW,QAAO,QAAQ,GAAG,GAAG;AAAA,IAC/B,MAAM,MAAM;AAAA,OACR,SAAS,EAAE,MAAM;AAAA,EACtB,CAAC,GACD,EAAE,SAAS,EAAE,cAAc,QAAQ,QAAQ,EAAE,EAAE,CAChD;AAAA;AAQD,eAAsB,MAA8B,GACjD,WACF,SACA,MACA,QAAmC,QAAQ,QAAQ,MAAM,OAAO,GAChC;AAAA,EAChC,IAAI,CAAC;AAAA,IAAO;AAAA,EACZ,IAAI;AAAA,IACH,OAAO,MAAM,KAAK,KAAK;AAAA,IACtB,OAAO,OAAO;AAAA,IACf,OAAO,gBAAe,KAAK,KAAK,MAAM,WAAW,MAAM,OAAO;AAAA;AAAA;AAQhE,eAAsB,aAAqC,CAC1D,SACA,SACA,MACA,WAC+C;AAAA,EAC/C,MAAM,UAAU,QAAQ,QAAQ,QAAQ,KAAK,OAAO;AAAA,EACpD,IAAI,CAAC;AAAA,IAAS,MAAM,eAAe,SAAS,OAAO;AAAA,EACnD,IAAI;AAAA,EACJ,IAAI;AAAA,IACH,QAAQ,MAAM,KAAK,QAAQ,KAAK;AAAA,IAC/B,OAAO,OAAO;AAAA,IACf,IAAI,gBAAe,KAAK,KAAK,MAAM,WAAW,KAAK;AAAA,MAClD,MAAM,eAAe,SAAS,OAAO;AAAA,IACtC;AAAA,IACA,MAAM,YAAW,KAAK;AAAA;AAAA,EAEvB,IAAI,CAAC;AAAA,IAAO,MAAM,eAAe,SAAS,OAAO;AAAA,EACjD,OAAO;AAAA,IACN,QAAQ;AAAA,IACR,SAAS,QAAQ,QAAQ,QAAQ,UAAU,SAAS,UAAU,KAAK,CAAC;AAAA,EACrE;AAAA;AAQD,eAAsB,OAA4B,CACjD,MACA;AAAA,EAEC;AAAA,EACA;AAAA,IAIG,CAAC,GACe;AAAA,EACpB,QAAQ,SAAS,UAAU;AAAA,EAC3B,MAAM,QAAQ,KAAK,SAAS,OAAO;AAAA,EACnC,MAAM,cAAc,MAAM,SAAS,EAAE,QAAQ,CAAC;AAAA,EAC9C,MAAM,UAAU,IAAI;AAAA,EACpB,QAAQ,OAAO,cAAc,QAAQ,QAAQ,QAAQ,CAAC;AAAA,EACtD,QAAQ,OAAO,cAAc,QAAQ,QAAQ,iBAAiB,CAAC;AAAA,EAC/D,WAAW,UAAU,QAAQ,QAAQ,KAAK,CAAC,GAAG;AAAA,IAC7C,QAAQ,OAAO,cAAc,MAAM;AAAA,EACpC;AAAA,EACA,OAAO,UACN,OAAO,YAAY,GAAG,MAAM,wBAAwB,UAAS,EAAE,GAC/D,EAAE,QAAQ,CACX;AAAA;;;AC5FD,SAAS,OAA4B,CACpC,SACoB;AAAA,EACpB,MAAM,QAAqB,KAAK,mBAAkB,QAAQ,MAAM;AAAA,EAChE,WAAW,OAAO,CAAC,UAAU,cAAc,GAAY;AAAA,IACtD,SAAS,MAAM,MAAM,EAAE,OAAO,uBAAuB,MAAM,CAAC;AAAA,EAC7D;AAAA,EACA,IAAI,CAAC,aAAa,QAAQ,IAAI,GAAG;AAAA,IAChC,MAAM,IAAI,UACT,qNACD;AAAA,EACD;AAAA,EACA,MAAM,cAAc,QAAQ,OAAO,eAAe;AAAA,EAClD,MAAM,gBAAgB,QAAQ,OAAO,iBAAiB;AAAA,EACtD,IAAI,cAAc,SAAS,WAAW,GAAG;AAAA,IACxC,MAAM,IAAI,UACT,0BAA0B,KAAK,UAAU,WAAW,wIACrD;AAAA,EACD;AAAA,EACA,OAAO,EAAE,SAAS,OAAO,aAAa,cAAc;AAAA;AAkB9C,SAAS,UAA2C,CAC1D,SACmB;AAAA,EACnB,MAAM,UAAU,QAAQ,OAAO;AAAA,EAC/B,QAAQ,SAAS,MAAM;AAAA,EACvB,OAAO;AAAA,IACN,SAAS,EAAE;AAAA,IACX,QAAQ,EAAE;AAAA,IACV,MAAM,EAAE;AAAA,IACR,aAAa,CAAC,aAAa,mBAAmB,GAAG,QAAQ;AAAA,IACzD,OAAO,MAAM,EAAE,QAAQ,KAAK,CAAC;AAAA,IAC7B,cAAc,EAAE;AAAA,IAChB,OAAO,CAAC,YAAY,aAAa,EAAE,MAAM,OAAO;AAAA,IAChD;AAAA,IACA,gBAAgB,CAAC,YAAY,eAAe,SAAS,OAAO;AAAA,IAC5D,SAAS,CAAC,QAAQ,SAAS,QAAQ,SAAS,QAAQ,IAAI;AAAA,IACxD,eAAe,CAAC,QAAQ,MAAM,MAAM,cAAc,SAAS,QAAQ,MAAM,CAAC;AAAA,IAC1E,YAAY,CAAC,KAAK,MAAM,WAAW,SAAS,KAAK,CAAC;AAAA,IAClD,aAAa,CAAC,OAAO,UAAU,aAC9B,YAAY,SAAS,OAAO,UAAU,QAAQ;AAAA,IAC/C,eAAe,CAAC,YAAY,cAAc,SAAS,OAAO;AAAA,IAC1D;AAAA,IACA,eAAe,CAAC,SAAS,MAAM,cAAc,SAAS,SAAS,CAAC;AAAA,IAChE,SAAS,CAAC,SAAS,MAAM,QAAQ,SAAS,SAAS,CAAC;AAAA,IACpD,QAAQ,CAAC,SAAS,MAAM,UAAU,OAAO,SAAS,SAAS,MAAM,KAAK;AAAA,IACtE,eAAe,CAAC,SAAS,MAAM,cAC9B,cAAc,SAAS,SAAS,MAAM,SAAS;AAAA,EACjD;AAAA;;ACjFD,iBAAS;;;ACGF,IAAM,mBAAmB;AAOzB,IAAM,eAAe;AAAA,EAC3B,GAAG;AAAA,EACH;AAAA,EACA;AAAA,EACA;AACD;;;ADiBA,SAAS,QAAyB,CACjC,OACmB;AAAA,EACnB,MAAM,cAAc,MAAM,OAAO,eAAe;AAAA,EAChD,MAAM,gBAAgB,MAAM,OAAO,iBAAiB;AAAA,EACpD,IAAI,cAAc,SAAS,WAAW,GAAG;AAAA,IACxC,MAAM,IAAI,UACT,2BAA2B,KAAK,UAAU,WAAW,wIACtD;AAAA,EACD;AAAA,EACA,OAAO;AAAA,IACN,SAAS,MAAM;AAAA,IACf;AAAA,IACA;AAAA,IACA;AAAA,IACA,gBAAgB,MAAM,OAAO,kBAAkB;AAAA,IAC/C,eAAe,MAAM,OAAO,iBAAiB;AAAA,EAC9C;AAAA;AAID,SAAS,OAAwB,GAC9B,SACF,OACA,UACS;AAAA,EACT,OACC,MAAM,UAAU,OAAO,QAAQ,KAC/B,MAAM,OAAO,OAAO,gBAAgB,OAAO,QAAQ;AAAA;AAKrD,SAAS,WAA6B,CACrC,SACA,OACA,KACA,UACiC;AAAA,EACjC,QAAQ,UAAU;AAAA,EAClB,MAAM,eAAe,gBAAe,KAAK;AAAA,EACzC,IAAI,iBAAiB,KAAK,QAAQ,gBAAgB;AAAA,IACjD,OAAO;AAAA,MACN,OAAO;AAAA,MACP;AAAA,MACA,QAAQ;AAAA,MACR,SAAS,MAAM,MAAM,QAAQ;AAAA,IAC9B;AAAA,EACD;AAAA,EACA,MAAM,SAAS,iBAAiB;AAAA,EAChC,MAAM,UACL,YACC,SACA,OACA,UACA,SAAS,MAAM,SAAS,QAAQ,IAAI,SACrC,KAAK,MAAM,OAAO,OAAO,gBAAgB,OAAO,QAAQ;AAAA,EACzD,OAAO;AAAA,IACN,OAAO;AAAA,OACH,iBAAiB,YAAY,CAAC,IAAI,EAAE,aAAa;AAAA,IACrD;AAAA,IACA;AAAA,EACD;AAAA;AAGD,eAAe,WAA6B,CAC3C,SACA,OACE,SAAS,YACwD;AAAA,EACnE,aAAa,QAAQ,MAAM,OAAO,MAAM,OAAO;AAAA,EAC/C,MAAM,SAAS;AAAA,EACf,IAAI;AAAA,IACH,QAAQ,WAAW,QAAQ,MAAM,IAAI;AAAA,IACrC,OAAO,EAAE,IAAI,MAAM,QAAQ,WAAW,IAAI;AAAA,IACzC,OAAO,OAAO;AAAA,IACf,MAAM,UAAU,QAAQ,MAAM,OAAO,OAAO,WAAW,OAAO,QAAQ;AAAA,IACtE,IAAI,CAAC;AAAA,MAAS,MAAM;AAAA,IACpB,MAAM,MAAM,gBAAe,KAAK;AAAA,IAChC,MAAM,UAAU,MAAM,QAAO,SAAS,OAAO,QAAQ,IAAI,QAAQ;AAAA,IACjE,MAAM,YAAY,OAAO,MAAM,SAAS,QAAQ;AAAA,IAChD,MAAM,aAAa,YAAY,aAAa,KAAK,IAAI;AAAA,IACrD,OAAO,MACN;AAAA,MACC,IAAI;AAAA,MACJ;AAAA,MACA,OAAO,YAAY,cAAc;AAAA,MACjC;AAAA,SACI,eAAe,YAAY,CAAC,IAAI,EAAE,WAAW;AAAA,IAClD,GACA,EAAE,QAAQ,QAAQ,OAAO,CAC1B;AAAA;AAAA;AAIF,eAAe,WAA6B,CAC3C,SACA,OACE,SAAS,UAAU,OAC8C;AAAA,EACnE,aAAa,QAAQ,MAAM,OAAO,MAAM,OAAO;AAAA,EAC/C,MAAM,SAAS;AAAA,EACf,IAAI;AAAA,IACH,MAAM,IAAI;AAAA,IACV,OAAO,EAAE,IAAI,MAAM,OAAO;AAAA,IACzB,OAAO,OAAO;AAAA,IACf,MAAM,UAAU,QAAQ,MAAM,OAAO,OAAO,WAAW,OAAO,QAAQ;AAAA,IACtE,IAAI,CAAC;AAAA,MAAS,MAAM;AAAA,IACpB,MAAM,OAAO,CACZ,YAEA,MACC,EAAE,IAAI,OAAO,WAAW,QAAQ,GAChC,EAAE,QAAQ,QAAQ,OAAO,CAC1B;AAAA,IACD,MAAM,SAAS,CAAC,YACf,KAAK,EAAE,OAAO,UAAgC,QAAQ,OAAO,QAAQ,CAAC;AAAA,IACvE,IAAI,CAAC,gBAAe,KAAK;AAAA,MAAG,OAAO,OAAO,QAAQ,OAAO;AAAA,IACzD,IAAI,MAAM,SAAS,QAAQ,aAAa;AAAA,MACvC,OAAO,KAAK,YAAY,SAAS,OAAO,KAAK,QAAQ,CAAC;AAAA,IACvD;AAAA,IACA,IAAI,MAAM,QAAQ,QAAQ,cAAc,SAAS,MAAM,IAAI,GAAG;AAAA,MAC7D,OAAO,KAAK;AAAA,QACX,OAAO;AAAA,QACP,QAAQ;AAAA,QACR,SAAS,QAAQ,MAAM,MAAM,QAAQ;AAAA,MACtC,CAAC;AAAA,IACF;AAAA,IACA,OAAO,OAAO,QAAO,SAAS,OAAO,QAAQ,CAAC;AAAA;AAAA;AAkBzC,SAAS,WAA6B,CAC5C,SACiB;AAAA,EACjB,MAAM,UAAU,SAAQ,OAAO;AAAA,EAC/B,OAAO;AAAA,IACN,SAAS,CAAC,KAAK,MAAM,YAAY,SAAS,KAAK,CAAC;AAAA,IAChD,SAAS,CAAC,KAAK,MAAM,YAAY,SAAS,KAAK,CAAC;AAAA,IAChD,kBAAkB,CAAC,UAClB,gBAAe,KAAK,KAAK,MAAM,SAAS,QAAQ;AAAA,EAClD;AAAA;",
|
|
21
|
+
"debugId": "A957A7CA74458AAE64756E2164756E21",
|
|
22
22
|
"names": []
|
|
23
23
|
}
|
package/dist/session/land.d.ts
CHANGED
|
@@ -3,7 +3,9 @@ import type { FlowContext, SignInAnswer, SignInFlow } from './flow-types';
|
|
|
3
3
|
* Ends the request's session at the API, when it carries one; a failure is
|
|
4
4
|
* ignored, the caller clears the cookie anyway. True when there was one.
|
|
5
5
|
*/
|
|
6
|
-
export declare function revokeSession<L extends string>({ options }: FlowContext<L>, request: Request
|
|
6
|
+
export declare function revokeSession<L extends string, C>({ options }: FlowContext<L, C>, request: Request, { context }?: {
|
|
7
|
+
readonly context?: C | undefined;
|
|
8
|
+
}): Promise<boolean>;
|
|
7
9
|
/** The condition a session opens on: a signed-in answer with a token and an end still to come. */
|
|
8
10
|
export declare const isLive: (answer: SignInAnswer) => answer is Extract<SignInAnswer, {
|
|
9
11
|
status: "signedIn";
|
|
@@ -15,12 +17,12 @@ export declare const isLive: (answer: SignInAnswer) => answer is Extract<SignInA
|
|
|
15
17
|
* signed-in one with no token or an end that is missing, unreadable or past,
|
|
16
18
|
* opens no session: `null`, the caller shows its own state.
|
|
17
19
|
*/
|
|
18
|
-
export declare function landing<L extends string>({ options, paths }: FlowContext<L>, answer: SignInAnswer, next: string): Response | null;
|
|
20
|
+
export declare function landing<L extends string, C>({ options, paths }: FlowContext<L, C>, answer: SignInAnswer, next: string): Response | null;
|
|
19
21
|
/**
|
|
20
22
|
* `landing`, ending the session the browser held first: one browser holds
|
|
21
23
|
* one session, so the one its cookie names, whoever's, is revoked when an
|
|
22
24
|
* answer in `revokeOn` lands. A signed-in answer's own cookie replaces the
|
|
23
25
|
* old one; any other answer clears it. A failed revoke is ignored.
|
|
24
26
|
*/
|
|
25
|
-
export declare function landReplacing<L extends string>(
|
|
27
|
+
export declare function landReplacing<L extends string, C>(flow: FlowContext<L, C>, answer: SignInAnswer, next: string, { request, revokeOn, context, }: Parameters<SignInFlow<L, C>['landReplacing']>[2]): Promise<Response | null>;
|
|
26
28
|
//# sourceMappingURL=land.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"land.d.ts","sourceRoot":"","sources":["../../src/session/land.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAK1E;;;GAGG;AACH,wBAAsB,aAAa,CAAC,CAAC,SAAS,MAAM,
|
|
1
|
+
{"version":3,"file":"land.d.ts","sourceRoot":"","sources":["../../src/session/land.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAK1E;;;GAGG;AACH,wBAAsB,aAAa,CAAC,CAAC,SAAS,MAAM,EAAE,CAAC,EACtD,EAAE,OAAO,EAAE,EAAE,WAAW,CAAC,CAAC,EAAE,CAAC,CAAC,EAC9B,OAAO,EAAE,OAAO,EAChB,EAAE,OAAO,EAAE,GAAE;IAAE,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC,GAAG,SAAS,CAAA;CAAO,GACpD,OAAO,CAAC,OAAO,CAAC,CAUlB;AAED,kGAAkG;AAClG,eAAO,MAAM,MAAM,GAClB,QAAQ,YAAY,KAClB,MAAM,IAAI,OAAO,CAAC,YAAY,EAAE;IAAE,MAAM,EAAE,UAAU,CAAA;CAAE,CAIjB,CAAC;AAEzC;;;;;;GAMG;AACH,wBAAgB,OAAO,CAAC,CAAC,SAAS,MAAM,EAAE,CAAC,EAC1C,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE,WAAW,CAAC,CAAC,EAAE,CAAC,CAAC,EACrC,MAAM,EAAE,YAAY,EACpB,IAAI,EAAE,MAAM,GACV,QAAQ,GAAG,IAAI,CA0BjB;AAED;;;;;GAKG;AACH,wBAAsB,aAAa,CAAC,CAAC,SAAS,MAAM,EAAE,CAAC,EACtD,IAAI,EAAE,WAAW,CAAC,CAAC,EAAE,CAAC,CAAC,EACvB,MAAM,EAAE,YAAY,EACpB,IAAI,EAAE,MAAM,EACZ,EACC,OAAO,EACP,QAAuB,EACvB,OAAO,GACP,EAAE,UAAU,CAAC,UAAU,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC,CAAC,GACjD,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC,CAU1B"}
|
|
@@ -7,12 +7,14 @@ import type { AuthLinks, LinkStayed, LinksContext } from './link-types';
|
|
|
7
7
|
* first: one browser holds one session. Only a JSON post goes on, before
|
|
8
8
|
* anything is revoked or landed.
|
|
9
9
|
*/
|
|
10
|
-
export declare function landByLink<L extends string>(options: LinksContext<L>, answer: SignInAnswer, { request, language, other }: Parameters<AuthLinks<L>['landByLink']>[1]): Promise<Response | LinkStayed>;
|
|
10
|
+
export declare function landByLink<L extends string, C>(options: LinksContext<L, C>, answer: SignInAnswer, { request, language, other, context, }: Parameters<AuthLinks<L, C>['landByLink']>[1]): Promise<Response | LinkStayed>;
|
|
11
11
|
/**
|
|
12
12
|
* After a password reset, which signs the account out everywhere: the
|
|
13
13
|
* browser's session is revoked (it may be another account's) and its cookies
|
|
14
14
|
* cleared (the session's, the challenge's, the flow's `clear()`), then the sign-in page says the password changed. Only a JSON post
|
|
15
15
|
* goes on.
|
|
16
16
|
*/
|
|
17
|
-
export declare function afterReset<L extends string>(options: LinksContext<L>, resetDone: string, request: Request
|
|
17
|
+
export declare function afterReset<L extends string, C>(options: LinksContext<L, C>, resetDone: string, request: Request, { context }?: {
|
|
18
|
+
readonly context?: C | undefined;
|
|
19
|
+
}): Promise<Response>;
|
|
18
20
|
//# sourceMappingURL=link-land.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"link-land.d.ts","sourceRoot":"","sources":["../../src/session/link-land.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAIjD,OAAO,KAAK,EAAE,SAAS,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAkBxE;;;;;;GAMG;AACH,wBAAsB,UAAU,CAAC,CAAC,SAAS,MAAM,
|
|
1
|
+
{"version":3,"file":"link-land.d.ts","sourceRoot":"","sources":["../../src/session/link-land.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAIjD,OAAO,KAAK,EAAE,SAAS,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAkBxE;;;;;;GAMG;AACH,wBAAsB,UAAU,CAAC,CAAC,SAAS,MAAM,EAAE,CAAC,EACnD,OAAO,EAAE,YAAY,CAAC,CAAC,EAAE,CAAC,CAAC,EAC3B,MAAM,EAAE,YAAY,EACpB,EACC,OAAO,EACP,QAAQ,EACR,KAAK,EACL,OAAO,GACP,EAAE,UAAU,CAAC,SAAS,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,CAAC,GAC7C,OAAO,CAAC,QAAQ,GAAG,UAAU,CAAC,CAsBhC;AAED;;;;;GAKG;AACH,wBAAsB,UAAU,CAAC,CAAC,SAAS,MAAM,EAAE,CAAC,EACnD,OAAO,EAAE,YAAY,CAAC,CAAC,EAAE,CAAC,CAAC,EAC3B,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,OAAO,EAChB,EAAE,OAAO,EAAE,GAAE;IAAE,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC,GAAG,SAAS,CAAA;CAAO,GACpD,OAAO,CAAC,QAAQ,CAAC,CAUnB"}
|
|
@@ -15,7 +15,7 @@ export declare function withTokenHeaders(headers?: HeadersInit): Headers;
|
|
|
15
15
|
/** A redirect replacing the current history entry: the token's, so Back never opens it again. */
|
|
16
16
|
export declare const stateRedirect: (path: string, state: string, headers?: HeadersInit) => Response;
|
|
17
17
|
export declare function linkArgs<S extends StandardSchemaV1>({ json }: Pick<LinksContext<string>, 'json'>, invalidState: string, schema: S, request: Request, path: string): Promise<InferOutput<S>>;
|
|
18
|
-
export declare function spendToken<L extends string, T>(options: LinksContext<L>, run: () => Promise<T>, { request, language, path, states, fields: shown }: SpendOptions<L>): Promise<Spent<T>>;
|
|
18
|
+
export declare function spendToken<L extends string, C, T>(options: LinksContext<L, C>, run: () => Promise<T>, { request, language, path, states, fields: shown }: SpendOptions<L>): Promise<Spent<T>>;
|
|
19
19
|
export declare const answerOf: (spent: Exclude<Spent<unknown>, {
|
|
20
20
|
ok: true;
|
|
21
21
|
}>) => Response | LinkStayed;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"link-token.d.ts","sourceRoot":"","sources":["../../src/session/link-token.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,WAAW,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AAE/E,OAAO,KAAK,EACX,UAAU,EACV,YAAY,EACZ,YAAY,EACZ,KAAK,EACL,MAAM,cAAc,CAAC;AAGtB;;;;GAIG;AACH,eAAO,MAAM,kBAAkB;;;CAGrB,CAAC;AAMX,eAAO,MAAM,SAAS,GAAI,MAAM,MAAM,EAAE,OAAO,MAAM,KAAG,MACW,CAAC;AAEpE,mDAAmD;AACnD,wBAAgB,gBAAgB,CAAC,OAAO,CAAC,EAAE,WAAW,GAAG,OAAO,CAM/D;AAED,iGAAiG;AACjG,eAAO,MAAM,aAAa,GACzB,MAAM,MAAM,EACZ,OAAO,MAAM,EACb,UAAU,WAAW,KACnB,QACqE,CAAC;AAWzE,wBAAsB,QAAQ,CAAC,CAAC,SAAS,gBAAgB,EACxD,EAAE,IAAI,EAAE,EAAE,IAAI,CAAC,YAAY,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,EAC5C,YAAY,EAAE,MAAM,EACpB,MAAM,EAAE,CAAC,EACT,OAAO,EAAE,OAAO,EAChB,IAAI,EAAE,MAAM,GACV,OAAO,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,CAYzB;AAED,wBAAsB,UAAU,CAAC,CAAC,SAAS,MAAM,EAAE,CAAC,
|
|
1
|
+
{"version":3,"file":"link-token.d.ts","sourceRoot":"","sources":["../../src/session/link-token.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,WAAW,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AAE/E,OAAO,KAAK,EACX,UAAU,EACV,YAAY,EACZ,YAAY,EACZ,KAAK,EACL,MAAM,cAAc,CAAC;AAGtB;;;;GAIG;AACH,eAAO,MAAM,kBAAkB;;;CAGrB,CAAC;AAMX,eAAO,MAAM,SAAS,GAAI,MAAM,MAAM,EAAE,OAAO,MAAM,KAAG,MACW,CAAC;AAEpE,mDAAmD;AACnD,wBAAgB,gBAAgB,CAAC,OAAO,CAAC,EAAE,WAAW,GAAG,OAAO,CAM/D;AAED,iGAAiG;AACjG,eAAO,MAAM,aAAa,GACzB,MAAM,MAAM,EACZ,OAAO,MAAM,EACb,UAAU,WAAW,KACnB,QACqE,CAAC;AAWzE,wBAAsB,QAAQ,CAAC,CAAC,SAAS,gBAAgB,EACxD,EAAE,IAAI,EAAE,EAAE,IAAI,CAAC,YAAY,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,EAC5C,YAAY,EAAE,MAAM,EACpB,MAAM,EAAE,CAAC,EACT,OAAO,EAAE,OAAO,EAChB,IAAI,EAAE,MAAM,GACV,OAAO,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,CAYzB;AAED,wBAAsB,UAAU,CAAC,CAAC,SAAS,MAAM,EAAE,CAAC,EAAE,CAAC,EACtD,OAAO,EAAE,YAAY,CAAC,CAAC,EAAE,CAAC,CAAC,EAC3B,GAAG,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,EACrB,EAAE,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,KAAU,EAAE,EAAE,YAAY,CAAC,CAAC,CAAC,GACtE,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAsBnB;AAED,eAAO,MAAM,QAAQ,GACpB,OAAO,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,EAAE;IAAE,EAAE,EAAE,IAAI,CAAA;CAAE,CAAC,KAC1C,QAAQ,GAAG,UAG+C,CAAC"}
|
|
@@ -8,12 +8,12 @@ export interface AuthLinksCodes {
|
|
|
8
8
|
/** The API's wait on mailed links; `MAIL_THROTTLED` by default. */
|
|
9
9
|
readonly mailThrottled?: string | undefined;
|
|
10
10
|
}
|
|
11
|
-
export interface AuthLinksOptions<L extends string> {
|
|
11
|
+
export interface AuthLinksOptions<L extends string, C = undefined> {
|
|
12
12
|
/**
|
|
13
13
|
* The app's sign-in flow: it lands the sessions a link opens, and its
|
|
14
14
|
* `cookies`, `errors` and `json` are the ones the links use.
|
|
15
15
|
*/
|
|
16
|
-
readonly signIn: SignInFlow<L>;
|
|
16
|
+
readonly signIn: SignInFlow<L, C>;
|
|
17
17
|
/** The codes the links tell apart; `@nxgt/janus`'s by default. */
|
|
18
18
|
readonly codes?: AuthLinksCodes | undefined;
|
|
19
19
|
/**
|
|
@@ -35,7 +35,7 @@ export interface AuthLinksOptions<L extends string> {
|
|
|
35
35
|
readonly invalidState?: string | undefined;
|
|
36
36
|
}
|
|
37
37
|
/** The options with what they read of the flow: the functions take this. */
|
|
38
|
-
export type LinksContext<L extends string> = AuthLinksOptions<L> & Pick<SignInFlow<L>, 'cookies' | 'errors' | 'json'>;
|
|
38
|
+
export type LinksContext<L extends string, C = undefined> = AuthLinksOptions<L, C> & Pick<SignInFlow<L, C>, 'cookies' | 'errors' | 'json'>;
|
|
39
39
|
/** What a token page answers when it stays on the page. */
|
|
40
40
|
export type LinkStayed = ReturnType<typeof data<FormErrors>>;
|
|
41
41
|
/** A link request's answer: always "sent", whoever the e-mail is. */
|
|
@@ -77,7 +77,7 @@ export interface SpendOptions<L extends string> {
|
|
|
77
77
|
/** The fields the page shows a refusal under; any other is the form's message. */
|
|
78
78
|
readonly fields?: readonly string[] | undefined;
|
|
79
79
|
}
|
|
80
|
-
export interface AuthLinks<L extends string> {
|
|
80
|
+
export interface AuthLinks<L extends string, C = undefined> {
|
|
81
81
|
/** The token page in an end state, without its token: `path?state=…`. */
|
|
82
82
|
statePath(path: string, state: string): string;
|
|
83
83
|
/** A redirect to a token page's end state, replacing the token's history entry. */
|
|
@@ -112,9 +112,13 @@ export interface AuthLinks<L extends string> {
|
|
|
112
112
|
readonly path: string;
|
|
113
113
|
readonly state: string;
|
|
114
114
|
} | undefined;
|
|
115
|
+
/** The caller context handed to the flow's `revoke`. */
|
|
116
|
+
readonly context?: C | undefined;
|
|
115
117
|
}): Promise<Response | LinkStayed>;
|
|
116
118
|
/** After a password reset: the browser's session revoked and cleared, then the sign-in page. */
|
|
117
|
-
afterReset(request: Request
|
|
119
|
+
afterReset(request: Request, options?: {
|
|
120
|
+
readonly context?: C | undefined;
|
|
121
|
+
}): Promise<Response>;
|
|
118
122
|
/** Requests a mail's link with `run`; only a same-site JSON post goes on. */
|
|
119
123
|
requestLink(run: () => Promise<unknown>, options: {
|
|
120
124
|
readonly request: Request;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"link-types.d.ts","sourceRoot":"","sources":["../../src/session/link-types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,cAAc,CAAC;AACzC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AACxD,OAAO,KAAK,EAAE,WAAW,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AAC/E,OAAO,KAAK,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAE7D,kEAAkE;AAClE,MAAM,WAAW,cAAc;IAC9B,mEAAmE;IACnE,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAC5C;AAED,MAAM,WAAW,gBAAgB,CAAC,CAAC,SAAS,MAAM;
|
|
1
|
+
{"version":3,"file":"link-types.d.ts","sourceRoot":"","sources":["../../src/session/link-types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,cAAc,CAAC;AACzC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AACxD,OAAO,KAAK,EAAE,WAAW,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AAC/E,OAAO,KAAK,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAE7D,kEAAkE;AAClE,MAAM,WAAW,cAAc;IAC9B,mEAAmE;IACnE,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAC5C;AAED,MAAM,WAAW,gBAAgB,CAAC,CAAC,SAAS,MAAM,EAAE,CAAC,GAAG,SAAS;IAChE;;;OAGG;IACH,QAAQ,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IAClC,kEAAkE;IAClE,QAAQ,CAAC,KAAK,CAAC,EAAE,cAAc,GAAG,SAAS,CAAC;IAC5C;;;;OAIG;IACH,QAAQ,CAAC,OAAO,CAAC,EACd,CAAC,CAAC,KAAK,EAAE,YAAY,EAAE,QAAQ,EAAE,CAAC,KAAK,MAAM,GAAG,SAAS,CAAC,GAC1D,SAAS,CAAC;IACb;;;;;OAKG;IACH,QAAQ,CAAC,KAAK,CAAC,EACZ,CAAC,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC,GACtD,SAAS,CAAC;IACb,4EAA4E;IAC5E,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACxC,yEAAyE;IACzE,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAC3C;AAED,4EAA4E;AAC5E,MAAM,MAAM,YAAY,CAAC,CAAC,SAAS,MAAM,EAAE,CAAC,GAAG,SAAS,IAAI,gBAAgB,CAC3E,CAAC,EACD,CAAC,CACD,GACA,IAAI,CAAC,UAAU,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,SAAS,GAAG,QAAQ,GAAG,MAAM,CAAC,CAAC;AAEvD,2DAA2D;AAC3D,MAAM,MAAM,UAAU,GAAG,UAAU,CAAC,OAAO,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC;AAE7D,qEAAqE;AACrE,MAAM,MAAM,iBAAiB,GAAG;IAC/B,IAAI,CAAC,EAAE,IAAI,CAAC;IACZ,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,+FAA+F;IAC/F,SAAS,CAAC,EAAE,IAAI,CAAC;IACjB,2FAA2F;IAC3F,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,yDAAyD;IACzD,WAAW,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;CACjC,CAAC;AAEF,MAAM,MAAM,KAAK,CAAC,CAAC,IAChB;IAAE,QAAQ,CAAC,EAAE,EAAE,IAAI,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,CAAC,CAAA;CAAE;AAC1C,2DAA2D;GACzD;IAAE,QAAQ,CAAC,EAAE,EAAE,KAAK,CAAC;IAAC,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAA;CAAE;AACrD,+CAA+C;GAC7C;IACA,QAAQ,CAAC,EAAE,EAAE,KAAK,CAAC;IACnB,QAAQ,CAAC,OAAO,EAAE,UAAU,CAAC;IAC7B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACvB,CAAC;AAEL,MAAM,WAAW,YAAY,CAAC,CAAC,SAAS,MAAM;IAC7C,6EAA6E;IAC7E,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,QAAQ,EAAE,CAAC,CAAC;IACrB,0CAA0C;IAC1C,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,qEAAqE;IACrE,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC;IAC3D,kFAAkF;IAClF,QAAQ,CAAC,MAAM,CAAC,EAAE,SAAS,MAAM,EAAE,GAAG,SAAS,CAAC;CAChD;AAED,MAAM,WAAW,SAAS,CAAC,CAAC,SAAS,MAAM,EAAE,CAAC,GAAG,SAAS;IACzD,yEAAyE;IACzE,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAAC;IAC/C,mFAAmF;IACnF,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,WAAW,GAAG,QAAQ,CAAC;IACpE;;;;;;OAMG;IACH,QAAQ,CAAC,CAAC,SAAS,gBAAgB,EAClC,MAAM,EAAE,CAAC,EACT,OAAO,EAAE,OAAO,EAChB,IAAI,EAAE,MAAM,GACV,OAAO,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC;IAC3B,wIAAwI;IACxI,UAAU,CAAC,CAAC,EACX,GAAG,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,EACrB,OAAO,EAAE,YAAY,CAAC,CAAC,CAAC,GACtB,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;IACrB,oDAAoD;IACpD,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,EAAE;QAAE,EAAE,EAAE,IAAI,CAAA;KAAE,CAAC,GAAG,QAAQ,GAAG,UAAU,CAAC;IAC9E;;;;;;;OAOG;IACH,UAAU,CACT,MAAM,EAAE,YAAY,EACpB,OAAO,EAAE;QACR,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;QAC1B,QAAQ,CAAC,QAAQ,EAAE,CAAC,CAAC;QACrB,mFAAmF;QACnF,QAAQ,CAAC,KAAK,CAAC,EACZ;YAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;YAAC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;SAAE,GACjD,SAAS,CAAC;QACb,wDAAwD;QACxD,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC,GAAG,SAAS,CAAC;KACjC,GACC,OAAO,CAAC,QAAQ,GAAG,UAAU,CAAC,CAAC;IAClC,gGAAgG;IAChG,UAAU,CACT,OAAO,EAAE,OAAO,EAChB,OAAO,CAAC,EAAE;QAAE,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC,GAAG,SAAS,CAAA;KAAE,GAC5C,OAAO,CAAC,QAAQ,CAAC,CAAC;IACrB,6EAA6E;IAC7E,WAAW,CACV,GAAG,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,EAC3B,OAAO,EAAE;QAAE,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;QAAC,QAAQ,CAAC,QAAQ,EAAE,CAAC,CAAA;KAAE,GAC1D,OAAO,CAAC,UAAU,CAAC,OAAO,IAAI,CAAC,iBAAiB,CAAC,CAAC,CAAC,CAAC;CACvD"}
|
package/dist/session/links.d.ts
CHANGED
|
@@ -10,5 +10,5 @@ import type { AuthLinks, AuthLinksOptions } from './link-types';
|
|
|
10
10
|
* export const links = authLinks({ signIn });
|
|
11
11
|
* // loader: return data({ ... }, { headers: TOKEN_PAGE_HEADERS });
|
|
12
12
|
*/
|
|
13
|
-
export declare function authLinks<L extends string>(links: AuthLinksOptions<L>): AuthLinks<L>;
|
|
13
|
+
export declare function authLinks<L extends string, C = undefined>(links: AuthLinksOptions<L, C>): AuthLinks<L, C>;
|
|
14
14
|
//# sourceMappingURL=links.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"links.d.ts","sourceRoot":"","sources":["../../src/session/links.ts"],"names":[],"mappings":"AAgBA,OAAO,KAAK,EACX,SAAS,EACT,gBAAgB,EAGhB,MAAM,cAAc,CAAC;AA+DtB;;;;;;;;;;GAUG;AACH,wBAAgB,SAAS,CAAC,CAAC,SAAS,MAAM,
|
|
1
|
+
{"version":3,"file":"links.d.ts","sourceRoot":"","sources":["../../src/session/links.ts"],"names":[],"mappings":"AAgBA,OAAO,KAAK,EACX,SAAS,EACT,gBAAgB,EAGhB,MAAM,cAAc,CAAC;AA+DtB;;;;;;;;;;GAUG;AACH,wBAAgB,SAAS,CAAC,CAAC,SAAS,MAAM,EAAE,CAAC,GAAG,SAAS,EACxD,KAAK,EAAE,gBAAgB,CAAC,CAAC,EAAE,CAAC,CAAC,GAC3B,SAAS,CAAC,CAAC,EAAE,CAAC,CAAC,CAsBjB"}
|
|
@@ -6,7 +6,7 @@ import type { SecondFactorKind } from './model';
|
|
|
6
6
|
* native form post goes back to its page, which then posts JSON; a
|
|
7
7
|
* cross-site post is a 403; a body of another shape is a 400.
|
|
8
8
|
*/
|
|
9
|
-
export declare function readCodeInput<L extends string>({ options }: FlowContext<L>, request: Request): Promise<{
|
|
9
|
+
export declare function readCodeInput<L extends string, C>({ options }: FlowContext<L, C>, request: Request): Promise<{
|
|
10
10
|
kind: SecondFactorKind;
|
|
11
11
|
code: string;
|
|
12
12
|
}>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"second-factor.d.ts","sourceRoot":"","sources":["../../src/session/second-factor.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAE1E,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAQhD;;;;;GAKG;AACH,wBAAsB,aAAa,CAAC,CAAC,SAAS,MAAM,
|
|
1
|
+
{"version":3,"file":"second-factor.d.ts","sourceRoot":"","sources":["../../src/session/second-factor.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAE1E,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAQhD;;;;;GAKG;AACH,wBAAsB,aAAa,CAAC,CAAC,SAAS,MAAM,EAAE,CAAC,EACtD,EAAE,OAAO,EAAE,EAAE,WAAW,CAAC,CAAC,EAAE,CAAC,CAAC,EAC9B,OAAO,EAAE,OAAO,GACd,OAAO,CAAC;IAAE,IAAI,EAAE,gBAAgB,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC,CAWnD;AAED;;;;GAIG;AACH,wBAAgB,QAAQ,CACvB,KAAK,EAAE;IAAE,QAAQ,CAAC,IAAI,EAAE,gBAAgB,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CAAE,EACjE,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,UAAU,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,GAClD,OAAO,CAAC,YAAY,CAAC,CAIvB"}
|
|
@@ -9,9 +9,10 @@ import type { SignInFlow, SignInFlowOptions } from './flow-types';
|
|
|
9
9
|
* @example
|
|
10
10
|
* export const signIn = signInFlow({
|
|
11
11
|
* cookies, errors, json,
|
|
12
|
-
* revoke: (token, request
|
|
12
|
+
* revoke: (token, request, caller?: Caller) =>
|
|
13
|
+
* execute(SignOutMutation, {}, { token, ...caller }),
|
|
13
14
|
* attemptsLeft: (count, language) => t('auth.attempts-left', { count }, language),
|
|
14
15
|
* });
|
|
15
16
|
*/
|
|
16
|
-
export declare function signInFlow<L extends string>(options: SignInFlowOptions<L>): SignInFlow<L>;
|
|
17
|
+
export declare function signInFlow<L extends string, C = undefined>(options: SignInFlowOptions<L, C>): SignInFlow<L, C>;
|
|
17
18
|
//# sourceMappingURL=sign-in.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sign-in.d.ts","sourceRoot":"","sources":["../../src/session/sign-in.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAe,UAAU,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AAqC/E
|
|
1
|
+
{"version":3,"file":"sign-in.d.ts","sourceRoot":"","sources":["../../src/session/sign-in.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAe,UAAU,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AAqC/E;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,UAAU,CAAC,CAAC,SAAS,MAAM,EAAE,CAAC,GAAG,SAAS,EACzD,OAAO,EAAE,iBAAiB,CAAC,CAAC,EAAE,CAAC,CAAC,GAC9B,UAAU,CAAC,CAAC,EAAE,CAAC,CAAC,CA0BlB"}
|
package/dist/session/step.d.ts
CHANGED
|
@@ -14,7 +14,7 @@ export declare const unavailableMessage: <L extends string>({ unavailable, error
|
|
|
14
14
|
* message (a wrong password, a lock with its delay); an unreachable API
|
|
15
15
|
* says `unavailable`, with a 503.
|
|
16
16
|
*/
|
|
17
|
-
export declare function signInStep<L extends string, T extends object>(
|
|
17
|
+
export declare function signInStep<L extends string, C, T extends object>(flow: FlowContext<L, C>, run: () => Promise<SignInAnswer>, { request, language, next, extra, refused, context, }: Parameters<SignInFlow<L, C>['signInStep']>[1] & {
|
|
18
18
|
extra: T;
|
|
19
19
|
}): Promise<Response | SignInStayed<T>>;
|
|
20
20
|
//# sourceMappingURL=step.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"step.d.ts","sourceRoot":"","sources":["../../src/session/step.ts"],"names":[],"mappings":"AACA,OAAO,EACN,KAAK,YAAY,EAGjB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,KAAK,EACX,WAAW,EACX,WAAW,EACX,YAAY,EACZ,UAAU,EACV,iBAAiB,EAEjB,YAAY,EACZ,MAAM,cAAc,CAAC;AAItB;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,CAAC,SAAS,MAAM,EAC3C,EAAE,OAAO,EAAE,WAAW,EAAE,aAAa,EAAE,EAAE,WAAW,CAAC,CAAC,CAAC,EACvD,KAAK,EAAE,YAAY,EACnB,QAAQ,EAAE,CAAC,EACX,QAAQ,CAAC,EAAE,MAAM,GACf,MAAM,GAAG,SAAS,CAapB;AAED,0DAA0D;AAC1D,eAAO,MAAM,kBAAkB,GAAI,CAAC,SAAS,MAAM,EAClD,yBAAyB,IAAI,CAAC,iBAAiB,CAAC,CAAC,CAAC,EAAE,aAAa,GAAG,QAAQ,CAAC,EAC7E,UAAU,CAAC,KACT,MAAwE,CAAC;AAE5E;;;;;GAKG;AACH,wBAAsB,UAAU,CAAC,CAAC,SAAS,MAAM,EAAE,CAAC,SAAS,MAAM,
|
|
1
|
+
{"version":3,"file":"step.d.ts","sourceRoot":"","sources":["../../src/session/step.ts"],"names":[],"mappings":"AACA,OAAO,EACN,KAAK,YAAY,EAGjB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,KAAK,EACX,WAAW,EACX,WAAW,EACX,YAAY,EACZ,UAAU,EACV,iBAAiB,EAEjB,YAAY,EACZ,MAAM,cAAc,CAAC;AAItB;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,CAAC,SAAS,MAAM,EAC3C,EAAE,OAAO,EAAE,WAAW,EAAE,aAAa,EAAE,EAAE,WAAW,CAAC,CAAC,CAAC,EACvD,KAAK,EAAE,YAAY,EACnB,QAAQ,EAAE,CAAC,EACX,QAAQ,CAAC,EAAE,MAAM,GACf,MAAM,GAAG,SAAS,CAapB;AAED,0DAA0D;AAC1D,eAAO,MAAM,kBAAkB,GAAI,CAAC,SAAS,MAAM,EAClD,yBAAyB,IAAI,CAAC,iBAAiB,CAAC,CAAC,CAAC,EAAE,aAAa,GAAG,QAAQ,CAAC,EAC7E,UAAU,CAAC,KACT,MAAwE,CAAC;AAE5E;;;;;GAKG;AACH,wBAAsB,UAAU,CAAC,CAAC,SAAS,MAAM,EAAE,CAAC,EAAE,CAAC,SAAS,MAAM,EACrE,IAAI,EAAE,WAAW,CAAC,CAAC,EAAE,CAAC,CAAC,EACvB,GAAG,EAAE,MAAM,OAAO,CAAC,YAAY,CAAC,EAChC,EACC,OAAO,EACP,QAAQ,EACR,IAAI,EACJ,KAAK,EACL,OAAO,EACP,OAAO,GACP,EAAE,UAAU,CAAC,UAAU,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG;IAAE,KAAK,EAAE,CAAC,CAAA;CAAE,GAC7D,OAAO,CAAC,QAAQ,GAAG,YAAY,CAAC,CAAC,CAAC,CAAC,CAwBrC"}
|
package/dist/session/viewer.d.ts
CHANGED
|
@@ -7,18 +7,18 @@ export declare const nextParam: (request: Request) => string;
|
|
|
7
7
|
* request that carried a session cookie says it ended when `endedParam` is
|
|
8
8
|
* set.
|
|
9
9
|
*/
|
|
10
|
-
export declare function signInRedirect<L extends string>({ options, paths }: FlowContext<L>, request: Request): Response;
|
|
10
|
+
export declare function signInRedirect<L extends string, C>({ options, paths }: FlowContext<L, C>, request: Request): Response;
|
|
11
11
|
/**
|
|
12
12
|
* For the sign-in pages: the viewer when the cookie holds a live session,
|
|
13
13
|
* `null` when the API refuses it (a 401), `undefined` without a cookie or
|
|
14
14
|
* when the API cannot tell.
|
|
15
15
|
*/
|
|
16
|
-
export declare function viewer<L extends string, V>({ options }: FlowContext<L>, request: Request, read: (token: string) => Promise<V | null>, token?: string | null | undefined): Promise<V | null | undefined>;
|
|
16
|
+
export declare function viewer<L extends string, C, V>({ options }: FlowContext<L, C>, request: Request, read: (token: string) => Promise<V | null>, token?: string | null | undefined): Promise<V | null | undefined>;
|
|
17
17
|
/**
|
|
18
18
|
* The signed-in viewer, else the sign-in redirect. Its `headers` move the
|
|
19
19
|
* cookie's expiry to the session's when the API moved it.
|
|
20
20
|
*/
|
|
21
|
-
export declare function requireViewer<L extends string, V>(context: FlowContext<L>, request: Request, read: (token: string) => Promise<V | null>, expiresAt: (viewer: V) => string | null): Promise<{
|
|
21
|
+
export declare function requireViewer<L extends string, C, V>(context: FlowContext<L, C>, request: Request, read: (token: string) => Promise<V | null>, expiresAt: (viewer: V) => string | null): Promise<{
|
|
22
22
|
viewer: V;
|
|
23
23
|
headers: HeadersInit;
|
|
24
24
|
}>;
|
|
@@ -27,7 +27,8 @@ export declare function requireViewer<L extends string, V>(context: FlowContext<
|
|
|
27
27
|
* `to`. The body is read as JSON first: JSON and same-origin only, so
|
|
28
28
|
* another site cannot sign the user out.
|
|
29
29
|
*/
|
|
30
|
-
export declare function signOut<L extends string>(
|
|
30
|
+
export declare function signOut<L extends string, C>(flow: FlowContext<L, C>, request: Request, { to, context, }?: {
|
|
31
31
|
readonly to?: string | undefined;
|
|
32
|
+
readonly context?: C | undefined;
|
|
32
33
|
}): Promise<Response>;
|
|
33
34
|
//# sourceMappingURL=viewer.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"viewer.d.ts","sourceRoot":"","sources":["../../src/session/viewer.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAIhD,sEAAsE;AACtE,eAAO,MAAM,SAAS,GAAI,SAAS,OAAO,KAAG,MACW,CAAC;AAEzD;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,CAAC,SAAS,MAAM,
|
|
1
|
+
{"version":3,"file":"viewer.d.ts","sourceRoot":"","sources":["../../src/session/viewer.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAIhD,sEAAsE;AACtE,eAAO,MAAM,SAAS,GAAI,SAAS,OAAO,KAAG,MACW,CAAC;AAEzD;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,CAAC,SAAS,MAAM,EAAE,CAAC,EACjD,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE,WAAW,CAAC,CAAC,EAAE,CAAC,CAAC,EACrC,OAAO,EAAE,OAAO,GACd,QAAQ,CAWV;AAED;;;;GAIG;AACH,wBAAsB,MAAM,CAAC,CAAC,SAAS,MAAM,EAAE,CAAC,EAAE,CAAC,EAClD,EAAE,OAAO,EAAE,EAAE,WAAW,CAAC,CAAC,EAAE,CAAC,CAAC,EAC9B,OAAO,EAAE,OAAO,EAChB,IAAI,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC,EAC1C,KAAK,GAAE,MAAM,GAAG,IAAI,GAAG,SAA0C,GAC/D,OAAO,CAAC,CAAC,GAAG,IAAI,GAAG,SAAS,CAAC,CAO/B;AAED;;;GAGG;AACH,wBAAsB,aAAa,CAAC,CAAC,SAAS,MAAM,EAAE,CAAC,EAAE,CAAC,EACzD,OAAO,EAAE,WAAW,CAAC,CAAC,EAAE,CAAC,CAAC,EAC1B,OAAO,EAAE,OAAO,EAChB,IAAI,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC,EAC1C,SAAS,EAAE,CAAC,MAAM,EAAE,CAAC,KAAK,MAAM,GAAG,IAAI,GACrC,OAAO,CAAC;IAAE,MAAM,EAAE,CAAC,CAAC;IAAC,OAAO,EAAE,WAAW,CAAA;CAAE,CAAC,CAiB9C;AAED;;;;GAIG;AACH,wBAAsB,OAAO,CAAC,CAAC,SAAS,MAAM,EAAE,CAAC,EAChD,IAAI,EAAE,WAAW,CAAC,CAAC,EAAE,CAAC,CAAC,EACvB,OAAO,EAAE,OAAO,EAChB,EACC,EAAE,EACF,OAAO,GACP,GAAE;IACF,QAAQ,CAAC,EAAE,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACjC,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC,GAAG,SAAS,CAAC;CAC5B,GACJ,OAAO,CAAC,QAAQ,CAAC,CAcnB"}
|
package/docs/guide/session.md
CHANGED
|
@@ -375,8 +375,9 @@ the form's refusals, the second factor, the viewer and sign-out. Build it once
|
|
|
375
375
|
([the example](#a-complete-sign-in) does, in `app/session.server.ts`), over the
|
|
376
376
|
app's `createApiErrors` and `jsonActions` (the same instances its other
|
|
377
377
|
actions use, so every body is read with the same same-origin check).
|
|
378
|
-
`revoke(token, request)` ends a session at the API and receives the
|
|
379
|
-
for the caller's
|
|
378
|
+
`revoke(token, request, context)` ends a session at the API and receives the
|
|
379
|
+
request, for its abort signal, and [the caller's context](#the-callers-context),
|
|
380
|
+
for the visitor's address and language.
|
|
380
381
|
|
|
381
382
|
`toAnswer` maps the API's words to `@nxgt/janus`'s: a
|
|
382
383
|
`SignInAnswer` is `{ status: 'signedIn', token, expiresAt }`,
|
|
@@ -440,6 +441,52 @@ answers `true` when the request carried one.
|
|
|
440
441
|
through `signInStep` (a link a mail carries). `revokeOn` lists the statuses
|
|
441
442
|
that end the browser's session; `['signedIn']` by default.
|
|
442
443
|
|
|
444
|
+
### The caller's context
|
|
445
|
+
|
|
446
|
+
A bare `Request` carries no socket, so `revoke` cannot resolve the visitor's
|
|
447
|
+
address from it, and reading `X-Forwarded-For` would trust a header the browser
|
|
448
|
+
can forge. The address and the language come from alxia's trusted proxy
|
|
449
|
+
resolution, which your loader or action reads from its `context`, and which
|
|
450
|
+
every other call to the API already carries. Hand that to the flow: every step
|
|
451
|
+
that can revoke takes an optional `context`, and passes it to `revoke` as its
|
|
452
|
+
third argument.
|
|
453
|
+
|
|
454
|
+
Name its type with the `revoke` parameter's annotation, and `signInFlow` infers
|
|
455
|
+
it (`C`) for every step. Without a context, `revoke` receives `undefined`.
|
|
456
|
+
|
|
457
|
+
```ts
|
|
458
|
+
// app/session.server.ts
|
|
459
|
+
import type { AlxiaContextOf } from '@alxia/react-router';
|
|
460
|
+
|
|
461
|
+
// The app's registered context: `ip` is the address alxia resolved
|
|
462
|
+
// (through its `proxy` option), and `language` is there when the app
|
|
463
|
+
// registers `@alxia/language`.
|
|
464
|
+
type Caller = AlxiaContextOf;
|
|
465
|
+
|
|
466
|
+
export const signIn = signInFlow({
|
|
467
|
+
cookies,
|
|
468
|
+
errors,
|
|
469
|
+
json,
|
|
470
|
+
revoke: (token, request, caller?: Caller) =>
|
|
471
|
+
execute(SignOutMutation, {}, { token, signal: request.signal, caller }),
|
|
472
|
+
attemptsLeft: (count, language) => t('auth.attempts-left', { count }, language),
|
|
473
|
+
});
|
|
474
|
+
|
|
475
|
+
// app/routes/sign-out.ts
|
|
476
|
+
export async function action({ context, request }: Route.ActionArgs) {
|
|
477
|
+
return signIn.signOut(request, { context: alxiaOf(context) });
|
|
478
|
+
}
|
|
479
|
+
```
|
|
480
|
+
|
|
481
|
+
The same `context` is taken by `revokeSession(request, { context })`,
|
|
482
|
+
`landReplacing(answer, next, { request, context })`,
|
|
483
|
+
`signInStep(run, { ..., context })`, and, on `authLinks`, by
|
|
484
|
+
`landByLink(answer, { ..., context })` and `afterReset(request, { context })`.
|
|
485
|
+
A callback written with two parameters keeps working. With an explicit
|
|
486
|
+
`signInFlow<Lang>`, `C` stays `undefined` and a `context` is a type error:
|
|
487
|
+
write both type arguments (`signInFlow<Lang, Caller>`) or none (see
|
|
488
|
+
[the troubleshooting entry](../troubleshooting.md#type--is-not-assignable-to-type-undefined-passing-a-context-to-a-step)).
|
|
489
|
+
|
|
443
490
|
### Which steps guard themselves
|
|
444
491
|
|
|
445
492
|
Every step that signs in, out, spends or mails runs the same guard before it
|
package/docs/troubleshooting.md
CHANGED
|
@@ -90,6 +90,7 @@ a loader, a message React Router or the browser prints, or an error from
|
|
|
90
90
|
- [`TypeError: linkArgs(): a token page's path must be a path of this site with no query or fragment, starting with a single "/"; got ….`](#typeerror-linkargs-a-token-pages-path-must-be-a-path-of-this-site-with-no-query-or-fragment-starting-with-a-single--got-)
|
|
91
91
|
- [`TypeError: spendToken(): a token page's path must be a path of this site with no query or fragment, starting with a single "/"; got ….`](#typeerror-spendtoken-a-token-pages-path-must-be-a-path-of-this-site-with-no-query-or-fragment-starting-with-a-single--got-)
|
|
92
92
|
- [`TypeError: authLinks(): resetDone must be a path of this site, starting with a single "/"; got ….`](#typeerror-authlinks-resetdone-must-be-a-path-of-this-site-starting-with-a-single--got-)
|
|
93
|
+
- [`Type '…' is not assignable to type 'undefined'` passing a context to a step](#type--is-not-assignable-to-type-undefined-passing-a-context-to-a-step)
|
|
93
94
|
|
|
94
95
|
**Types**
|
|
95
96
|
|
|
@@ -2671,6 +2672,33 @@ leading `/` is refused.
|
|
|
2671
2672
|
authLinks({ signIn, resetDone: '/sign-in?password-reset=1' });
|
|
2672
2673
|
```
|
|
2673
2674
|
|
|
2675
|
+
### `Type '…' is not assignable to type 'undefined'` passing a context to a step
|
|
2676
|
+
|
|
2677
|
+
`tsc` refuses `signIn.signOut(request, { context: alxiaOf(context) })`, or the
|
|
2678
|
+
`context` of `revokeSession`, `landReplacing`, `signInStep`, `landByLink` or
|
|
2679
|
+
`afterReset`, with:
|
|
2680
|
+
|
|
2681
|
+
```text
|
|
2682
|
+
Type 'AlxiaContextOf<…>' is not assignable to type 'undefined'.
|
|
2683
|
+
```
|
|
2684
|
+
|
|
2685
|
+
**Why.** The flow was built with `signInFlow<Lang>(…)`. Giving one type
|
|
2686
|
+
argument stops TypeScript from inferring the second, `C`, the caller context,
|
|
2687
|
+
which then keeps its default, `undefined`. Every step's `context` is then
|
|
2688
|
+
typed `undefined`.
|
|
2689
|
+
|
|
2690
|
+
**Fix.** Write both type arguments, or none, and let `L` and `C` be inferred
|
|
2691
|
+
from `errors` and the annotated third parameter of `revoke`:
|
|
2692
|
+
|
|
2693
|
+
```ts
|
|
2694
|
+
type Caller = AlxiaContextOf;
|
|
2695
|
+
|
|
2696
|
+
export const signIn = signInFlow<Lang, Caller>({
|
|
2697
|
+
// …
|
|
2698
|
+
revoke: (token, request, caller) => revokeAtApi(token, caller),
|
|
2699
|
+
});
|
|
2700
|
+
```
|
|
2701
|
+
|
|
2674
2702
|
### A token page's token shows in a Referer or a search result
|
|
2675
2703
|
|
|
2676
2704
|
The token page's URL appears in another site's logs, or a search engine lists
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@alxia/react-router",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.15.1",
|
|
4
4
|
"description": "alxia as the server of a React Router framework app: server rendering behind the app's hooks, loaders reading its typed context, the client's assets served; the code with no UI an installable app shares (`/pwa`), its hooks (`/pwa/react`), `noCache` for the worker's script (`/pwa/server`), the service worker on Workbox (`/pwa/worker`) and its build (`/pwa/build`), an API's refusals worded for the page (`/errors`), the BFF session: cookies, sign-in, step-up and mail links (`/session`) and what it shares with the page (`/session/model`), the page's CSP nonce and inline boot scripts (`/document`) and a streaming `entry.server` (`/entry`)",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -111,9 +111,9 @@
|
|
|
111
111
|
]
|
|
112
112
|
},
|
|
113
113
|
"devDependencies": {
|
|
114
|
-
"@alxia/compress": "^0.3.
|
|
115
|
-
"@alxia/core": "^0.
|
|
116
|
-
"@alxia/openapi": "^0.6.
|
|
114
|
+
"@alxia/compress": "^0.3.11",
|
|
115
|
+
"@alxia/core": "^0.16.0",
|
|
116
|
+
"@alxia/openapi": "^0.6.11",
|
|
117
117
|
"@nxgt/graphql-client": "^0.4.0",
|
|
118
118
|
"@nxgt/janus": "^0.29.0",
|
|
119
119
|
"@react-router/dev": "^8.5.0",
|
|
@@ -136,7 +136,7 @@
|
|
|
136
136
|
"zod": "^4.2.0"
|
|
137
137
|
},
|
|
138
138
|
"peerDependencies": {
|
|
139
|
-
"@alxia/core": "^0.
|
|
139
|
+
"@alxia/core": "^0.16.0",
|
|
140
140
|
"isbot": "^5.0.0",
|
|
141
141
|
"react": "^19.0.0",
|
|
142
142
|
"react-dom": "^19.0.0",
|