@idp.global/sdk 19.3.0 → 19.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/changelog.md +66 -0
  2. package/dist_ts/00_commitinfo_data.js +1 -1
  3. package/dist_ts_browser/classes.idpclient.js +1 -1
  4. package/dist_ts_browser/classes.idprequests.js +1 -1
  5. package/dist_ts_browser/classes.relyingpartybearerclient.d.ts +313 -0
  6. package/dist_ts_browser/classes.relyingpartybearerclient.js +646 -0
  7. package/dist_ts_browser/index.d.ts +1 -0
  8. package/dist_ts_browser/index.js +2 -1
  9. package/dist_ts_browser/plugins.js +1 -1
  10. package/dist_ts_browser/secret-delivery.js +1 -1
  11. package/dist_ts_relyingparty/classes.authorityobserver.d.ts +23 -0
  12. package/dist_ts_relyingparty/classes.authorityobserver.js +68 -13
  13. package/dist_ts_relyingparty/classes.errors.d.ts +5 -1
  14. package/dist_ts_relyingparty/classes.errors.js +3 -1
  15. package/dist_ts_relyingparty/classes.relyingpartyroutes.d.ts +12 -0
  16. package/dist_ts_relyingparty/classes.relyingpartyroutes.js +33 -6
  17. package/dist_ts_relyingparty/classes.relyingpartysessions.d.ts +161 -11
  18. package/dist_ts_relyingparty/classes.relyingpartysessions.js +381 -55
  19. package/dist_ts_relyingparty/classes.rpmodels.d.ts +6 -4
  20. package/dist_ts_relyingparty/classes.rpmodels.js +8 -6
  21. package/dist_ts_relyingparty/contract.bearer.d.ts +119 -0
  22. package/dist_ts_relyingparty/contract.bearer.js +13 -0
  23. package/dist_ts_relyingparty/helpers.bearer.d.ts +3 -4
  24. package/dist_ts_relyingparty/helpers.bearer.js +4 -6
  25. package/dist_ts_relyingparty/interfaces.d.ts +75 -26
  26. package/package.json +2 -1
  27. package/readme.md +363 -21
  28. package/ts/00_commitinfo_data.ts +1 -1
  29. package/ts_browser/classes.relyingpartybearerclient.ts +901 -0
  30. package/ts_browser/index.ts +1 -0
  31. package/ts_relyingparty/classes.authorityobserver.ts +80 -12
  32. package/ts_relyingparty/classes.errors.ts +7 -1
  33. package/ts_relyingparty/classes.relyingpartyroutes.ts +38 -7
  34. package/ts_relyingparty/classes.relyingpartysessions.ts +463 -65
  35. package/ts_relyingparty/classes.rpmodels.ts +7 -4
  36. package/ts_relyingparty/contract.bearer.ts +145 -0
  37. package/ts_relyingparty/helpers.bearer.ts +10 -14
  38. package/ts_relyingparty/interfaces.ts +93 -33
@@ -1,6 +1,6 @@
1
1
  import { type IOidcProvider } from './classes.idpoidcprovider.js';
2
2
  import type { IRelyingPartyModels } from './classes.rpmodels.js';
3
- import type { IAuthorityTokenSource, IProviderToken, IRelyingPartyBearerDelivery, IRelyingPartyBearerHandoff, IRelyingPartyBearerLoginRequest, IRelyingPartyBearerLoginStart, IRelyingPartyBearerOptions, IRelyingPartyBearerReturnTargets, IRelyingPartyBearerTokens, IRelyingPartyHooks, IRelyingPartyLiveSession, IRelyingPartyLoginOptions, IRelyingPartyLoginStart, IRelyingPartySession, IRelyingPartySessionOpening, IResolvedRelyingPartySession, TRelyingPartyPrincipals, TRelyingPartySessionEndReason } from './interfaces.js';
3
+ import type { IAuthorityTokenSource, IProviderToken, IRelyingPartyBearerDelivery, IRelyingPartyBearerHandoff, IRelyingPartyBearerHandoffOpening, IRelyingPartyBearerLoginRequest, IRelyingPartyBearerLoginStart, IRelyingPartyBearerOptions, IRelyingPartyBearerReturnTargets, IRelyingPartyBearerTokens, IRelyingPartyHooks, IRelyingPartyLiveSession, IRelyingPartyLoginOptions, IRelyingPartyLoginStart, IRelyingPartySession, IRelyingPartySessionOpening, IRelyingPartySessionsErasure, IRelyingPartySessionsPage, IRelyingPartySessionsPageOptions, IResolvedRelyingPartySession, TRelyingPartyPrincipals, TRelyingPartySessionEndReason } from './interfaces.js';
4
4
  export declare const RELYING_PARTY_DEFAULT_SESSION_MAX_AGE_SECONDS: number;
5
5
  export declare const RELYING_PARTY_DEFAULT_LOGIN_PATH_PREFIX = "/auth/";
6
6
  /** The label the CSRF HMAC starts with unless the application names its own. */
@@ -9,6 +9,10 @@ export declare const RELYING_PARTY_DEFAULT_CSRF_LABEL: string;
9
9
  export declare const LOGIN_ATTEMPT_MAX_AGE_SECONDS: number;
10
10
  /** The longest intent a login carries (`IRelyingPartyLoginOptions.intent`), in characters. */
11
11
  export declare const RELYING_PARTY_MAX_INTENT_LENGTH = 4096;
12
+ /** How many sessions one page of `sessionsOfPrincipal` holds unless its `limit` says otherwise. */
13
+ export declare const RELYING_PARTY_DEFAULT_SESSIONS_PAGE_LIMIT = 100;
14
+ /** The largest `limit` of `sessionsOfPrincipal`; the store reads no larger page. */
15
+ export declare const RELYING_PARTY_MAX_SESSIONS_PAGE_LIMIT = 1000;
12
16
  /** How long a bearer hand-off code can be claimed, at most; never past its session. */
13
17
  export declare const BEARER_HANDOFF_MAX_AGE_SECONDS = 60;
14
18
  /** How long a bearer access token lives unless `bearer.accessTokenMaxAgeSeconds` says otherwise. */
@@ -40,6 +44,12 @@ export interface IRelyingPartySessionsOptions<TPrincipal extends object | undefi
40
44
  sessionEncryptionKey: Buffer;
41
45
  /** 32 bytes; the CSRF HMAC key */
42
46
  csrfKey: Buffer;
47
+ /**
48
+ * `true` (the default): cookie sessions with a session-bound CSRF token,
49
+ * beside bearer hand-off mode when `bearer` is given. Bearer hand-off alone,
50
+ * without any cookie, is `IRelyingPartyBearerOnlySessionsOptions`.
51
+ */
52
+ cookieSessions?: true;
43
53
  /** the label the CSRF HMAC starts with; an application with live sessions keeps its own */
44
54
  csrfLabel?: string;
45
55
  sessionMaxAgeSeconds?: number;
@@ -63,16 +73,43 @@ export interface IRelyingPartySessionsOptions<TPrincipal extends object | undefi
63
73
  /** the clock every comparison reads; tests step it instead of sleeping */
64
74
  now?: () => number;
65
75
  }
76
+ /**
77
+ * Bearer-only mode (`cookieSessions: false`): the application's own clients
78
+ * sign in through bearer hand-off and nothing else. The relying party never
79
+ * reads or sets a cookie — not even the clearing intent cookie of a callback
80
+ * that is no bearer login — so it has no CSRF token and takes no CSRF key:
81
+ * `csrfKey`, `csrfLabel` and `cookieNames` are refused, not ignored. The
82
+ * cookie steps (`openSession`, `startLogin`, `completeLogin`, `abandonLogin`,
83
+ * `finalizeLogin`, `clearSessionCookie`, `clearIntentCookie`) throw
84
+ * `RelyingPartyError('login_method_unavailable')` and `resolveCookieSession`
85
+ * answers `null`.
86
+ */
87
+ export interface IRelyingPartyBearerOnlySessionsOptions<TPrincipal extends object | undefined = undefined> extends Omit<IRelyingPartySessionsOptions<TPrincipal>, 'provider' | 'csrfKey' | 'csrfLabel' | 'cookieNames' | 'cookieSessions' | 'bearer'> {
88
+ cookieSessions: false;
89
+ /** every bearer session comes from a live provider login, directly or through a hand-off opened from one */
90
+ provider: IOidcProvider;
91
+ bearer: IRelyingPartyBearerOptions;
92
+ csrfKey?: never;
93
+ csrfLabel?: never;
94
+ cookieNames?: never;
95
+ }
66
96
  /**
67
97
  * The relying-party session layer: provider logins through the authorization
68
98
  * code flow with PKCE, HttpOnly cookie sessions with a session-bound CSRF
69
99
  * token, provider tokens sealed at rest and rotated under a cross-replica
70
100
  * lock, and session ends that hand the tokens back.
71
101
  *
72
- * Nothing here runs on a timer: expired login attempts are swept when the next
73
- * login starts, and the only timer — the revocation budget — is unreferenced
74
- * and cleared on every path. Nothing here does I/O while it is constructed,
75
- * so it is built before the database connects.
102
+ * With `cookieSessions: false` it runs bearer hand-off mode alone and never
103
+ * touches a cookie (`IRelyingPartyBearerOnlySessionsOptions`).
104
+ *
105
+ * Nothing here runs on a timer: expired login attempts, bearer state and
106
+ * ended sessions are swept one bounded batch at a time when a login starts or
107
+ * the application opens a session, and the only timer — the revocation
108
+ * budget — is unreferenced and cleared on every path. Every session comes
109
+ * from a login start, `openSession` or `openBearerHandoff`, and each of those
110
+ * sweeps a batch of at least one first, so removal keeps pace with creation
111
+ * and the ended sessions kept stay bounded. Nothing here does I/O while it is
112
+ * constructed, so it is built before the database connects.
76
113
  *
77
114
  * `TPrincipal` is what a `principals.resolve` answers; with `isActive`
78
115
  * principals it stays `undefined`.
@@ -85,9 +122,11 @@ export declare class RelyingPartySessions<TPrincipal extends object | undefined
85
122
  readonly intentCookieName: string;
86
123
  readonly loginPathPrefix: string;
87
124
  readonly sessionMaxAgeSeconds: number;
88
- constructor(optionsArg: IRelyingPartySessionsOptions<TPrincipal>);
125
+ constructor(optionsArg: IRelyingPartySessionsOptions<TPrincipal> | IRelyingPartyBearerOnlySessionsOptions<TPrincipal>);
89
126
  /** Whether provider logins are offered at all. */
90
127
  get providerLoginEnabled(): boolean;
128
+ /** Whether cookie sessions are offered; `false` only in bearer-only mode (`cookieSessions: false`). */
129
+ get cookieSessionsEnabled(): boolean;
91
130
  /** Whether bearer hand-off mode is offered. */
92
131
  get bearerLoginEnabled(): boolean;
93
132
  /** The return targets bearer clients may name, per kind, as configured; `undefined` without bearer mode. */
@@ -109,7 +148,11 @@ export declare class RelyingPartySessions<TPrincipal extends object | undefined
109
148
  principalId: string;
110
149
  maxAgeSeconds?: number;
111
150
  }): Promise<IRelyingPartySessionOpening>;
112
- /** The live session behind the session cookie, or `null`. A read touches it at most once a minute. */
151
+ /**
152
+ * The live session behind the session cookie, or `null`. A read touches it
153
+ * at most once a minute. Always `null` in bearer-only mode, which reads no
154
+ * cookie.
155
+ */
113
156
  resolveCookieSession(cookieHeaderArg: string | null | undefined): Promise<IResolvedRelyingPartySession<TPrincipal> | null>;
114
157
  /**
115
158
  * Re-checks a session by its id with the same strength as a cookie request:
@@ -142,6 +185,34 @@ export declare class RelyingPartySessions<TPrincipal extends object | undefined
142
185
  endSessionsOfPrincipal(principalIdArg: string, optionsArg?: {
143
186
  keepSessionId?: string;
144
187
  }): Promise<number>;
188
+ /**
189
+ * Erases every session of a principal, live or ended, and every row that
190
+ * belongs to one, without waiting for a sweep: what an application runs
191
+ * when it deletes an account, after its own erasers and before its user
192
+ * record.
193
+ *
194
+ * First each session not revoked yet, an expired one too, ends as
195
+ * `endSession` ends it: its tokens stop working at once, `onSessionEnded`
196
+ * names the principal, and its provider tokens go back to idp.global,
197
+ * which a deleted row could no longer do. Then each session's rows go: in
198
+ * bearer mode its hand-offs, claimed or not, and its token generations;
199
+ * the login attempt a provider session came out of; and the session row
200
+ * last, because it is the only link from the principal to the others, so
201
+ * an erase that fails half way leaves a retry everything it needs. A
202
+ * session opened or a token issued while the erase runs is not covered;
203
+ * it works only while the principal is active.
204
+ */
205
+ eraseSessionsOfPrincipal(principalIdArg: string): Promise<IRelyingPartySessionsErasure>;
206
+ /**
207
+ * One page of a principal's live sessions, newest first: the ones neither
208
+ * ended nor expired, and none at all while the principal is not active —
209
+ * exactly the sessions `isSessionLive` confirms. Pass the page's
210
+ * `nextCursor` as `cursor` to read the next one; the last page has none.
211
+ * The cursor is opaque, and one this method did not answer is refused with
212
+ * `invalid_sessions_cursor`. A session opened while the pages are read is
213
+ * listed only when it sorts after the cursor; none is listed twice.
214
+ */
215
+ sessionsOfPrincipal(principalIdArg: string, optionsArg?: IRelyingPartySessionsPageOptions): Promise<IRelyingPartySessionsPage>;
145
216
  clearSessionCookie(): string;
146
217
  clearIntentCookie(): string;
147
218
  /**
@@ -195,6 +266,35 @@ export declare class RelyingPartySessions<TPrincipal extends object | undefined
195
266
  * at most and only its hash is stored. A code nobody claims ends its session.
196
267
  */
197
268
  completeBearerLogin(callbackUrlArg: URL): Promise<IRelyingPartyBearerHandoff>;
269
+ /**
270
+ * Opens a bearer session for a device the application pairs to a person
271
+ * who is signed in here, and answers its single-use hand-off code, which
272
+ * the device claims at `bearer/claim` with the verifier of `codeChallenge`,
273
+ * exactly like the code of a bearer login.
274
+ *
275
+ * `openerSessionId` names the session that confirmed the pairing, taken
276
+ * from the application's own record, never from the device. It must be a
277
+ * live provider login: not ended, not expired, its principal active, and
278
+ * with a provider login of its own. A session the application opened
279
+ * itself or one opened here does not qualify, so a paired device never
280
+ * pairs another one. Anything else throws
281
+ * `RelyingPartyError('invalid_handoff_opener')`.
282
+ *
283
+ * The new session acts as the opener's principal and has no provider login
284
+ * of its own: it ends by its expiry, `endSession`, `endSessionsOfPrincipal`,
285
+ * `bearer/logout` or the reuse of one of its credentials, never by an
286
+ * idp.global logout, and `AuthorityObserver` refuses it
287
+ * (`authority_login_required`). `maxAgeSeconds` is a whole number from 1 to
288
+ * `sessionMaxAgeSeconds`, the default, as for `openSession`. The code lives
289
+ * `BEARER_HANDOFF_MAX_AGE_SECONDS` at most and never past the session, and
290
+ * only its hash is stored; a code nobody claims ends its session. Nothing
291
+ * is stored for a refused opening.
292
+ */
293
+ openBearerHandoff(optionsArg: {
294
+ openerSessionId: string;
295
+ codeChallenge: string;
296
+ maxAgeSeconds?: number;
297
+ }): Promise<IRelyingPartyBearerHandoffOpening>;
198
298
  /** `abandonLogin` for a bearer login: the provider came back without a code. */
199
299
  abandonBearerLogin(callbackUrlArg: URL): Promise<void>;
200
300
  /**
@@ -210,9 +310,11 @@ export declare class RelyingPartySessions<TPrincipal extends object | undefined
210
310
  * access and refresh token answer, and the old access token stops working.
211
311
  * Rotation is strict — a client serializes its refreshes — because a
212
312
  * refresh token that comes back after it was spent ends the whole session.
213
- * The provider login is checked on the way: a login idp.global ended ends
214
- * the session here (`invalid_bearer_grant`), an unreachable provider fails
215
- * with `AuthorityTokenError('authority_unavailable')` and spends nothing.
313
+ * The provider login of a bearer login is checked on the way: a login
314
+ * idp.global ended ends the session here (`invalid_bearer_grant`), an
315
+ * unreachable provider fails with `AuthorityTokenError('authority_unavailable')`
316
+ * and spends nothing. A session `openBearerHandoff` opened has no provider
317
+ * login (no `idpSubject` is stored) and rotates without the provider.
216
318
  */
217
319
  refreshBearerTokens(refreshTokenArg: unknown): Promise<IRelyingPartyBearerTokens>;
218
320
  /**
@@ -235,8 +337,25 @@ export declare class RelyingPartySessions<TPrincipal extends object | undefined
235
337
  * is unknown — is rotated first. The session has to belong to
236
338
  * `expectedPrincipalIdArg`, so a credential is never evaluated with another
237
339
  * person's tokens.
340
+ *
341
+ * With `rejectedAccessToken` (`IAuthorityTokenSource`) the call rotates the
342
+ * session's token whatever is stored, and only a rotation that it starts
343
+ * marks its answer `rotatedAfterRejection`: one that started before it, in
344
+ * this process or another replica's, says nothing about the rejection, so
345
+ * the call awaits it and throws `authority_unavailable`. A refused rotation
346
+ * ends the session (`refresh_token_refused`) and throws
347
+ * `authority_login_required`. A session without a refresh token has nothing
348
+ * to rotate with: it throws `authority_unavailable`, or ends as
349
+ * `access_token_unusable` when its token is dead.
350
+ */
351
+ liveProviderToken(sessionIdArg: string, expectedPrincipalIdArg: string, optionsArg?: {
352
+ rejectedAccessToken?: string;
353
+ }): Promise<IProviderToken>;
354
+ /**
355
+ * One rotation of the session in this process at a time. A forced call that
356
+ * finds one running waits for it, so a refusal still ends it, and is
357
+ * unavailable otherwise: that rotation started before the call.
238
358
  */
239
- liveProviderToken(sessionIdArg: string, expectedPrincipalIdArg: string): Promise<IProviderToken>;
240
359
  private refreshProviderToken;
241
360
  private runProviderRefresh;
242
361
  /**
@@ -246,6 +365,7 @@ export declare class RelyingPartySessions<TPrincipal extends object | undefined
246
365
  */
247
366
  private tokenAfterForeignRefresh;
248
367
  private requireProvider;
368
+ private requireCookieSessions;
249
369
  private requireBearer;
250
370
  private checkLoginOptions;
251
371
  /** An unsaved attempt with its state, nonce and sealed verifier; the caller binds it and names where it returns. */
@@ -270,6 +390,12 @@ export declare class RelyingPartySessions<TPrincipal extends object | undefined
270
390
  private createProviderSession;
271
391
  /** Where a bearer attempt's outcome goes, while its return target is still configured; `null` otherwise. */
272
392
  private bearerDeliveryOfAttempt;
393
+ /**
394
+ * Stores a new hand-off of a session, bound to the client's challenge, and
395
+ * answers its code, which is stored only as a hash. The code lives
396
+ * `BEARER_HANDOFF_MAX_AGE_SECONDS` at most and never past the session.
397
+ */
398
+ private storeBearerHandoff;
273
399
  /** Stores the next token generation of a live session and answers its tokens; none of them is stored in the clear. */
274
400
  private issueBearerTokens;
275
401
  /** A spent hand-off code or refresh token came back: whoever holds it, the session ends. */
@@ -282,6 +408,19 @@ export declare class RelyingPartySessions<TPrincipal extends object | undefined
282
408
  private endUnclaimedHandoff;
283
409
  /** Revokes a session that was never handed out and erases its sealed tokens. */
284
410
  private discardUnopenedSession;
411
+ /**
412
+ * Revokes a session not revoked yet and erases its sealed tokens; answers
413
+ * the document as it was before, `null` when it was over already. Its
414
+ * expiry comes forward to the revocation, never later, so the sweep finds
415
+ * an ended session by its expiry alone and `revokedAt` keeps when and that
416
+ * it was ended. Two conditional writes keep the earlier of the two times:
417
+ * the first ends a session within its expiry, the second one past it. A
418
+ * crash between them leaves an expired row unrevoked, which reads as over
419
+ * and is swept; a row revoked before 19.4.0 kept its later expiry, which is
420
+ * harmless because every reader checks `revokedAt` or reads live rows only,
421
+ * and it ages out.
422
+ */
423
+ private revokeSessionDocument;
285
424
  /**
286
425
  * Tells the application a session ended. Its hook never stands between the
287
426
  * local revocation and the provider revocation: a throw or a rejection is
@@ -326,6 +465,17 @@ export declare class RelyingPartySessions<TPrincipal extends object | undefined
326
465
  * sign-in.
327
466
  */
328
467
  private sweepBearerState;
468
+ /**
469
+ * Removes sessions an hour past their expiry, oldest first, one bounded
470
+ * page per login start and per session the application opens. An ended
471
+ * session's expiry is its revocation at the latest, so the expiry index
472
+ * finds expired and revoked sessions alike. Each of them ended already: the
473
+ * row goes without an end, a hook or a provider call. A row is deleted only
474
+ * while its expiry is still past the cutoff, so a row whose expiry moved
475
+ * after the page was read stays. A sweep that cannot run never blocks a
476
+ * sign-in.
477
+ */
478
+ private sweepEndedSessions;
329
479
  /**
330
480
  * Reports one diagnostic. Every cleanup step reports through here, so a
331
481
  * throwing sink must not end it: the event and the sink's failure go to