@idp.global/sdk 19.1.0 → 19.2.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.
@@ -1,5 +1,6 @@
1
1
  import * as plugins from './plugins.js';
2
2
  import {
3
+ OidcAuthorityUnavailableError,
3
4
  OidcCompletionError,
4
5
  OidcRefreshRefusedError,
5
6
  type IOidcAuthorizationOptions,
@@ -60,6 +61,9 @@ interface IIssuedToken {
60
61
 
61
62
  const randomToken = (): string => plugins.crypto.randomBytes(18).toString('base64url');
62
63
 
64
+ /** What `IdpOidcProvider` fails with when a step needs the discovery that has not succeeded. */
65
+ const notDiscovered = (): Error => new Error('The identity provider is not initialized');
66
+
63
67
  /**
64
68
  * The provider as `IOidcProvider` sees it. A test adds personas, names the one
65
69
  * the next login signs in as (`signInNextAs`), follows the authorization URL
@@ -68,7 +72,9 @@ const randomToken = (): string => plugins.crypto.randomBytes(18).toString('base6
68
72
  * their login's state; access tokens introspect for their subject while they
69
73
  * live; refresh tokens rotate, and a superseded one coming back revokes its
70
74
  * whole family, as idp.global's reuse detection does. Every call is recorded,
71
- * and each step can be made to fail.
75
+ * and each step can be made to fail. Discovery as well (`discoveryFailure`):
76
+ * until an `init` succeeds, the double then answers as `IdpOidcProvider` does
77
+ * before discovery.
72
78
  */
73
79
  export class RelyingPartyProviderDouble implements IOidcProvider {
74
80
  public readonly requestTimeoutSeconds: number;
@@ -80,7 +86,18 @@ export class RelyingPartyProviderDouble implements IOidcProvider {
80
86
  public readonly revocations: Array<{ token: string; hint: TOidcTokenHint }> = [];
81
87
  public readonly introspections: Array<{ accessToken: string; subject: string; organizationId: string }> = [];
82
88
  public readonly refreshes: string[] = [];
89
+ /** how often `init` was called, failed or not */
90
+ public discoveries = 0;
83
91
 
92
+ /**
93
+ * when set, an `init` before one has succeeded fails with it, and from then
94
+ * until an `init` succeeds no authorization URL is built, a completion fails
95
+ * at the token exchange and introspection and refresh fail as unavailable,
96
+ * as before discovery; such a step is not recorded, as no request of it
97
+ * reached the provider. Once an `init` has succeeded, discovery stays done,
98
+ * as with `IdpOidcProvider`
99
+ */
100
+ public discoveryFailure?: Error;
84
101
  /** when set, the completion of a login fails with it (an `OidcCompletionError` names its stage) */
85
102
  public completionFailure?: Error;
86
103
  /** when set, every introspection fails with it (an `OidcAuthorityUnavailableError` for an unreachable provider) */
@@ -99,6 +116,9 @@ export class RelyingPartyProviderDouble implements IOidcProvider {
99
116
  readonly #codes = new Map<string, IIssuedCode>();
100
117
  readonly #tokens = new Map<string, IIssuedToken>();
101
118
  #next?: string;
119
+ /** the double counts as discovered until an `init` fails, and for good once one has succeeded */
120
+ #discovered = true;
121
+ #initialized = false;
102
122
 
103
123
  constructor(optionsArg: IProviderDoubleOptions = {}) {
104
124
  this.issuer = new URL(optionsArg.issuer ?? 'https://idp.test').origin;
@@ -172,9 +192,23 @@ export class RelyingPartyProviderDouble implements IOidcProvider {
172
192
 
173
193
  // ---- IOidcProvider ----
174
194
 
175
- public async init(): Promise<void> {}
195
+ public async init(): Promise<void> {
196
+ this.discoveries += 1;
197
+ if (this.#initialized) {
198
+ return;
199
+ }
200
+ if (this.discoveryFailure) {
201
+ this.#discovered = false;
202
+ throw this.discoveryFailure;
203
+ }
204
+ this.#discovered = true;
205
+ this.#initialized = true;
206
+ }
176
207
 
177
208
  public async buildAuthorizationUrl(optionsArg: IOidcAuthorizationOptions): Promise<URL> {
209
+ if (!this.#discovered) {
210
+ throw notDiscovered();
211
+ }
178
212
  if (!this.#next) {
179
213
  throw new Error('RelyingPartyProviderDouble: name the persona this login signs in as with signInNextAs');
180
214
  }
@@ -194,6 +228,9 @@ export class RelyingPartyProviderDouble implements IOidcProvider {
194
228
  }
195
229
 
196
230
  public async completeAuthorization(optionsArg: IOidcCompletionOptions): Promise<IOidcCompletion> {
231
+ if (!this.#discovered) {
232
+ throw new OidcCompletionError('token_exchange', notDiscovered());
233
+ }
197
234
  const code = optionsArg.callbackUrl.searchParams.get('code') ?? '';
198
235
  const issued = this.#codes.get(code);
199
236
  this.#codes.delete(code);
@@ -224,6 +261,9 @@ export class RelyingPartyProviderDouble implements IOidcProvider {
224
261
  }
225
262
 
226
263
  public async revokeToken(tokenArg: string, hintArg: TOidcTokenHint): Promise<void> {
264
+ if (!this.#discovered) {
265
+ throw notDiscovered();
266
+ }
227
267
  this.revocations.push({ token: tokenArg, hint: hintArg });
228
268
  if (this.revocationFailure) {
229
269
  throw this.revocationFailure;
@@ -239,6 +279,9 @@ export class RelyingPartyProviderDouble implements IOidcProvider {
239
279
  subject: string;
240
280
  organizationId: string;
241
281
  }): Promise<IOidcOrganizationObservation> {
282
+ if (!this.#discovered) {
283
+ throw new OidcAuthorityUnavailableError('unavailable', notDiscovered());
284
+ }
242
285
  this.introspections.push({ ...optionsArg });
243
286
  await this.onIntrospect?.({ ...optionsArg });
244
287
  if (this.introspectionFailure) {
@@ -257,6 +300,9 @@ export class RelyingPartyProviderDouble implements IOidcProvider {
257
300
  }
258
301
 
259
302
  public async refreshTokens(refreshTokenArg: string): Promise<IOidcRefreshedTokens> {
303
+ if (!this.#discovered) {
304
+ throw new OidcAuthorityUnavailableError('unavailable', notDiscovered());
305
+ }
260
306
  this.refreshes.push(refreshTokenArg);
261
307
  await this.onRefresh?.(refreshTokenArg);
262
308
  if (this.refreshFailure) {