@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/README.md +6 -0
- package/dist/index.d.mts +253 -10
- package/dist/index.mjs +757 -3
- package/dist/index.mjs.map +1 -0
- package/package.json +12 -11
- package/dist/client-DU98lpa_.d.mts +0 -91
- package/dist/client-D_0eAARH.mjs +0 -136
- package/dist/client-D_0eAARH.mjs.map +0 -1
- package/dist/server/index.d.mts +0 -73
- package/dist/server/index.mjs +0 -47
- package/dist/server/index.mjs.map +0 -1
- package/dist/types-2IoKrgnl.mjs +0 -20
- package/dist/types-2IoKrgnl.mjs.map +0 -1
- package/dist/web/index.d.mts +0 -45
- package/dist/web/index.mjs +0 -37
- package/dist/web/index.mjs.map +0 -1
package/dist/index.mjs
CHANGED
|
@@ -1,3 +1,757 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
|
|
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
|