@securitydept/client-angular 0.3.0-beta.3 → 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.
|
@@ -1,554 +1,581 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
import {
|
|
1
|
+
import { InjectionToken, makeEnvironmentProviders, inject, DestroyRef, Injector, Optional } from '@angular/core';
|
|
2
|
+
import { Router } from '@angular/router';
|
|
3
|
+
import { createSecuritydeptDestroyRef, SecuritydeptDestroyRef, SecuritydeptInjector, readSecuritydeptRouteMetadata, RouteCompositionRequirementPlanner, PipelineOutcome, injectRequirementPlannerHost, RequirementPlannerHost, ENVIRONMENT_TOKEN, writeSecuritydeptRouteMetadata, provideRequirementPlannerHost, UriReferenceStringSchema, validateTraitInput, throwValidationClientError, RouterNavigationMode, UriReferenceString, TransportErrorCode, ClientError, ClientErrorSource, ClientErrorKind, getSecuritydeptProviderToken, createProviderIfTokenMissing, ROUTER_TRAIT_TOKEN, TRANSPORT_TRAIT_TOKEN, notMissingProvider, SYMBOL_OBSERVABLE } from '@securitydept/client';
|
|
4
|
+
import { HttpErrorResponse, HttpClient } from '@angular/common/http';
|
|
5
|
+
import { type } from 'arktype';
|
|
6
|
+
import { takeUntil, from, firstValueFrom } from 'rxjs';
|
|
7
|
+
import { toSignal, rxResource } from '@angular/core/rxjs-interop';
|
|
7
8
|
|
|
8
|
-
const
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
9
|
+
const SECURITYDEPT_INJECTOR = new InjectionToken("SECURITYDEPT_INJECTOR");
|
|
10
|
+
function provideSecuritydept(options = {}) {
|
|
11
|
+
return makeEnvironmentProviders([
|
|
12
|
+
{
|
|
13
|
+
provide: SECURITYDEPT_INJECTOR,
|
|
14
|
+
useFactory: () => {
|
|
15
|
+
const parentInjector = inject(SECURITYDEPT_INJECTOR, {
|
|
16
|
+
optional: true,
|
|
17
|
+
skipSelf: true,
|
|
18
|
+
}) ?? undefined;
|
|
19
|
+
const angularDestroyRef = inject(DestroyRef);
|
|
20
|
+
const securitydeptDestroyRef = options.autoCreateDestroyRef === false
|
|
21
|
+
? null
|
|
22
|
+
: createSecuritydeptDestroyRef();
|
|
23
|
+
if (securitydeptDestroyRef) {
|
|
24
|
+
angularDestroyRef.onDestroy(() => securitydeptDestroyRef.dispose());
|
|
25
|
+
}
|
|
26
|
+
const providers = [
|
|
27
|
+
...(options.providers ?? []),
|
|
28
|
+
...(securitydeptDestroyRef
|
|
29
|
+
? [
|
|
30
|
+
{
|
|
31
|
+
provide: SecuritydeptDestroyRef,
|
|
32
|
+
useValue: securitydeptDestroyRef,
|
|
33
|
+
},
|
|
34
|
+
]
|
|
35
|
+
: []),
|
|
36
|
+
];
|
|
37
|
+
return parentInjector
|
|
38
|
+
? SecuritydeptInjector.fromParentInjector(parentInjector, providers)
|
|
39
|
+
: SecuritydeptInjector.resolveAndCreate(providers);
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
]);
|
|
38
43
|
}
|
|
39
44
|
|
|
40
|
-
// Angular
|
|
45
|
+
// Angular route metadata projection
|
|
41
46
|
//
|
|
42
47
|
// Canonical import path:
|
|
43
|
-
// import {
|
|
48
|
+
// import { projectAngularRouteSegments } from "@securitydept/client-angular"
|
|
49
|
+
//
|
|
50
|
+
// Reads serializable auth requirement metadata declared in Angular route
|
|
51
|
+
// `data` and projects a matched route chain (`pathFromRoot`) into the SDK's
|
|
52
|
+
// framework-agnostic `RouteTreeSegment[]`. Composition folding is owned by the
|
|
53
|
+
// `RouteCompositionRequirementPlanner`, so this layer only extracts per-segment
|
|
54
|
+
// declarations.
|
|
44
55
|
//
|
|
45
|
-
//
|
|
46
|
-
//
|
|
47
|
-
// - Provider helpers for app / module / route scoped planners
|
|
48
|
-
// - InjectionToken + provider helpers for ScopedRequirementsClientSet
|
|
49
|
-
// - injectPlannerHost() convenience function with fail-fast
|
|
56
|
+
// Route metadata storage is owned by `@securitydept/client`
|
|
57
|
+
// ({@link readSecuritydeptRouteMetadata} / {@link writeSecuritydeptRouteMetadata}).
|
|
50
58
|
//
|
|
51
59
|
// Architecture boundary:
|
|
52
|
-
// - Does NOT own
|
|
53
|
-
// - Does NOT carry token-set-specific mapping
|
|
54
|
-
// -
|
|
60
|
+
// - Does NOT own auth enforcement, planner logic, or navigation.
|
|
61
|
+
// - Does NOT carry token-set-specific mapping or policy.
|
|
62
|
+
// - Framework-specific (Angular Router types) but requirement-system-agnostic.
|
|
55
63
|
//
|
|
56
64
|
// Stability: provisional
|
|
57
|
-
// ---------------------------------------------------------------------------
|
|
58
|
-
// PlannerHost injection token
|
|
59
|
-
// ---------------------------------------------------------------------------
|
|
60
65
|
/**
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
* Provide at the app level via {@link provideAuthPlannerHost}.
|
|
64
|
-
* Guards use {@link injectPlannerHost} to look up the nearest instance.
|
|
66
|
+
* Resolved route `data` for a segment: static `routeConfig.data` merged with
|
|
67
|
+
* snapshot `data` (snapshot wins on key collision).
|
|
65
68
|
*/
|
|
66
|
-
|
|
69
|
+
function routeDataFromSegment(segment) {
|
|
70
|
+
return {
|
|
71
|
+
...(segment.routeConfig?.data ?? {}),
|
|
72
|
+
...segment.data,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
67
75
|
/**
|
|
68
|
-
*
|
|
76
|
+
* Project the `pathFromRoot` of an `ActivatedRouteSnapshot` into the SDK's
|
|
77
|
+
* `RouteTreeSegment[]` chain (root -> leaf).
|
|
69
78
|
*
|
|
70
|
-
*
|
|
71
|
-
*
|
|
79
|
+
* Each segment carries its declared requirements and composition strategy;
|
|
80
|
+
* inherit/merge/replace folding is applied later by the
|
|
81
|
+
* `RouteCompositionRequirementPlanner`.
|
|
72
82
|
*
|
|
73
|
-
*
|
|
74
|
-
*
|
|
75
|
-
* // app.config.ts — app-level planner (default sequential strategy)
|
|
76
|
-
* export const appConfig: ApplicationConfig = {
|
|
77
|
-
* providers: [provideAuthPlannerHost()],
|
|
78
|
-
* };
|
|
79
|
-
*
|
|
80
|
-
* // Feature route — override with custom async chooser strategy
|
|
81
|
-
* {
|
|
82
|
-
* path: "admin",
|
|
83
|
-
* providers: [provideAuthPlannerHost({
|
|
84
|
-
* selectCandidate: async (candidates) => showAdminChooser(candidates),
|
|
85
|
-
* })],
|
|
86
|
-
* children: [...]
|
|
87
|
-
* }
|
|
88
|
-
* ```
|
|
83
|
+
* Segments without a `routeConfig` (e.g. the root) get routeId `"__root__"`;
|
|
84
|
+
* empty-path segments get `"__index__"`.
|
|
89
85
|
*/
|
|
90
|
-
function
|
|
91
|
-
const
|
|
92
|
-
|
|
93
|
-
|
|
86
|
+
function projectAngularRouteSegments(leafRoute) {
|
|
87
|
+
const chain = leafRoute.pathFromRoot ?? [leafRoute];
|
|
88
|
+
return chain.map((segment) => segmentToTreeNode(segment));
|
|
89
|
+
}
|
|
90
|
+
function segmentToTreeNode(segment) {
|
|
91
|
+
const metadata = readSecuritydeptRouteMetadata(routeDataFromSegment(segment));
|
|
92
|
+
const requirements = metadata?.requirements ? [...metadata.requirements] : [];
|
|
93
|
+
const composition = metadata?.composition;
|
|
94
|
+
return composition
|
|
95
|
+
? { routeId: resolveRouteId(segment), requirements, composition }
|
|
96
|
+
: { routeId: resolveRouteId(segment), requirements };
|
|
97
|
+
}
|
|
98
|
+
function resolveRouteId(segment) {
|
|
99
|
+
if (!segment.routeConfig) {
|
|
100
|
+
return "__root__";
|
|
94
101
|
}
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
102
|
+
const path = segment.routeConfig.path;
|
|
103
|
+
if (path === undefined || path === null || path === "") {
|
|
104
|
+
return "__index__";
|
|
105
|
+
}
|
|
106
|
+
return path;
|
|
98
107
|
}
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
108
|
+
|
|
109
|
+
// Angular route guards — bridge to RouteCompositionRequirementPlanner
|
|
110
|
+
//
|
|
111
|
+
// Canonical import path:
|
|
112
|
+
// import {
|
|
113
|
+
// createAngularCanActivate,
|
|
114
|
+
// createAngularCanActivateChild,
|
|
115
|
+
// } from "@securitydept/client-angular"
|
|
116
|
+
//
|
|
117
|
+
// Produces Angular `CanActivateFn` / `CanActivateChildFn` guards that project
|
|
118
|
+
// the matched route chain into `RouteTreeSegment[]`, run the SDK's
|
|
119
|
+
// `RouteCompositionRequirementPlanner` against the nearest
|
|
120
|
+
// `RequirementPlannerHost` resolved from the current Securitydept injector,
|
|
121
|
+
// and map the pipeline result onto Angular's guard return contract (`true` /
|
|
122
|
+
// `false` / `UrlTree`).
|
|
123
|
+
//
|
|
124
|
+
// Injection-context discipline: DI reads (host, Router) happen synchronously at
|
|
125
|
+
// guard entry, before any `await`. Behaviour that needs DI services should be
|
|
126
|
+
// provided through `provideSecuritydept({ providers: [...] })`, not inline
|
|
127
|
+
// callbacks that call `inject()` here.
|
|
128
|
+
//
|
|
129
|
+
// Architecture boundary:
|
|
130
|
+
// - Does NOT own behaviour (checkAuthenticated / onUnauthenticated) — that is
|
|
131
|
+
// resolved through the RequirementPlannerHost chain.
|
|
132
|
+
// - Does NOT carry token-set-specific mapping or policy.
|
|
133
|
+
//
|
|
134
|
+
// Stability: provisional
|
|
135
|
+
async function runRouteGuard(route, state, host, router) {
|
|
136
|
+
const segments = projectAngularRouteSegments(route);
|
|
137
|
+
const planner = RouteCompositionRequirementPlanner.fromRouteSegments(host, segments, { url: state.url });
|
|
138
|
+
const result = await planner.runUntilSettled();
|
|
139
|
+
switch (result.outcome) {
|
|
140
|
+
case PipelineOutcome.Settled:
|
|
141
|
+
return true;
|
|
142
|
+
case PipelineOutcome.Blocked:
|
|
143
|
+
return false;
|
|
144
|
+
case PipelineOutcome.Redirect:
|
|
145
|
+
return router.parseUrl(result.location);
|
|
116
146
|
}
|
|
117
|
-
return host;
|
|
118
147
|
}
|
|
119
|
-
// ---------------------------------------------------------------------------
|
|
120
|
-
// Requirements client set injection token — stores the already-resolved
|
|
121
|
-
// effective options for the current scope (parent + child composed).
|
|
122
|
-
// ---------------------------------------------------------------------------
|
|
123
148
|
/**
|
|
124
|
-
*
|
|
125
|
-
*
|
|
126
|
-
* The value is always the **resolved** options after composing all ancestor
|
|
127
|
-
* scopes — it is NOT the raw `ScopedRequirementsClientSet` declaration.
|
|
128
|
-
*
|
|
129
|
-
* Provide via {@link provideRouteScopedRequirements}.
|
|
130
|
-
* Guards inject this token to get the effective scope requirements and then
|
|
131
|
-
* merge their own declared candidates on top.
|
|
149
|
+
* Create a `CanActivateFn` bridging the route chain to the requirement
|
|
150
|
+
* planner pipeline.
|
|
132
151
|
*/
|
|
133
|
-
|
|
152
|
+
function createAngularCanActivate(options) {
|
|
153
|
+
return (route, state) => {
|
|
154
|
+
const securitydeptInjector = inject(SECURITYDEPT_INJECTOR, {
|
|
155
|
+
optional: true,
|
|
156
|
+
});
|
|
157
|
+
const parentHost = securitydeptInjector
|
|
158
|
+
? injectRequirementPlannerHost({ injector: securitydeptInjector })
|
|
159
|
+
: null;
|
|
160
|
+
const host = options?.plannerHost ??
|
|
161
|
+
(options?.behaviour
|
|
162
|
+
? RequirementPlannerHost.fromBehaviour(typeof options.behaviour === "function"
|
|
163
|
+
? options.behaviour()
|
|
164
|
+
: options.behaviour, {
|
|
165
|
+
parent: parentHost ?? undefined,
|
|
166
|
+
environment: securitydeptInjector?.get(ENVIRONMENT_TOKEN, null) ?? undefined,
|
|
167
|
+
})
|
|
168
|
+
: parentHost) ??
|
|
169
|
+
RequirementPlannerHost.fromBehaviour({});
|
|
170
|
+
const router = inject(Router);
|
|
171
|
+
return runRouteGuard(route, state, host, router);
|
|
172
|
+
};
|
|
173
|
+
}
|
|
134
174
|
/**
|
|
135
|
-
*
|
|
136
|
-
*
|
|
137
|
-
* Internally, the provider resolves the effective client set by composing the
|
|
138
|
-
* declared `scopedSet` with the parent scope's already-resolved options
|
|
139
|
-
* (injected via `skipSelf: true`). The token value stored in DI is the
|
|
140
|
-
* resolved `readonly AuthGuardClientOption[]`, not the raw declaration.
|
|
141
|
-
*
|
|
142
|
-
* This mirrors the React `AuthRequirementsClientSetProvider` semantics:
|
|
143
|
-
* each level stores its resolved effective options, so children inherit
|
|
144
|
-
* the composed result without re-resolving the full ancestor chain.
|
|
145
|
-
*
|
|
146
|
-
* The `scopedSet` argument may also be a **factory function** — it will be
|
|
147
|
-
* called inside Angular's `useFactory` context, so `inject()` is valid.
|
|
148
|
-
* Use this when the requirement options need DI-resolved services (e.g. a
|
|
149
|
-
* token-set registry or a redirect service).
|
|
150
|
-
*
|
|
151
|
-
* @example
|
|
152
|
-
* ```ts
|
|
153
|
-
* // routes.ts — static declaration (no DI needed)
|
|
154
|
-
* {
|
|
155
|
-
* path: "",
|
|
156
|
-
* providers: [
|
|
157
|
-
* provideRouteScopedRequirements({
|
|
158
|
-
* composition: RequirementsClientSetComposition.Replace,
|
|
159
|
-
* options: [sessionClientOption],
|
|
160
|
-
* }),
|
|
161
|
-
* ],
|
|
162
|
-
* }
|
|
163
|
-
*
|
|
164
|
-
* // Feature route merges OIDC on top of the parent's session requirement
|
|
165
|
-
* {
|
|
166
|
-
* path: "confluence",
|
|
167
|
-
* providers: [
|
|
168
|
-
* provideRouteScopedRequirements({
|
|
169
|
-
* composition: RequirementsClientSetComposition.Merge,
|
|
170
|
-
* options: [confluenceOidcClientOption],
|
|
171
|
-
* }),
|
|
172
|
-
* ],
|
|
173
|
-
* // Route-level DI scope only. Angular Router adopters should prefer
|
|
174
|
-
* // secureRouteRoot()/secureRoute() over direct guard wiring.
|
|
175
|
-
* }
|
|
176
|
-
*
|
|
177
|
-
* // Factory form — use when options depend on injected services
|
|
178
|
-
* {
|
|
179
|
-
* path: "protected",
|
|
180
|
-
* providers: [
|
|
181
|
-
* provideRouteScopedRequirements(() => {
|
|
182
|
-
* const registry = inject(TokenSetAuthRegistry);
|
|
183
|
-
* const authService = inject(AuthService);
|
|
184
|
-
* return {
|
|
185
|
-
* composition: RequirementsClientSetComposition.Replace,
|
|
186
|
-
* options: [{
|
|
187
|
-
* requirementId: "oidc",
|
|
188
|
-
* requirementKind: "frontend_oidc",
|
|
189
|
-
* checkAuthenticated: () => registry.require(clientKey).isAuthenticated(),
|
|
190
|
-
* onUnauthenticated: () => { authService.redirectToLogin(clientKey).subscribe(); return false; },
|
|
191
|
-
* }],
|
|
192
|
-
* };
|
|
193
|
-
* }),
|
|
194
|
-
* ],
|
|
195
|
-
* }
|
|
196
|
-
* ```
|
|
175
|
+
* Create a `CanActivateChildFn` bridging the child route chain to the
|
|
176
|
+
* requirement planner pipeline.
|
|
197
177
|
*/
|
|
198
|
-
function
|
|
199
|
-
return
|
|
200
|
-
{
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
178
|
+
function createAngularCanActivateChild(options) {
|
|
179
|
+
return (childRoute, state) => {
|
|
180
|
+
const injector = inject(SECURITYDEPT_INJECTOR, {
|
|
181
|
+
optional: true,
|
|
182
|
+
});
|
|
183
|
+
const parentHost = injector
|
|
184
|
+
? injectRequirementPlannerHost({ injector })
|
|
185
|
+
: null;
|
|
186
|
+
const host = options?.plannerHost ??
|
|
187
|
+
(options?.behaviour
|
|
188
|
+
? RequirementPlannerHost.fromBehaviour(typeof options.behaviour === "function"
|
|
189
|
+
? options.behaviour()
|
|
190
|
+
: options.behaviour, {
|
|
191
|
+
parent: parentHost ?? undefined,
|
|
192
|
+
environment: injector?.get(ENVIRONMENT_TOKEN, null) ?? undefined,
|
|
193
|
+
})
|
|
194
|
+
: parentHost) ??
|
|
195
|
+
RequirementPlannerHost.fromBehaviour({});
|
|
196
|
+
const router = inject(Router);
|
|
197
|
+
return runRouteGuard(childRoute, state, host, router);
|
|
198
|
+
};
|
|
217
199
|
}
|
|
218
200
|
|
|
219
|
-
// Angular
|
|
201
|
+
// Angular secure route helpers — declarative route assembly
|
|
220
202
|
//
|
|
221
203
|
// Canonical import path:
|
|
222
|
-
// import {
|
|
204
|
+
// import { secureRoute, secureRouteRoot } from "@securitydept/client-angular"
|
|
205
|
+
//
|
|
206
|
+
// Two composable helpers for wiring auth requirements onto Angular routes:
|
|
207
|
+
// - secureRoute: writes requirement metadata into route `data` ONLY.
|
|
208
|
+
// - secureRouteRoot: writes metadata AND attaches the canActivate /
|
|
209
|
+
// canActivateChild guards (and, optionally, a scoped
|
|
210
|
+
// RequirementPlannerHost provider).
|
|
223
211
|
//
|
|
224
|
-
//
|
|
225
|
-
//
|
|
226
|
-
//
|
|
227
|
-
// get full type-safety and IDE autocomplete without any casting.
|
|
212
|
+
// The split lets a single guarded root route cover a whole subtree: child
|
|
213
|
+
// segments declare their own requirements via `secureRoute`, and the root's
|
|
214
|
+
// canActivateChild folds the full chain through the planner.
|
|
228
215
|
//
|
|
229
216
|
// Architecture boundary:
|
|
230
|
-
// - Does NOT own
|
|
231
|
-
// -
|
|
232
|
-
// - Does NOT carry token-set-specific mapping or policy.
|
|
233
|
-
// - The adopter wires this into their guards / resolvers.
|
|
217
|
+
// - Does NOT own behaviour or token-set-specific policy.
|
|
218
|
+
// - Delegates enforcement to createAngularCanActivate/Child.
|
|
234
219
|
//
|
|
235
220
|
// Stability: provisional
|
|
236
|
-
|
|
237
|
-
const
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
// ---------------------------------------------------------------------------
|
|
241
|
-
/**
|
|
242
|
-
* Angular service that bridges Angular Router's `ActivatedRouteSnapshot` to
|
|
243
|
-
* the SDK's framework-agnostic `RouteMatchNode[]` contract.
|
|
244
|
-
*
|
|
245
|
-
* Inject this into `CanActivateFn` guards or class-based guards.
|
|
246
|
-
*
|
|
247
|
-
* @example
|
|
248
|
-
* ```ts
|
|
249
|
-
* export const authGuard: CanActivateFn = (route) => {
|
|
250
|
-
* const adapter = inject(AuthRouteAdapter);
|
|
251
|
-
* const result = adapter.check(route, orchestrator);
|
|
252
|
-
* if (!result.canActivate && result.pendingRequirement) {
|
|
253
|
-
* // Trigger auth flow for the pending requirement
|
|
254
|
-
* }
|
|
255
|
-
* return result.canActivate;
|
|
256
|
-
* };
|
|
257
|
-
* ```
|
|
258
|
-
*/
|
|
259
|
-
class AuthRouteAdapter {
|
|
260
|
-
/**
|
|
261
|
-
* Project the `pathFromRoot` of an `ActivatedRouteSnapshot` into the SDK's
|
|
262
|
-
* `RouteMatchNode[]` chain.
|
|
263
|
-
*
|
|
264
|
-
* Route segments without a `routeConfig` (e.g. the root segment) are
|
|
265
|
-
* assigned routeId `"__root__"`. Segments with a config use the `path`
|
|
266
|
-
* as their routeId (or `"__index__"` for empty-path segments).
|
|
267
|
-
*/
|
|
268
|
-
projectRouteMatch(leafRoute, options) {
|
|
269
|
-
const key = options?.requirementsKey ?? DEFAULT_ROUTE_REQUIREMENTS_KEY;
|
|
270
|
-
const chain = leafRoute.pathFromRoot ?? [leafRoute];
|
|
271
|
-
return chain.map((segment) => this.segmentToNode(segment, key));
|
|
272
|
-
}
|
|
273
|
-
/**
|
|
274
|
-
* Project an explicit array of `ActivatedRouteSnapshot` segments into the
|
|
275
|
-
* SDK's `RouteMatchNode[]` chain.
|
|
276
|
-
*
|
|
277
|
-
* Use when you already have a flattened chain (e.g. from
|
|
278
|
-
* `ActivatedRoute.pathFromRoot`).
|
|
279
|
-
*/
|
|
280
|
-
projectRouteSegments(segments, options) {
|
|
281
|
-
const key = options?.requirementsKey ?? DEFAULT_ROUTE_REQUIREMENTS_KEY;
|
|
282
|
-
return segments.map((segment) => this.segmentToNode(segment, key));
|
|
221
|
+
function writeRequirementData(security, base) {
|
|
222
|
+
const patch = {};
|
|
223
|
+
if (security.requirements !== undefined) {
|
|
224
|
+
patch.requirements = security.requirements;
|
|
283
225
|
}
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
*
|
|
287
|
-
* @param leafRoute - The leaf `ActivatedRouteSnapshot` from the guard call.
|
|
288
|
-
* @param orchestrator - The SDK orchestrator managing the requirement plan.
|
|
289
|
-
* @param options - Optional projection options.
|
|
290
|
-
*/
|
|
291
|
-
check(leafRoute, orchestrator, options) {
|
|
292
|
-
const chain = this.projectRouteMatch(leafRoute, options);
|
|
293
|
-
orchestrator.activateMatchedRoutes(chain);
|
|
294
|
-
const snap = orchestrator.snapshot();
|
|
295
|
-
return {
|
|
296
|
-
canActivate: snap.settled,
|
|
297
|
-
pendingRequirement: snap.pendingRequirement,
|
|
298
|
-
};
|
|
226
|
+
if (security.composition !== undefined) {
|
|
227
|
+
patch.composition = security.composition;
|
|
299
228
|
}
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
const rawRequirements = segment.data[key] ?? segment.routeConfig?.data?.[key];
|
|
303
|
-
const requirements = Array.isArray(rawRequirements)
|
|
304
|
-
? rawRequirements
|
|
305
|
-
: [];
|
|
306
|
-
return { routeId, requirements };
|
|
229
|
+
if (Object.keys(patch).length === 0) {
|
|
230
|
+
return { ...(base ?? {}) };
|
|
307
231
|
}
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
232
|
+
return writeSecuritydeptRouteMetadata(base, patch);
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* Declare a secure route segment: writes requirement metadata into route
|
|
236
|
+
* `data` only. No guard is attached — a guarded ancestor (see
|
|
237
|
+
* {@link secureRouteRoot}) enforces the folded chain.
|
|
238
|
+
*/
|
|
239
|
+
function secureRoute(path, security, routeOptions) {
|
|
240
|
+
return {
|
|
241
|
+
...routeOptions,
|
|
242
|
+
path,
|
|
243
|
+
data: writeRequirementData(security, routeOptions?.data),
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* Declare a guarded root route: writes requirement metadata AND attaches
|
|
248
|
+
* `canActivate` + `canActivateChild` guards bound to the requirement planner.
|
|
249
|
+
*
|
|
250
|
+
* When `security.behaviour` is set, a scoped {@link RequirementPlannerHost}
|
|
251
|
+
* provider is mounted so descendant guards inherit it through DI.
|
|
252
|
+
*/
|
|
253
|
+
function secureRouteRoot(path, security, routeOptions) {
|
|
254
|
+
const guardOptions = security.plannerHost
|
|
255
|
+
? { plannerHost: security.plannerHost }
|
|
256
|
+
: undefined;
|
|
257
|
+
const providers = security.behaviour
|
|
258
|
+
? [
|
|
259
|
+
...(routeOptions?.providers ?? []),
|
|
260
|
+
provideSecuritydept({
|
|
261
|
+
providers: [
|
|
262
|
+
provideRequirementPlannerHost(security.behaviour),
|
|
263
|
+
],
|
|
264
|
+
}),
|
|
265
|
+
]
|
|
266
|
+
: routeOptions?.providers;
|
|
267
|
+
const route = {
|
|
268
|
+
...routeOptions,
|
|
269
|
+
path,
|
|
270
|
+
data: writeRequirementData(security, routeOptions?.data),
|
|
271
|
+
canActivate: [
|
|
272
|
+
...(routeOptions?.canActivate ?? []),
|
|
273
|
+
createAngularCanActivate(guardOptions),
|
|
274
|
+
],
|
|
275
|
+
canActivateChild: [
|
|
276
|
+
...(routeOptions?.canActivateChild ?? []),
|
|
277
|
+
createAngularCanActivateChild(guardOptions),
|
|
278
|
+
],
|
|
279
|
+
};
|
|
280
|
+
if (providers) {
|
|
281
|
+
route.providers = providers;
|
|
315
282
|
}
|
|
316
|
-
|
|
317
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: AuthRouteAdapter, providedIn: "root" });
|
|
283
|
+
return route;
|
|
318
284
|
}
|
|
319
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: AuthRouteAdapter, decorators: [{
|
|
320
|
-
type: Injectable,
|
|
321
|
-
args: [{ providedIn: "root" }]
|
|
322
|
-
}] });
|
|
323
285
|
|
|
324
|
-
// Angular
|
|
286
|
+
// Angular Router trait adapter
|
|
325
287
|
//
|
|
326
288
|
// Canonical import path:
|
|
327
|
-
// import {
|
|
289
|
+
// import { createRouterForAngular } from "@securitydept/client-angular"
|
|
328
290
|
//
|
|
329
|
-
//
|
|
330
|
-
//
|
|
331
|
-
//
|
|
332
|
-
//
|
|
291
|
+
// Projects an Angular `Router` (or any navigation-capable object) onto the
|
|
292
|
+
// framework-agnostic `RouterTrait` (currentUrl + navigate) from
|
|
293
|
+
// `@securitydept/client`. Adopters use this to give SDK primitives a way to
|
|
294
|
+
// read the current URL and start navigations without depending on Angular.
|
|
333
295
|
//
|
|
334
296
|
// Architecture boundary:
|
|
335
|
-
// - Does NOT own
|
|
297
|
+
// - Does NOT own the Angular router, guards, or navigation lifecycle.
|
|
298
|
+
// - Does NOT read auth requirements or run planners (see route-guard.ts).
|
|
336
299
|
// - Does NOT carry token-set-specific mapping or policy.
|
|
337
|
-
// - Provides typed helpers for declaring and reading requirements from route data.
|
|
338
|
-
// - framework-specific (Angular Router types) but requirement-system-agnostic.
|
|
339
|
-
//
|
|
340
|
-
// Relationship to provideRouteScopedRequirements():
|
|
341
|
-
// - provideRouteScopedRequirements() is a lower-level DI-scope helper.
|
|
342
|
-
// - This module is the canonical path for Angular Router auth requirement
|
|
343
|
-
// declaration when full-route aggregation is needed.
|
|
344
|
-
//
|
|
345
|
-
// Key sentinel note:
|
|
346
|
-
// DEFAULT_ROUTE_REQUIREMENTS_KEY is defined in router.ts (AuthRouteAdapter's
|
|
347
|
-
// companion constant). ROUTE_REQUIREMENTS_DATA_KEY exported here is its
|
|
348
|
-
// semantic alias — both hold the same value ("authRequirements").
|
|
349
|
-
// Adopters may use either name; they are interchangeable.
|
|
350
300
|
//
|
|
351
301
|
// Stability: provisional
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
* Use either name — they are interchangeable.
|
|
361
|
-
*
|
|
362
|
-
* Set this key in `Route.data` to declare auth requirements for a route segment:
|
|
363
|
-
* ```ts
|
|
364
|
-
* { path: "dashboard", data: withRouteRequirements([{ id: "session", kind: "session" }]) }
|
|
365
|
-
* ```
|
|
366
|
-
*
|
|
367
|
-
* The key is also read by {@link AuthRouteAdapter} when projecting snapshots.
|
|
368
|
-
*
|
|
369
|
-
* @see {@link withRouteRequirements}
|
|
370
|
-
* @see {@link extractFullRouteRequirements}
|
|
371
|
-
*/
|
|
372
|
-
const ROUTE_REQUIREMENTS_DATA_KEY = DEFAULT_ROUTE_REQUIREMENTS_KEY;
|
|
373
|
-
/**
|
|
374
|
-
* Default key for the route-level composition strategy in Angular route `data`.
|
|
375
|
-
*
|
|
376
|
-
* Used together with {@link ROUTE_REQUIREMENTS_DATA_KEY}. The default
|
|
377
|
-
* composition is `merge` when the key is omitted.
|
|
378
|
-
*/
|
|
379
|
-
const ROUTE_REQUIREMENTS_COMPOSITION_DATA_KEY = "authRequirementsComposition";
|
|
380
|
-
// ---------------------------------------------------------------------------
|
|
381
|
-
// Declaration helper
|
|
382
|
-
// ---------------------------------------------------------------------------
|
|
302
|
+
const AngularRouterNavigationLikeSchema = type({
|
|
303
|
+
url: "string?",
|
|
304
|
+
navigateByUrl: "Function",
|
|
305
|
+
});
|
|
306
|
+
const ResolvedRouterForAngularCreateOptionsSchema = type({
|
|
307
|
+
router: AngularRouterNavigationLikeSchema,
|
|
308
|
+
currentUrl: UriReferenceStringSchema.or("null"),
|
|
309
|
+
});
|
|
383
310
|
/**
|
|
384
|
-
*
|
|
385
|
-
* single route segment.
|
|
386
|
-
*
|
|
387
|
-
* Use this as the `data` field in an Angular `Route` config. Nested routes
|
|
388
|
-
* can declare their own requirements; a full-route aggregation guard will
|
|
389
|
-
* walk the entire `pathFromRoot` chain to collect all requirements in one pass.
|
|
311
|
+
* Create a {@link RouterTrait} backed by an Angular `Router`.
|
|
390
312
|
*
|
|
391
313
|
* @example
|
|
392
314
|
* ```ts
|
|
393
|
-
*
|
|
394
|
-
* {
|
|
395
|
-
* path: "app",
|
|
396
|
-
* data: withRouteRequirements([{ id: "session", kind: "session" }]),
|
|
397
|
-
* children: [
|
|
398
|
-
* // Child route adds an additional requirement
|
|
399
|
-
* {
|
|
400
|
-
* path: "admin",
|
|
401
|
-
* data: withRouteRequirements([{ id: "admin-oidc", kind: "backend_oidc" }]),
|
|
402
|
-
* canActivate: [createTokenSetRouteAggregationGuard(...)],
|
|
403
|
-
* },
|
|
404
|
-
* ],
|
|
405
|
-
* }
|
|
315
|
+
* const routerTrait = createRouterForAngular({ router: inject(Router) });
|
|
406
316
|
* ```
|
|
407
|
-
*
|
|
408
|
-
* @param requirements - One or more auth requirements for this route segment.
|
|
409
|
-
* @param extra - Optional additional data properties to merge.
|
|
410
317
|
*/
|
|
411
|
-
function
|
|
412
|
-
const
|
|
318
|
+
function createRouterForAngular(options) {
|
|
319
|
+
const routerInput = options.router;
|
|
320
|
+
const resolvedCreateOptions = {
|
|
321
|
+
router: options.router,
|
|
322
|
+
get currentUrl() {
|
|
323
|
+
return Object.hasOwn(options, "currentUrl")
|
|
324
|
+
? (options.currentUrl ?? null)
|
|
325
|
+
: (routerInput?.url ?? null);
|
|
326
|
+
},
|
|
327
|
+
};
|
|
328
|
+
validateTraitInput({
|
|
329
|
+
value: resolvedCreateOptions,
|
|
330
|
+
bundledSchema: ResolvedRouterForAngularCreateOptionsSchema,
|
|
331
|
+
validator: options.validators?.router,
|
|
332
|
+
onInvalid: (failure) => throwValidationClientError({
|
|
333
|
+
code: "client_angular.router.invalid_router_options",
|
|
334
|
+
source: "client-angular",
|
|
335
|
+
messagePrefix: "createRouterForAngular could not validate routerForAngularCreateOptions",
|
|
336
|
+
failure,
|
|
337
|
+
}),
|
|
338
|
+
});
|
|
339
|
+
const { router } = resolvedCreateOptions;
|
|
413
340
|
return {
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
341
|
+
currentUrl() {
|
|
342
|
+
const currentUrl = resolvedCreateOptions.currentUrl;
|
|
343
|
+
return currentUrl == null ? null : UriReferenceString.parse(currentUrl);
|
|
344
|
+
},
|
|
345
|
+
async navigate(request) {
|
|
346
|
+
await router.navigateByUrl(request.url.toString(), {
|
|
347
|
+
replaceUrl: request.mode === RouterNavigationMode.Replace,
|
|
348
|
+
state: request.state,
|
|
349
|
+
});
|
|
350
|
+
},
|
|
417
351
|
};
|
|
418
352
|
}
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
353
|
+
|
|
354
|
+
const BaseTransportForAngularCreateOptionsSchema = type({
|
|
355
|
+
httpClient: {
|
|
356
|
+
request: "Function",
|
|
357
|
+
},
|
|
358
|
+
baseUrl: "unknown",
|
|
359
|
+
});
|
|
360
|
+
function createBaseTransportForAngular(options) {
|
|
361
|
+
const resolvedCreateOptions = {
|
|
362
|
+
httpClient: options.httpClient,
|
|
363
|
+
baseUrl: options.baseUrl,
|
|
364
|
+
};
|
|
365
|
+
validateTraitInput({
|
|
366
|
+
value: resolvedCreateOptions,
|
|
367
|
+
bundledSchema: BaseTransportForAngularCreateOptionsSchema,
|
|
368
|
+
validator: options.validators,
|
|
369
|
+
onInvalid: (failure) => throwValidationClientError({
|
|
370
|
+
code: TransportErrorCode.InvalidAngularOptions,
|
|
371
|
+
source: "client-angular",
|
|
372
|
+
messagePrefix: "createBaseTransportForAngular could not validate transportForAngularCreateOptions",
|
|
373
|
+
failure,
|
|
374
|
+
}),
|
|
375
|
+
});
|
|
376
|
+
const { httpClient, baseUrl } = resolvedCreateOptions;
|
|
377
|
+
return {
|
|
378
|
+
async execute(request) {
|
|
379
|
+
const requestUrl = baseUrl
|
|
380
|
+
? new URL(request.url, baseUrl).toString()
|
|
381
|
+
: request.url;
|
|
382
|
+
const requestOptions = {
|
|
383
|
+
headers: request.headers,
|
|
384
|
+
observe: "response",
|
|
385
|
+
responseType: "text",
|
|
386
|
+
...(request.body !== undefined &&
|
|
387
|
+
request.method !== "GET" &&
|
|
388
|
+
request.method !== "HEAD"
|
|
389
|
+
? {
|
|
390
|
+
body: typeof request.body === "string"
|
|
391
|
+
? request.body
|
|
392
|
+
: JSON.stringify(request.body),
|
|
393
|
+
}
|
|
394
|
+
: {}),
|
|
395
|
+
};
|
|
396
|
+
try {
|
|
397
|
+
const responseSource = request.cancellationToken
|
|
398
|
+
? httpClient
|
|
399
|
+
.request(request.method, requestUrl, requestOptions)
|
|
400
|
+
.pipe(takeUntil(from(request.cancellationToken)))
|
|
401
|
+
: httpClient.request(request.method, requestUrl, requestOptions);
|
|
402
|
+
const response = await firstValueFrom(responseSource);
|
|
403
|
+
try {
|
|
404
|
+
return angularResponseToHttpResponse(response);
|
|
405
|
+
}
|
|
406
|
+
catch (error) {
|
|
407
|
+
throw new ClientError({
|
|
408
|
+
kind: ClientErrorKind.Protocol,
|
|
409
|
+
message: "The HTTP response body could not be decoded",
|
|
410
|
+
code: TransportErrorCode.ResponseDecodeFailed,
|
|
411
|
+
source: ClientErrorSource.Transport,
|
|
412
|
+
cause: error,
|
|
413
|
+
});
|
|
414
|
+
}
|
|
472
415
|
}
|
|
473
|
-
|
|
474
|
-
|
|
416
|
+
catch (error) {
|
|
417
|
+
if (error instanceof HttpErrorResponse && error.status !== 0) {
|
|
418
|
+
try {
|
|
419
|
+
return angularErrorResponseToHttpResponse(error);
|
|
420
|
+
}
|
|
421
|
+
catch (decodeError) {
|
|
422
|
+
throw new ClientError({
|
|
423
|
+
kind: ClientErrorKind.Protocol,
|
|
424
|
+
message: "The HTTP response body could not be decoded",
|
|
425
|
+
code: TransportErrorCode.ResponseDecodeFailed,
|
|
426
|
+
source: ClientErrorSource.Transport,
|
|
427
|
+
cause: decodeError,
|
|
428
|
+
});
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
const normalized = normalizeAngularTransportError(request.cancellationToken, error);
|
|
432
|
+
if (normalized instanceof ClientError) {
|
|
433
|
+
throw normalized;
|
|
434
|
+
}
|
|
435
|
+
throw new ClientError({
|
|
436
|
+
kind: ClientErrorKind.Transport,
|
|
437
|
+
message: "The HTTP request could not reach the remote service",
|
|
438
|
+
code: TransportErrorCode.RequestFailed,
|
|
439
|
+
source: ClientErrorSource.Transport,
|
|
440
|
+
retryable: true,
|
|
441
|
+
cause: error,
|
|
442
|
+
});
|
|
475
443
|
}
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
case RequirementsClientSetComposition.Replace:
|
|
479
|
-
return [...child.requirements];
|
|
480
|
-
default: {
|
|
481
|
-
const _exhaustive = child.composition;
|
|
482
|
-
throw new Error(`[resolveEffectiveRequirements] Unknown composition: ${_exhaustive}`);
|
|
483
|
-
}
|
|
484
|
-
}
|
|
444
|
+
},
|
|
445
|
+
};
|
|
485
446
|
}
|
|
486
|
-
function
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
}
|
|
447
|
+
function angularResponseToHttpResponse(response) {
|
|
448
|
+
return {
|
|
449
|
+
status: response.status,
|
|
450
|
+
headers: angularHeadersToRecord(response.headers),
|
|
451
|
+
body: parseAngularResponseBody(response.status, response.headers, response.body),
|
|
452
|
+
};
|
|
453
|
+
}
|
|
454
|
+
function angularErrorResponseToHttpResponse(error) {
|
|
495
455
|
return {
|
|
496
|
-
|
|
497
|
-
|
|
456
|
+
status: error.status,
|
|
457
|
+
headers: angularHeadersToRecord(error.headers),
|
|
458
|
+
body: parseAngularResponseBody(error.status, error.headers, error.error),
|
|
498
459
|
};
|
|
499
460
|
}
|
|
500
|
-
function
|
|
501
|
-
const
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
461
|
+
function angularHeadersToRecord(headers) {
|
|
462
|
+
const responseHeaders = {};
|
|
463
|
+
for (const key of headers.keys()) {
|
|
464
|
+
const value = headers.get(key);
|
|
465
|
+
if (value !== null) {
|
|
466
|
+
responseHeaders[key] = value;
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
return responseHeaders;
|
|
470
|
+
}
|
|
471
|
+
function parseAngularResponseBody(status, headers, body) {
|
|
472
|
+
const contentType = headers.get("content-type");
|
|
473
|
+
if (contentType?.includes("application/json") && typeof body === "string") {
|
|
474
|
+
return body.length > 0 ? JSON.parse(body) : undefined;
|
|
475
|
+
}
|
|
476
|
+
if (status !== 302 && status !== 303 && status !== 301) {
|
|
477
|
+
return body;
|
|
506
478
|
}
|
|
507
479
|
return undefined;
|
|
508
480
|
}
|
|
481
|
+
function normalizeAngularTransportError(token, error) {
|
|
482
|
+
if (!token?.isCancellationRequested) {
|
|
483
|
+
return error;
|
|
484
|
+
}
|
|
485
|
+
return token.cancellationError ?? error;
|
|
486
|
+
}
|
|
509
487
|
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
target.set(source.get());
|
|
488
|
+
const ENVIRONMENT = new InjectionToken("ENVIRONMENT");
|
|
489
|
+
const ENVIRONMENT_PROVIDER = new InjectionToken("ENVIRONMENT_PROVIDER");
|
|
490
|
+
function createEnvironmentForAngular(options) {
|
|
491
|
+
const { createBaseEnvironment, routerForAngularCreateOptions, transportForAngularCreateOptions, injectorForAngularProvider, providers = [], ...baseOptions } = options;
|
|
492
|
+
const externalProviderTokens = new Set(providers.map((provider) => getSecuritydeptProviderToken(provider)));
|
|
493
|
+
const angularProviders = [
|
|
494
|
+
createProviderIfTokenMissing(externalProviderTokens, ROUTER_TRAIT_TOKEN, () => ({
|
|
495
|
+
provide: ROUTER_TRAIT_TOKEN,
|
|
496
|
+
useValue: createRouterForAngular(routerForAngularCreateOptions),
|
|
497
|
+
})),
|
|
498
|
+
createProviderIfTokenMissing(externalProviderTokens, TRANSPORT_TRAIT_TOKEN, () => ({
|
|
499
|
+
provide: TRANSPORT_TRAIT_TOKEN,
|
|
500
|
+
useValue: createBaseTransportForAngular(transportForAngularCreateOptions),
|
|
501
|
+
})),
|
|
502
|
+
injectorForAngularProvider
|
|
503
|
+
? createProviderIfTokenMissing(externalProviderTokens, Injector, () => ({
|
|
504
|
+
provide: Injector,
|
|
505
|
+
useValue: injectorForAngularProvider,
|
|
506
|
+
}))
|
|
507
|
+
: null,
|
|
508
|
+
].filter(notMissingProvider);
|
|
509
|
+
return createBaseEnvironment({
|
|
510
|
+
...baseOptions,
|
|
511
|
+
providers: [...angularProviders, ...providers],
|
|
535
512
|
});
|
|
536
513
|
}
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
514
|
+
function provideEnvironment(options) {
|
|
515
|
+
return [
|
|
516
|
+
{
|
|
517
|
+
provide: ENVIRONMENT,
|
|
518
|
+
useFactory: (router, httpClient, injector, destroyRef, providers) => {
|
|
519
|
+
const { autoCreateDestroyRef, providers: optionProviders, ...environmentOptions } = options;
|
|
520
|
+
const securitydeptDestroyRef = autoCreateDestroyRef === false
|
|
521
|
+
? null
|
|
522
|
+
: createSecuritydeptDestroyRef();
|
|
523
|
+
if (securitydeptDestroyRef) {
|
|
524
|
+
destroyRef.onDestroy(() => securitydeptDestroyRef.dispose());
|
|
525
|
+
}
|
|
526
|
+
const environmentProviders = [
|
|
527
|
+
...(optionProviders ?? []),
|
|
528
|
+
...(providers ?? []),
|
|
529
|
+
...(securitydeptDestroyRef
|
|
530
|
+
? [
|
|
531
|
+
{
|
|
532
|
+
provide: SecuritydeptDestroyRef,
|
|
533
|
+
useValue: securitydeptDestroyRef,
|
|
534
|
+
},
|
|
535
|
+
]
|
|
536
|
+
: []),
|
|
537
|
+
];
|
|
538
|
+
return createEnvironmentForAngular({
|
|
539
|
+
...environmentOptions,
|
|
540
|
+
routerForAngularCreateOptions: {
|
|
541
|
+
router,
|
|
542
|
+
...environmentOptions.routerForAngularCreateOptions,
|
|
543
|
+
},
|
|
544
|
+
transportForAngularCreateOptions: {
|
|
545
|
+
httpClient,
|
|
546
|
+
...environmentOptions.transportForAngularCreateOptions,
|
|
547
|
+
},
|
|
548
|
+
injectorForAngularProvider: injector,
|
|
549
|
+
providers: environmentProviders,
|
|
550
|
+
});
|
|
551
|
+
},
|
|
552
|
+
deps: [
|
|
553
|
+
Router,
|
|
554
|
+
HttpClient,
|
|
555
|
+
Injector,
|
|
556
|
+
DestroyRef,
|
|
557
|
+
[new Optional(), ENVIRONMENT_PROVIDER],
|
|
558
|
+
],
|
|
559
|
+
},
|
|
560
|
+
{
|
|
561
|
+
provide: SECURITYDEPT_INJECTOR,
|
|
562
|
+
useFactory: (environment) => environment.injector,
|
|
563
|
+
deps: [ENVIRONMENT],
|
|
564
|
+
},
|
|
565
|
+
];
|
|
566
|
+
}
|
|
567
|
+
function provideEnvironmentProvider(provider) {
|
|
568
|
+
return {
|
|
569
|
+
provide: ENVIRONMENT_PROVIDER,
|
|
570
|
+
useValue: provider,
|
|
571
|
+
multi: true,
|
|
572
|
+
};
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
function toNgSignal(source, options) {
|
|
576
|
+
return toSignal(source[SYMBOL_OBSERVABLE](), options);
|
|
551
577
|
}
|
|
578
|
+
const toNgResource = rxResource;
|
|
552
579
|
|
|
553
580
|
/*
|
|
554
581
|
* Public API Surface of @securitydept/client-angular
|
|
@@ -558,5 +585,5 @@ function signalToObservable(source) {
|
|
|
558
585
|
* Generated bundle index. Do not edit.
|
|
559
586
|
*/
|
|
560
587
|
|
|
561
|
-
export {
|
|
588
|
+
export { AngularRouterNavigationLikeSchema, BaseTransportForAngularCreateOptionsSchema, ENVIRONMENT, ENVIRONMENT_PROVIDER, SECURITYDEPT_INJECTOR, createAngularCanActivate, createAngularCanActivateChild, createBaseTransportForAngular, createEnvironmentForAngular, createRouterForAngular, projectAngularRouteSegments, provideEnvironment, provideEnvironmentProvider, provideSecuritydept, routeDataFromSegment, secureRoute, secureRouteRoot, toNgResource, toNgSignal };
|
|
562
589
|
//# sourceMappingURL=securitydept-client-angular.mjs.map
|