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