@securitydept/basic-auth-context-client 0.3.0-beta.2 → 0.3.0-beta.5

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