@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.
- package/changelog.md +17 -0
- package/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts_relyingparty/classes.actorscope.d.ts +23 -0
- package/dist_ts_relyingparty/classes.actorscope.js +22 -0
- package/dist_ts_relyingparty/classes.authorityobserver.d.ts +81 -0
- package/dist_ts_relyingparty/classes.authorityobserver.js +185 -0
- package/dist_ts_relyingparty/classes.errors.d.ts +33 -0
- package/dist_ts_relyingparty/classes.errors.js +53 -0
- package/dist_ts_relyingparty/classes.idpoidcprovider.d.ts +142 -0
- package/dist_ts_relyingparty/classes.idpoidcprovider.js +255 -0
- package/dist_ts_relyingparty/classes.relyingpartyroutes.d.ts +84 -0
- package/dist_ts_relyingparty/classes.relyingpartyroutes.js +232 -0
- package/dist_ts_relyingparty/classes.relyingpartysessions.d.ts +210 -0
- package/dist_ts_relyingparty/classes.relyingpartysessions.js +786 -0
- package/dist_ts_relyingparty/classes.rpmodels.d.ts +79 -0
- package/dist_ts_relyingparty/classes.rpmodels.js +283 -0
- package/dist_ts_relyingparty/classes.secretbox.d.ts +14 -0
- package/dist_ts_relyingparty/classes.secretbox.js +45 -0
- package/dist_ts_relyingparty/classes.socketbinding.d.ts +75 -0
- package/dist_ts_relyingparty/classes.socketbinding.js +94 -0
- package/dist_ts_relyingparty/helpers.cookies.d.ts +11 -0
- package/dist_ts_relyingparty/helpers.cookies.js +62 -0
- package/dist_ts_relyingparty/helpers.crypto.d.ts +12 -0
- package/dist_ts_relyingparty/helpers.crypto.js +40 -0
- package/dist_ts_relyingparty/helpers.returnpath.d.ts +9 -0
- package/dist_ts_relyingparty/helpers.returnpath.js +39 -0
- package/dist_ts_relyingparty/index.d.ts +13 -0
- package/dist_ts_relyingparty/index.js +14 -0
- package/dist_ts_relyingparty/interfaces.d.ts +113 -0
- package/dist_ts_relyingparty/interfaces.js +2 -0
- package/dist_ts_relyingparty/plugins.d.ts +10 -0
- package/dist_ts_relyingparty/plugins.js +11 -0
- package/package.json +13 -10
- package/readme.md +158 -4
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts_relyingparty/classes.actorscope.ts +39 -0
- package/ts_relyingparty/classes.authorityobserver.ts +253 -0
- package/ts_relyingparty/classes.errors.ts +69 -0
- package/ts_relyingparty/classes.idpoidcprovider.ts +386 -0
- package/ts_relyingparty/classes.relyingpartyroutes.ts +286 -0
- package/ts_relyingparty/classes.relyingpartysessions.ts +970 -0
- package/ts_relyingparty/classes.rpmodels.ts +146 -0
- package/ts_relyingparty/classes.secretbox.ts +49 -0
- package/ts_relyingparty/classes.socketbinding.ts +142 -0
- package/ts_relyingparty/helpers.cookies.ts +79 -0
- package/ts_relyingparty/helpers.crypto.ts +49 -0
- package/ts_relyingparty/helpers.returnpath.ts +44 -0
- package/ts_relyingparty/index.ts +13 -0
- package/ts_relyingparty/interfaces.ts +133 -0
- package/ts_relyingparty/plugins.ts +17 -0
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import * as plugins from './plugins.js';
|
|
2
|
+
|
|
3
|
+
export interface IActorScope<TContext> {
|
|
4
|
+
/** Runs `operationArg` and everything it awaits as this actor. */
|
|
5
|
+
run<T>(contextArg: TContext, operationArg: () => T): T;
|
|
6
|
+
/** The actor of the operation in flight, or `undefined` outside one. */
|
|
7
|
+
current(): TContext | undefined;
|
|
8
|
+
/** Runs `operationArg` without any actor, for background work entered from a request. */
|
|
9
|
+
exit<T>(operationArg: () => T): T;
|
|
10
|
+
/**
|
|
11
|
+
* The request scope of a `TypedRouter` (`addRequestScope`): middleware and
|
|
12
|
+
* handler of every dispatch run as the actor the transport's trusted local
|
|
13
|
+
* data names. A dispatch without one runs without an actor.
|
|
14
|
+
*/
|
|
15
|
+
readonly requestScope: plugins.typedrequest.TTypedRouterRequestScope;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* An AsyncLocalStorage-backed actor context. The application decides what an
|
|
20
|
+
* actor is and how it is read from the trusted local data its transports set;
|
|
21
|
+
* nothing a handler can reach decides it.
|
|
22
|
+
*/
|
|
23
|
+
export const createActorScope = <TContext>(optionsArg: {
|
|
24
|
+
fromLocalData(localDataArg: Readonly<Record<string, unknown>>): TContext | undefined;
|
|
25
|
+
}): IActorScope<TContext> => {
|
|
26
|
+
const storage = new plugins.asyncHooks.AsyncLocalStorage<TContext>();
|
|
27
|
+
return {
|
|
28
|
+
run: (contextArg, operationArg) => storage.run(contextArg, operationArg),
|
|
29
|
+
current: () => storage.getStore(),
|
|
30
|
+
exit: (operationArg) => storage.exit(operationArg),
|
|
31
|
+
requestScope: (_typedRequestArg, nextArg, contextArg) => {
|
|
32
|
+
const actor = optionsArg.fromLocalData(contextArg.localData);
|
|
33
|
+
if (actor === undefined) {
|
|
34
|
+
return nextArg();
|
|
35
|
+
}
|
|
36
|
+
return storage.run(actor, nextArg);
|
|
37
|
+
},
|
|
38
|
+
};
|
|
39
|
+
};
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
import { AuthorityTokenError } from './classes.errors.js';
|
|
2
|
+
import { OidcAuthorityUnavailableError, type IOidcProvider } from './classes.idpoidcprovider.js';
|
|
3
|
+
import type { IAuthorityTokenSource } from './interfaces.js';
|
|
4
|
+
|
|
5
|
+
/** Why an observation refused: no membership at all, or roles the application maps to nothing. */
|
|
6
|
+
export type TAuthorityRefusalReason = 'idp_membership_absent' | 'idp_roles_unmapped';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* idp.global no longer reports a usable membership of the organization.
|
|
10
|
+
* `firstRefusal` is `true` exactly once per transition from access to refusal
|
|
11
|
+
* for one login session and organization, so an audit trail records the
|
|
12
|
+
* transition and not every refused request.
|
|
13
|
+
*/
|
|
14
|
+
export class AuthorityRefusedError extends Error {
|
|
15
|
+
public readonly code = 'organization_access_revoked';
|
|
16
|
+
public readonly status = 403;
|
|
17
|
+
|
|
18
|
+
constructor(
|
|
19
|
+
public readonly reason: TAuthorityRefusalReason,
|
|
20
|
+
public readonly firstRefusal: boolean,
|
|
21
|
+
) {
|
|
22
|
+
super('organization_access_revoked');
|
|
23
|
+
this.name = 'AuthorityRefusedError';
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** One live observation, as the caller reads it. */
|
|
28
|
+
export interface IAuthorityObservation<TGrant> {
|
|
29
|
+
/** what the application's projection made of the roles */
|
|
30
|
+
grant: TGrant;
|
|
31
|
+
/** the idp role keys the provider reported, sorted */
|
|
32
|
+
roles: readonly string[];
|
|
33
|
+
/** the observation's own `exp` in epoch milliseconds */
|
|
34
|
+
tokenExpiresAt: number;
|
|
35
|
+
observedAt: number;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface IAuthorityObserverOptions<TGrant> {
|
|
39
|
+
provider: IOidcProvider;
|
|
40
|
+
tokens: IAuthorityTokenSource;
|
|
41
|
+
/** the application's role table: what the provider's role keys grant here, `undefined` for nothing */
|
|
42
|
+
project(rolesArg: readonly string[]): TGrant | undefined;
|
|
43
|
+
/**
|
|
44
|
+
* How long a positive observation answers later requests of the same login
|
|
45
|
+
* session and organization. `0` (the default) observes freshly for every
|
|
46
|
+
* operation, which is the provider's contract; a positive value is an
|
|
47
|
+
* explicit opt-in under which a revocation at idp.global takes effect here
|
|
48
|
+
* only once the cached answer expires.
|
|
49
|
+
*/
|
|
50
|
+
positiveTtlMs?: number;
|
|
51
|
+
/** How long a refusal is reused; `0` (the default) never reuses one. */
|
|
52
|
+
negativeTtlMs?: number;
|
|
53
|
+
/** the bound of remembered observations */
|
|
54
|
+
maxEntries?: number;
|
|
55
|
+
now?: () => number;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const DEFAULT_MAX_ENTRIES = 2_000;
|
|
59
|
+
/** how much room an eviction makes, so it does not run on every insert */
|
|
60
|
+
const EVICTION_HEADROOM = 0.1;
|
|
61
|
+
|
|
62
|
+
interface ICachedObservation<TGrant> {
|
|
63
|
+
outcome: 'active' | 'inactive';
|
|
64
|
+
grant?: TGrant;
|
|
65
|
+
reason?: TAuthorityRefusalReason;
|
|
66
|
+
roles: readonly string[];
|
|
67
|
+
tokenExpiresAt: number;
|
|
68
|
+
observedAt: number;
|
|
69
|
+
/** an entry at or past it never answers a request */
|
|
70
|
+
expiresAt: number;
|
|
71
|
+
/** the refusal whose transition was already reported */
|
|
72
|
+
reported: boolean;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const assertTtl = (valueArg: number, labelArg: string): number => {
|
|
76
|
+
if (!Number.isSafeInteger(valueArg) || valueArg < 0) {
|
|
77
|
+
throw new Error(labelArg + ' must be a non-negative integer of milliseconds');
|
|
78
|
+
}
|
|
79
|
+
return valueArg;
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Live organization authority through idp.global: one `POST /oauth/introspect`
|
|
84
|
+
* observation of a login session's subject in one organization, projected by
|
|
85
|
+
* the application's role table.
|
|
86
|
+
*
|
|
87
|
+
* Observations of one key run once however many callers ask at the same time.
|
|
88
|
+
* A failure to observe is never remembered, so the caller fails closed on
|
|
89
|
+
* every attempt while the provider is unreachable. A session that is forgotten
|
|
90
|
+
* while its observation is in flight never stores a positive answer. Nothing
|
|
91
|
+
* runs on a timer: the cache is pruned when it is written to.
|
|
92
|
+
*/
|
|
93
|
+
export class AuthorityObserver<TGrant> {
|
|
94
|
+
readonly #cache = new Map<string, ICachedObservation<TGrant>>();
|
|
95
|
+
readonly #inFlight = new Map<string, Promise<ICachedObservation<TGrant>>>();
|
|
96
|
+
readonly #forgotten = new Set<string>();
|
|
97
|
+
readonly #provider: IOidcProvider;
|
|
98
|
+
readonly #tokens: IAuthorityTokenSource;
|
|
99
|
+
readonly #project: (rolesArg: readonly string[]) => TGrant | undefined;
|
|
100
|
+
readonly #positiveTtlMs: number;
|
|
101
|
+
readonly #negativeTtlMs: number;
|
|
102
|
+
readonly #maxEntries: number;
|
|
103
|
+
readonly #now: () => number;
|
|
104
|
+
|
|
105
|
+
constructor(optionsArg: IAuthorityObserverOptions<TGrant>) {
|
|
106
|
+
this.#provider = optionsArg.provider;
|
|
107
|
+
this.#tokens = optionsArg.tokens;
|
|
108
|
+
this.#project = optionsArg.project;
|
|
109
|
+
this.#positiveTtlMs = assertTtl(optionsArg.positiveTtlMs ?? 0, 'positiveTtlMs');
|
|
110
|
+
this.#negativeTtlMs = assertTtl(optionsArg.negativeTtlMs ?? 0, 'negativeTtlMs');
|
|
111
|
+
this.#maxEntries = optionsArg.maxEntries ?? DEFAULT_MAX_ENTRIES;
|
|
112
|
+
if (!Number.isSafeInteger(this.#maxEntries) || this.#maxEntries < 1) {
|
|
113
|
+
throw new Error('maxEntries must be a positive integer');
|
|
114
|
+
}
|
|
115
|
+
this.#now = optionsArg.now ?? (() => Date.now());
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/** How many observations are remembered right now. */
|
|
119
|
+
public get size(): number {
|
|
120
|
+
return this.#cache.size;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* The grant of `principalId`'s login session in one organization at
|
|
125
|
+
* idp.global, or a throw: `AuthorityRefusedError` for a refusal,
|
|
126
|
+
* `AuthorityTokenError` when there is no live login to observe with or the
|
|
127
|
+
* provider could not be observed.
|
|
128
|
+
*/
|
|
129
|
+
public async observe(optionsArg: {
|
|
130
|
+
loginSessionId: string;
|
|
131
|
+
principalId: string;
|
|
132
|
+
idpOrganizationId: string;
|
|
133
|
+
}): Promise<IAuthorityObservation<TGrant>> {
|
|
134
|
+
const key = `${optionsArg.loginSessionId} ${optionsArg.idpOrganizationId}`;
|
|
135
|
+
const cached = this.liveEntry(key);
|
|
136
|
+
const observation = cached ?? (await this.observeOnce(key, optionsArg));
|
|
137
|
+
if (observation.outcome !== 'active' || observation.grant === undefined) {
|
|
138
|
+
const firstRefusal = cached === undefined && !observation.reported;
|
|
139
|
+
observation.reported = true;
|
|
140
|
+
throw new AuthorityRefusedError(observation.reason ?? 'idp_membership_absent', firstRefusal);
|
|
141
|
+
}
|
|
142
|
+
return {
|
|
143
|
+
grant: observation.grant,
|
|
144
|
+
roles: observation.roles,
|
|
145
|
+
tokenExpiresAt: observation.tokenExpiresAt,
|
|
146
|
+
observedAt: observation.observedAt,
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/** Drops every observation of a session; wire it to the session layer's `onSessionEnded`. */
|
|
151
|
+
public forgetSession(sessionIdArg: string): void {
|
|
152
|
+
const prefix = `${sessionIdArg} `;
|
|
153
|
+
for (const key of this.#cache.keys()) {
|
|
154
|
+
if (key.startsWith(prefix)) {
|
|
155
|
+
this.#cache.delete(key);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
for (const key of this.#inFlight.keys()) {
|
|
159
|
+
if (key.startsWith(prefix)) {
|
|
160
|
+
this.#forgotten.add(key);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
private liveEntry(keyArg: string): ICachedObservation<TGrant> | undefined {
|
|
166
|
+
const entry = this.#cache.get(keyArg);
|
|
167
|
+
return entry && entry.expiresAt > this.#now() ? entry : undefined;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
private async observeOnce(
|
|
171
|
+
keyArg: string,
|
|
172
|
+
optionsArg: { loginSessionId: string; principalId: string; idpOrganizationId: string },
|
|
173
|
+
): Promise<ICachedObservation<TGrant>> {
|
|
174
|
+
const running = this.#inFlight.get(keyArg);
|
|
175
|
+
if (running) {
|
|
176
|
+
return running;
|
|
177
|
+
}
|
|
178
|
+
const pending = this.makeObservation(keyArg, optionsArg).finally(() => {
|
|
179
|
+
this.#inFlight.delete(keyArg);
|
|
180
|
+
this.#forgotten.delete(keyArg);
|
|
181
|
+
});
|
|
182
|
+
this.#inFlight.set(keyArg, pending);
|
|
183
|
+
return pending;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
private async makeObservation(
|
|
187
|
+
keyArg: string,
|
|
188
|
+
optionsArg: { loginSessionId: string; principalId: string; idpOrganizationId: string },
|
|
189
|
+
): Promise<ICachedObservation<TGrant>> {
|
|
190
|
+
const token = await this.#tokens.liveProviderToken(optionsArg.loginSessionId, optionsArg.principalId);
|
|
191
|
+
let answer: Awaited<ReturnType<IOidcProvider['introspectOrganization']>>;
|
|
192
|
+
try {
|
|
193
|
+
answer = await this.#provider.introspectOrganization({
|
|
194
|
+
accessToken: token.accessToken,
|
|
195
|
+
subject: token.subject,
|
|
196
|
+
organizationId: optionsArg.idpOrganizationId,
|
|
197
|
+
});
|
|
198
|
+
} catch (error) {
|
|
199
|
+
if (error instanceof OidcAuthorityUnavailableError) {
|
|
200
|
+
throw new AuthorityTokenError('authority_unavailable', error);
|
|
201
|
+
}
|
|
202
|
+
throw error;
|
|
203
|
+
}
|
|
204
|
+
const grant = answer.active ? this.#project(answer.roles) : undefined;
|
|
205
|
+
const active = answer.active && grant !== undefined;
|
|
206
|
+
const previous = this.#cache.get(keyArg);
|
|
207
|
+
const entry: ICachedObservation<TGrant> = {
|
|
208
|
+
outcome: active ? 'active' : 'inactive',
|
|
209
|
+
...(active ? { grant } : { reason: answer.active ? 'idp_roles_unmapped' : 'idp_membership_absent' }),
|
|
210
|
+
roles: answer.roles,
|
|
211
|
+
tokenExpiresAt: answer.expiresAt,
|
|
212
|
+
observedAt: this.#now(),
|
|
213
|
+
expiresAt: this.#now() + (active ? this.#positiveTtlMs : this.#negativeTtlMs),
|
|
214
|
+
// a refusal after a reported refusal is the same transition
|
|
215
|
+
reported: !active && previous?.outcome === 'inactive' && previous.reported,
|
|
216
|
+
};
|
|
217
|
+
if (active && this.#forgotten.has(keyArg)) {
|
|
218
|
+
// the request holding this answer finishes — it was authorized before
|
|
219
|
+
// the session ended — and every later one observes again
|
|
220
|
+
entry.expiresAt = 0;
|
|
221
|
+
return entry;
|
|
222
|
+
}
|
|
223
|
+
// a refusal is kept even when it answers nothing (`negativeTtlMs` 0): it is
|
|
224
|
+
// what reports one transition instead of one per refused request
|
|
225
|
+
this.#cache.delete(keyArg);
|
|
226
|
+
this.#cache.set(keyArg, entry);
|
|
227
|
+
this.prune();
|
|
228
|
+
return entry;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/** Expired entries go first; if that is not enough, the oldest do. */
|
|
232
|
+
private prune(): void {
|
|
233
|
+
if (this.#cache.size <= this.#maxEntries) {
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
const now = this.#now();
|
|
237
|
+
for (const [key, entry] of this.#cache) {
|
|
238
|
+
if (entry.expiresAt <= now) {
|
|
239
|
+
this.#cache.delete(key);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
if (this.#cache.size <= this.#maxEntries) {
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
245
|
+
const target = Math.floor(this.#maxEntries * (1 - EVICTION_HEADROOM));
|
|
246
|
+
for (const key of this.#cache.keys()) {
|
|
247
|
+
if (this.#cache.size <= target) {
|
|
248
|
+
break;
|
|
249
|
+
}
|
|
250
|
+
this.#cache.delete(key);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import type { TLoginCallbackStage } from './interfaces.js';
|
|
2
|
+
|
|
3
|
+
export type TRelyingPartyErrorCode =
|
|
4
|
+
| 'login_method_unavailable'
|
|
5
|
+
| 'invalid_login_callback'
|
|
6
|
+
| 'missing_login_intent'
|
|
7
|
+
| 'invalid_or_replayed_login'
|
|
8
|
+
| 'invalid_login_finalize';
|
|
9
|
+
|
|
10
|
+
const RELYING_PARTY_ERROR_STATUS: Readonly<Record<TRelyingPartyErrorCode, number>> = {
|
|
11
|
+
login_method_unavailable: 404,
|
|
12
|
+
invalid_login_callback: 400,
|
|
13
|
+
missing_login_intent: 400,
|
|
14
|
+
invalid_or_replayed_login: 400,
|
|
15
|
+
invalid_login_finalize: 400,
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
/** A request the login flow refuses; the code is safe to answer, the browser learns nothing more. */
|
|
19
|
+
export class RelyingPartyError extends Error {
|
|
20
|
+
public readonly status: number;
|
|
21
|
+
|
|
22
|
+
constructor(public readonly code: TRelyingPartyErrorCode) {
|
|
23
|
+
super(code);
|
|
24
|
+
this.name = 'RelyingPartyError';
|
|
25
|
+
this.status = RELYING_PARTY_ERROR_STATUS[code];
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* The application refuses a verified provider login (a disabled account, a
|
|
31
|
+
* subject of another issuer, no organization it serves). Thrown from
|
|
32
|
+
* `IRelyingPartyPrincipals.admit`; it reaches the caller unchanged.
|
|
33
|
+
*/
|
|
34
|
+
export class RelyingPartyRefusal extends Error {
|
|
35
|
+
constructor(
|
|
36
|
+
public readonly code: string,
|
|
37
|
+
public readonly status: number,
|
|
38
|
+
) {
|
|
39
|
+
super(code);
|
|
40
|
+
this.name = 'RelyingPartyRefusal';
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** A login callback that failed for a reason other than a refusal; the stage is for the log only. */
|
|
45
|
+
export class LoginCallbackError extends Error {
|
|
46
|
+
constructor(
|
|
47
|
+
public readonly stage: TLoginCallbackStage,
|
|
48
|
+
causeArg: unknown,
|
|
49
|
+
) {
|
|
50
|
+
super('The login callback could not be completed', { cause: causeArg });
|
|
51
|
+
this.name = 'LoginCallbackError';
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export type TAuthorityTokenErrorCode =
|
|
56
|
+
/** there is no live provider login this credential could be observed with */
|
|
57
|
+
| 'authority_login_required'
|
|
58
|
+
/** the provider could not be reached; the caller fails closed */
|
|
59
|
+
| 'authority_unavailable';
|
|
60
|
+
|
|
61
|
+
export class AuthorityTokenError extends Error {
|
|
62
|
+
public readonly status: number;
|
|
63
|
+
|
|
64
|
+
constructor(public readonly code: TAuthorityTokenErrorCode, causeArg?: unknown) {
|
|
65
|
+
super(code, { cause: causeArg });
|
|
66
|
+
this.name = 'AuthorityTokenError';
|
|
67
|
+
this.status = code === 'authority_login_required' ? 403 : 503;
|
|
68
|
+
}
|
|
69
|
+
}
|