@idp.global/sdk 18.0.1 → 18.1.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 (50) hide show
  1. package/changelog.md +17 -0
  2. package/dist_ts/00_commitinfo_data.js +1 -1
  3. package/dist_ts_relyingparty/classes.actorscope.d.ts +23 -0
  4. package/dist_ts_relyingparty/classes.actorscope.js +22 -0
  5. package/dist_ts_relyingparty/classes.authorityobserver.d.ts +81 -0
  6. package/dist_ts_relyingparty/classes.authorityobserver.js +185 -0
  7. package/dist_ts_relyingparty/classes.errors.d.ts +33 -0
  8. package/dist_ts_relyingparty/classes.errors.js +53 -0
  9. package/dist_ts_relyingparty/classes.idpoidcprovider.d.ts +142 -0
  10. package/dist_ts_relyingparty/classes.idpoidcprovider.js +255 -0
  11. package/dist_ts_relyingparty/classes.relyingpartyroutes.d.ts +84 -0
  12. package/dist_ts_relyingparty/classes.relyingpartyroutes.js +232 -0
  13. package/dist_ts_relyingparty/classes.relyingpartysessions.d.ts +210 -0
  14. package/dist_ts_relyingparty/classes.relyingpartysessions.js +786 -0
  15. package/dist_ts_relyingparty/classes.rpmodels.d.ts +79 -0
  16. package/dist_ts_relyingparty/classes.rpmodels.js +283 -0
  17. package/dist_ts_relyingparty/classes.secretbox.d.ts +14 -0
  18. package/dist_ts_relyingparty/classes.secretbox.js +45 -0
  19. package/dist_ts_relyingparty/classes.socketbinding.d.ts +75 -0
  20. package/dist_ts_relyingparty/classes.socketbinding.js +94 -0
  21. package/dist_ts_relyingparty/helpers.cookies.d.ts +11 -0
  22. package/dist_ts_relyingparty/helpers.cookies.js +62 -0
  23. package/dist_ts_relyingparty/helpers.crypto.d.ts +12 -0
  24. package/dist_ts_relyingparty/helpers.crypto.js +40 -0
  25. package/dist_ts_relyingparty/helpers.returnpath.d.ts +9 -0
  26. package/dist_ts_relyingparty/helpers.returnpath.js +39 -0
  27. package/dist_ts_relyingparty/index.d.ts +13 -0
  28. package/dist_ts_relyingparty/index.js +14 -0
  29. package/dist_ts_relyingparty/interfaces.d.ts +113 -0
  30. package/dist_ts_relyingparty/interfaces.js +2 -0
  31. package/dist_ts_relyingparty/plugins.d.ts +10 -0
  32. package/dist_ts_relyingparty/plugins.js +11 -0
  33. package/package.json +13 -10
  34. package/readme.md +158 -4
  35. package/ts/00_commitinfo_data.ts +1 -1
  36. package/ts_relyingparty/classes.actorscope.ts +39 -0
  37. package/ts_relyingparty/classes.authorityobserver.ts +253 -0
  38. package/ts_relyingparty/classes.errors.ts +69 -0
  39. package/ts_relyingparty/classes.idpoidcprovider.ts +386 -0
  40. package/ts_relyingparty/classes.relyingpartyroutes.ts +286 -0
  41. package/ts_relyingparty/classes.relyingpartysessions.ts +970 -0
  42. package/ts_relyingparty/classes.rpmodels.ts +146 -0
  43. package/ts_relyingparty/classes.secretbox.ts +49 -0
  44. package/ts_relyingparty/classes.socketbinding.ts +142 -0
  45. package/ts_relyingparty/helpers.cookies.ts +79 -0
  46. package/ts_relyingparty/helpers.crypto.ts +49 -0
  47. package/ts_relyingparty/helpers.returnpath.ts +44 -0
  48. package/ts_relyingparty/index.ts +13 -0
  49. package/ts_relyingparty/interfaces.ts +133 -0
  50. package/ts_relyingparty/plugins.ts +17 -0
package/changelog.md CHANGED
@@ -1,5 +1,22 @@
1
1
  # Changelog
2
2
 
3
+ ## 2026-09-29 - 18.1.0
4
+
5
+ ### Features
6
+
7
+ - add the relying-party session layer as `@idp.global/sdk/relyingparty` (relyingparty)
8
+ - Ported from nevermind.finance mainapp 0b693cb (`ts/identity/`, `ts/helpers.cookies.ts`, `ts/helpers.secrets.ts`, `ts/classes.actorcontext.ts`, `ts/socket/classes.socketgate.ts`) so nevermind.finance and Docbox share one implementation.
9
+ - `IdpOidcProvider`: discovery restricted to S256 PKCE, RS256 ID tokens and the authorization_code and refresh_token grants; code exchange with a subject-checked UserInfo read; refresh with `invalid_grant` as `OidcRefreshRefusedError`; revocation; organization introspection through `OidcIntrospectionClient`.
10
+ - `RelyingPartySessions` on `createRelyingPartyModels({ db, collectionNames? })`: single-use login attempts bound to the browser, return-path sanitising, HttpOnly cookie sessions with an HMAC-derived CSRF token, sessions the application opens itself, provider tokens sealed with `SecretBox` and rotated once per process under a stored cross-replica lock, conditional session ends and failed logins that hand the tokens back within a three-second budget, and application hooks for principal admission, session ends and diagnostics whose failures never skip that cleanup (`hook_failed`). A `__Host-` or `__Secure-` cookie name is refused for an http origin.
11
+ - `AuthorityObserver`: live organization authority projected through the application's role table. It observes freshly for every operation by default; `positiveTtlMs` and `negativeTtlMs` are explicit opt-ins, documented with their trade-off.
12
+ - `RelyingPartyRoutes` (login, callback, finalize, session, logout, the logout body capped while it streams) on a typedserver route context, `createActorScope()` for `TypedRouter.addRequestScope`, and `RelyingPartySocketBinding` for TypedSocket browser registration and per-request re-validation.
13
+ - Read-compatible with nevermind.finance's stored sessions: bound to `SessionDoc` and `LoginAttemptDoc`, the models declare the same fields and index names, `SecretBox` reads the stored `v1:` sealed format, and the application passes its own cookie names and CSRF label.
14
+ - Adds the dependency `openid-client` ^6.8.8 and raises `@api.global/typedrequest` to ^8.2.0 and `@api.global/typedsocket` to ^8.7.0; the `@lossless.org/client` peer range `>=1.0.0 <2.0.0` is unchanged.
15
+
16
+ ### Maintenance
17
+
18
+ - release tooling: `@git.zone/tsbuild` 5.0.0, `@git.zone/tstest` 6.3.2, `@git.zone/tsrun` 3.0.2, `@git.zone/tsdoc` 2.2.2, `@types/node` 26.6.2 and `pnpm@12.5.1`; pnpm 12.6.0 and `@types/node` 26.6.3 wait for the seven-day release-age window.
19
+
3
20
  ## 2026-09-23 - 18.0.1
4
21
 
5
22
  ### Maintenance
@@ -3,7 +3,7 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: '@idp.global/sdk',
6
- version: '18.0.1',
6
+ version: '18.1.0',
7
7
  description: 'Reusable browser and server SDK for idp.global authentication.'
8
8
  };
9
9
  //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiMDBfY29tbWl0aW5mb19kYXRhLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vdHMvMDBfY29tbWl0aW5mb19kYXRhLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBOztHQUVHO0FBQ0gsTUFBTSxDQUFDLE1BQU0sVUFBVSxHQUFHO0lBQ3hCLElBQUksRUFBRSxpQkFBaUI7SUFDdkIsT0FBTyxFQUFFLFFBQVE7SUFDakIsV0FBVyxFQUFFLGdFQUFnRTtDQUM5RSxDQUFBIn0=
@@ -0,0 +1,23 @@
1
+ import * as plugins from './plugins.js';
2
+ export interface IActorScope<TContext> {
3
+ /** Runs `operationArg` and everything it awaits as this actor. */
4
+ run<T>(contextArg: TContext, operationArg: () => T): T;
5
+ /** The actor of the operation in flight, or `undefined` outside one. */
6
+ current(): TContext | undefined;
7
+ /** Runs `operationArg` without any actor, for background work entered from a request. */
8
+ exit<T>(operationArg: () => T): T;
9
+ /**
10
+ * The request scope of a `TypedRouter` (`addRequestScope`): middleware and
11
+ * handler of every dispatch run as the actor the transport's trusted local
12
+ * data names. A dispatch without one runs without an actor.
13
+ */
14
+ readonly requestScope: plugins.typedrequest.TTypedRouterRequestScope;
15
+ }
16
+ /**
17
+ * An AsyncLocalStorage-backed actor context. The application decides what an
18
+ * actor is and how it is read from the trusted local data its transports set;
19
+ * nothing a handler can reach decides it.
20
+ */
21
+ export declare const createActorScope: <TContext>(optionsArg: {
22
+ fromLocalData(localDataArg: Readonly<Record<string, unknown>>): TContext | undefined;
23
+ }) => IActorScope<TContext>;
@@ -0,0 +1,22 @@
1
+ import * as plugins from './plugins.js';
2
+ /**
3
+ * An AsyncLocalStorage-backed actor context. The application decides what an
4
+ * actor is and how it is read from the trusted local data its transports set;
5
+ * nothing a handler can reach decides it.
6
+ */
7
+ export const createActorScope = (optionsArg) => {
8
+ const storage = new plugins.asyncHooks.AsyncLocalStorage();
9
+ return {
10
+ run: (contextArg, operationArg) => storage.run(contextArg, operationArg),
11
+ current: () => storage.getStore(),
12
+ exit: (operationArg) => storage.exit(operationArg),
13
+ requestScope: (_typedRequestArg, nextArg, contextArg) => {
14
+ const actor = optionsArg.fromLocalData(contextArg.localData);
15
+ if (actor === undefined) {
16
+ return nextArg();
17
+ }
18
+ return storage.run(actor, nextArg);
19
+ },
20
+ };
21
+ };
22
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xhc3Nlcy5hY3RvcnNjb3BlLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vdHNfcmVseWluZ3BhcnR5L2NsYXNzZXMuYWN0b3JzY29wZS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEtBQUssT0FBTyxNQUFNLGNBQWMsQ0FBQztBQWlCeEM7Ozs7R0FJRztBQUNILE1BQU0sQ0FBQyxNQUFNLGdCQUFnQixHQUFHLENBQVcsVUFFMUMsRUFBeUIsRUFBRTtJQUMxQixNQUFNLE9BQU8sR0FBRyxJQUFJLE9BQU8sQ0FBQyxVQUFVLENBQUMsaUJBQWlCLEVBQVksQ0FBQztJQUNyRSxPQUFPO1FBQ0wsR0FBRyxFQUFFLENBQUMsVUFBVSxFQUFFLFlBQVksRUFBRSxFQUFFLENBQUMsT0FBTyxDQUFDLEdBQUcsQ0FBQyxVQUFVLEVBQUUsWUFBWSxDQUFDO1FBQ3hFLE9BQU8sRUFBRSxHQUFHLEVBQUUsQ0FBQyxPQUFPLENBQUMsUUFBUSxFQUFFO1FBQ2pDLElBQUksRUFBRSxDQUFDLFlBQVksRUFBRSxFQUFFLENBQUMsT0FBTyxDQUFDLElBQUksQ0FBQyxZQUFZLENBQUM7UUFDbEQsWUFBWSxFQUFFLENBQUMsZ0JBQWdCLEVBQUUsT0FBTyxFQUFFLFVBQVUsRUFBRSxFQUFFO1lBQ3RELE1BQU0sS0FBSyxHQUFHLFVBQVUsQ0FBQyxhQUFhLENBQUMsVUFBVSxDQUFDLFNBQVMsQ0FBQyxDQUFDO1lBQzdELElBQUksS0FBSyxLQUFLLFNBQVMsRUFBRSxDQUFDO2dCQUN4QixPQUFPLE9BQU8sRUFBRSxDQUFDO1lBQ25CLENBQUM7WUFDRCxPQUFPLE9BQU8sQ0FBQyxHQUFHLENBQUMsS0FBSyxFQUFFLE9BQU8sQ0FBQyxDQUFDO1FBQ3JDLENBQUM7S0FDRixDQUFDO0FBQ0osQ0FBQyxDQUFDIn0=
@@ -0,0 +1,81 @@
1
+ import { type IOidcProvider } from './classes.idpoidcprovider.js';
2
+ import type { IAuthorityTokenSource } from './interfaces.js';
3
+ /** Why an observation refused: no membership at all, or roles the application maps to nothing. */
4
+ export type TAuthorityRefusalReason = 'idp_membership_absent' | 'idp_roles_unmapped';
5
+ /**
6
+ * idp.global no longer reports a usable membership of the organization.
7
+ * `firstRefusal` is `true` exactly once per transition from access to refusal
8
+ * for one login session and organization, so an audit trail records the
9
+ * transition and not every refused request.
10
+ */
11
+ export declare class AuthorityRefusedError extends Error {
12
+ readonly reason: TAuthorityRefusalReason;
13
+ readonly firstRefusal: boolean;
14
+ readonly code = "organization_access_revoked";
15
+ readonly status = 403;
16
+ constructor(reason: TAuthorityRefusalReason, firstRefusal: boolean);
17
+ }
18
+ /** One live observation, as the caller reads it. */
19
+ export interface IAuthorityObservation<TGrant> {
20
+ /** what the application's projection made of the roles */
21
+ grant: TGrant;
22
+ /** the idp role keys the provider reported, sorted */
23
+ roles: readonly string[];
24
+ /** the observation's own `exp` in epoch milliseconds */
25
+ tokenExpiresAt: number;
26
+ observedAt: number;
27
+ }
28
+ export interface IAuthorityObserverOptions<TGrant> {
29
+ provider: IOidcProvider;
30
+ tokens: IAuthorityTokenSource;
31
+ /** the application's role table: what the provider's role keys grant here, `undefined` for nothing */
32
+ project(rolesArg: readonly string[]): TGrant | undefined;
33
+ /**
34
+ * How long a positive observation answers later requests of the same login
35
+ * session and organization. `0` (the default) observes freshly for every
36
+ * operation, which is the provider's contract; a positive value is an
37
+ * explicit opt-in under which a revocation at idp.global takes effect here
38
+ * only once the cached answer expires.
39
+ */
40
+ positiveTtlMs?: number;
41
+ /** How long a refusal is reused; `0` (the default) never reuses one. */
42
+ negativeTtlMs?: number;
43
+ /** the bound of remembered observations */
44
+ maxEntries?: number;
45
+ now?: () => number;
46
+ }
47
+ /**
48
+ * Live organization authority through idp.global: one `POST /oauth/introspect`
49
+ * observation of a login session's subject in one organization, projected by
50
+ * the application's role table.
51
+ *
52
+ * Observations of one key run once however many callers ask at the same time.
53
+ * A failure to observe is never remembered, so the caller fails closed on
54
+ * every attempt while the provider is unreachable. A session that is forgotten
55
+ * while its observation is in flight never stores a positive answer. Nothing
56
+ * runs on a timer: the cache is pruned when it is written to.
57
+ */
58
+ export declare class AuthorityObserver<TGrant> {
59
+ #private;
60
+ constructor(optionsArg: IAuthorityObserverOptions<TGrant>);
61
+ /** How many observations are remembered right now. */
62
+ get size(): number;
63
+ /**
64
+ * The grant of `principalId`'s login session in one organization at
65
+ * idp.global, or a throw: `AuthorityRefusedError` for a refusal,
66
+ * `AuthorityTokenError` when there is no live login to observe with or the
67
+ * provider could not be observed.
68
+ */
69
+ observe(optionsArg: {
70
+ loginSessionId: string;
71
+ principalId: string;
72
+ idpOrganizationId: string;
73
+ }): Promise<IAuthorityObservation<TGrant>>;
74
+ /** Drops every observation of a session; wire it to the session layer's `onSessionEnded`. */
75
+ forgetSession(sessionIdArg: string): void;
76
+ private liveEntry;
77
+ private observeOnce;
78
+ private makeObservation;
79
+ /** Expired entries go first; if that is not enough, the oldest do. */
80
+ private prune;
81
+ }
@@ -0,0 +1,185 @@
1
+ import { AuthorityTokenError } from './classes.errors.js';
2
+ import { OidcAuthorityUnavailableError } from './classes.idpoidcprovider.js';
3
+ /**
4
+ * idp.global no longer reports a usable membership of the organization.
5
+ * `firstRefusal` is `true` exactly once per transition from access to refusal
6
+ * for one login session and organization, so an audit trail records the
7
+ * transition and not every refused request.
8
+ */
9
+ export class AuthorityRefusedError extends Error {
10
+ reason;
11
+ firstRefusal;
12
+ code = 'organization_access_revoked';
13
+ status = 403;
14
+ constructor(reason, firstRefusal) {
15
+ super('organization_access_revoked');
16
+ this.reason = reason;
17
+ this.firstRefusal = firstRefusal;
18
+ this.name = 'AuthorityRefusedError';
19
+ }
20
+ }
21
+ const DEFAULT_MAX_ENTRIES = 2_000;
22
+ /** how much room an eviction makes, so it does not run on every insert */
23
+ const EVICTION_HEADROOM = 0.1;
24
+ const assertTtl = (valueArg, labelArg) => {
25
+ if (!Number.isSafeInteger(valueArg) || valueArg < 0) {
26
+ throw new Error(labelArg + ' must be a non-negative integer of milliseconds');
27
+ }
28
+ return valueArg;
29
+ };
30
+ /**
31
+ * Live organization authority through idp.global: one `POST /oauth/introspect`
32
+ * observation of a login session's subject in one organization, projected by
33
+ * the application's role table.
34
+ *
35
+ * Observations of one key run once however many callers ask at the same time.
36
+ * A failure to observe is never remembered, so the caller fails closed on
37
+ * every attempt while the provider is unreachable. A session that is forgotten
38
+ * while its observation is in flight never stores a positive answer. Nothing
39
+ * runs on a timer: the cache is pruned when it is written to.
40
+ */
41
+ export class AuthorityObserver {
42
+ #cache = new Map();
43
+ #inFlight = new Map();
44
+ #forgotten = new Set();
45
+ #provider;
46
+ #tokens;
47
+ #project;
48
+ #positiveTtlMs;
49
+ #negativeTtlMs;
50
+ #maxEntries;
51
+ #now;
52
+ constructor(optionsArg) {
53
+ this.#provider = optionsArg.provider;
54
+ this.#tokens = optionsArg.tokens;
55
+ this.#project = optionsArg.project;
56
+ this.#positiveTtlMs = assertTtl(optionsArg.positiveTtlMs ?? 0, 'positiveTtlMs');
57
+ this.#negativeTtlMs = assertTtl(optionsArg.negativeTtlMs ?? 0, 'negativeTtlMs');
58
+ this.#maxEntries = optionsArg.maxEntries ?? DEFAULT_MAX_ENTRIES;
59
+ if (!Number.isSafeInteger(this.#maxEntries) || this.#maxEntries < 1) {
60
+ throw new Error('maxEntries must be a positive integer');
61
+ }
62
+ this.#now = optionsArg.now ?? (() => Date.now());
63
+ }
64
+ /** How many observations are remembered right now. */
65
+ get size() {
66
+ return this.#cache.size;
67
+ }
68
+ /**
69
+ * The grant of `principalId`'s login session in one organization at
70
+ * idp.global, or a throw: `AuthorityRefusedError` for a refusal,
71
+ * `AuthorityTokenError` when there is no live login to observe with or the
72
+ * provider could not be observed.
73
+ */
74
+ async observe(optionsArg) {
75
+ const key = `${optionsArg.loginSessionId} ${optionsArg.idpOrganizationId}`;
76
+ const cached = this.liveEntry(key);
77
+ const observation = cached ?? (await this.observeOnce(key, optionsArg));
78
+ if (observation.outcome !== 'active' || observation.grant === undefined) {
79
+ const firstRefusal = cached === undefined && !observation.reported;
80
+ observation.reported = true;
81
+ throw new AuthorityRefusedError(observation.reason ?? 'idp_membership_absent', firstRefusal);
82
+ }
83
+ return {
84
+ grant: observation.grant,
85
+ roles: observation.roles,
86
+ tokenExpiresAt: observation.tokenExpiresAt,
87
+ observedAt: observation.observedAt,
88
+ };
89
+ }
90
+ /** Drops every observation of a session; wire it to the session layer's `onSessionEnded`. */
91
+ forgetSession(sessionIdArg) {
92
+ const prefix = `${sessionIdArg} `;
93
+ for (const key of this.#cache.keys()) {
94
+ if (key.startsWith(prefix)) {
95
+ this.#cache.delete(key);
96
+ }
97
+ }
98
+ for (const key of this.#inFlight.keys()) {
99
+ if (key.startsWith(prefix)) {
100
+ this.#forgotten.add(key);
101
+ }
102
+ }
103
+ }
104
+ liveEntry(keyArg) {
105
+ const entry = this.#cache.get(keyArg);
106
+ return entry && entry.expiresAt > this.#now() ? entry : undefined;
107
+ }
108
+ async observeOnce(keyArg, optionsArg) {
109
+ const running = this.#inFlight.get(keyArg);
110
+ if (running) {
111
+ return running;
112
+ }
113
+ const pending = this.makeObservation(keyArg, optionsArg).finally(() => {
114
+ this.#inFlight.delete(keyArg);
115
+ this.#forgotten.delete(keyArg);
116
+ });
117
+ this.#inFlight.set(keyArg, pending);
118
+ return pending;
119
+ }
120
+ async makeObservation(keyArg, optionsArg) {
121
+ const token = await this.#tokens.liveProviderToken(optionsArg.loginSessionId, optionsArg.principalId);
122
+ let answer;
123
+ try {
124
+ answer = await this.#provider.introspectOrganization({
125
+ accessToken: token.accessToken,
126
+ subject: token.subject,
127
+ organizationId: optionsArg.idpOrganizationId,
128
+ });
129
+ }
130
+ catch (error) {
131
+ if (error instanceof OidcAuthorityUnavailableError) {
132
+ throw new AuthorityTokenError('authority_unavailable', error);
133
+ }
134
+ throw error;
135
+ }
136
+ const grant = answer.active ? this.#project(answer.roles) : undefined;
137
+ const active = answer.active && grant !== undefined;
138
+ const previous = this.#cache.get(keyArg);
139
+ const entry = {
140
+ outcome: active ? 'active' : 'inactive',
141
+ ...(active ? { grant } : { reason: answer.active ? 'idp_roles_unmapped' : 'idp_membership_absent' }),
142
+ roles: answer.roles,
143
+ tokenExpiresAt: answer.expiresAt,
144
+ observedAt: this.#now(),
145
+ expiresAt: this.#now() + (active ? this.#positiveTtlMs : this.#negativeTtlMs),
146
+ // a refusal after a reported refusal is the same transition
147
+ reported: !active && previous?.outcome === 'inactive' && previous.reported,
148
+ };
149
+ if (active && this.#forgotten.has(keyArg)) {
150
+ // the request holding this answer finishes — it was authorized before
151
+ // the session ended — and every later one observes again
152
+ entry.expiresAt = 0;
153
+ return entry;
154
+ }
155
+ // a refusal is kept even when it answers nothing (`negativeTtlMs` 0): it is
156
+ // what reports one transition instead of one per refused request
157
+ this.#cache.delete(keyArg);
158
+ this.#cache.set(keyArg, entry);
159
+ this.prune();
160
+ return entry;
161
+ }
162
+ /** Expired entries go first; if that is not enough, the oldest do. */
163
+ prune() {
164
+ if (this.#cache.size <= this.#maxEntries) {
165
+ return;
166
+ }
167
+ const now = this.#now();
168
+ for (const [key, entry] of this.#cache) {
169
+ if (entry.expiresAt <= now) {
170
+ this.#cache.delete(key);
171
+ }
172
+ }
173
+ if (this.#cache.size <= this.#maxEntries) {
174
+ return;
175
+ }
176
+ const target = Math.floor(this.#maxEntries * (1 - EVICTION_HEADROOM));
177
+ for (const key of this.#cache.keys()) {
178
+ if (this.#cache.size <= target) {
179
+ break;
180
+ }
181
+ this.#cache.delete(key);
182
+ }
183
+ }
184
+ }
185
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xhc3Nlcy5hdXRob3JpdHlvYnNlcnZlci5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3RzX3JlbHlpbmdwYXJ0eS9jbGFzc2VzLmF1dGhvcml0eW9ic2VydmVyLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sRUFBRSxtQkFBbUIsRUFBRSxNQUFNLHFCQUFxQixDQUFDO0FBQzFELE9BQU8sRUFBRSw2QkFBNkIsRUFBc0IsTUFBTSw4QkFBOEIsQ0FBQztBQU1qRzs7Ozs7R0FLRztBQUNILE1BQU0sT0FBTyxxQkFBc0IsU0FBUSxLQUFLO0lBSzVCO0lBQ0E7SUFMRixJQUFJLEdBQUcsNkJBQTZCLENBQUM7SUFDckMsTUFBTSxHQUFHLEdBQUcsQ0FBQztJQUU3QixZQUNrQixNQUErQixFQUMvQixZQUFxQjtRQUVyQyxLQUFLLENBQUMsNkJBQTZCLENBQUMsQ0FBQztRQUhyQixXQUFNLEdBQU4sTUFBTSxDQUF5QjtRQUMvQixpQkFBWSxHQUFaLFlBQVksQ0FBUztRQUdyQyxJQUFJLENBQUMsSUFBSSxHQUFHLHVCQUF1QixDQUFDO0lBQ3RDLENBQUM7Q0FDRjtBQWlDRCxNQUFNLG1CQUFtQixHQUFHLEtBQUssQ0FBQztBQUNsQywwRUFBMEU7QUFDMUUsTUFBTSxpQkFBaUIsR0FBRyxHQUFHLENBQUM7QUFlOUIsTUFBTSxTQUFTLEdBQUcsQ0FBQyxRQUFnQixFQUFFLFFBQWdCLEVBQVUsRUFBRTtJQUMvRCxJQUFJLENBQUMsTUFBTSxDQUFDLGFBQWEsQ0FBQyxRQUFRLENBQUMsSUFBSSxRQUFRLEdBQUcsQ0FBQyxFQUFFLENBQUM7UUFDcEQsTUFBTSxJQUFJLEtBQUssQ0FBQyxRQUFRLEdBQUcsaURBQWlELENBQUMsQ0FBQztJQUNoRixDQUFDO0lBQ0QsT0FBTyxRQUFRLENBQUM7QUFDbEIsQ0FBQyxDQUFDO0FBRUY7Ozs7Ozs7Ozs7R0FVRztBQUNILE1BQU0sT0FBTyxpQkFBaUI7SUFDbkIsTUFBTSxHQUFHLElBQUksR0FBRyxFQUFzQyxDQUFDO0lBQ3ZELFNBQVMsR0FBRyxJQUFJLEdBQUcsRUFBK0MsQ0FBQztJQUNuRSxVQUFVLEdBQUcsSUFBSSxHQUFHLEVBQVUsQ0FBQztJQUMvQixTQUFTLENBQWdCO0lBQ3pCLE9BQU8sQ0FBd0I7SUFDL0IsUUFBUSxDQUFzRDtJQUM5RCxjQUFjLENBQVM7SUFDdkIsY0FBYyxDQUFTO0lBQ3ZCLFdBQVcsQ0FBUztJQUNwQixJQUFJLENBQWU7SUFFNUIsWUFBWSxVQUE2QztRQUN2RCxJQUFJLENBQUMsU0FBUyxHQUFHLFVBQVUsQ0FBQyxRQUFRLENBQUM7UUFDckMsSUFBSSxDQUFDLE9BQU8sR0FBRyxVQUFVLENBQUMsTUFBTSxDQUFDO1FBQ2pDLElBQUksQ0FBQyxRQUFRLEdBQUcsVUFBVSxDQUFDLE9BQU8sQ0FBQztRQUNuQyxJQUFJLENBQUMsY0FBYyxHQUFHLFNBQVMsQ0FBQyxVQUFVLENBQUMsYUFBYSxJQUFJLENBQUMsRUFBRSxlQUFlLENBQUMsQ0FBQztRQUNoRixJQUFJLENBQUMsY0FBYyxHQUFHLFNBQVMsQ0FBQyxVQUFVLENBQUMsYUFBYSxJQUFJLENBQUMsRUFBRSxlQUFlLENBQUMsQ0FBQztRQUNoRixJQUFJLENBQUMsV0FBVyxHQUFHLFVBQVUsQ0FBQyxVQUFVLElBQUksbUJBQW1CLENBQUM7UUFDaEUsSUFBSSxDQUFDLE1BQU0sQ0FBQyxhQUFhLENBQUMsSUFBSSxDQUFDLFdBQVcsQ0FBQyxJQUFJLElBQUksQ0FBQyxXQUFXLEdBQUcsQ0FBQyxFQUFFLENBQUM7WUFDcEUsTUFBTSxJQUFJLEtBQUssQ0FBQyx1Q0FBdUMsQ0FBQyxDQUFDO1FBQzNELENBQUM7UUFDRCxJQUFJLENBQUMsSUFBSSxHQUFHLFVBQVUsQ0FBQyxHQUFHLElBQUksQ0FBQyxHQUFHLEVBQUUsQ0FBQyxJQUFJLENBQUMsR0FBRyxFQUFFLENBQUMsQ0FBQztJQUNuRCxDQUFDO0lBRUQsc0RBQXNEO0lBQ3RELElBQVcsSUFBSTtRQUNiLE9BQU8sSUFBSSxDQUFDLE1BQU0sQ0FBQyxJQUFJLENBQUM7SUFDMUIsQ0FBQztJQUVEOzs7OztPQUtHO0lBQ0ksS0FBSyxDQUFDLE9BQU8sQ0FBQyxVQUlwQjtRQUNDLE1BQU0sR0FBRyxHQUFHLEdBQUcsVUFBVSxDQUFDLGNBQWMsSUFBSSxVQUFVLENBQUMsaUJBQWlCLEVBQUUsQ0FBQztRQUMzRSxNQUFNLE1BQU0sR0FBRyxJQUFJLENBQUMsU0FBUyxDQUFDLEdBQUcsQ0FBQyxDQUFDO1FBQ25DLE1BQU0sV0FBVyxHQUFHLE1BQU0sSUFBSSxDQUFDLE1BQU0sSUFBSSxDQUFDLFdBQVcsQ0FBQyxHQUFHLEVBQUUsVUFBVSxDQUFDLENBQUMsQ0FBQztRQUN4RSxJQUFJLFdBQVcsQ0FBQyxPQUFPLEtBQUssUUFBUSxJQUFJLFdBQVcsQ0FBQyxLQUFLLEtBQUssU0FBUyxFQUFFLENBQUM7WUFDeEUsTUFBTSxZQUFZLEdBQUcsTUFBTSxLQUFLLFNBQVMsSUFBSSxDQUFDLFdBQVcsQ0FBQyxRQUFRLENBQUM7WUFDbkUsV0FBVyxDQUFDLFFBQVEsR0FBRyxJQUFJLENBQUM7WUFDNUIsTUFBTSxJQUFJLHFCQUFxQixDQUFDLFdBQVcsQ0FBQyxNQUFNLElBQUksdUJBQXVCLEVBQUUsWUFBWSxDQUFDLENBQUM7UUFDL0YsQ0FBQztRQUNELE9BQU87WUFDTCxLQUFLLEVBQUUsV0FBVyxDQUFDLEtBQUs7WUFDeEIsS0FBSyxFQUFFLFdBQVcsQ0FBQyxLQUFLO1lBQ3hCLGNBQWMsRUFBRSxXQUFXLENBQUMsY0FBYztZQUMxQyxVQUFVLEVBQUUsV0FBVyxDQUFDLFVBQVU7U0FDbkMsQ0FBQztJQUNKLENBQUM7SUFFRCw2RkFBNkY7SUFDdEYsYUFBYSxDQUFDLFlBQW9CO1FBQ3ZDLE1BQU0sTUFBTSxHQUFHLEdBQUcsWUFBWSxHQUFHLENBQUM7UUFDbEMsS0FBSyxNQUFNLEdBQUcsSUFBSSxJQUFJLENBQUMsTUFBTSxDQUFDLElBQUksRUFBRSxFQUFFLENBQUM7WUFDckMsSUFBSSxHQUFHLENBQUMsVUFBVSxDQUFDLE1BQU0sQ0FBQyxFQUFFLENBQUM7Z0JBQzNCLElBQUksQ0FBQyxNQUFNLENBQUMsTUFBTSxDQUFDLEdBQUcsQ0FBQyxDQUFDO1lBQzFCLENBQUM7UUFDSCxDQUFDO1FBQ0QsS0FBSyxNQUFNLEdBQUcsSUFBSSxJQUFJLENBQUMsU0FBUyxDQUFDLElBQUksRUFBRSxFQUFFLENBQUM7WUFDeEMsSUFBSSxHQUFHLENBQUMsVUFBVSxDQUFDLE1BQU0sQ0FBQyxFQUFFLENBQUM7Z0JBQzNCLElBQUksQ0FBQyxVQUFVLENBQUMsR0FBRyxDQUFDLEdBQUcsQ0FBQyxDQUFDO1lBQzNCLENBQUM7UUFDSCxDQUFDO0lBQ0gsQ0FBQztJQUVPLFNBQVMsQ0FBQyxNQUFjO1FBQzlCLE1BQU0sS0FBSyxHQUFHLElBQUksQ0FBQyxNQUFNLENBQUMsR0FBRyxDQUFDLE1BQU0sQ0FBQyxDQUFDO1FBQ3RDLE9BQU8sS0FBSyxJQUFJLEtBQUssQ0FBQyxTQUFTLEdBQUcsSUFBSSxDQUFDLElBQUksRUFBRSxDQUFDLENBQUMsQ0FBQyxLQUFLLENBQUMsQ0FBQyxDQUFDLFNBQVMsQ0FBQztJQUNwRSxDQUFDO0lBRU8sS0FBSyxDQUFDLFdBQVcsQ0FDdkIsTUFBYyxFQUNkLFVBQXNGO1FBRXRGLE1BQU0sT0FBTyxHQUFHLElBQUksQ0FBQyxTQUFTLENBQUMsR0FBRyxDQUFDLE1BQU0sQ0FBQyxDQUFDO1FBQzNDLElBQUksT0FBTyxFQUFFLENBQUM7WUFDWixPQUFPLE9BQU8sQ0FBQztRQUNqQixDQUFDO1FBQ0QsTUFBTSxPQUFPLEdBQUcsSUFBSSxDQUFDLGVBQWUsQ0FBQyxNQUFNLEVBQUUsVUFBVSxDQUFDLENBQUMsT0FBTyxDQUFDLEdBQUcsRUFBRTtZQUNwRSxJQUFJLENBQUMsU0FBUyxDQUFDLE1BQU0sQ0FBQyxNQUFNLENBQUMsQ0FBQztZQUM5QixJQUFJLENBQUMsVUFBVSxDQUFDLE1BQU0sQ0FBQyxNQUFNLENBQUMsQ0FBQztRQUNqQyxDQUFDLENBQUMsQ0FBQztRQUNILElBQUksQ0FBQyxTQUFTLENBQUMsR0FBRyxDQUFDLE1BQU0sRUFBRSxPQUFPLENBQUMsQ0FBQztRQUNwQyxPQUFPLE9BQU8sQ0FBQztJQUNqQixDQUFDO0lBRU8sS0FBSyxDQUFDLGVBQWUsQ0FDM0IsTUFBYyxFQUNkLFVBQXNGO1FBRXRGLE1BQU0sS0FBSyxHQUFHLE1BQU0sSUFBSSxDQUFDLE9BQU8sQ0FBQyxpQkFBaUIsQ0FBQyxVQUFVLENBQUMsY0FBYyxFQUFFLFVBQVUsQ0FBQyxXQUFXLENBQUMsQ0FBQztRQUN0RyxJQUFJLE1BQW9FLENBQUM7UUFDekUsSUFBSSxDQUFDO1lBQ0gsTUFBTSxHQUFHLE1BQU0sSUFBSSxDQUFDLFNBQVMsQ0FBQyxzQkFBc0IsQ0FBQztnQkFDbkQsV0FBVyxFQUFFLEtBQUssQ0FBQyxXQUFXO2dCQUM5QixPQUFPLEVBQUUsS0FBSyxDQUFDLE9BQU87Z0JBQ3RCLGNBQWMsRUFBRSxVQUFVLENBQUMsaUJBQWlCO2FBQzdDLENBQUMsQ0FBQztRQUNMLENBQUM7UUFBQyxPQUFPLEtBQUssRUFBRSxDQUFDO1lBQ2YsSUFBSSxLQUFLLFlBQVksNkJBQTZCLEVBQUUsQ0FBQztnQkFDbkQsTUFBTSxJQUFJLG1CQUFtQixDQUFDLHVCQUF1QixFQUFFLEtBQUssQ0FBQyxDQUFDO1lBQ2hFLENBQUM7WUFDRCxNQUFNLEtBQUssQ0FBQztRQUNkLENBQUM7UUFDRCxNQUFNLEtBQUssR0FBRyxNQUFNLENBQUMsTUFBTSxDQUFDLENBQUMsQ0FBQyxJQUFJLENBQUMsUUFBUSxDQUFDLE1BQU0sQ0FBQyxLQUFLLENBQUMsQ0FBQyxDQUFDLENBQUMsU0FBUyxDQUFDO1FBQ3RFLE1BQU0sTUFBTSxHQUFHLE1BQU0sQ0FBQyxNQUFNLElBQUksS0FBSyxLQUFLLFNBQVMsQ0FBQztRQUNwRCxNQUFNLFFBQVEsR0FBRyxJQUFJLENBQUMsTUFBTSxDQUFDLEdBQUcsQ0FBQyxNQUFNLENBQUMsQ0FBQztRQUN6QyxNQUFNLEtBQUssR0FBK0I7WUFDeEMsT0FBTyxFQUFFLE1BQU0sQ0FBQyxDQUFDLENBQUMsUUFBUSxDQUFDLENBQUMsQ0FBQyxVQUFVO1lBQ3ZDLEdBQUcsQ0FBQyxNQUFNLENBQUMsQ0FBQyxDQUFDLEVBQUUsS0FBSyxFQUFFLENBQUMsQ0FBQyxDQUFDLEVBQUUsTUFBTSxFQUFFLE1BQU0sQ0FBQyxNQUFNLENBQUMsQ0FBQyxDQUFDLG9CQUFvQixDQUFDLENBQUMsQ0FBQyx1QkFBdUIsRUFBRSxDQUFDO1lBQ3BHLEtBQUssRUFBRSxNQUFNLENBQUMsS0FBSztZQUNuQixjQUFjLEVBQUUsTUFBTSxDQUFDLFNBQVM7WUFDaEMsVUFBVSxFQUFFLElBQUksQ0FBQyxJQUFJLEVBQUU7WUFDdkIsU0FBUyxFQUFFLElBQUksQ0FBQyxJQUFJLEVBQUUsR0FBRyxDQUFDLE1BQU0sQ0FBQyxDQUFDLENBQUMsSUFBSSxDQUFDLGNBQWMsQ0FBQyxDQUFDLENBQUMsSUFBSSxDQUFDLGNBQWMsQ0FBQztZQUM3RSw0REFBNEQ7WUFDNUQsUUFBUSxFQUFFLENBQUMsTUFBTSxJQUFJLFFBQVEsRUFBRSxPQUFPLEtBQUssVUFBVSxJQUFJLFFBQVEsQ0FBQyxRQUFRO1NBQzNFLENBQUM7UUFDRixJQUFJLE1BQU0sSUFBSSxJQUFJLENBQUMsVUFBVSxDQUFDLEdBQUcsQ0FBQyxNQUFNLENBQUMsRUFBRSxDQUFDO1lBQzFDLHNFQUFzRTtZQUN0RSx5REFBeUQ7WUFDekQsS0FBSyxDQUFDLFNBQVMsR0FBRyxDQUFDLENBQUM7WUFDcEIsT0FBTyxLQUFLLENBQUM7UUFDZixDQUFDO1FBQ0QsNEVBQTRFO1FBQzVFLGlFQUFpRTtRQUNqRSxJQUFJLENBQUMsTUFBTSxDQUFDLE1BQU0sQ0FBQyxNQUFNLENBQUMsQ0FBQztRQUMzQixJQUFJLENBQUMsTUFBTSxDQUFDLEdBQUcsQ0FBQyxNQUFNLEVBQUUsS0FBSyxDQUFDLENBQUM7UUFDL0IsSUFBSSxDQUFDLEtBQUssRUFBRSxDQUFDO1FBQ2IsT0FBTyxLQUFLLENBQUM7SUFDZixDQUFDO0lBRUQsc0VBQXNFO0lBQzlELEtBQUs7UUFDWCxJQUFJLElBQUksQ0FBQyxNQUFNLENBQUMsSUFBSSxJQUFJLElBQUksQ0FBQyxXQUFXLEVBQUUsQ0FBQztZQUN6QyxPQUFPO1FBQ1QsQ0FBQztRQUNELE1BQU0sR0FBRyxHQUFHLElBQUksQ0FBQyxJQUFJLEVBQUUsQ0FBQztRQUN4QixLQUFLLE1BQU0sQ0FBQyxHQUFHLEVBQUUsS0FBSyxDQUFDLElBQUksSUFBSSxDQUFDLE1BQU0sRUFBRSxDQUFDO1lBQ3ZDLElBQUksS0FBSyxDQUFDLFNBQVMsSUFBSSxHQUFHLEVBQUUsQ0FBQztnQkFDM0IsSUFBSSxDQUFDLE1BQU0sQ0FBQyxNQUFNLENBQUMsR0FBRyxDQUFDLENBQUM7WUFDMUIsQ0FBQztRQUNILENBQUM7UUFDRCxJQUFJLElBQUksQ0FBQyxNQUFNLENBQUMsSUFBSSxJQUFJLElBQUksQ0FBQyxXQUFXLEVBQUUsQ0FBQztZQUN6QyxPQUFPO1FBQ1QsQ0FBQztRQUNELE1BQU0sTUFBTSxHQUFHLElBQUksQ0FBQyxLQUFLLENBQUMsSUFBSSxDQUFDLFdBQVcsR0FBRyxDQUFDLENBQUMsR0FBRyxpQkFBaUIsQ0FBQyxDQUFDLENBQUM7UUFDdEUsS0FBSyxNQUFNLEdBQUcsSUFBSSxJQUFJLENBQUMsTUFBTSxDQUFDLElBQUksRUFBRSxFQUFFLENBQUM7WUFDckMsSUFBSSxJQUFJLENBQUMsTUFBTSxDQUFDLElBQUksSUFBSSxNQUFNLEVBQUUsQ0FBQztnQkFDL0IsTUFBTTtZQUNSLENBQUM7WUFDRCxJQUFJLENBQUMsTUFBTSxDQUFDLE1BQU0sQ0FBQyxHQUFHLENBQUMsQ0FBQztRQUMxQixDQUFDO0lBQ0gsQ0FBQztDQUNGIn0=
@@ -0,0 +1,33 @@
1
+ import type { TLoginCallbackStage } from './interfaces.js';
2
+ export type TRelyingPartyErrorCode = 'login_method_unavailable' | 'invalid_login_callback' | 'missing_login_intent' | 'invalid_or_replayed_login' | 'invalid_login_finalize';
3
+ /** A request the login flow refuses; the code is safe to answer, the browser learns nothing more. */
4
+ export declare class RelyingPartyError extends Error {
5
+ readonly code: TRelyingPartyErrorCode;
6
+ readonly status: number;
7
+ constructor(code: TRelyingPartyErrorCode);
8
+ }
9
+ /**
10
+ * The application refuses a verified provider login (a disabled account, a
11
+ * subject of another issuer, no organization it serves). Thrown from
12
+ * `IRelyingPartyPrincipals.admit`; it reaches the caller unchanged.
13
+ */
14
+ export declare class RelyingPartyRefusal extends Error {
15
+ readonly code: string;
16
+ readonly status: number;
17
+ constructor(code: string, status: number);
18
+ }
19
+ /** A login callback that failed for a reason other than a refusal; the stage is for the log only. */
20
+ export declare class LoginCallbackError extends Error {
21
+ readonly stage: TLoginCallbackStage;
22
+ constructor(stage: TLoginCallbackStage, causeArg: unknown);
23
+ }
24
+ export type TAuthorityTokenErrorCode =
25
+ /** there is no live provider login this credential could be observed with */
26
+ 'authority_login_required'
27
+ /** the provider could not be reached; the caller fails closed */
28
+ | 'authority_unavailable';
29
+ export declare class AuthorityTokenError extends Error {
30
+ readonly code: TAuthorityTokenErrorCode;
31
+ readonly status: number;
32
+ constructor(code: TAuthorityTokenErrorCode, causeArg?: unknown);
33
+ }
@@ -0,0 +1,53 @@
1
+ const RELYING_PARTY_ERROR_STATUS = {
2
+ login_method_unavailable: 404,
3
+ invalid_login_callback: 400,
4
+ missing_login_intent: 400,
5
+ invalid_or_replayed_login: 400,
6
+ invalid_login_finalize: 400,
7
+ };
8
+ /** A request the login flow refuses; the code is safe to answer, the browser learns nothing more. */
9
+ export class RelyingPartyError extends Error {
10
+ code;
11
+ status;
12
+ constructor(code) {
13
+ super(code);
14
+ this.code = code;
15
+ this.name = 'RelyingPartyError';
16
+ this.status = RELYING_PARTY_ERROR_STATUS[code];
17
+ }
18
+ }
19
+ /**
20
+ * The application refuses a verified provider login (a disabled account, a
21
+ * subject of another issuer, no organization it serves). Thrown from
22
+ * `IRelyingPartyPrincipals.admit`; it reaches the caller unchanged.
23
+ */
24
+ export class RelyingPartyRefusal extends Error {
25
+ code;
26
+ status;
27
+ constructor(code, status) {
28
+ super(code);
29
+ this.code = code;
30
+ this.status = status;
31
+ this.name = 'RelyingPartyRefusal';
32
+ }
33
+ }
34
+ /** A login callback that failed for a reason other than a refusal; the stage is for the log only. */
35
+ export class LoginCallbackError extends Error {
36
+ stage;
37
+ constructor(stage, causeArg) {
38
+ super('The login callback could not be completed', { cause: causeArg });
39
+ this.stage = stage;
40
+ this.name = 'LoginCallbackError';
41
+ }
42
+ }
43
+ export class AuthorityTokenError extends Error {
44
+ code;
45
+ status;
46
+ constructor(code, causeArg) {
47
+ super(code, { cause: causeArg });
48
+ this.code = code;
49
+ this.name = 'AuthorityTokenError';
50
+ this.status = code === 'authority_login_required' ? 403 : 503;
51
+ }
52
+ }
53
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xhc3Nlcy5lcnJvcnMuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi90c19yZWx5aW5ncGFydHkvY2xhc3Nlcy5lcnJvcnMudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBU0EsTUFBTSwwQkFBMEIsR0FBcUQ7SUFDbkYsd0JBQXdCLEVBQUUsR0FBRztJQUM3QixzQkFBc0IsRUFBRSxHQUFHO0lBQzNCLG9CQUFvQixFQUFFLEdBQUc7SUFDekIseUJBQXlCLEVBQUUsR0FBRztJQUM5QixzQkFBc0IsRUFBRSxHQUFHO0NBQzVCLENBQUM7QUFFRixxR0FBcUc7QUFDckcsTUFBTSxPQUFPLGlCQUFrQixTQUFRLEtBQUs7SUFHZDtJQUZaLE1BQU0sQ0FBUztJQUUvQixZQUE0QixJQUE0QjtRQUN0RCxLQUFLLENBQUMsSUFBSSxDQUFDLENBQUM7UUFEYyxTQUFJLEdBQUosSUFBSSxDQUF3QjtRQUV0RCxJQUFJLENBQUMsSUFBSSxHQUFHLG1CQUFtQixDQUFDO1FBQ2hDLElBQUksQ0FBQyxNQUFNLEdBQUcsMEJBQTBCLENBQUMsSUFBSSxDQUFDLENBQUM7SUFDakQsQ0FBQztDQUNGO0FBRUQ7Ozs7R0FJRztBQUNILE1BQU0sT0FBTyxtQkFBb0IsU0FBUSxLQUFLO0lBRTFCO0lBQ0E7SUFGbEIsWUFDa0IsSUFBWSxFQUNaLE1BQWM7UUFFOUIsS0FBSyxDQUFDLElBQUksQ0FBQyxDQUFDO1FBSEksU0FBSSxHQUFKLElBQUksQ0FBUTtRQUNaLFdBQU0sR0FBTixNQUFNLENBQVE7UUFHOUIsSUFBSSxDQUFDLElBQUksR0FBRyxxQkFBcUIsQ0FBQztJQUNwQyxDQUFDO0NBQ0Y7QUFFRCxxR0FBcUc7QUFDckcsTUFBTSxPQUFPLGtCQUFtQixTQUFRLEtBQUs7SUFFekI7SUFEbEIsWUFDa0IsS0FBMEIsRUFDMUMsUUFBaUI7UUFFakIsS0FBSyxDQUFDLDJDQUEyQyxFQUFFLEVBQUUsS0FBSyxFQUFFLFFBQVEsRUFBRSxDQUFDLENBQUM7UUFIeEQsVUFBSyxHQUFMLEtBQUssQ0FBcUI7UUFJMUMsSUFBSSxDQUFDLElBQUksR0FBRyxvQkFBb0IsQ0FBQztJQUNuQyxDQUFDO0NBQ0Y7QUFRRCxNQUFNLE9BQU8sbUJBQW9CLFNBQVEsS0FBSztJQUdoQjtJQUZaLE1BQU0sQ0FBUztJQUUvQixZQUE0QixJQUE4QixFQUFFLFFBQWtCO1FBQzVFLEtBQUssQ0FBQyxJQUFJLEVBQUUsRUFBRSxLQUFLLEVBQUUsUUFBUSxFQUFFLENBQUMsQ0FBQztRQURQLFNBQUksR0FBSixJQUFJLENBQTBCO1FBRXhELElBQUksQ0FBQyxJQUFJLEdBQUcscUJBQXFCLENBQUM7UUFDbEMsSUFBSSxDQUFDLE1BQU0sR0FBRyxJQUFJLEtBQUssMEJBQTBCLENBQUMsQ0FBQyxDQUFDLEdBQUcsQ0FBQyxDQUFDLENBQUMsR0FBRyxDQUFDO0lBQ2hFLENBQUM7Q0FDRiJ9
@@ -0,0 +1,142 @@
1
+ export interface IOidcAuthorizationOptions {
2
+ state: string;
3
+ nonce: string;
4
+ pkceVerifier: string;
5
+ }
6
+ export interface IOidcCompletionOptions extends IOidcAuthorizationOptions {
7
+ /** the callback as the application's own public origin saw it */
8
+ callbackUrl: URL;
9
+ }
10
+ /** One organization the provider reports for the subject; display data, not an authorization grant. */
11
+ export interface IOidcOrganizationClaim {
12
+ id: string;
13
+ name: string;
14
+ slug: string;
15
+ roles: string[];
16
+ }
17
+ export interface IOidcCompletion {
18
+ subject: string;
19
+ accessToken: string;
20
+ refreshToken?: string;
21
+ /** epoch milliseconds; `0` when the token response named no lifetime */
22
+ accessTokenExpiresAt: number;
23
+ email?: string;
24
+ emailVerified: boolean;
25
+ displayName?: string;
26
+ organizations: IOidcOrganizationClaim[];
27
+ }
28
+ /**
29
+ * One live observation of a subject's authority in one organization, as
30
+ * `POST /oauth/introspect` answers it. An inactive answer names no reason:
31
+ * revoked token, dead login session, removed membership and disconnected app
32
+ * are deliberately indistinguishable.
33
+ */
34
+ export interface IOidcOrganizationObservation {
35
+ active: boolean;
36
+ /** the sorted membership role keys; empty when inactive */
37
+ roles: readonly string[];
38
+ /** the observation's own `exp` in epoch milliseconds; `0` when inactive */
39
+ expiresAt: number;
40
+ }
41
+ export interface IOidcRefreshedTokens {
42
+ accessToken: string;
43
+ /** the rotated refresh token; absent when the provider returned none */
44
+ refreshToken?: string;
45
+ /** epoch milliseconds; `0` when the token response named no lifetime */
46
+ accessTokenExpiresAt: number;
47
+ }
48
+ /** Why an observation or a refresh could not be made; never why access was refused. */
49
+ export type TOidcAuthorityFailure = 'unavailable' | 'invalid_response';
50
+ /** The provider could not be asked, or did not answer in its own contract. The caller fails closed on it. */
51
+ export declare class OidcAuthorityUnavailableError extends Error {
52
+ readonly reason: TOidcAuthorityFailure;
53
+ constructor(reason: TOidcAuthorityFailure, causeArg?: unknown);
54
+ }
55
+ /** The provider refused the refresh with `invalid_grant`: the refresh family is revoked, expired or was reused. */
56
+ export declare class OidcRefreshRefusedError extends Error {
57
+ constructor(causeArg?: unknown);
58
+ }
59
+ export type TOidcTokenHint = 'access_token' | 'refresh_token';
60
+ export type TOidcCompletionFailureStage = 'token_exchange' | 'token_validation' | 'userinfo';
61
+ export declare class OidcCompletionError extends Error {
62
+ readonly stage: TOidcCompletionFailureStage;
63
+ constructor(stage: TOidcCompletionFailureStage, causeArg: unknown);
64
+ }
65
+ /**
66
+ * What the session layer needs from the provider. `IdpOidcProvider` is the
67
+ * implementation; tests bind a double to it, so no test opens a socket.
68
+ */
69
+ export interface IOidcProvider {
70
+ /**
71
+ * The deadline of every provider request, in seconds. The refresh lock that
72
+ * keeps two replicas from spending one refresh token outlives it.
73
+ */
74
+ readonly requestTimeoutSeconds: number;
75
+ init(): Promise<void>;
76
+ buildAuthorizationUrl(optionsArg: IOidcAuthorizationOptions): Promise<URL>;
77
+ completeAuthorization(optionsArg: IOidcCompletionOptions): Promise<IOidcCompletion>;
78
+ revokeToken(tokenArg: string, hintArg: TOidcTokenHint): Promise<void>;
79
+ /** Throws `OidcAuthorityUnavailableError` rather than guessing an answer. */
80
+ introspectOrganization(optionsArg: {
81
+ accessToken: string;
82
+ subject: string;
83
+ organizationId: string;
84
+ }): Promise<IOidcOrganizationObservation>;
85
+ /** Throws `OidcRefreshRefusedError` on `invalid_grant`, `OidcAuthorityUnavailableError` for everything else. */
86
+ refreshTokens(refreshTokenArg: string): Promise<IOidcRefreshedTokens>;
87
+ }
88
+ /**
89
+ * The scopes a relying party asks for. `organizations` and `roles` are what
90
+ * idp.global reports memberships under.
91
+ */
92
+ export declare const IDP_OIDC_DEFAULT_SCOPES = "openid profile email organizations roles";
93
+ export declare const IDP_OIDC_DEFAULT_REQUEST_TIMEOUT_SECONDS = 10;
94
+ export interface IIdpOidcProviderOptions {
95
+ /** the issuer origin, `https://…`; the introspection client refuses anything else */
96
+ issuer: URL;
97
+ clientId: string;
98
+ clientSecret: string;
99
+ /** the registered callback, on the application's public origin */
100
+ redirectUri: URL;
101
+ scopes?: string;
102
+ /** deadline of discovery, token, UserInfo, refresh and revocation requests */
103
+ requestTimeoutSeconds?: number;
104
+ /** deadline of one introspection; the introspection client's default applies when absent */
105
+ introspectionTimeoutMs?: number;
106
+ }
107
+ /**
108
+ * Organization claims as idp.global documents them: `id`, `name`, `slug` and
109
+ * the member's roles. Anything that is not that exact shape is dropped rather
110
+ * than guessed at.
111
+ */
112
+ export declare const readOrganizationClaims: (valueArg: unknown) => IOidcOrganizationClaim[];
113
+ /**
114
+ * The relying-party side of the authorization code flow with S256 PKCE on
115
+ * `openid-client`, in the profile idp.global supports: code responses, S256
116
+ * challenges, RS256 ID tokens and `client_secret_basic` for a confidential
117
+ * client. Live organization authority is observed with the sdk's
118
+ * `OidcIntrospectionClient`.
119
+ */
120
+ export declare class IdpOidcProvider implements IOidcProvider {
121
+ #private;
122
+ readonly requestTimeoutSeconds: number;
123
+ constructor(optionsArg: IIdpOidcProviderOptions);
124
+ private get configuration();
125
+ init(): Promise<void>;
126
+ buildAuthorizationUrl(optionsArg: IOidcAuthorizationOptions): Promise<URL>;
127
+ completeAuthorization(optionsArg: IOidcCompletionOptions): Promise<IOidcCompletion>;
128
+ revokeToken(tokenArg: string, hintArg: TOidcTokenHint): Promise<void>;
129
+ /**
130
+ * One `POST /oauth/introspect` through the confidential introspection
131
+ * client, which validates the whole answer before this code sees it. A
132
+ * refusal (`active: false`) is an answer; anything else is a failure to
133
+ * observe.
134
+ */
135
+ introspectOrganization(optionsArg: {
136
+ accessToken: string;
137
+ subject: string;
138
+ organizationId: string;
139
+ }): Promise<IOidcOrganizationObservation>;
140
+ /** The refresh grant with rotation; a superseded refresh token is never kept by the caller. */
141
+ refreshTokens(refreshTokenArg: string): Promise<IOidcRefreshedTokens>;
142
+ }