@securitydept/client-angular 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.
|
@@ -1,521 +1,581 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
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';
|
|
6
8
|
|
|
7
|
-
|
|
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
|
+
]);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Angular route metadata projection
|
|
8
46
|
//
|
|
9
47
|
// Canonical import path:
|
|
10
|
-
// 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.
|
|
11
55
|
//
|
|
12
|
-
//
|
|
13
|
-
//
|
|
14
|
-
// - Provider helpers for app / module / route scoped planners
|
|
15
|
-
// - InjectionToken + provider helpers for ScopedRequirementsClientSet
|
|
16
|
-
// - injectPlannerHost() convenience function with fail-fast
|
|
56
|
+
// Route metadata storage is owned by `@securitydept/client`
|
|
57
|
+
// ({@link readSecuritydeptRouteMetadata} / {@link writeSecuritydeptRouteMetadata}).
|
|
17
58
|
//
|
|
18
59
|
// Architecture boundary:
|
|
19
|
-
// - Does NOT own
|
|
20
|
-
// - Does NOT carry token-set-specific mapping
|
|
21
|
-
// -
|
|
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.
|
|
22
63
|
//
|
|
23
64
|
// Stability: provisional
|
|
24
|
-
// ---------------------------------------------------------------------------
|
|
25
|
-
// PlannerHost injection token
|
|
26
|
-
// ---------------------------------------------------------------------------
|
|
27
65
|
/**
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
* Provide at the app level via {@link provideAuthPlannerHost}.
|
|
31
|
-
* 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).
|
|
32
68
|
*/
|
|
33
|
-
|
|
69
|
+
function routeDataFromSegment(segment) {
|
|
70
|
+
return {
|
|
71
|
+
...(segment.routeConfig?.data ?? {}),
|
|
72
|
+
...segment.data,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
34
75
|
/**
|
|
35
|
-
*
|
|
76
|
+
* Project the `pathFromRoot` of an `ActivatedRouteSnapshot` into the SDK's
|
|
77
|
+
* `RouteTreeSegment[]` chain (root -> leaf).
|
|
36
78
|
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
79
|
+
* Each segment carries its declared requirements and composition strategy;
|
|
80
|
+
* inherit/merge/replace folding is applied later by the
|
|
81
|
+
* `RouteCompositionRequirementPlanner`.
|
|
39
82
|
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
* // app.config.ts — app-level planner (default sequential strategy)
|
|
43
|
-
* export const appConfig: ApplicationConfig = {
|
|
44
|
-
* providers: [provideAuthPlannerHost()],
|
|
45
|
-
* };
|
|
46
|
-
*
|
|
47
|
-
* // Feature route — override with custom async chooser strategy
|
|
48
|
-
* {
|
|
49
|
-
* path: "admin",
|
|
50
|
-
* providers: [provideAuthPlannerHost({
|
|
51
|
-
* selectCandidate: async (candidates) => showAdminChooser(candidates),
|
|
52
|
-
* })],
|
|
53
|
-
* children: [...]
|
|
54
|
-
* }
|
|
55
|
-
* ```
|
|
83
|
+
* Segments without a `routeConfig` (e.g. the root) get routeId `"__root__"`;
|
|
84
|
+
* empty-path segments get `"__index__"`.
|
|
56
85
|
*/
|
|
57
|
-
function
|
|
58
|
-
const
|
|
59
|
-
|
|
60
|
-
|
|
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__";
|
|
61
101
|
}
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
102
|
+
const path = segment.routeConfig.path;
|
|
103
|
+
if (path === undefined || path === null || path === "") {
|
|
104
|
+
return "__index__";
|
|
105
|
+
}
|
|
106
|
+
return path;
|
|
65
107
|
}
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
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);
|
|
83
146
|
}
|
|
84
|
-
return host;
|
|
85
147
|
}
|
|
86
|
-
// ---------------------------------------------------------------------------
|
|
87
|
-
// Requirements client set injection token — stores the already-resolved
|
|
88
|
-
// effective options for the current scope (parent + child composed).
|
|
89
|
-
// ---------------------------------------------------------------------------
|
|
90
148
|
/**
|
|
91
|
-
*
|
|
92
|
-
*
|
|
93
|
-
* The value is always the **resolved** options after composing all ancestor
|
|
94
|
-
* scopes — it is NOT the raw `ScopedRequirementsClientSet` declaration.
|
|
95
|
-
*
|
|
96
|
-
* Provide via {@link provideRouteScopedRequirements}.
|
|
97
|
-
* Guards inject this token to get the effective scope requirements and then
|
|
98
|
-
* merge their own declared candidates on top.
|
|
149
|
+
* Create a `CanActivateFn` bridging the route chain to the requirement
|
|
150
|
+
* planner pipeline.
|
|
99
151
|
*/
|
|
100
|
-
|
|
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
|
+
}
|
|
101
174
|
/**
|
|
102
|
-
*
|
|
103
|
-
*
|
|
104
|
-
* Internally, the provider resolves the effective client set by composing the
|
|
105
|
-
* declared `scopedSet` with the parent scope's already-resolved options
|
|
106
|
-
* (injected via `skipSelf: true`). The token value stored in DI is the
|
|
107
|
-
* resolved `readonly AuthGuardClientOption[]`, not the raw declaration.
|
|
108
|
-
*
|
|
109
|
-
* This mirrors the React `AuthRequirementsClientSetProvider` semantics:
|
|
110
|
-
* each level stores its resolved effective options, so children inherit
|
|
111
|
-
* the composed result without re-resolving the full ancestor chain.
|
|
112
|
-
*
|
|
113
|
-
* The `scopedSet` argument may also be a **factory function** — it will be
|
|
114
|
-
* called inside Angular's `useFactory` context, so `inject()` is valid.
|
|
115
|
-
* Use this when the requirement options need DI-resolved services (e.g. a
|
|
116
|
-
* token-set registry or a redirect service).
|
|
117
|
-
*
|
|
118
|
-
* @example
|
|
119
|
-
* ```ts
|
|
120
|
-
* // routes.ts — static declaration (no DI needed)
|
|
121
|
-
* {
|
|
122
|
-
* path: "",
|
|
123
|
-
* providers: [
|
|
124
|
-
* provideRouteScopedRequirements({
|
|
125
|
-
* composition: RequirementsClientSetComposition.Replace,
|
|
126
|
-
* options: [sessionClientOption],
|
|
127
|
-
* }),
|
|
128
|
-
* ],
|
|
129
|
-
* }
|
|
130
|
-
*
|
|
131
|
-
* // Feature route merges OIDC on top of the parent's session requirement
|
|
132
|
-
* {
|
|
133
|
-
* path: "confluence",
|
|
134
|
-
* providers: [
|
|
135
|
-
* provideRouteScopedRequirements({
|
|
136
|
-
* composition: RequirementsClientSetComposition.Merge,
|
|
137
|
-
* options: [confluenceOidcClientOption],
|
|
138
|
-
* }),
|
|
139
|
-
* ],
|
|
140
|
-
* // Route-level DI scope only. Angular Router adopters should prefer
|
|
141
|
-
* // secureRouteRoot()/secureRoute() over direct guard wiring.
|
|
142
|
-
* }
|
|
143
|
-
*
|
|
144
|
-
* // Factory form — use when options depend on injected services
|
|
145
|
-
* {
|
|
146
|
-
* path: "protected",
|
|
147
|
-
* providers: [
|
|
148
|
-
* provideRouteScopedRequirements(() => {
|
|
149
|
-
* const registry = inject(TokenSetAuthRegistry);
|
|
150
|
-
* const authService = inject(AuthService);
|
|
151
|
-
* return {
|
|
152
|
-
* composition: RequirementsClientSetComposition.Replace,
|
|
153
|
-
* options: [{
|
|
154
|
-
* requirementId: "oidc",
|
|
155
|
-
* requirementKind: "frontend_oidc",
|
|
156
|
-
* checkAuthenticated: () => registry.require(clientKey).isAuthenticated(),
|
|
157
|
-
* onUnauthenticated: () => { authService.redirectToLogin(clientKey).subscribe(); return false; },
|
|
158
|
-
* }],
|
|
159
|
-
* };
|
|
160
|
-
* }),
|
|
161
|
-
* ],
|
|
162
|
-
* }
|
|
163
|
-
* ```
|
|
175
|
+
* Create a `CanActivateChildFn` bridging the child route chain to the
|
|
176
|
+
* requirement planner pipeline.
|
|
164
177
|
*/
|
|
165
|
-
function
|
|
166
|
-
return
|
|
167
|
-
{
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
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
|
+
};
|
|
184
199
|
}
|
|
185
200
|
|
|
186
|
-
// Angular
|
|
201
|
+
// Angular secure route helpers — declarative route assembly
|
|
187
202
|
//
|
|
188
203
|
// Canonical import path:
|
|
189
|
-
// import {
|
|
204
|
+
// import { secureRoute, secureRouteRoot } from "@securitydept/client-angular"
|
|
190
205
|
//
|
|
191
|
-
//
|
|
192
|
-
//
|
|
193
|
-
//
|
|
194
|
-
//
|
|
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).
|
|
211
|
+
//
|
|
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.
|
|
195
215
|
//
|
|
196
216
|
// Architecture boundary:
|
|
197
|
-
// - Does NOT own
|
|
198
|
-
// -
|
|
199
|
-
// - Does NOT carry token-set-specific mapping or policy.
|
|
200
|
-
// - The adopter wires this into their guards / resolvers.
|
|
217
|
+
// - Does NOT own behaviour or token-set-specific policy.
|
|
218
|
+
// - Delegates enforcement to createAngularCanActivate/Child.
|
|
201
219
|
//
|
|
202
220
|
// Stability: provisional
|
|
203
|
-
|
|
204
|
-
const
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
// ---------------------------------------------------------------------------
|
|
208
|
-
/**
|
|
209
|
-
* Angular service that bridges Angular Router's `ActivatedRouteSnapshot` to
|
|
210
|
-
* the SDK's framework-agnostic `RouteMatchNode[]` contract.
|
|
211
|
-
*
|
|
212
|
-
* Inject this into `CanActivateFn` guards or class-based guards.
|
|
213
|
-
*
|
|
214
|
-
* @example
|
|
215
|
-
* ```ts
|
|
216
|
-
* export const authGuard: CanActivateFn = (route) => {
|
|
217
|
-
* const adapter = inject(AuthRouteAdapter);
|
|
218
|
-
* const result = adapter.check(route, orchestrator);
|
|
219
|
-
* if (!result.canActivate && result.pendingRequirement) {
|
|
220
|
-
* // Trigger auth flow for the pending requirement
|
|
221
|
-
* }
|
|
222
|
-
* return result.canActivate;
|
|
223
|
-
* };
|
|
224
|
-
* ```
|
|
225
|
-
*/
|
|
226
|
-
class AuthRouteAdapter {
|
|
227
|
-
/**
|
|
228
|
-
* Project the `pathFromRoot` of an `ActivatedRouteSnapshot` into the SDK's
|
|
229
|
-
* `RouteMatchNode[]` chain.
|
|
230
|
-
*
|
|
231
|
-
* Route segments without a `routeConfig` (e.g. the root segment) are
|
|
232
|
-
* assigned routeId `"__root__"`. Segments with a config use the `path`
|
|
233
|
-
* as their routeId (or `"__index__"` for empty-path segments).
|
|
234
|
-
*/
|
|
235
|
-
projectRouteMatch(leafRoute, options) {
|
|
236
|
-
const key = options?.requirementsKey ?? DEFAULT_ROUTE_REQUIREMENTS_KEY;
|
|
237
|
-
const chain = leafRoute.pathFromRoot ?? [leafRoute];
|
|
238
|
-
return chain.map((segment) => this.segmentToNode(segment, key));
|
|
221
|
+
function writeRequirementData(security, base) {
|
|
222
|
+
const patch = {};
|
|
223
|
+
if (security.requirements !== undefined) {
|
|
224
|
+
patch.requirements = security.requirements;
|
|
239
225
|
}
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
* SDK's `RouteMatchNode[]` chain.
|
|
243
|
-
*
|
|
244
|
-
* Use when you already have a flattened chain (e.g. from
|
|
245
|
-
* `ActivatedRoute.pathFromRoot`).
|
|
246
|
-
*/
|
|
247
|
-
projectRouteSegments(segments, options) {
|
|
248
|
-
const key = options?.requirementsKey ?? DEFAULT_ROUTE_REQUIREMENTS_KEY;
|
|
249
|
-
return segments.map((segment) => this.segmentToNode(segment, key));
|
|
226
|
+
if (security.composition !== undefined) {
|
|
227
|
+
patch.composition = security.composition;
|
|
250
228
|
}
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
*
|
|
254
|
-
* @param leafRoute - The leaf `ActivatedRouteSnapshot` from the guard call.
|
|
255
|
-
* @param orchestrator - The SDK orchestrator managing the requirement plan.
|
|
256
|
-
* @param options - Optional projection options.
|
|
257
|
-
*/
|
|
258
|
-
check(leafRoute, orchestrator, options) {
|
|
259
|
-
const chain = this.projectRouteMatch(leafRoute, options);
|
|
260
|
-
orchestrator.activateMatchedRoutes(chain);
|
|
261
|
-
const snap = orchestrator.snapshot();
|
|
262
|
-
return {
|
|
263
|
-
canActivate: snap.settled,
|
|
264
|
-
pendingRequirement: snap.pendingRequirement,
|
|
265
|
-
};
|
|
229
|
+
if (Object.keys(patch).length === 0) {
|
|
230
|
+
return { ...(base ?? {}) };
|
|
266
231
|
}
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
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;
|
|
282
282
|
}
|
|
283
|
-
|
|
284
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: AuthRouteAdapter, providedIn: "root" });
|
|
283
|
+
return route;
|
|
285
284
|
}
|
|
286
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: AuthRouteAdapter, decorators: [{
|
|
287
|
-
type: Injectable,
|
|
288
|
-
args: [{ providedIn: "root" }]
|
|
289
|
-
}] });
|
|
290
285
|
|
|
291
|
-
// Angular
|
|
286
|
+
// Angular Router trait adapter
|
|
292
287
|
//
|
|
293
288
|
// Canonical import path:
|
|
294
|
-
// import {
|
|
289
|
+
// import { createRouterForAngular } from "@securitydept/client-angular"
|
|
295
290
|
//
|
|
296
|
-
//
|
|
297
|
-
//
|
|
298
|
-
//
|
|
299
|
-
//
|
|
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.
|
|
300
295
|
//
|
|
301
296
|
// Architecture boundary:
|
|
302
|
-
// - 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).
|
|
303
299
|
// - Does NOT carry token-set-specific mapping or policy.
|
|
304
|
-
// - Provides typed helpers for declaring and reading requirements from route data.
|
|
305
|
-
// - framework-specific (Angular Router types) but requirement-system-agnostic.
|
|
306
|
-
//
|
|
307
|
-
// Relationship to provideRouteScopedRequirements():
|
|
308
|
-
// - provideRouteScopedRequirements() is a lower-level DI-scope helper.
|
|
309
|
-
// - This module is the canonical path for Angular Router auth requirement
|
|
310
|
-
// declaration when full-route aggregation is needed.
|
|
311
|
-
//
|
|
312
|
-
// Key sentinel note:
|
|
313
|
-
// DEFAULT_ROUTE_REQUIREMENTS_KEY is defined in router.ts (AuthRouteAdapter's
|
|
314
|
-
// companion constant). ROUTE_REQUIREMENTS_DATA_KEY exported here is its
|
|
315
|
-
// semantic alias — both hold the same value ("authRequirements").
|
|
316
|
-
// Adopters may use either name; they are interchangeable.
|
|
317
300
|
//
|
|
318
301
|
// Stability: provisional
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
302
|
+
const AngularRouterNavigationLikeSchema = type({
|
|
303
|
+
url: "string?",
|
|
304
|
+
navigateByUrl: "Function",
|
|
305
|
+
});
|
|
306
|
+
const ResolvedRouterForAngularCreateOptionsSchema = type({
|
|
307
|
+
router: AngularRouterNavigationLikeSchema,
|
|
308
|
+
currentUrl: UriReferenceStringSchema.or("null"),
|
|
309
|
+
});
|
|
322
310
|
/**
|
|
323
|
-
*
|
|
324
|
-
*
|
|
325
|
-
* Semantic alias for {@link DEFAULT_ROUTE_REQUIREMENTS_KEY} (defined in
|
|
326
|
-
* `router.ts`). Both hold the same value (`"authRequirements"`).
|
|
327
|
-
* Use either name — they are interchangeable.
|
|
328
|
-
*
|
|
329
|
-
* Set this key in `Route.data` to declare auth requirements for a route segment:
|
|
330
|
-
* ```ts
|
|
331
|
-
* { path: "dashboard", data: withRouteRequirements([{ id: "session", kind: "session" }]) }
|
|
332
|
-
* ```
|
|
333
|
-
*
|
|
334
|
-
* The key is also read by {@link AuthRouteAdapter} when projecting snapshots.
|
|
335
|
-
*
|
|
336
|
-
* @see {@link withRouteRequirements}
|
|
337
|
-
* @see {@link extractFullRouteRequirements}
|
|
338
|
-
*/
|
|
339
|
-
const ROUTE_REQUIREMENTS_DATA_KEY = DEFAULT_ROUTE_REQUIREMENTS_KEY;
|
|
340
|
-
/**
|
|
341
|
-
* Default key for the route-level composition strategy in Angular route `data`.
|
|
342
|
-
*
|
|
343
|
-
* Used together with {@link ROUTE_REQUIREMENTS_DATA_KEY}. The default
|
|
344
|
-
* composition is `merge` when the key is omitted.
|
|
345
|
-
*/
|
|
346
|
-
const ROUTE_REQUIREMENTS_COMPOSITION_DATA_KEY = "authRequirementsComposition";
|
|
347
|
-
// ---------------------------------------------------------------------------
|
|
348
|
-
// Declaration helper
|
|
349
|
-
// ---------------------------------------------------------------------------
|
|
350
|
-
/**
|
|
351
|
-
* Build an Angular route `data` object declaring auth requirements for a
|
|
352
|
-
* single route segment.
|
|
353
|
-
*
|
|
354
|
-
* Use this as the `data` field in an Angular `Route` config. Nested routes
|
|
355
|
-
* can declare their own requirements; a full-route aggregation guard will
|
|
356
|
-
* walk the entire `pathFromRoot` chain to collect all requirements in one pass.
|
|
311
|
+
* Create a {@link RouterTrait} backed by an Angular `Router`.
|
|
357
312
|
*
|
|
358
313
|
* @example
|
|
359
314
|
* ```ts
|
|
360
|
-
*
|
|
361
|
-
* {
|
|
362
|
-
* path: "app",
|
|
363
|
-
* data: withRouteRequirements([{ id: "session", kind: "session" }]),
|
|
364
|
-
* children: [
|
|
365
|
-
* // Child route adds an additional requirement
|
|
366
|
-
* {
|
|
367
|
-
* path: "admin",
|
|
368
|
-
* data: withRouteRequirements([{ id: "admin-oidc", kind: "backend_oidc" }]),
|
|
369
|
-
* canActivate: [createTokenSetRouteAggregationGuard(...)],
|
|
370
|
-
* },
|
|
371
|
-
* ],
|
|
372
|
-
* }
|
|
315
|
+
* const routerTrait = createRouterForAngular({ router: inject(Router) });
|
|
373
316
|
* ```
|
|
374
|
-
*
|
|
375
|
-
* @param requirements - One or more auth requirements for this route segment.
|
|
376
|
-
* @param extra - Optional additional data properties to merge.
|
|
377
317
|
*/
|
|
378
|
-
function
|
|
379
|
-
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;
|
|
380
340
|
return {
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
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
|
+
},
|
|
384
351
|
};
|
|
385
352
|
}
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
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
|
+
}
|
|
439
415
|
}
|
|
440
|
-
|
|
441
|
-
|
|
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
|
+
});
|
|
442
443
|
}
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
case RequirementsClientSetComposition.Replace:
|
|
446
|
-
return [...child.requirements];
|
|
447
|
-
default: {
|
|
448
|
-
const _exhaustive = child.composition;
|
|
449
|
-
throw new Error(`[resolveEffectiveRequirements] Unknown composition: ${_exhaustive}`);
|
|
450
|
-
}
|
|
451
|
-
}
|
|
444
|
+
},
|
|
445
|
+
};
|
|
452
446
|
}
|
|
453
|
-
function
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
}
|
|
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) {
|
|
462
455
|
return {
|
|
463
|
-
|
|
464
|
-
|
|
456
|
+
status: error.status,
|
|
457
|
+
headers: angularHeadersToRecord(error.headers),
|
|
458
|
+
body: parseAngularResponseBody(error.status, error.headers, error.error),
|
|
465
459
|
};
|
|
466
460
|
}
|
|
467
|
-
function
|
|
468
|
-
const
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
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;
|
|
473
478
|
}
|
|
474
479
|
return undefined;
|
|
475
480
|
}
|
|
481
|
+
function normalizeAngularTransportError(token, error) {
|
|
482
|
+
if (!token?.isCancellationRequested) {
|
|
483
|
+
return error;
|
|
484
|
+
}
|
|
485
|
+
return token.cancellationError ?? error;
|
|
486
|
+
}
|
|
476
487
|
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
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],
|
|
502
512
|
});
|
|
503
513
|
}
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
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);
|
|
518
577
|
}
|
|
578
|
+
const toNgResource = rxResource;
|
|
519
579
|
|
|
520
580
|
/*
|
|
521
581
|
* Public API Surface of @securitydept/client-angular
|
|
@@ -525,5 +585,5 @@ function signalToObservable(source) {
|
|
|
525
585
|
* Generated bundle index. Do not edit.
|
|
526
586
|
*/
|
|
527
587
|
|
|
528
|
-
export {
|
|
588
|
+
export { AngularRouterNavigationLikeSchema, BaseTransportForAngularCreateOptionsSchema, ENVIRONMENT, ENVIRONMENT_PROVIDER, SECURITYDEPT_INJECTOR, createAngularCanActivate, createAngularCanActivateChild, createBaseTransportForAngular, createEnvironmentForAngular, createRouterForAngular, projectAngularRouteSegments, provideEnvironment, provideEnvironmentProvider, provideSecuritydept, routeDataFromSegment, secureRoute, secureRouteRoot, toNgResource, toNgSignal };
|
|
529
589
|
//# sourceMappingURL=securitydept-client-angular.mjs.map
|