@securitydept/basic-auth-context-client 0.3.0-beta.3 → 0.3.0-beta.6

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/dist/index.mjs CHANGED
@@ -1,3 +1,768 @@
1
- import { n as readBasicAuthBoundaryKind, r as BasicAuthContextClientConfigSchema, t as BasicAuthContextClient } from "./client-D_0eAARH.mjs";
2
- import { n as AuthGuardResultKind, r as BasicAuthBoundaryKind, t as AuthGuardRedirectStatus } from "./types-2IoKrgnl.mjs";
3
- export { AuthGuardRedirectStatus, AuthGuardResultKind, BasicAuthBoundaryKind, BasicAuthContextClient, BasicAuthContextClientConfigSchema, readBasicAuthBoundaryKind };
1
+ import { ClientError, ClientErrorKind, ENVIRONMENT_TOKEN, INJECTOR_TOKEN, ResourceSnapshotUpdateKind, ResourceStatus, RouterNavigationIntent, RouterNavigationMode, SYMBOL_DISPOSE, SecuritydeptDestroyRef, SecuritydeptInjectionToken, UriReferenceString, createCancellationTokenSource, createLinkedCancellationToken, createOnceAsyncLockCallable, createSignal, defineInstrumentMethodDecorator, describeError, encodeUint8ArrayToBase64, injectDisposableStackFrom, mapResource, readonlySignal, reduceResourceSnapshot, resourceFromSnapshots, throwValidationClientError, validateTraitInput, withDisposableStack } from "@securitydept/client";
2
+ import { RxEventReplaySubject, RxEventSubject, RxStateSignal, commandResponseData, concatCommand, dispatchCommandLocallyToStream } from "@securitydept/client/rx";
3
+ import { filter, from, lastValueFrom, take, takeUntil } from "rxjs";
4
+ import { v7 } from "uuid";
5
+ import { type } from "arktype";
6
+ //#region src/authorization-header.ts
7
+ function createBasicAuthorizationHeaderValue(options) {
8
+ if (options.username.includes(":")) throw new ClientError({
9
+ kind: ClientErrorKind.Configuration,
10
+ code: "basic_auth.authorization_header.username_contains_colon",
11
+ message: "A Basic authentication username cannot contain a colon.",
12
+ source: "basic_auth"
13
+ });
14
+ return `Basic ${encodeUint8ArrayToBase64(new TextEncoder().encode(`${options.username}:${options.password}`))}`;
15
+ }
16
+ //#endregion
17
+ //#region src/schemas.ts
18
+ const BasicAuthZoneConfigSchema = type({
19
+ zonePrefix: "string > 0",
20
+ "loginSubpath?": "string"
21
+ });
22
+ const BasicAuthContextClientConfigSchema = type({
23
+ "id?": "string > 0",
24
+ baseUrl: "string > 0",
25
+ zones: BasicAuthZoneConfigSchema.array().atLeastLength(1),
26
+ "probePath?": "string > 0",
27
+ "autoStart?": "boolean",
28
+ "tracing?": {
29
+ "target?": "string > 0",
30
+ "prefix?": "string > 0"
31
+ }
32
+ });
33
+ //#endregion
34
+ //#region src/tokens.ts
35
+ const BASIC_AUTH_CONTEXT_CLIENT = new SecuritydeptInjectionToken("BASIC_AUTH_CONTEXT_CLIENT");
36
+ const BASIC_AUTH_CONTEXT_CLIENT_CONFIG = new SecuritydeptInjectionToken("BASIC_AUTH_CONTEXT_CLIENT_CONFIG");
37
+ //#endregion
38
+ //#region src/types.ts
39
+ /** Configuration for a single Basic Auth zone. */ const BasicAuthContextErrorCode = {
40
+ OperationFailed: "basic_auth.operation_failed",
41
+ InvalidConfig: "basic_auth.invalid_config",
42
+ ClientDisposed: "basic_auth.client_disposed",
43
+ RouterUnavailable: "basic_auth.router_unavailable",
44
+ ProbePathRequired: "basic_auth.probe_path_required",
45
+ ZoneNotFound: "basic_auth.zone_not_found",
46
+ ZoneRequired: "basic_auth.zone_required"
47
+ };
48
+ const BasicAuthBoundaryKind = {
49
+ Authenticated: "authenticated",
50
+ Challenge: "challenge",
51
+ Unauthorized: "unauthorized"
52
+ };
53
+ const BasicAuthContextSource = { BasicAuthContext: "basic-auth-context" };
54
+ const BasicAuthContextEventType = {
55
+ BoundaryRefreshStarted: "basic_auth.boundary.refresh.started",
56
+ BoundaryRefreshSucceeded: "basic_auth.boundary.refresh.succeeded",
57
+ BoundaryRefreshFailed: "basic_auth.boundary.refresh.failed",
58
+ /** These logout events describe local boundary-projection clearing only. */ LogoutStarted: "basic_auth.logout.started",
59
+ LogoutSucceeded: "basic_auth.logout.succeeded",
60
+ LogoutFailed: "basic_auth.logout.failed",
61
+ LoginRedirectStarted: "basic_auth.login.redirect.started",
62
+ LoginRedirectSucceeded: "basic_auth.login.redirect.succeeded",
63
+ LoginRedirectFailed: "basic_auth.login.redirect.failed"
64
+ };
65
+ const AuthGuardResultKind = {
66
+ Ok: "ok",
67
+ Redirect: "redirect"
68
+ };
69
+ const AuthGuardRedirectStatus = {
70
+ Found: 302,
71
+ SeeOther: 303,
72
+ TemporaryRedirect: 307
73
+ };
74
+ //#endregion
75
+ //#region src/client.ts
76
+ function toPrimitive(input, hint) {
77
+ if (typeof input !== "object" || input === null) return input;
78
+ var prim = input[Symbol.toPrimitive];
79
+ if (prim !== void 0) {
80
+ var res = prim.call(input, hint || "default");
81
+ if (typeof res !== "object") return res;
82
+ throw new TypeError("@@toPrimitive must return a primitive value.");
83
+ }
84
+ return (hint === "string" ? String : Number)(input);
85
+ }
86
+ function toPropertyKey(arg) {
87
+ var key = toPrimitive(arg, "string");
88
+ return typeof key === "symbol" ? key : String(key);
89
+ }
90
+ function checkInRHS(value) {
91
+ if (Object(value) !== value) throw TypeError("right-hand side of 'in' should be an object, got " + (value !== null ? typeof value : "null"));
92
+ return value;
93
+ }
94
+ function setFunctionName(fn, name, prefix) {
95
+ if (typeof name === "symbol") {
96
+ name = name.description;
97
+ name = name ? "[" + name + "]" : "";
98
+ }
99
+ try {
100
+ Object.defineProperty(fn, "name", {
101
+ configurable: true,
102
+ value: prefix ? prefix + " " + name : name
103
+ });
104
+ } catch (_) {}
105
+ return fn;
106
+ }
107
+ function _apply_decs_2311(targetClass, classDecs, memberDecs, classDecsHaveThis, instanceBrand, parentClass) {
108
+ var symbolMetadata = Symbol.metadata || Symbol.for("Symbol.metadata");
109
+ var defineProperty = Object.defineProperty;
110
+ var create = Object.create;
111
+ var metadata;
112
+ var existingNonFields = [create(null), create(null)];
113
+ var hasClassDecs = classDecs.length;
114
+ var _;
115
+ function createRunInitializers(initializers, useStaticThis, hasValue) {
116
+ return function(thisArg, value) {
117
+ if (useStaticThis) {
118
+ value = thisArg;
119
+ thisArg = targetClass;
120
+ }
121
+ for (var i = 0; i < initializers.length; i++) value = initializers[i].apply(thisArg, hasValue ? [value] : []);
122
+ return hasValue ? value : thisArg;
123
+ };
124
+ }
125
+ function assertCallable(fn, hint1, hint2, throwUndefined) {
126
+ if (typeof fn !== "function") {
127
+ if (throwUndefined || fn !== void 0) throw new TypeError(hint1 + " must " + (hint2 || "be") + " a function" + (throwUndefined ? "" : " or undefined"));
128
+ }
129
+ return fn;
130
+ }
131
+ function applyDec(Class, decInfo, decoratorsHaveThis, name, kind, initializers, ret, isStatic, isPrivate, isField, hasPrivateBrand) {
132
+ function assertInstanceIfPrivate(target) {
133
+ if (!hasPrivateBrand(target)) throw new TypeError("Attempted to access private element on non-instance");
134
+ }
135
+ var decs = [].concat(decInfo[0]);
136
+ var decVal = decInfo[3];
137
+ var isClass = !ret;
138
+ var isAccessor = kind === 1;
139
+ var isGetter = kind === 3;
140
+ var isSetter = kind === 4;
141
+ var isMethod = kind === 2;
142
+ function bindPropCall(name, useStaticThis, before) {
143
+ return function(_this, value) {
144
+ if (useStaticThis) {
145
+ value = _this;
146
+ _this = Class;
147
+ }
148
+ if (before) before(_this);
149
+ return desc[name].call(_this, value);
150
+ };
151
+ }
152
+ var desc1 = {};
153
+ var init = [];
154
+ var key = isGetter ? "get" : isSetter || isAccessor ? "set" : "value";
155
+ if (!isClass) {
156
+ if (isPrivate) {
157
+ if (isField || isAccessor) desc1 = {
158
+ get: setFunctionName(function() {
159
+ return decVal(this);
160
+ }, name, "get"),
161
+ set: function(value) {
162
+ decInfo[4](this, value);
163
+ }
164
+ };
165
+ else desc1[key] = decVal;
166
+ if (!isField) setFunctionName(desc1[key], name, isMethod ? "" : key);
167
+ } else if (!isField) desc1 = Object.getOwnPropertyDescriptor(Class, name);
168
+ if (!isField && !isPrivate) {
169
+ _ = existingNonFields[+isStatic][name];
170
+ if (_ && (_ ^ kind) !== 7) throw new Error("Decorating two elements with the same name (" + desc1[key].name + ") is not supported yet");
171
+ existingNonFields[+isStatic][name] = kind < 3 ? 1 : kind;
172
+ }
173
+ }
174
+ var newValue = Class;
175
+ for (var i = decs.length - 1; i >= 0; i -= decoratorsHaveThis ? 2 : 1) {
176
+ var dec = assertCallable(decs[i], "A decorator", "be", true);
177
+ var decThis = decoratorsHaveThis ? decs[i - 1] : void 0;
178
+ var decoratorFinishedRef = {};
179
+ var ctx = {
180
+ kind: [
181
+ "field",
182
+ "accessor",
183
+ "method",
184
+ "getter",
185
+ "setter",
186
+ "class"
187
+ ][kind],
188
+ name,
189
+ metadata,
190
+ addInitializer: (function(decoratorFinishedRef, initializer) {
191
+ if (decoratorFinishedRef.v) throw new TypeError("attempted to call addInitializer after decoration was finished");
192
+ assertCallable(initializer, "An initializer", "be", true);
193
+ initializers.push(initializer);
194
+ }).bind(null, decoratorFinishedRef)
195
+ };
196
+ if (isClass) {
197
+ _ = dec.call(decThis, newValue, ctx);
198
+ decoratorFinishedRef.v = 1;
199
+ if (assertCallable(_, "class decorators", "return")) newValue = _;
200
+ continue;
201
+ }
202
+ ctx.static = isStatic;
203
+ ctx.private = isPrivate;
204
+ _ = ctx.access = { has: isPrivate ? hasPrivateBrand.bind() : function(target) {
205
+ return name in target;
206
+ } };
207
+ if (!isSetter) _.get = isPrivate ? isMethod ? function(_this) {
208
+ assertInstanceIfPrivate(_this);
209
+ return desc1.value;
210
+ } : bindPropCall("get", 0, assertInstanceIfPrivate) : function(target) {
211
+ return target[name];
212
+ };
213
+ if (!isMethod && !isGetter) _.set = isPrivate ? bindPropCall("set", 0, assertInstanceIfPrivate) : function(target, value) {
214
+ target[name] = value;
215
+ };
216
+ newValue = dec.call(decThis, isAccessor ? {
217
+ get: desc1.get,
218
+ set: desc1.set
219
+ } : desc1[key], ctx);
220
+ decoratorFinishedRef.v = 1;
221
+ if (isAccessor) {
222
+ if (typeof newValue === "object" && newValue) {
223
+ if (_ = assertCallable(newValue.get, "accessor.get")) desc1.get = _;
224
+ if (_ = assertCallable(newValue.set, "accessor.set")) desc1.set = _;
225
+ if (_ = assertCallable(newValue.init, "accessor.init")) init.unshift(_);
226
+ } else if (newValue !== void 0) throw new TypeError("accessor decorators must return an object with get, set, or init properties or undefined");
227
+ } else if (assertCallable(newValue, (isField ? "field" : "method") + " decorators", "return")) if (isField) init.unshift(newValue);
228
+ else desc1[key] = newValue;
229
+ }
230
+ if (kind < 2) ret.push(createRunInitializers(init, isStatic, 1), createRunInitializers(initializers, isStatic, 0));
231
+ if (!isField && !isClass) if (isPrivate) if (isAccessor) ret.splice(-1, 0, bindPropCall("get", isStatic), bindPropCall("set", isStatic));
232
+ else ret.push(isMethod ? desc1[key] : assertCallable.call.bind(desc1[key]));
233
+ else defineProperty(Class, name, desc1);
234
+ return newValue;
235
+ }
236
+ function applyMemberDecs() {
237
+ var ret = [];
238
+ var protoInitializers;
239
+ var staticInitializers;
240
+ var pushInitializers = function(initializers) {
241
+ if (initializers) ret.push(createRunInitializers(initializers));
242
+ };
243
+ var applyMemberDecsOfKind = function(isStatic, isField) {
244
+ for (var i = 0; i < memberDecs.length; i++) {
245
+ var decInfo = memberDecs[i];
246
+ var kind = decInfo[1];
247
+ var kindOnly = kind & 7;
248
+ if ((kind & 8) == isStatic && !kindOnly == isField) {
249
+ var name = decInfo[2];
250
+ var isPrivate = !!decInfo[3];
251
+ var decoratorsHaveThis = kind & 16;
252
+ applyDec(isStatic ? targetClass : targetClass.prototype, decInfo, decoratorsHaveThis, isPrivate ? "#" + name : toPropertyKey(name), kindOnly, kindOnly < 2 ? [] : isStatic ? staticInitializers = staticInitializers || [] : protoInitializers = protoInitializers || [], ret, !!isStatic, isPrivate, isField, isStatic && isPrivate ? function(_) {
253
+ return checkInRHS(_) === targetClass;
254
+ } : instanceBrand);
255
+ }
256
+ }
257
+ };
258
+ applyMemberDecsOfKind(8, 0);
259
+ applyMemberDecsOfKind(0, 0);
260
+ applyMemberDecsOfKind(8, 1);
261
+ applyMemberDecsOfKind(0, 1);
262
+ pushInitializers(protoInitializers);
263
+ pushInitializers(staticInitializers);
264
+ return ret;
265
+ }
266
+ function defineMetadata(Class) {
267
+ return defineProperty(Class, symbolMetadata, {
268
+ configurable: true,
269
+ enumerable: true,
270
+ value: metadata
271
+ });
272
+ }
273
+ if (parentClass !== void 0) metadata = parentClass[symbolMetadata];
274
+ metadata = create(metadata == null ? null : metadata);
275
+ _ = applyMemberDecs();
276
+ if (!hasClassDecs) defineMetadata(targetClass);
277
+ return {
278
+ e: _,
279
+ get c() {
280
+ var initializers = [];
281
+ return hasClassDecs && [defineMetadata(targetClass = applyDec(targetClass, [classDecs], classDecsHaveThis, targetClass.name, 5, initializers)), createRunInitializers(initializers, 1)];
282
+ }
283
+ };
284
+ }
285
+ let _dec;
286
+ let _dec1;
287
+ let _dec2;
288
+ let _dec3;
289
+ let _dec4;
290
+ let _dec5;
291
+ let _dec6;
292
+ let _initProto;
293
+ const DEFAULT_LOGIN_SUBPATH = "/login";
294
+ const POST_AUTH_REDIRECT_PARAM = "post_auth_redirect_uri";
295
+ const instrumentBasicAuthMethod = defineInstrumentMethodDecorator(({ factoryArgs: [operation] }) => function() {
296
+ return {
297
+ environment: this.environment,
298
+ span: this.span,
299
+ name: `${this.config.tracing.prefix}.${operation}`,
300
+ target: this.config.tracing.target,
301
+ fields: { operation },
302
+ normalizeError: (error) => ClientError.fromUnknown(error, {
303
+ code: BasicAuthContextErrorCode.OperationFailed,
304
+ message: "The Basic Auth operation failed unexpectedly",
305
+ source: BasicAuthContextSource.BasicAuthContext
306
+ })
307
+ };
308
+ });
309
+ function resolveZone(config) {
310
+ const prefix = config.zonePrefix.replace(/\/+$/, "");
311
+ return {
312
+ zonePrefix: prefix,
313
+ loginPath: prefix + (config.loginSubpath ?? DEFAULT_LOGIN_SUBPATH)
314
+ };
315
+ }
316
+ function readBasicAuthBoundaryKind(options) {
317
+ if (options.status < 400) return BasicAuthBoundaryKind.Authenticated;
318
+ if (options.challengeHeader) return BasicAuthBoundaryKind.Challenge;
319
+ return BasicAuthBoundaryKind.Unauthorized;
320
+ }
321
+ _dec = instrumentBasicAuthMethod("start"), _dec1 = withDisposableStack(0, true), _dec2 = instrumentBasicAuthMethod("refresh"), _dec3 = withDisposableStack(0, true), _dec4 = instrumentBasicAuthMethod("logout"), _dec5 = withDisposableStack(0, true), _dec6 = instrumentBasicAuthMethod("login.redirect");
322
+ /**
323
+ * Basic Auth Context Client.
324
+ *
325
+ * Owns zone-aware Basic Auth boundary observations and login navigation.
326
+ * It does not manage browser credentials or principal data.
327
+ */ var BasicAuthContextClient = class BasicAuthContextClient {
328
+ static {
329
+ ({e: [_initProto]} = _apply_decs_2311(this, [], [
330
+ [
331
+ _dec,
332
+ 2,
333
+ "_start"
334
+ ],
335
+ [
336
+ _dec1,
337
+ 2,
338
+ "refresh"
339
+ ],
340
+ [
341
+ _dec2,
342
+ 2,
343
+ "_refresh"
344
+ ],
345
+ [
346
+ _dec3,
347
+ 2,
348
+ "logout"
349
+ ],
350
+ [
351
+ _dec4,
352
+ 2,
353
+ "_logout"
354
+ ],
355
+ [
356
+ _dec5,
357
+ 2,
358
+ "loginWithRedirect"
359
+ ],
360
+ [
361
+ _dec6,
362
+ 2,
363
+ "_loginWithRedirect"
364
+ ]
365
+ ]));
366
+ }
367
+ static defaultOptions = {
368
+ loginSubpath: DEFAULT_LOGIN_SUBPATH,
369
+ tracing: {
370
+ target: "basic-auth-context-client",
371
+ prefix: "basic_auth_context"
372
+ }
373
+ };
374
+ _environment;
375
+ _config;
376
+ _span;
377
+ _rootCancellation = (_initProto(this), createCancellationTokenSource());
378
+ _destroyed = RxStateSignal.fromInitialValue(false);
379
+ destroyed$ = from(this._destroyed).pipe(filter((value) => value), take(1));
380
+ _boundarySnapshotSignal = RxStateSignal.fromInitialValue({ status: ResourceStatus.Idle });
381
+ _operationSignals = {
382
+ startPending: createSignal(false),
383
+ refreshPending: createSignal(false),
384
+ logoutPending: createSignal(false),
385
+ loginRedirectPending: createSignal(false)
386
+ };
387
+ _eventSubject = new RxEventReplaySubject(100);
388
+ _refreshCommandSubject = new RxEventSubject();
389
+ _refreshResponseSubject = new RxEventSubject();
390
+ _startOnce = createOnceAsyncLockCallable(async (cancellationToken, operationSpan) => {
391
+ cancellationToken.throwIfCancellationRequested();
392
+ this._operationSignals.startPending.set(true);
393
+ try {
394
+ return await this._dispatchRefresh(this._resolveRefreshOptions({}), cancellationToken, operationSpan);
395
+ } finally {
396
+ this._operationSignals.startPending.set(false);
397
+ }
398
+ });
399
+ id;
400
+ zones;
401
+ boundarySnapshot;
402
+ boundaryResource;
403
+ isAuthenticated;
404
+ operations;
405
+ events;
406
+ get environment() {
407
+ return this._environment;
408
+ }
409
+ get span() {
410
+ return this._span;
411
+ }
412
+ get config() {
413
+ return this._config;
414
+ }
415
+ static fromEnvironmentConfig(options) {
416
+ return new BasicAuthContextClient(options.config, options.environment);
417
+ }
418
+ static fromInjector(injector) {
419
+ const client = BasicAuthContextClient.fromEnvironmentConfig({
420
+ config: injector.get(BASIC_AUTH_CONTEXT_CLIENT_CONFIG),
421
+ environment: injector.get(ENVIRONMENT_TOKEN)
422
+ });
423
+ injector.get(SecuritydeptDestroyRef, null)?.onDestroy(() => client.dispose());
424
+ return client;
425
+ }
426
+ constructor(config, environment) {
427
+ validateTraitInput({
428
+ value: config,
429
+ bundledSchema: BasicAuthContextClientConfigSchema,
430
+ onInvalid: (failure) => throwValidationClientError({
431
+ code: BasicAuthContextErrorCode.InvalidConfig,
432
+ source: BasicAuthContextSource.BasicAuthContext,
433
+ messagePrefix: "BasicAuthContextClient could not validate config",
434
+ failure
435
+ })
436
+ });
437
+ const resolvedConfig = {
438
+ id: config.id ?? v7(),
439
+ baseUrl: (config.baseUrl ?? "").replace(/\/+$/, ""),
440
+ zones: (config.zones ?? []).map(resolveZone),
441
+ probePath: config.probePath,
442
+ tracing: {
443
+ target: config.tracing?.target ?? BasicAuthContextClient.defaultOptions.tracing.target,
444
+ prefix: config.tracing?.prefix ?? BasicAuthContextClient.defaultOptions.tracing.prefix
445
+ }
446
+ };
447
+ this._environment = environment;
448
+ this._config = resolvedConfig;
449
+ this.id = resolvedConfig.id;
450
+ this.zones = resolvedConfig.zones;
451
+ this._span = environment.span.fork({ attributes: {
452
+ clientName: this.constructor.name,
453
+ id: this.id
454
+ } });
455
+ this.boundarySnapshot = readonlySignal(this._boundarySnapshotSignal);
456
+ this.boundaryResource = resourceFromSnapshots(() => this._boundarySnapshotSignal.get());
457
+ this.isAuthenticated = mapResource(this.boundaryResource, (snapshot) => snapshot?.authenticated === true);
458
+ this.operations = {
459
+ startPending: readonlySignal(this._operationSignals.startPending),
460
+ refreshPending: readonlySignal(this._operationSignals.refreshPending),
461
+ logoutPending: readonlySignal(this._operationSignals.logoutPending),
462
+ loginRedirectPending: readonlySignal(this._operationSignals.loginRedirectPending)
463
+ };
464
+ this.events = this._eventSubject;
465
+ this.destroyed$.subscribe(() => {
466
+ this._rootCancellation.cancel(new ClientError({
467
+ kind: ClientErrorKind.Cancelled,
468
+ code: BasicAuthContextErrorCode.ClientDisposed,
469
+ message: "BasicAuthContextClient has been disposed.",
470
+ source: BasicAuthContextSource.BasicAuthContext
471
+ }));
472
+ this.isAuthenticated.dispose();
473
+ this.boundaryResource.dispose();
474
+ });
475
+ from(this._refreshCommandSubject).pipe(takeUntil(this.destroyed$), concatCommand((command) => this._executeRefresh(command.payload, command.cancellationToken, command.operationSpan))).subscribe(this._refreshResponseSubject);
476
+ if (config.autoStart === true) this.start().catch(() => {});
477
+ }
478
+ async start() {
479
+ const cancellationToken = this._rootCancellation.token;
480
+ return await this._start(cancellationToken);
481
+ }
482
+ async _start(cancellationToken, operationSpan) {
483
+ return await this._startOnce(cancellationToken, operationSpan);
484
+ }
485
+ async refresh(options = {}) {
486
+ const disposableStack = injectDisposableStackFrom(options, true);
487
+ const cancellationToken = createLinkedCancellationToken(this._rootCancellation.token, options.cancellationToken);
488
+ disposableStack?.use(cancellationToken);
489
+ return await this._refresh(options, cancellationToken);
490
+ }
491
+ async _refresh(options, cancellationToken, operationSpan) {
492
+ cancellationToken.throwIfCancellationRequested();
493
+ return await this._dispatchRefresh(this._resolveRefreshOptions(options), cancellationToken, operationSpan);
494
+ }
495
+ /**
496
+ * Clears only this client's in-memory Basic Auth boundary projection.
497
+ *
498
+ * Browsers own HTTP Basic credentials. This method cannot revoke or clear
499
+ * those credentials, and a later probe may observe an authenticated state
500
+ * again when the browser continues sending them.
501
+ */ async logout(options = {}) {
502
+ const disposableStack = injectDisposableStackFrom(options, true);
503
+ const cancellationToken = createLinkedCancellationToken(this._rootCancellation.token, options.cancellationToken);
504
+ disposableStack?.use(cancellationToken);
505
+ return await this._logout(options, cancellationToken);
506
+ }
507
+ async _logout(_options, cancellationToken, operationSpan) {
508
+ cancellationToken.throwIfCancellationRequested();
509
+ this._operationSignals.logoutPending.set(true);
510
+ this._emitEvent({
511
+ type: BasicAuthContextEventType.LogoutStarted,
512
+ snapshot: this._readCurrentSnapshot()
513
+ });
514
+ try {
515
+ cancellationToken.throwIfCancellationRequested();
516
+ this._boundarySnapshotSignal.set({
517
+ status: ResourceStatus.Resolved,
518
+ value: null
519
+ });
520
+ operationSpan?.setAttributes({ localProjectionCleared: true });
521
+ this._emitEvent({
522
+ type: BasicAuthContextEventType.LogoutSucceeded,
523
+ snapshot: null
524
+ });
525
+ } catch (error) {
526
+ this._emitEvent({
527
+ type: BasicAuthContextEventType.LogoutFailed,
528
+ snapshot: this._readCurrentSnapshot(),
529
+ errorSummary: describeError(error)
530
+ });
531
+ throw error;
532
+ } finally {
533
+ this._operationSignals.logoutPending.set(false);
534
+ }
535
+ }
536
+ async loginWithRedirect(options) {
537
+ const disposableStack = injectDisposableStackFrom(options, true);
538
+ const cancellationToken = createLinkedCancellationToken(this._rootCancellation.token, options.cancellationToken);
539
+ disposableStack?.use(cancellationToken);
540
+ await this._loginWithRedirect(options, cancellationToken);
541
+ }
542
+ async _loginWithRedirect(options, cancellationToken, _operationSpan) {
543
+ cancellationToken.throwIfCancellationRequested();
544
+ const router = this._environment.router;
545
+ if (!router) throw new ClientError({
546
+ kind: ClientErrorKind.Configuration,
547
+ code: BasicAuthContextErrorCode.RouterUnavailable,
548
+ message: "loginWithRedirect() requires environment.router.",
549
+ source: BasicAuthContextSource.BasicAuthContext
550
+ });
551
+ const zone = this._resolveZoneForAction(options, "login redirect");
552
+ const postAuthRedirectUri = options.postAuthRedirectUri ?? options.currentPath;
553
+ this._operationSignals.loginRedirectPending.set(true);
554
+ this._emitEvent({
555
+ type: BasicAuthContextEventType.LoginRedirectStarted,
556
+ zone
557
+ });
558
+ try {
559
+ await router.navigate({
560
+ url: UriReferenceString.parse(this.loginUrl(zone, postAuthRedirectUri)),
561
+ intent: RouterNavigationIntent.AuthRedirect,
562
+ mode: RouterNavigationMode.External
563
+ });
564
+ cancellationToken.throwIfCancellationRequested();
565
+ this._emitEvent({
566
+ type: BasicAuthContextEventType.LoginRedirectSucceeded,
567
+ zone
568
+ });
569
+ } catch (error) {
570
+ this._emitEvent({
571
+ type: BasicAuthContextEventType.LoginRedirectFailed,
572
+ zone,
573
+ errorSummary: describeError(error)
574
+ });
575
+ throw error;
576
+ } finally {
577
+ this._operationSignals.loginRedirectPending.set(false);
578
+ }
579
+ }
580
+ /** Find the zone that contains the given path. */ zoneForPath(path) {
581
+ let matchedZone;
582
+ for (const zone of this.zones) {
583
+ if (!(path === zone.zonePrefix || path.startsWith(`${zone.zonePrefix}/`))) continue;
584
+ if (!matchedZone || zone.zonePrefix.length > matchedZone.zonePrefix.length) matchedZone = zone;
585
+ }
586
+ return matchedZone;
587
+ }
588
+ zoneForPrefix(zonePrefix) {
589
+ return this.zones.find((zone) => zone.zonePrefix === zonePrefix);
590
+ }
591
+ /** Check whether a path falls inside any configured zone. */ isInZone(path) {
592
+ return this.zoneForPath(path) !== void 0;
593
+ }
594
+ /** Build the full login URL for a zone, optionally with a post-auth redirect. */ loginUrl(zone, postAuthRedirectUri) {
595
+ const base = this._config.baseUrl + zone.loginPath;
596
+ if (postAuthRedirectUri) return `${base}?${new URLSearchParams({ [POST_AUTH_REDIRECT_PARAM]: postAuthRedirectUri }).toString()}`;
597
+ return base;
598
+ }
599
+ loginUrlForZonePrefix(zonePrefix, postAuthRedirectUri) {
600
+ const zone = this.zoneForPrefix(zonePrefix);
601
+ return zone ? this.loginUrl(zone, postAuthRedirectUri) : null;
602
+ }
603
+ /**
604
+ * Handle a 401 response: if the current path is inside a zone,
605
+ * return a redirect instruction to the zone's login URL.
606
+ */ handleUnauthorized(currentPath, responseStatus) {
607
+ if (responseStatus !== 401) return {
608
+ kind: AuthGuardResultKind.Ok,
609
+ value: null
610
+ };
611
+ const zone = this.zoneForPath(currentPath);
612
+ if (!zone) return {
613
+ kind: AuthGuardResultKind.Ok,
614
+ value: null
615
+ };
616
+ return {
617
+ kind: AuthGuardResultKind.Redirect,
618
+ status: AuthGuardRedirectStatus.Found,
619
+ location: this.loginUrl(zone, currentPath)
620
+ };
621
+ }
622
+ dispose() {
623
+ this._destroyed.set(true);
624
+ }
625
+ [SYMBOL_DISPOSE]() {
626
+ this.dispose();
627
+ }
628
+ _resolveRefreshOptions(options) {
629
+ const path = options.path ?? this._config.probePath;
630
+ if (!path) throw new ClientError({
631
+ kind: ClientErrorKind.Configuration,
632
+ code: BasicAuthContextErrorCode.ProbePathRequired,
633
+ message: "BasicAuthContextClient.refresh() requires options.path or config.probePath.",
634
+ source: BasicAuthContextSource.BasicAuthContext
635
+ });
636
+ return { path };
637
+ }
638
+ _resolveZoneForAction(options, action) {
639
+ if (options.zonePrefix) {
640
+ const zone = this.zoneForPrefix(options.zonePrefix);
641
+ if (zone) return zone;
642
+ throw new ClientError({
643
+ kind: ClientErrorKind.Configuration,
644
+ code: BasicAuthContextErrorCode.ZoneNotFound,
645
+ message: `BasicAuthContextClient ${action} could not find zone "${options.zonePrefix}".`,
646
+ source: BasicAuthContextSource.BasicAuthContext
647
+ });
648
+ }
649
+ const zone = typeof options.currentPath === "string" ? this.zoneForPath(options.currentPath) : void 0;
650
+ if (zone) return zone;
651
+ throw new ClientError({
652
+ kind: ClientErrorKind.Configuration,
653
+ code: BasicAuthContextErrorCode.ZoneRequired,
654
+ message: `BasicAuthContextClient ${action} requires options.currentPath inside a configured zone or options.zonePrefix.`,
655
+ source: BasicAuthContextSource.BasicAuthContext
656
+ });
657
+ }
658
+ async _dispatchRefresh(payload, cancellationToken, operationSpan) {
659
+ cancellationToken.throwIfCancellationRequested();
660
+ const snapshot = await lastValueFrom(dispatchCommandLocallyToStream({
661
+ payload,
662
+ requestStream: this._refreshCommandSubject,
663
+ responseStream: this._refreshResponseSubject,
664
+ createCommandExtra: () => ({
665
+ cancellationToken,
666
+ operationSpan
667
+ })
668
+ }).pipe(commandResponseData()));
669
+ cancellationToken.throwIfCancellationRequested();
670
+ return snapshot;
671
+ }
672
+ async _executeRefresh(options, cancellationToken, operationSpan) {
673
+ cancellationToken.throwIfCancellationRequested();
674
+ this._operationSignals.refreshPending.set(true);
675
+ const previous = this._boundarySnapshotSignal.get();
676
+ const loadingSnapshot = reduceResourceSnapshot(previous, { kind: ResourceSnapshotUpdateKind.Load });
677
+ this._boundarySnapshotSignal.set(loadingSnapshot);
678
+ this._emitEvent({
679
+ type: BasicAuthContextEventType.BoundaryRefreshStarted,
680
+ snapshot: this._readCurrentSnapshot()
681
+ });
682
+ try {
683
+ const response = await this._environment.transport.execute({
684
+ url: this._config.baseUrl + options.path,
685
+ method: "GET",
686
+ headers: { accept: "application/json" },
687
+ cancellationToken
688
+ });
689
+ if (response.status >= 400 && response.status !== 401 && response.status !== 403) throw ClientError.fromHttpResponse({
690
+ status: response.status,
691
+ body: response.body
692
+ });
693
+ cancellationToken.throwIfCancellationRequested();
694
+ const snapshot = this._snapshotFromResponse(options.path, response);
695
+ this._boundarySnapshotSignal.set(reduceResourceSnapshot(loadingSnapshot, {
696
+ kind: ResourceSnapshotUpdateKind.Resolve,
697
+ value: snapshot
698
+ }));
699
+ operationSpan?.setAttributes({
700
+ authenticated: snapshot.authenticated,
701
+ boundaryKind: snapshot.boundaryKind
702
+ });
703
+ this._emitEvent({
704
+ type: BasicAuthContextEventType.BoundaryRefreshSucceeded,
705
+ snapshot,
706
+ zone: snapshot.zone
707
+ });
708
+ return snapshot;
709
+ } catch (error) {
710
+ this._boundarySnapshotSignal.set(reduceResourceSnapshot(loadingSnapshot, {
711
+ kind: ResourceSnapshotUpdateKind.Fail,
712
+ error
713
+ }));
714
+ this._emitEvent({
715
+ type: BasicAuthContextEventType.BoundaryRefreshFailed,
716
+ snapshot: this._readCurrentSnapshot(),
717
+ errorSummary: describeError(error)
718
+ });
719
+ throw error;
720
+ } finally {
721
+ this._operationSignals.refreshPending.set(false);
722
+ }
723
+ }
724
+ _snapshotFromResponse(path, response) {
725
+ const challengeHeader = response.headers["WWW-Authenticate"] ?? response.headers["www-authenticate"] ?? null;
726
+ const zone = this.zoneForPath(path);
727
+ const boundaryKind = readBasicAuthBoundaryKind({
728
+ status: response.status,
729
+ challengeHeader,
730
+ requestPath: path
731
+ });
732
+ return {
733
+ authenticated: boundaryKind === BasicAuthBoundaryKind.Authenticated,
734
+ boundaryKind,
735
+ status: response.status,
736
+ path,
737
+ challengeHeader,
738
+ zone
739
+ };
740
+ }
741
+ _readCurrentSnapshot() {
742
+ const snapshot = this._boundarySnapshotSignal.get();
743
+ return snapshot.status === ResourceStatus.Reloading || snapshot.status === ResourceStatus.Resolved || snapshot.status === ResourceStatus.Error ? snapshot.value : null;
744
+ }
745
+ _emitEvent(input) {
746
+ this._eventSubject.next({
747
+ ...input,
748
+ at: this._environment.time.now(),
749
+ client: { id: this.id }
750
+ });
751
+ }
752
+ };
753
+ //#endregion
754
+ //#region src/providers.ts
755
+ function provideBasicAuthContext(options) {
756
+ return [{
757
+ provide: BASIC_AUTH_CONTEXT_CLIENT_CONFIG,
758
+ useValue: options.config
759
+ }, {
760
+ provide: BASIC_AUTH_CONTEXT_CLIENT,
761
+ useFactory: (injector) => BasicAuthContextClient.fromInjector(injector),
762
+ deps: [INJECTOR_TOKEN]
763
+ }];
764
+ }
765
+ //#endregion
766
+ export { AuthGuardRedirectStatus, AuthGuardResultKind, BASIC_AUTH_CONTEXT_CLIENT, BASIC_AUTH_CONTEXT_CLIENT_CONFIG, BasicAuthBoundaryKind, BasicAuthContextClient, BasicAuthContextClientConfigSchema, BasicAuthContextErrorCode, BasicAuthContextEventType, BasicAuthContextSource, createBasicAuthorizationHeaderValue, provideBasicAuthContext, readBasicAuthBoundaryKind };
767
+
768
+ //# sourceMappingURL=index.mjs.map