@anarchitects/auth-angular 0.4.0 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +88 -10
- package/data-access/README.md +10 -0
- package/feature/README.md +32 -3
- package/fesm2022/anarchitects-auth-angular-data-access.mjs +84 -65
- package/fesm2022/anarchitects-auth-angular-data-access.mjs.map +1 -1
- package/fesm2022/anarchitects-auth-angular-feature.mjs +61 -6
- package/fesm2022/anarchitects-auth-angular-feature.mjs.map +1 -1
- package/fesm2022/anarchitects-auth-angular-state.mjs +194 -47
- package/fesm2022/anarchitects-auth-angular-state.mjs.map +1 -1
- package/fesm2022/anarchitects-auth-angular-ui.mjs +338 -262
- package/fesm2022/anarchitects-auth-angular-ui.mjs.map +1 -1
- package/fesm2022/anarchitects-auth-angular-util.mjs +36 -3
- package/fesm2022/anarchitects-auth-angular-util.mjs.map +1 -1
- package/package.json +7 -7
- package/state/README.md +36 -1
- package/types/anarchitects-auth-angular-data-access.d.ts +19 -3
- package/types/anarchitects-auth-angular-feature.d.ts +4 -2
- package/types/anarchitects-auth-angular-state.d.ts +25 -8
- package/types/anarchitects-auth-angular-ui.d.ts +11 -11
- package/types/anarchitects-auth-angular-util.d.ts +11 -13
- package/util/README.md +28 -2
package/README.md
CHANGED
|
@@ -14,11 +14,22 @@ Angular domain libraries for the Anarchitecture auth domain. The package is orga
|
|
|
14
14
|
|
|
15
15
|
- `config`: DI tokens and provider helpers (API base URL, defaults)
|
|
16
16
|
- `data-access`: generated OpenAPI clients plus adapters over the Nest API
|
|
17
|
-
- `state`: signal-based store plus explicit provider helper for login/logout, token refresh, and ability hydration
|
|
18
|
-
- `feature`:
|
|
19
|
-
- `util`: CASL ability helpers (`createAppAbility`, `AppAbility`)
|
|
17
|
+
- `state`: signal-based store plus explicit provider helper for login/logout, token refresh, eager session restore, and ability hydration
|
|
18
|
+
- `feature`: coarse route guard, resource-aware route guard, and orchestration components that delegate rendering to auth UI components
|
|
19
|
+
- `util`: CASL ability helpers (`createAppAbility`, `canAccessResource`, `canAccessResourceField`, `AppAbility`)
|
|
20
20
|
- `ui`: presentational auth domain form components built on `AnarchitectsUiForm`
|
|
21
21
|
|
|
22
|
+
## Authorization Model
|
|
23
|
+
|
|
24
|
+
CASL integration in `@anarchitects/auth-angular` mirrors the backend split instead of pretending every check is the same:
|
|
25
|
+
|
|
26
|
+
- `policyGuard` is coarse and answers "may this user attempt work on this subject at all?"
|
|
27
|
+
- `resourcePolicyGuard` checks a resolved concrete resource route
|
|
28
|
+
- `canAccessResource(...)` and `canAccessResourceField(...)` are the intended UI checks for edit buttons, row actions, and field-sensitive affordances
|
|
29
|
+
- `AuthStore` hydrates both raw `rbac` rules and the derived CASL ability
|
|
30
|
+
|
|
31
|
+
Angular should hide or redirect on unauthorized work, but Nest remains the final enforcement boundary for instance-sensitive access.
|
|
32
|
+
|
|
22
33
|
## Installation
|
|
23
34
|
|
|
24
35
|
```bash
|
|
@@ -51,7 +62,7 @@ export const appConfig: ApplicationConfig = {
|
|
|
51
62
|
apiBaseUrl: 'https://api.anarchitects.dev',
|
|
52
63
|
}),
|
|
53
64
|
provideAuthDataAccess(),
|
|
54
|
-
provideAuthState(),
|
|
65
|
+
...provideAuthState(),
|
|
55
66
|
],
|
|
56
67
|
};
|
|
57
68
|
```
|
|
@@ -80,7 +91,10 @@ export class AppComponent {
|
|
|
80
91
|
```ts
|
|
81
92
|
// app.routes.ts
|
|
82
93
|
import { Routes } from '@angular/router';
|
|
83
|
-
import {
|
|
94
|
+
import {
|
|
95
|
+
policyGuard,
|
|
96
|
+
resourcePolicyGuard,
|
|
97
|
+
} from '@anarchitects/auth-angular/feature';
|
|
84
98
|
|
|
85
99
|
export const routes: Routes = [
|
|
86
100
|
{
|
|
@@ -90,19 +104,80 @@ export const routes: Routes = [
|
|
|
90
104
|
loadComponent: () =>
|
|
91
105
|
import('./admin.component').then((m) => m.AdminComponent),
|
|
92
106
|
},
|
|
107
|
+
{
|
|
108
|
+
path: 'posts/:postId/edit',
|
|
109
|
+
canMatch: [policyGuard],
|
|
110
|
+
canActivate: [resourcePolicyGuard],
|
|
111
|
+
data: {
|
|
112
|
+
action: 'update',
|
|
113
|
+
subject: 'Post',
|
|
114
|
+
resourceKey: 'post',
|
|
115
|
+
unauthorizedRedirectTo: '/posts',
|
|
116
|
+
},
|
|
117
|
+
loadComponent: () =>
|
|
118
|
+
import('./post-edit.component').then((m) => m.PostEditComponent),
|
|
119
|
+
},
|
|
93
120
|
];
|
|
94
121
|
```
|
|
95
122
|
|
|
123
|
+
`policyGuard` is a coarse route-attempt guard. It answers "may this user attempt work on this subject at all?" by using the shared route matcher from `@anarchitects/auth-ts`. Concrete ownership checks still belong to loaded resources. Use `resourcePolicyGuard` for resolved edit/detail routes and `canAccessResource(...)` / `canAccessResourceField(...)` for UI elements such as edit buttons.
|
|
124
|
+
|
|
125
|
+
Blog ownership example:
|
|
126
|
+
|
|
127
|
+
```ts
|
|
128
|
+
import { Component, computed, inject, input } from '@angular/core';
|
|
129
|
+
import {
|
|
130
|
+
canAccessResource,
|
|
131
|
+
canAccessResourceField,
|
|
132
|
+
} from '@anarchitects/auth-angular/util';
|
|
133
|
+
import { AuthStore } from '@anarchitects/auth-angular/state';
|
|
134
|
+
|
|
135
|
+
@Component({
|
|
136
|
+
selector: 'app-post-actions',
|
|
137
|
+
template: `
|
|
138
|
+
@if (canEdit()) {
|
|
139
|
+
<a [routerLink]="['/posts', post().id, 'edit']">Edit</a>
|
|
140
|
+
}
|
|
141
|
+
@if (canEditTitle()) {
|
|
142
|
+
<button type="button">Rename title</button>
|
|
143
|
+
}
|
|
144
|
+
`,
|
|
145
|
+
})
|
|
146
|
+
export class PostActionsComponent {
|
|
147
|
+
readonly post = input.required<{ id: string; authorId: string }>();
|
|
148
|
+
private readonly authStore = inject(AuthStore);
|
|
149
|
+
|
|
150
|
+
readonly canEdit = computed(() =>
|
|
151
|
+
canAccessResource(
|
|
152
|
+
this.authStore.ability(),
|
|
153
|
+
'update',
|
|
154
|
+
'Post',
|
|
155
|
+
this.post(),
|
|
156
|
+
),
|
|
157
|
+
);
|
|
158
|
+
|
|
159
|
+
readonly canEditTitle = computed(() =>
|
|
160
|
+
canAccessResourceField(
|
|
161
|
+
this.authStore.ability(),
|
|
162
|
+
'update',
|
|
163
|
+
'Post',
|
|
164
|
+
'title',
|
|
165
|
+
this.post(),
|
|
166
|
+
),
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
```
|
|
170
|
+
|
|
96
171
|
## Entry points
|
|
97
172
|
|
|
98
173
|
| Import path | Description |
|
|
99
174
|
| ---------------------------------------- | --------------------------------------- |
|
|
100
175
|
| `@anarchitects/auth-angular/config` | DI tokens and providers |
|
|
101
176
|
| `@anarchitects/auth-angular/data-access` | Generated API clients and HTTP adapters |
|
|
102
|
-
| `@anarchitects/auth-angular/state` | Signal store
|
|
103
|
-
| `@anarchitects/auth-angular/feature` |
|
|
177
|
+
| `@anarchitects/auth-angular/state` | Signal store, eager restore, CASL ability sync |
|
|
178
|
+
| `@anarchitects/auth-angular/feature` | Coarse and resource-aware router guards |
|
|
104
179
|
| `@anarchitects/auth-angular/ui` | Auth domain form UI components |
|
|
105
|
-
| `@anarchitects/auth-angular/util` | CASL ability
|
|
180
|
+
| `@anarchitects/auth-angular/util` | CASL ability/resource helpers and typings |
|
|
106
181
|
|
|
107
182
|
## Nx scripts
|
|
108
183
|
|
|
@@ -114,8 +189,11 @@ export const routes: Routes = [
|
|
|
114
189
|
|
|
115
190
|
- DTOs live in `@anarchitects/auth-ts`; regenerate OpenAPI docs when route schemas change (`nx run api-specs:generate`).
|
|
116
191
|
- Data-access layer should always use the generated OpenAPI clients—no manual HTTP calls.
|
|
117
|
-
- State layer uses Angular signals via `@ngrx/signals` for reactive updates
|
|
118
|
-
-
|
|
192
|
+
- State layer uses Angular signals via `@ngrx/signals` for reactive updates, hydrates raw RBAC rules plus the derived CASL ability, and restores sessions eagerly when provided.
|
|
193
|
+
- `AuthStore.initialized()` and `AuthStore.restoring()` let apps avoid auth flicker while bootstrap restore completes.
|
|
194
|
+
- `/auth/me` RBAC payloads are parsed at the frontend trust boundary; malformed authorization data fails closed instead of producing a partially trusted ability.
|
|
195
|
+
- Ability creation and concrete resource checks are centralised in `@anarchitects/auth-angular/util`; import the helpers instead of instantiating CASL directly.
|
|
196
|
+
- `policyGuard` is coarse by design; use `resourcePolicyGuard` and backend instance checks for ownership-sensitive routes.
|
|
119
197
|
- Keep UI, feature, data-access, state, and config layers decoupled per architecture guidelines.
|
|
120
198
|
|
|
121
199
|
## License
|
package/data-access/README.md
CHANGED
|
@@ -41,3 +41,13 @@ export const appConfig: ApplicationConfig = {
|
|
|
41
41
|
providers: [provideHttpClient(withAuthHttpInterceptors())],
|
|
42
42
|
};
|
|
43
43
|
```
|
|
44
|
+
|
|
45
|
+
## Authorization Payload Expectations
|
|
46
|
+
|
|
47
|
+
`AuthApi.getLoggedInUserInfo()` is the frontend trust boundary for `/auth/me` authorization data:
|
|
48
|
+
|
|
49
|
+
- it expects `rbac` to match the shared `PolicyRule` contract from `@anarchitects/auth-ts`
|
|
50
|
+
- malformed `rbac` payloads are rejected fail-closed before they reach `AuthStore`
|
|
51
|
+
- bootstrap restore may suppress forced login redirects while still rejecting malformed authorization data
|
|
52
|
+
|
|
53
|
+
Use the state and util layers for authorization behavior after this boundary rather than trusting raw HTTP JSON in feature/UI code.
|
package/feature/README.md
CHANGED
|
@@ -4,7 +4,8 @@ Feature-level orchestration for Angular auth. It ships route guards plus standal
|
|
|
4
4
|
|
|
5
5
|
## Exports
|
|
6
6
|
|
|
7
|
-
- `policyGuard`: standalone `CanMatchFn`
|
|
7
|
+
- `policyGuard`: standalone coarse `CanMatchFn` for route-attempt checks.
|
|
8
|
+
- `resourcePolicyGuard`: standalone `CanActivateFn` for resolved-resource authorization checks.
|
|
8
9
|
- `AnarchitectsFeatureRegister`
|
|
9
10
|
- `AnarchitectsFeatureLogin`
|
|
10
11
|
- `AnarchitectsFeatureActivateUser`
|
|
@@ -20,7 +21,10 @@ Feature-level orchestration for Angular auth. It ships route guards plus standal
|
|
|
20
21
|
|
|
21
22
|
```ts
|
|
22
23
|
import { Routes } from '@angular/router';
|
|
23
|
-
import {
|
|
24
|
+
import {
|
|
25
|
+
policyGuard,
|
|
26
|
+
resourcePolicyGuard,
|
|
27
|
+
} from '@anarchitects/auth-angular/feature';
|
|
24
28
|
|
|
25
29
|
export const routes: Routes = [
|
|
26
30
|
{
|
|
@@ -29,10 +33,35 @@ export const routes: Routes = [
|
|
|
29
33
|
data: { action: 'manage', subject: 'admin-section' },
|
|
30
34
|
loadComponent: () => import('./admin.component').then((m) => m.AdminComponent),
|
|
31
35
|
},
|
|
36
|
+
{
|
|
37
|
+
path: 'posts/:postId/edit',
|
|
38
|
+
canMatch: [policyGuard],
|
|
39
|
+
canActivate: [resourcePolicyGuard],
|
|
40
|
+
data: {
|
|
41
|
+
action: 'update',
|
|
42
|
+
subject: 'Post',
|
|
43
|
+
resourceKey: 'post',
|
|
44
|
+
unauthorizedRedirectTo: '/posts',
|
|
45
|
+
},
|
|
46
|
+
loadComponent: () => import('./post-edit.component').then((m) => m.PostEditComponent),
|
|
47
|
+
},
|
|
32
48
|
];
|
|
33
49
|
```
|
|
34
50
|
|
|
35
|
-
|
|
51
|
+
`policyGuard` reads the hydrated RBAC rules and applies the shared coarse route matcher from `@anarchitects/auth-ts`. It intentionally does not decide ownership-sensitive access by itself.
|
|
52
|
+
|
|
53
|
+
Use `resourcePolicyGuard` when the route already has the concrete entity in `route.data`. It evaluates the hydrated CASL ability against that loaded resource and redirects away when access is denied.
|
|
54
|
+
|
|
55
|
+
This means a typical ownership-sensitive flow looks like this:
|
|
56
|
+
|
|
57
|
+
- `policyGuard` allows the route attempt for `{ action, subject }`
|
|
58
|
+
- `resourcePolicyGuard` checks the resolved entity when the route already has it
|
|
59
|
+
- UI elements such as edit buttons still use `canAccessResource(...)` or `canAccessResourceField(...)`
|
|
60
|
+
- the backend remains responsible for the final instance-level authorization decision
|
|
61
|
+
|
|
62
|
+
Do not treat `policyGuard` as a full `PolicyRule` mirror. It is intentionally coarse.
|
|
63
|
+
|
|
64
|
+
Ensure the state layer is explicitly provided in your app/route providers by wiring `...provideAuthState()` from `@anarchitects/auth-angular/state`.
|
|
36
65
|
|
|
37
66
|
### Token-driven actions
|
|
38
67
|
|
|
@@ -1,56 +1,11 @@
|
|
|
1
1
|
import { injectApiResourcePath } from '@anarchitects/auth-angular/config';
|
|
2
|
-
import {
|
|
2
|
+
import { parsePolicyRuleArrayDTO } from '@anarchitects/auth-ts/dtos';
|
|
3
|
+
import { HttpContextToken, HttpErrorResponse, HttpStatusCode, HttpBackend, HttpClient, HttpContext, withInterceptors } from '@angular/common/http';
|
|
3
4
|
import * as i0 from '@angular/core';
|
|
4
5
|
import { inject, Injectable } from '@angular/core';
|
|
5
|
-
import {
|
|
6
|
+
import { tap, finalize, shareReplay, catchError, throwError, switchMap, map } from 'rxjs';
|
|
6
7
|
import { Router } from '@angular/router';
|
|
7
|
-
import {
|
|
8
|
-
|
|
9
|
-
class AuthApi {
|
|
10
|
-
http = inject(HttpClient);
|
|
11
|
-
resourceUrl = `/api/${injectApiResourcePath()}`;
|
|
12
|
-
registerUser(dto) {
|
|
13
|
-
return this.http.post(`${this.resourceUrl}/register`, dto);
|
|
14
|
-
}
|
|
15
|
-
activateUser(dto) {
|
|
16
|
-
return this.http.patch(`${this.resourceUrl}/activate`, dto);
|
|
17
|
-
}
|
|
18
|
-
login(dto) {
|
|
19
|
-
return this.http.post(`${this.resourceUrl}/login`, dto);
|
|
20
|
-
}
|
|
21
|
-
logout(dto) {
|
|
22
|
-
return this.http.post(`${this.resourceUrl}/logout`, dto);
|
|
23
|
-
}
|
|
24
|
-
changePassword(userId, dto) {
|
|
25
|
-
return this.http.patch(`${this.resourceUrl}/change-password/${userId}`, dto);
|
|
26
|
-
}
|
|
27
|
-
forgotPassword(dto) {
|
|
28
|
-
return this.http.post(`${this.resourceUrl}/forgot-password`, dto);
|
|
29
|
-
}
|
|
30
|
-
resetPassword(dto) {
|
|
31
|
-
return this.http.post(`${this.resourceUrl}/reset-password`, dto);
|
|
32
|
-
}
|
|
33
|
-
verifyEmail(dto) {
|
|
34
|
-
return this.http.post(`${this.resourceUrl}/verify-email`, dto);
|
|
35
|
-
}
|
|
36
|
-
updateEmail(userId, dto) {
|
|
37
|
-
return this.http.patch(`${this.resourceUrl}/update-email/${userId}`, dto);
|
|
38
|
-
}
|
|
39
|
-
refreshTokens(userId, dto) {
|
|
40
|
-
return this.http.post(`${this.resourceUrl}/refresh-tokens/${userId}`, dto);
|
|
41
|
-
}
|
|
42
|
-
getLoggedInUserInfo() {
|
|
43
|
-
return this.http.get(`${this.resourceUrl}/me`);
|
|
44
|
-
}
|
|
45
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: AuthApi, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
46
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: AuthApi, providedIn: 'root' });
|
|
47
|
-
}
|
|
48
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: AuthApi, decorators: [{
|
|
49
|
-
type: Injectable,
|
|
50
|
-
args: [{
|
|
51
|
-
providedIn: 'root',
|
|
52
|
-
}]
|
|
53
|
-
}] });
|
|
8
|
+
import { jwtDecode } from 'jwt-decode';
|
|
54
9
|
|
|
55
10
|
const ACCESS_TOKEN_STORAGE_KEY = 'accessToken';
|
|
56
11
|
const REFRESH_TOKEN_STORAGE_KEY = 'refreshToken';
|
|
@@ -102,19 +57,8 @@ function resolveUserIdFromAccessToken(accessToken) {
|
|
|
102
57
|
}
|
|
103
58
|
}
|
|
104
59
|
|
|
105
|
-
const authBearerTokenInterceptor = (req, next) => {
|
|
106
|
-
const accessToken = getStoredToken(ACCESS_TOKEN_STORAGE_KEY);
|
|
107
|
-
if (!accessToken || req.headers.has('Authorization')) {
|
|
108
|
-
return next(req);
|
|
109
|
-
}
|
|
110
|
-
return next(req.clone({
|
|
111
|
-
setHeaders: {
|
|
112
|
-
Authorization: `Bearer ${accessToken}`,
|
|
113
|
-
},
|
|
114
|
-
}));
|
|
115
|
-
};
|
|
116
|
-
|
|
117
60
|
const AUTH_RETRY_ATTEMPTED = new HttpContextToken(() => false);
|
|
61
|
+
const SUPPRESS_AUTH_FAILURE_REDIRECT = new HttpContextToken(() => false);
|
|
118
62
|
const LOGIN_REDIRECT_PATH = '/login';
|
|
119
63
|
let refreshTokensRequest$ = null;
|
|
120
64
|
function isUnauthorizedError(error) {
|
|
@@ -145,6 +89,9 @@ function redirectToLogin(router) {
|
|
|
145
89
|
window.location.assign(LOGIN_REDIRECT_PATH);
|
|
146
90
|
}
|
|
147
91
|
}
|
|
92
|
+
function shouldRedirectToLogin(req) {
|
|
93
|
+
return !req.context.get(SUPPRESS_AUTH_FAILURE_REDIRECT);
|
|
94
|
+
}
|
|
148
95
|
function getRefreshTokensRequest(http, refreshUrl, refreshToken) {
|
|
149
96
|
if (!refreshTokensRequest$) {
|
|
150
97
|
refreshTokensRequest$ = http
|
|
@@ -169,7 +116,9 @@ const authErrorInterceptor = (req, next) => {
|
|
|
169
116
|
isAuthPublicEndpoint(req.url, authBaseUrl)) {
|
|
170
117
|
if (req.context.get(AUTH_RETRY_ATTEMPTED)) {
|
|
171
118
|
clearStoredTokens();
|
|
172
|
-
|
|
119
|
+
if (shouldRedirectToLogin(req)) {
|
|
120
|
+
redirectToLogin(router ?? null);
|
|
121
|
+
}
|
|
173
122
|
}
|
|
174
123
|
return throwError(() => error);
|
|
175
124
|
}
|
|
@@ -178,7 +127,9 @@ const authErrorInterceptor = (req, next) => {
|
|
|
178
127
|
const userId = resolveUserIdFromAccessToken(accessToken);
|
|
179
128
|
if (!refreshToken || !userId) {
|
|
180
129
|
clearStoredTokens();
|
|
181
|
-
|
|
130
|
+
if (shouldRedirectToLogin(req)) {
|
|
131
|
+
redirectToLogin(router ?? null);
|
|
132
|
+
}
|
|
182
133
|
return throwError(() => error);
|
|
183
134
|
}
|
|
184
135
|
const refreshUrl = `${authBaseUrl}/refresh-tokens/${userId}`;
|
|
@@ -192,12 +143,80 @@ const authErrorInterceptor = (req, next) => {
|
|
|
192
143
|
return next(retryRequest);
|
|
193
144
|
}), catchError((refreshError) => {
|
|
194
145
|
clearStoredTokens();
|
|
195
|
-
|
|
146
|
+
if (shouldRedirectToLogin(req)) {
|
|
147
|
+
redirectToLogin(router ?? null);
|
|
148
|
+
}
|
|
196
149
|
return throwError(() => refreshError);
|
|
197
150
|
}));
|
|
198
151
|
}));
|
|
199
152
|
};
|
|
200
153
|
|
|
154
|
+
class AuthApi {
|
|
155
|
+
http = inject(HttpClient);
|
|
156
|
+
resourceUrl = `/api/${injectApiResourcePath()}`;
|
|
157
|
+
registerUser(dto) {
|
|
158
|
+
return this.http.post(`${this.resourceUrl}/register`, dto);
|
|
159
|
+
}
|
|
160
|
+
activateUser(dto) {
|
|
161
|
+
return this.http.patch(`${this.resourceUrl}/activate`, dto);
|
|
162
|
+
}
|
|
163
|
+
login(dto) {
|
|
164
|
+
return this.http.post(`${this.resourceUrl}/login`, dto);
|
|
165
|
+
}
|
|
166
|
+
logout(dto) {
|
|
167
|
+
return this.http.post(`${this.resourceUrl}/logout`, dto);
|
|
168
|
+
}
|
|
169
|
+
changePassword(userId, dto) {
|
|
170
|
+
return this.http.patch(`${this.resourceUrl}/change-password/${userId}`, dto);
|
|
171
|
+
}
|
|
172
|
+
forgotPassword(dto) {
|
|
173
|
+
return this.http.post(`${this.resourceUrl}/forgot-password`, dto);
|
|
174
|
+
}
|
|
175
|
+
resetPassword(dto) {
|
|
176
|
+
return this.http.post(`${this.resourceUrl}/reset-password`, dto);
|
|
177
|
+
}
|
|
178
|
+
verifyEmail(dto) {
|
|
179
|
+
return this.http.post(`${this.resourceUrl}/verify-email`, dto);
|
|
180
|
+
}
|
|
181
|
+
updateEmail(userId, dto) {
|
|
182
|
+
return this.http.patch(`${this.resourceUrl}/update-email/${userId}`, dto);
|
|
183
|
+
}
|
|
184
|
+
refreshTokens(userId, dto) {
|
|
185
|
+
return this.http.post(`${this.resourceUrl}/refresh-tokens/${userId}`, dto);
|
|
186
|
+
}
|
|
187
|
+
getLoggedInUserInfo(options = {}) {
|
|
188
|
+
const context = options.suppressAuthFailureRedirect
|
|
189
|
+
? new HttpContext().set(SUPPRESS_AUTH_FAILURE_REDIRECT, true)
|
|
190
|
+
: undefined;
|
|
191
|
+
return this.http
|
|
192
|
+
.get(`${this.resourceUrl}/me`, context ? { context } : undefined)
|
|
193
|
+
.pipe(map(({ user, rbac }) => ({
|
|
194
|
+
user,
|
|
195
|
+
rbac: parsePolicyRuleArrayDTO(rbac, 'rbac'),
|
|
196
|
+
})));
|
|
197
|
+
}
|
|
198
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: AuthApi, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
199
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: AuthApi, providedIn: 'root' });
|
|
200
|
+
}
|
|
201
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: AuthApi, decorators: [{
|
|
202
|
+
type: Injectable,
|
|
203
|
+
args: [{
|
|
204
|
+
providedIn: 'root',
|
|
205
|
+
}]
|
|
206
|
+
}] });
|
|
207
|
+
|
|
208
|
+
const authBearerTokenInterceptor = (req, next) => {
|
|
209
|
+
const accessToken = getStoredToken(ACCESS_TOKEN_STORAGE_KEY);
|
|
210
|
+
if (!accessToken || req.headers.has('Authorization')) {
|
|
211
|
+
return next(req);
|
|
212
|
+
}
|
|
213
|
+
return next(req.clone({
|
|
214
|
+
setHeaders: {
|
|
215
|
+
Authorization: `Bearer ${accessToken}`,
|
|
216
|
+
},
|
|
217
|
+
}));
|
|
218
|
+
};
|
|
219
|
+
|
|
201
220
|
const AUTH_HTTP_INTERCEPTORS = [
|
|
202
221
|
authBearerTokenInterceptor,
|
|
203
222
|
authErrorInterceptor,
|
|
@@ -210,5 +229,5 @@ function withAuthHttpInterceptors() {
|
|
|
210
229
|
* Generated bundle index. Do not edit.
|
|
211
230
|
*/
|
|
212
231
|
|
|
213
|
-
export { AUTH_HTTP_INTERCEPTORS, AuthApi, authBearerTokenInterceptor, authErrorInterceptor, withAuthHttpInterceptors };
|
|
232
|
+
export { ACCESS_TOKEN_STORAGE_KEY, AUTH_HTTP_INTERCEPTORS, AuthApi, REFRESH_TOKEN_STORAGE_KEY, SUPPRESS_AUTH_FAILURE_REDIRECT, authBearerTokenInterceptor, authErrorInterceptor, clearStoredTokens, getStoredToken, resolveUserIdFromAccessToken, storeTokens, withAuthHttpInterceptors };
|
|
214
233
|
//# sourceMappingURL=anarchitects-auth-angular-data-access.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"anarchitects-auth-angular-data-access.mjs","sources":["../../../../../libs/auth/angular/data-access/src/auth-api.ts","../../../../../libs/auth/angular/data-access/src/interceptors/auth-token.utils.ts","../../../../../libs/auth/angular/data-access/src/interceptors/bearer-token.interceptor.ts","../../../../../libs/auth/angular/data-access/src/interceptors/auth-error.interceptor.ts","../../../../../libs/auth/angular/data-access/src/interceptors/index.ts","../../../../../libs/auth/angular/data-access/src/anarchitects-auth-angular-data-access.ts"],"sourcesContent":["import { injectApiResourcePath } from '@anarchitects/auth-angular/config';\nimport {\n ActivateUserRequestDTO,\n ChangePasswordRequestDTO,\n ForgotPasswordRequestDTO,\n LoginRequestDTO,\n LoginResponseDTO,\n LogoutRequestDTO,\n RefreshTokenRequestDTO,\n RegisterRequestDTO,\n RegisterResponseDTO,\n ResetPasswordRequestDTO,\n UpdateEmailRequestDTO,\n VerifyEmailRequestDTO,\n} from '@anarchitects/auth-ts/dtos';\nimport { PolicyRule, User } from '@anarchitects/auth-ts/models';\nimport { HttpClient } from '@angular/common/http';\nimport { inject, Injectable } from '@angular/core';\n\n@Injectable({\n providedIn: 'root',\n})\nexport class AuthApi {\n private readonly http = inject(HttpClient);\n private readonly resourceUrl = `/api/${injectApiResourcePath()}`;\n\n registerUser(dto: RegisterRequestDTO) {\n return this.http.post<RegisterResponseDTO>(\n `${this.resourceUrl}/register`,\n dto\n );\n }\n\n activateUser(dto: ActivateUserRequestDTO) {\n return this.http.patch<{ success: boolean }>(\n `${this.resourceUrl}/activate`,\n dto\n );\n }\n\n login(dto: LoginRequestDTO) {\n return this.http.post<LoginResponseDTO>(`${this.resourceUrl}/login`, dto);\n }\n\n logout(dto: LogoutRequestDTO) {\n return this.http.post<{ success: boolean }>(\n `${this.resourceUrl}/logout`,\n dto\n );\n }\n\n changePassword(userId: string, dto: ChangePasswordRequestDTO) {\n return this.http.patch<{ success: boolean }>(\n `${this.resourceUrl}/change-password/${userId}`,\n dto\n );\n }\n\n forgotPassword(dto: ForgotPasswordRequestDTO) {\n return this.http.post<{ success: boolean }>(\n `${this.resourceUrl}/forgot-password`,\n dto\n );\n }\n\n resetPassword(dto: ResetPasswordRequestDTO) {\n return this.http.post<{ success: boolean }>(\n `${this.resourceUrl}/reset-password`,\n dto\n );\n }\n\n verifyEmail(dto: VerifyEmailRequestDTO) {\n return this.http.post<{ success: boolean }>(\n `${this.resourceUrl}/verify-email`,\n dto\n );\n }\n\n updateEmail(userId: string, dto: UpdateEmailRequestDTO) {\n return this.http.patch<{ success: boolean }>(\n `${this.resourceUrl}/update-email/${userId}`,\n dto\n );\n }\n\n refreshTokens(userId: string, dto: RefreshTokenRequestDTO) {\n return this.http.post<LoginResponseDTO>(\n `${this.resourceUrl}/refresh-tokens/${userId}`,\n dto\n );\n }\n\n getLoggedInUserInfo() {\n return this.http.get<{ user: User; rbac: PolicyRule[] }>(\n `${this.resourceUrl}/me`\n );\n }\n}\n","import { jwtDecode } from 'jwt-decode';\n\nexport const ACCESS_TOKEN_STORAGE_KEY = 'accessToken';\nexport const REFRESH_TOKEN_STORAGE_KEY = 'refreshToken';\n\nexport type LoginTokens = {\n accessToken: string;\n refreshToken: string;\n};\n\nexport function getStoredToken(key: string): string | undefined {\n try {\n if (typeof localStorage === 'undefined') {\n return undefined;\n }\n\n return localStorage.getItem(key) ?? undefined;\n } catch {\n return undefined;\n }\n}\n\nexport function storeTokens(tokens: LoginTokens): void {\n try {\n if (typeof localStorage === 'undefined') {\n return;\n }\n\n localStorage.setItem(ACCESS_TOKEN_STORAGE_KEY, tokens.accessToken);\n localStorage.setItem(REFRESH_TOKEN_STORAGE_KEY, tokens.refreshToken);\n } catch {\n // noop: storage can be unavailable in non-browser environments\n }\n}\n\nexport function clearStoredTokens(): void {\n try {\n if (typeof localStorage === 'undefined') {\n return;\n }\n\n localStorage.removeItem(ACCESS_TOKEN_STORAGE_KEY);\n localStorage.removeItem(REFRESH_TOKEN_STORAGE_KEY);\n } catch {\n // noop: storage can be unavailable in non-browser environments\n }\n}\n\nexport function resolveUserIdFromAccessToken(\n accessToken: string | undefined\n): string | undefined {\n if (!accessToken) {\n return undefined;\n }\n\n try {\n const decoded = jwtDecode<{ sub?: string }>(accessToken);\n return decoded.sub;\n } catch {\n return undefined;\n }\n}\n","import { HttpInterceptorFn } from '@angular/common/http';\nimport { ACCESS_TOKEN_STORAGE_KEY, getStoredToken } from './auth-token.utils';\n\nexport const authBearerTokenInterceptor: HttpInterceptorFn = (req, next) => {\n const accessToken = getStoredToken(ACCESS_TOKEN_STORAGE_KEY);\n\n if (!accessToken || req.headers.has('Authorization')) {\n return next(req);\n }\n\n return next(\n req.clone({\n setHeaders: {\n Authorization: `Bearer ${accessToken}`,\n },\n })\n );\n};\n","import {\n HttpBackend,\n HttpClient,\n HttpContextToken,\n HttpErrorResponse,\n HttpInterceptorFn,\n HttpStatusCode,\n} from '@angular/common/http';\nimport { inject } from '@angular/core';\nimport { injectApiResourcePath } from '@anarchitects/auth-angular/config';\nimport { LoginResponseDTO } from '@anarchitects/auth-ts/dtos';\nimport { Router } from '@angular/router';\nimport {\n catchError,\n finalize,\n Observable,\n shareReplay,\n switchMap,\n tap,\n throwError,\n} from 'rxjs';\nimport {\n ACCESS_TOKEN_STORAGE_KEY,\n REFRESH_TOKEN_STORAGE_KEY,\n clearStoredTokens,\n getStoredToken,\n resolveUserIdFromAccessToken,\n storeTokens,\n} from './auth-token.utils';\n\nconst AUTH_RETRY_ATTEMPTED = new HttpContextToken<boolean>(() => false);\nconst LOGIN_REDIRECT_PATH = '/login';\n\nlet refreshTokensRequest$: Observable<LoginResponseDTO> | null = null;\n\nfunction isUnauthorizedError(error: unknown): error is HttpErrorResponse {\n return (\n error instanceof HttpErrorResponse &&\n (error.status === HttpStatusCode.Unauthorized ||\n error.status === HttpStatusCode.Forbidden)\n );\n}\n\nfunction isAuthPublicEndpoint(url: string, authBaseUrl: string): boolean {\n const publicEndpoints = [\n '/login',\n '/register',\n '/activate',\n '/forgot-password',\n '/reset-password',\n '/verify-email',\n ];\n\n return publicEndpoints.some((endpoint) =>\n url.includes(`${authBaseUrl}${endpoint}`)\n );\n}\n\nfunction isRefreshEndpoint(url: string, authBaseUrl: string): boolean {\n return url.includes(`${authBaseUrl}/refresh-tokens/`);\n}\n\nfunction redirectToLogin(router: Router | null): void {\n if (router) {\n void router.navigateByUrl(LOGIN_REDIRECT_PATH);\n return;\n }\n\n if (typeof window !== 'undefined') {\n window.location.assign(LOGIN_REDIRECT_PATH);\n }\n}\n\nfunction getRefreshTokensRequest(\n http: HttpClient,\n refreshUrl: string,\n refreshToken: string\n): Observable<LoginResponseDTO> {\n if (!refreshTokensRequest$) {\n refreshTokensRequest$ = http\n .post<LoginResponseDTO>(refreshUrl, { refreshToken })\n .pipe(\n tap((tokens) => storeTokens(tokens)),\n finalize(() => {\n refreshTokensRequest$ = null;\n }),\n shareReplay(1)\n );\n }\n\n return refreshTokensRequest$;\n}\n\nexport const authErrorInterceptor: HttpInterceptorFn = (req, next) => {\n const authBaseUrl = `/api/${injectApiResourcePath()}`;\n const backend = inject(HttpBackend);\n const router = inject(Router, { optional: true });\n const rawHttp = new HttpClient(backend);\n\n return next(req).pipe(\n catchError((error) => {\n if (!isUnauthorizedError(error)) {\n return throwError(() => error);\n }\n\n if (\n req.context.get(AUTH_RETRY_ATTEMPTED) ||\n isRefreshEndpoint(req.url, authBaseUrl) ||\n isAuthPublicEndpoint(req.url, authBaseUrl)\n ) {\n if (req.context.get(AUTH_RETRY_ATTEMPTED)) {\n clearStoredTokens();\n redirectToLogin(router ?? null);\n }\n\n return throwError(() => error);\n }\n\n const refreshToken = getStoredToken(REFRESH_TOKEN_STORAGE_KEY);\n const accessToken = getStoredToken(ACCESS_TOKEN_STORAGE_KEY);\n const userId = resolveUserIdFromAccessToken(accessToken);\n\n if (!refreshToken || !userId) {\n clearStoredTokens();\n redirectToLogin(router ?? null);\n return throwError(() => error);\n }\n\n const refreshUrl = `${authBaseUrl}/refresh-tokens/${userId}`;\n\n return getRefreshTokensRequest(rawHttp, refreshUrl, refreshToken).pipe(\n switchMap(({ accessToken: nextAccessToken }) => {\n const retryRequest = req.clone({\n context: req.context.set(AUTH_RETRY_ATTEMPTED, true),\n setHeaders: {\n Authorization: `Bearer ${nextAccessToken}`,\n },\n });\n\n return next(retryRequest);\n }),\n catchError((refreshError) => {\n clearStoredTokens();\n redirectToLogin(router ?? null);\n return throwError(() => refreshError);\n })\n );\n })\n );\n};\n","import { HttpInterceptorFn, withInterceptors } from '@angular/common/http';\nimport { authBearerTokenInterceptor } from './bearer-token.interceptor';\nimport { authErrorInterceptor } from './auth-error.interceptor';\n\nexport const AUTH_HTTP_INTERCEPTORS: HttpInterceptorFn[] = [\n authBearerTokenInterceptor,\n authErrorInterceptor,\n];\n\nexport function withAuthHttpInterceptors() {\n return withInterceptors(AUTH_HTTP_INTERCEPTORS);\n}\n\nexport * from './bearer-token.interceptor';\nexport * from './auth-error.interceptor';\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":";;;;;;;;MAsBa,OAAO,CAAA;AACD,IAAA,IAAI,GAAG,MAAM,CAAC,UAAU,CAAC;AACzB,IAAA,WAAW,GAAG,CAAA,KAAA,EAAQ,qBAAqB,EAAE,EAAE;AAEhE,IAAA,YAAY,CAAC,GAAuB,EAAA;AAClC,QAAA,OAAO,IAAI,CAAC,IAAI,CAAC,IAAI,CACnB,CAAA,EAAG,IAAI,CAAC,WAAW,CAAA,SAAA,CAAW,EAC9B,GAAG,CACJ;IACH;AAEA,IAAA,YAAY,CAAC,GAA2B,EAAA;AACtC,QAAA,OAAO,IAAI,CAAC,IAAI,CAAC,KAAK,CACpB,CAAA,EAAG,IAAI,CAAC,WAAW,CAAA,SAAA,CAAW,EAC9B,GAAG,CACJ;IACH;AAEA,IAAA,KAAK,CAAC,GAAoB,EAAA;AACxB,QAAA,OAAO,IAAI,CAAC,IAAI,CAAC,IAAI,CAAmB,CAAA,EAAG,IAAI,CAAC,WAAW,CAAA,MAAA,CAAQ,EAAE,GAAG,CAAC;IAC3E;AAEA,IAAA,MAAM,CAAC,GAAqB,EAAA;AAC1B,QAAA,OAAO,IAAI,CAAC,IAAI,CAAC,IAAI,CACnB,CAAA,EAAG,IAAI,CAAC,WAAW,CAAA,OAAA,CAAS,EAC5B,GAAG,CACJ;IACH;IAEA,cAAc,CAAC,MAAc,EAAE,GAA6B,EAAA;AAC1D,QAAA,OAAO,IAAI,CAAC,IAAI,CAAC,KAAK,CACpB,CAAA,EAAG,IAAI,CAAC,WAAW,oBAAoB,MAAM,CAAA,CAAE,EAC/C,GAAG,CACJ;IACH;AAEA,IAAA,cAAc,CAAC,GAA6B,EAAA;AAC1C,QAAA,OAAO,IAAI,CAAC,IAAI,CAAC,IAAI,CACnB,CAAA,EAAG,IAAI,CAAC,WAAW,CAAA,gBAAA,CAAkB,EACrC,GAAG,CACJ;IACH;AAEA,IAAA,aAAa,CAAC,GAA4B,EAAA;AACxC,QAAA,OAAO,IAAI,CAAC,IAAI,CAAC,IAAI,CACnB,CAAA,EAAG,IAAI,CAAC,WAAW,CAAA,eAAA,CAAiB,EACpC,GAAG,CACJ;IACH;AAEA,IAAA,WAAW,CAAC,GAA0B,EAAA;AACpC,QAAA,OAAO,IAAI,CAAC,IAAI,CAAC,IAAI,CACnB,CAAA,EAAG,IAAI,CAAC,WAAW,CAAA,aAAA,CAAe,EAClC,GAAG,CACJ;IACH;IAEA,WAAW,CAAC,MAAc,EAAE,GAA0B,EAAA;AACpD,QAAA,OAAO,IAAI,CAAC,IAAI,CAAC,KAAK,CACpB,CAAA,EAAG,IAAI,CAAC,WAAW,iBAAiB,MAAM,CAAA,CAAE,EAC5C,GAAG,CACJ;IACH;IAEA,aAAa,CAAC,MAAc,EAAE,GAA2B,EAAA;AACvD,QAAA,OAAO,IAAI,CAAC,IAAI,CAAC,IAAI,CACnB,CAAA,EAAG,IAAI,CAAC,WAAW,mBAAmB,MAAM,CAAA,CAAE,EAC9C,GAAG,CACJ;IACH;IAEA,mBAAmB,GAAA;AACjB,QAAA,OAAO,IAAI,CAAC,IAAI,CAAC,GAAG,CAClB,CAAA,EAAG,IAAI,CAAC,WAAW,CAAA,GAAA,CAAK,CACzB;IACH;uGA3EW,OAAO,EAAA,IAAA,EAAA,EAAA,EAAA,MAAA,EAAA,EAAA,CAAA,eAAA,CAAA,UAAA,EAAA,CAAA;AAAP,IAAA,OAAA,KAAA,GAAA,EAAA,CAAA,qBAAA,CAAA,EAAA,UAAA,EAAA,QAAA,EAAA,OAAA,EAAA,QAAA,EAAA,QAAA,EAAA,EAAA,EAAA,IAAA,EAAA,OAAO,cAFN,MAAM,EAAA,CAAA;;2FAEP,OAAO,EAAA,UAAA,EAAA,CAAA;kBAHnB,UAAU;AAAC,YAAA,IAAA,EAAA,CAAA;AACV,oBAAA,UAAU,EAAE,MAAM;AACnB,iBAAA;;;ACnBM,MAAM,wBAAwB,GAAG,aAAa;AAC9C,MAAM,yBAAyB,GAAG,cAAc;AAOjD,SAAU,cAAc,CAAC,GAAW,EAAA;AACxC,IAAA,IAAI;AACF,QAAA,IAAI,OAAO,YAAY,KAAK,WAAW,EAAE;AACvC,YAAA,OAAO,SAAS;QAClB;QAEA,OAAO,YAAY,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,SAAS;IAC/C;AAAE,IAAA,MAAM;AACN,QAAA,OAAO,SAAS;IAClB;AACF;AAEM,SAAU,WAAW,CAAC,MAAmB,EAAA;AAC7C,IAAA,IAAI;AACF,QAAA,IAAI,OAAO,YAAY,KAAK,WAAW,EAAE;YACvC;QACF;QAEA,YAAY,CAAC,OAAO,CAAC,wBAAwB,EAAE,MAAM,CAAC,WAAW,CAAC;QAClE,YAAY,CAAC,OAAO,CAAC,yBAAyB,EAAE,MAAM,CAAC,YAAY,CAAC;IACtE;AAAE,IAAA,MAAM;;IAER;AACF;SAEgB,iBAAiB,GAAA;AAC/B,IAAA,IAAI;AACF,QAAA,IAAI,OAAO,YAAY,KAAK,WAAW,EAAE;YACvC;QACF;AAEA,QAAA,YAAY,CAAC,UAAU,CAAC,wBAAwB,CAAC;AACjD,QAAA,YAAY,CAAC,UAAU,CAAC,yBAAyB,CAAC;IACpD;AAAE,IAAA,MAAM;;IAER;AACF;AAEM,SAAU,4BAA4B,CAC1C,WAA+B,EAAA;IAE/B,IAAI,CAAC,WAAW,EAAE;AAChB,QAAA,OAAO,SAAS;IAClB;AAEA,IAAA,IAAI;AACF,QAAA,MAAM,OAAO,GAAG,SAAS,CAAmB,WAAW,CAAC;QACxD,OAAO,OAAO,CAAC,GAAG;IACpB;AAAE,IAAA,MAAM;AACN,QAAA,OAAO,SAAS;IAClB;AACF;;MC1Da,0BAA0B,GAAsB,CAAC,GAAG,EAAE,IAAI,KAAI;AACzE,IAAA,MAAM,WAAW,GAAG,cAAc,CAAC,wBAAwB,CAAC;AAE5D,IAAA,IAAI,CAAC,WAAW,IAAI,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,eAAe,CAAC,EAAE;AACpD,QAAA,OAAO,IAAI,CAAC,GAAG,CAAC;IAClB;AAEA,IAAA,OAAO,IAAI,CACT,GAAG,CAAC,KAAK,CAAC;AACR,QAAA,UAAU,EAAE;YACV,aAAa,EAAE,CAAA,OAAA,EAAU,WAAW,CAAA,CAAE;AACvC,SAAA;AACF,KAAA,CAAC,CACH;AACH;;ACaA,MAAM,oBAAoB,GAAG,IAAI,gBAAgB,CAAU,MAAM,KAAK,CAAC;AACvE,MAAM,mBAAmB,GAAG,QAAQ;AAEpC,IAAI,qBAAqB,GAAwC,IAAI;AAErE,SAAS,mBAAmB,CAAC,KAAc,EAAA;IACzC,QACE,KAAK,YAAY,iBAAiB;AAClC,SAAC,KAAK,CAAC,MAAM,KAAK,cAAc,CAAC,YAAY;YAC3C,KAAK,CAAC,MAAM,KAAK,cAAc,CAAC,SAAS,CAAC;AAEhD;AAEA,SAAS,oBAAoB,CAAC,GAAW,EAAE,WAAmB,EAAA;AAC5D,IAAA,MAAM,eAAe,GAAG;QACtB,QAAQ;QACR,WAAW;QACX,WAAW;QACX,kBAAkB;QAClB,iBAAiB;QACjB,eAAe;KAChB;IAED,OAAO,eAAe,CAAC,IAAI,CAAC,CAAC,QAAQ,KACnC,GAAG,CAAC,QAAQ,CAAC,GAAG,WAAW,CAAA,EAAG,QAAQ,CAAA,CAAE,CAAC,CAC1C;AACH;AAEA,SAAS,iBAAiB,CAAC,GAAW,EAAE,WAAmB,EAAA;IACzD,OAAO,GAAG,CAAC,QAAQ,CAAC,GAAG,WAAW,CAAA,gBAAA,CAAkB,CAAC;AACvD;AAEA,SAAS,eAAe,CAAC,MAAqB,EAAA;IAC5C,IAAI,MAAM,EAAE;AACV,QAAA,KAAK,MAAM,CAAC,aAAa,CAAC,mBAAmB,CAAC;QAC9C;IACF;AAEA,IAAA,IAAI,OAAO,MAAM,KAAK,WAAW,EAAE;AACjC,QAAA,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,mBAAmB,CAAC;IAC7C;AACF;AAEA,SAAS,uBAAuB,CAC9B,IAAgB,EAChB,UAAkB,EAClB,YAAoB,EAAA;IAEpB,IAAI,CAAC,qBAAqB,EAAE;AAC1B,QAAA,qBAAqB,GAAG;AACrB,aAAA,IAAI,CAAmB,UAAU,EAAE,EAAE,YAAY,EAAE;AACnD,aAAA,IAAI,CACH,GAAG,CAAC,CAAC,MAAM,KAAK,WAAW,CAAC,MAAM,CAAC,CAAC,EACpC,QAAQ,CAAC,MAAK;YACZ,qBAAqB,GAAG,IAAI;AAC9B,QAAA,CAAC,CAAC,EACF,WAAW,CAAC,CAAC,CAAC,CACf;IACL;AAEA,IAAA,OAAO,qBAAqB;AAC9B;MAEa,oBAAoB,GAAsB,CAAC,GAAG,EAAE,IAAI,KAAI;AACnE,IAAA,MAAM,WAAW,GAAG,CAAA,KAAA,EAAQ,qBAAqB,EAAE,EAAE;AACrD,IAAA,MAAM,OAAO,GAAG,MAAM,CAAC,WAAW,CAAC;AACnC,IAAA,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;AACjD,IAAA,MAAM,OAAO,GAAG,IAAI,UAAU,CAAC,OAAO,CAAC;AAEvC,IAAA,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CACnB,UAAU,CAAC,CAAC,KAAK,KAAI;AACnB,QAAA,IAAI,CAAC,mBAAmB,CAAC,KAAK,CAAC,EAAE;AAC/B,YAAA,OAAO,UAAU,CAAC,MAAM,KAAK,CAAC;QAChC;AAEA,QAAA,IACE,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,oBAAoB,CAAC;AACrC,YAAA,iBAAiB,CAAC,GAAG,CAAC,GAAG,EAAE,WAAW,CAAC;YACvC,oBAAoB,CAAC,GAAG,CAAC,GAAG,EAAE,WAAW,CAAC,EAC1C;YACA,IAAI,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,oBAAoB,CAAC,EAAE;AACzC,gBAAA,iBAAiB,EAAE;AACnB,gBAAA,eAAe,CAAC,MAAM,IAAI,IAAI,CAAC;YACjC;AAEA,YAAA,OAAO,UAAU,CAAC,MAAM,KAAK,CAAC;QAChC;AAEA,QAAA,MAAM,YAAY,GAAG,cAAc,CAAC,yBAAyB,CAAC;AAC9D,QAAA,MAAM,WAAW,GAAG,cAAc,CAAC,wBAAwB,CAAC;AAC5D,QAAA,MAAM,MAAM,GAAG,4BAA4B,CAAC,WAAW,CAAC;AAExD,QAAA,IAAI,CAAC,YAAY,IAAI,CAAC,MAAM,EAAE;AAC5B,YAAA,iBAAiB,EAAE;AACnB,YAAA,eAAe,CAAC,MAAM,IAAI,IAAI,CAAC;AAC/B,YAAA,OAAO,UAAU,CAAC,MAAM,KAAK,CAAC;QAChC;AAEA,QAAA,MAAM,UAAU,GAAG,CAAA,EAAG,WAAW,CAAA,gBAAA,EAAmB,MAAM,EAAE;QAE5D,OAAO,uBAAuB,CAAC,OAAO,EAAE,UAAU,EAAE,YAAY,CAAC,CAAC,IAAI,CACpE,SAAS,CAAC,CAAC,EAAE,WAAW,EAAE,eAAe,EAAE,KAAI;AAC7C,YAAA,MAAM,YAAY,GAAG,GAAG,CAAC,KAAK,CAAC;gBAC7B,OAAO,EAAE,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,oBAAoB,EAAE,IAAI,CAAC;AACpD,gBAAA,UAAU,EAAE;oBACV,aAAa,EAAE,CAAA,OAAA,EAAU,eAAe,CAAA,CAAE;AAC3C,iBAAA;AACF,aAAA,CAAC;AAEF,YAAA,OAAO,IAAI,CAAC,YAAY,CAAC;AAC3B,QAAA,CAAC,CAAC,EACF,UAAU,CAAC,CAAC,YAAY,KAAI;AAC1B,YAAA,iBAAiB,EAAE;AACnB,YAAA,eAAe,CAAC,MAAM,IAAI,IAAI,CAAC;AAC/B,YAAA,OAAO,UAAU,CAAC,MAAM,YAAY,CAAC;QACvC,CAAC,CAAC,CACH;IACH,CAAC,CAAC,CACH;AACH;;ACjJO,MAAM,sBAAsB,GAAwB;IACzD,0BAA0B;IAC1B,oBAAoB;;SAGN,wBAAwB,GAAA;AACtC,IAAA,OAAO,gBAAgB,CAAC,sBAAsB,CAAC;AACjD;;ACXA;;AAEG;;;;"}
|
|
1
|
+
{"version":3,"file":"anarchitects-auth-angular-data-access.mjs","sources":["../../../../../libs/auth/angular/data-access/src/interceptors/auth-token.utils.ts","../../../../../libs/auth/angular/data-access/src/interceptors/auth-error.interceptor.ts","../../../../../libs/auth/angular/data-access/src/auth-api.ts","../../../../../libs/auth/angular/data-access/src/interceptors/bearer-token.interceptor.ts","../../../../../libs/auth/angular/data-access/src/interceptors/index.ts","../../../../../libs/auth/angular/data-access/src/anarchitects-auth-angular-data-access.ts"],"sourcesContent":["import { jwtDecode } from 'jwt-decode';\n\nexport const ACCESS_TOKEN_STORAGE_KEY = 'accessToken';\nexport const REFRESH_TOKEN_STORAGE_KEY = 'refreshToken';\n\nexport type LoginTokens = {\n accessToken: string;\n refreshToken: string;\n};\n\nexport function getStoredToken(key: string): string | undefined {\n try {\n if (typeof localStorage === 'undefined') {\n return undefined;\n }\n\n return localStorage.getItem(key) ?? undefined;\n } catch {\n return undefined;\n }\n}\n\nexport function storeTokens(tokens: LoginTokens): void {\n try {\n if (typeof localStorage === 'undefined') {\n return;\n }\n\n localStorage.setItem(ACCESS_TOKEN_STORAGE_KEY, tokens.accessToken);\n localStorage.setItem(REFRESH_TOKEN_STORAGE_KEY, tokens.refreshToken);\n } catch {\n // noop: storage can be unavailable in non-browser environments\n }\n}\n\nexport function clearStoredTokens(): void {\n try {\n if (typeof localStorage === 'undefined') {\n return;\n }\n\n localStorage.removeItem(ACCESS_TOKEN_STORAGE_KEY);\n localStorage.removeItem(REFRESH_TOKEN_STORAGE_KEY);\n } catch {\n // noop: storage can be unavailable in non-browser environments\n }\n}\n\nexport function resolveUserIdFromAccessToken(\n accessToken: string | undefined\n): string | undefined {\n if (!accessToken) {\n return undefined;\n }\n\n try {\n const decoded = jwtDecode<{ sub?: string }>(accessToken);\n return decoded.sub;\n } catch {\n return undefined;\n }\n}\n","import {\n HttpBackend,\n HttpClient,\n HttpContextToken,\n HttpErrorResponse,\n HttpInterceptorFn,\n HttpStatusCode,\n} from '@angular/common/http';\nimport { inject } from '@angular/core';\nimport { injectApiResourcePath } from '@anarchitects/auth-angular/config';\nimport { LoginResponseDTO } from '@anarchitects/auth-ts/dtos';\nimport { Router } from '@angular/router';\nimport {\n catchError,\n finalize,\n Observable,\n shareReplay,\n switchMap,\n tap,\n throwError,\n} from 'rxjs';\nimport {\n ACCESS_TOKEN_STORAGE_KEY,\n REFRESH_TOKEN_STORAGE_KEY,\n clearStoredTokens,\n getStoredToken,\n resolveUserIdFromAccessToken,\n storeTokens,\n} from './auth-token.utils';\n\nconst AUTH_RETRY_ATTEMPTED = new HttpContextToken<boolean>(() => false);\nexport const SUPPRESS_AUTH_FAILURE_REDIRECT = new HttpContextToken<boolean>(\n () => false,\n);\nconst LOGIN_REDIRECT_PATH = '/login';\n\nlet refreshTokensRequest$: Observable<LoginResponseDTO> | null = null;\n\nfunction isUnauthorizedError(error: unknown): error is HttpErrorResponse {\n return (\n error instanceof HttpErrorResponse &&\n (error.status === HttpStatusCode.Unauthorized ||\n error.status === HttpStatusCode.Forbidden)\n );\n}\n\nfunction isAuthPublicEndpoint(url: string, authBaseUrl: string): boolean {\n const publicEndpoints = [\n '/login',\n '/register',\n '/activate',\n '/forgot-password',\n '/reset-password',\n '/verify-email',\n ];\n\n return publicEndpoints.some((endpoint) =>\n url.includes(`${authBaseUrl}${endpoint}`)\n );\n}\n\nfunction isRefreshEndpoint(url: string, authBaseUrl: string): boolean {\n return url.includes(`${authBaseUrl}/refresh-tokens/`);\n}\n\nfunction redirectToLogin(router: Router | null): void {\n if (router) {\n void router.navigateByUrl(LOGIN_REDIRECT_PATH);\n return;\n }\n\n if (typeof window !== 'undefined') {\n window.location.assign(LOGIN_REDIRECT_PATH);\n }\n}\n\nfunction shouldRedirectToLogin(req: Parameters<HttpInterceptorFn>[0]): boolean {\n return !req.context.get(SUPPRESS_AUTH_FAILURE_REDIRECT);\n}\n\nfunction getRefreshTokensRequest(\n http: HttpClient,\n refreshUrl: string,\n refreshToken: string\n): Observable<LoginResponseDTO> {\n if (!refreshTokensRequest$) {\n refreshTokensRequest$ = http\n .post<LoginResponseDTO>(refreshUrl, { refreshToken })\n .pipe(\n tap((tokens) => storeTokens(tokens)),\n finalize(() => {\n refreshTokensRequest$ = null;\n }),\n shareReplay(1)\n );\n }\n\n return refreshTokensRequest$;\n}\n\nexport const authErrorInterceptor: HttpInterceptorFn = (req, next) => {\n const authBaseUrl = `/api/${injectApiResourcePath()}`;\n const backend = inject(HttpBackend);\n const router = inject(Router, { optional: true });\n const rawHttp = new HttpClient(backend);\n\n return next(req).pipe(\n catchError((error) => {\n if (!isUnauthorizedError(error)) {\n return throwError(() => error);\n }\n\n if (\n req.context.get(AUTH_RETRY_ATTEMPTED) ||\n isRefreshEndpoint(req.url, authBaseUrl) ||\n isAuthPublicEndpoint(req.url, authBaseUrl)\n ) {\n if (req.context.get(AUTH_RETRY_ATTEMPTED)) {\n clearStoredTokens();\n if (shouldRedirectToLogin(req)) {\n redirectToLogin(router ?? null);\n }\n }\n\n return throwError(() => error);\n }\n\n const refreshToken = getStoredToken(REFRESH_TOKEN_STORAGE_KEY);\n const accessToken = getStoredToken(ACCESS_TOKEN_STORAGE_KEY);\n const userId = resolveUserIdFromAccessToken(accessToken);\n\n if (!refreshToken || !userId) {\n clearStoredTokens();\n if (shouldRedirectToLogin(req)) {\n redirectToLogin(router ?? null);\n }\n return throwError(() => error);\n }\n\n const refreshUrl = `${authBaseUrl}/refresh-tokens/${userId}`;\n\n return getRefreshTokensRequest(rawHttp, refreshUrl, refreshToken).pipe(\n switchMap(({ accessToken: nextAccessToken }) => {\n const retryRequest = req.clone({\n context: req.context.set(AUTH_RETRY_ATTEMPTED, true),\n setHeaders: {\n Authorization: `Bearer ${nextAccessToken}`,\n },\n });\n\n return next(retryRequest);\n }),\n catchError((refreshError) => {\n clearStoredTokens();\n if (shouldRedirectToLogin(req)) {\n redirectToLogin(router ?? null);\n }\n return throwError(() => refreshError);\n })\n );\n })\n );\n};\n","import { injectApiResourcePath } from '@anarchitects/auth-angular/config';\nimport {\n ActivateUserRequestDTO,\n ChangePasswordRequestDTO,\n ForgotPasswordRequestDTO,\n LoginRequestDTO,\n LoginResponseDTO,\n LogoutRequestDTO,\n RefreshTokenRequestDTO,\n RegisterRequestDTO,\n RegisterResponseDTO,\n ResetPasswordRequestDTO,\n UpdateEmailRequestDTO,\n VerifyEmailRequestDTO,\n parsePolicyRuleArrayDTO,\n} from '@anarchitects/auth-ts/dtos';\nimport { PolicyRule, User } from '@anarchitects/auth-ts/models';\nimport { HttpClient, HttpContext } from '@angular/common/http';\nimport { inject, Injectable } from '@angular/core';\nimport { map } from 'rxjs';\nimport { SUPPRESS_AUTH_FAILURE_REDIRECT } from './interceptors/auth-error.interceptor';\n\nexport type AuthApiRequestOptions = {\n suppressAuthFailureRedirect?: boolean;\n};\n\n@Injectable({\n providedIn: 'root',\n})\nexport class AuthApi {\n private readonly http = inject(HttpClient);\n private readonly resourceUrl = `/api/${injectApiResourcePath()}`;\n\n registerUser(dto: RegisterRequestDTO) {\n return this.http.post<RegisterResponseDTO>(\n `${this.resourceUrl}/register`,\n dto,\n );\n }\n\n activateUser(dto: ActivateUserRequestDTO) {\n return this.http.patch<{ success: boolean }>(\n `${this.resourceUrl}/activate`,\n dto,\n );\n }\n\n login(dto: LoginRequestDTO) {\n return this.http.post<LoginResponseDTO>(`${this.resourceUrl}/login`, dto);\n }\n\n logout(dto: LogoutRequestDTO) {\n return this.http.post<{ success: boolean }>(\n `${this.resourceUrl}/logout`,\n dto,\n );\n }\n\n changePassword(userId: string, dto: ChangePasswordRequestDTO) {\n return this.http.patch<{ success: boolean }>(\n `${this.resourceUrl}/change-password/${userId}`,\n dto,\n );\n }\n\n forgotPassword(dto: ForgotPasswordRequestDTO) {\n return this.http.post<{ success: boolean }>(\n `${this.resourceUrl}/forgot-password`,\n dto,\n );\n }\n\n resetPassword(dto: ResetPasswordRequestDTO) {\n return this.http.post<{ success: boolean }>(\n `${this.resourceUrl}/reset-password`,\n dto,\n );\n }\n\n verifyEmail(dto: VerifyEmailRequestDTO) {\n return this.http.post<{ success: boolean }>(\n `${this.resourceUrl}/verify-email`,\n dto,\n );\n }\n\n updateEmail(userId: string, dto: UpdateEmailRequestDTO) {\n return this.http.patch<{ success: boolean }>(\n `${this.resourceUrl}/update-email/${userId}`,\n dto,\n );\n }\n\n refreshTokens(userId: string, dto: RefreshTokenRequestDTO) {\n return this.http.post<LoginResponseDTO>(\n `${this.resourceUrl}/refresh-tokens/${userId}`,\n dto,\n );\n }\n\n getLoggedInUserInfo(options: AuthApiRequestOptions = {}) {\n const context = options.suppressAuthFailureRedirect\n ? new HttpContext().set(SUPPRESS_AUTH_FAILURE_REDIRECT, true)\n : undefined;\n\n return this.http\n .get<{ user: User; rbac: unknown }>(\n `${this.resourceUrl}/me`,\n context ? { context } : undefined,\n )\n .pipe(\n map(({ user, rbac }) => ({\n user,\n rbac: parsePolicyRuleArrayDTO(rbac, 'rbac') as PolicyRule[],\n })),\n );\n }\n}\n","import { HttpInterceptorFn } from '@angular/common/http';\nimport { ACCESS_TOKEN_STORAGE_KEY, getStoredToken } from './auth-token.utils';\n\nexport const authBearerTokenInterceptor: HttpInterceptorFn = (req, next) => {\n const accessToken = getStoredToken(ACCESS_TOKEN_STORAGE_KEY);\n\n if (!accessToken || req.headers.has('Authorization')) {\n return next(req);\n }\n\n return next(\n req.clone({\n setHeaders: {\n Authorization: `Bearer ${accessToken}`,\n },\n })\n );\n};\n","import { HttpInterceptorFn, withInterceptors } from '@angular/common/http';\nimport { authBearerTokenInterceptor } from './bearer-token.interceptor';\nimport { authErrorInterceptor } from './auth-error.interceptor';\n\nexport const AUTH_HTTP_INTERCEPTORS: HttpInterceptorFn[] = [\n authBearerTokenInterceptor,\n authErrorInterceptor,\n];\n\nexport function withAuthHttpInterceptors() {\n return withInterceptors(AUTH_HTTP_INTERCEPTORS);\n}\n\nexport * from './bearer-token.interceptor';\nexport * from './auth-error.interceptor';\nexport * from './auth-token.utils';\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}
|
|
@@ -3,6 +3,11 @@ import { AnarchitectsAuthUiRegisterForm, AnarchitectsAuthUiActivateUserForm, Ana
|
|
|
3
3
|
import * as i0 from '@angular/core';
|
|
4
4
|
import { inject, input, ChangeDetectionStrategy, Component } from '@angular/core';
|
|
5
5
|
import { jwtDecode } from 'jwt-decode';
|
|
6
|
+
import { canAttemptRoutePolicy } from '@anarchitects/auth-ts/models';
|
|
7
|
+
import { toObservable } from '@angular/core/rxjs-interop';
|
|
8
|
+
import { of, filter, take, map } from 'rxjs';
|
|
9
|
+
import { canAccessResource } from '@anarchitects/auth-angular/util';
|
|
10
|
+
import { Router } from '@angular/router';
|
|
6
11
|
|
|
7
12
|
class AnarchitectsFeatureRegister {
|
|
8
13
|
authStore = inject(AuthStore);
|
|
@@ -232,14 +237,64 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImpor
|
|
|
232
237
|
args: [{ selector: 'anarchitects-auth-feature-refresh-tokens', imports: [AnarchitectsAuthUiRefreshTokensForm], changeDetection: ChangeDetectionStrategy.OnPush, template: "<anarchitects-auth-ui-refresh-tokens-form\n [layout]=\"layout()\"\n [layoutOptions]=\"layoutOptions()\"\n (submitted)=\"submitForm($event)\"\n>\n <ng-content select=\"ng-template[anxTemplate]\"></ng-content>\n <ng-content select=\"[anxSlot]\"></ng-content>\n</anarchitects-auth-ui-refresh-tokens-form>\n", styles: [":host{display:block}\n"] }]
|
|
233
238
|
}], propDecorators: { userId: [{ type: i0.Input, args: [{ isSignal: true, alias: "userId", required: false }] }], layout: [{ type: i0.Input, args: [{ isSignal: true, alias: "layout", required: false }] }], layoutOptions: [{ type: i0.Input, args: [{ isSignal: true, alias: "layoutOptions", required: false }] }] } });
|
|
234
239
|
|
|
240
|
+
const waitForAuthInitialization$1 = (initialized) => {
|
|
241
|
+
if (initialized()) {
|
|
242
|
+
return of(true);
|
|
243
|
+
}
|
|
244
|
+
return toObservable(initialized).pipe(filter(Boolean), take(1));
|
|
245
|
+
};
|
|
235
246
|
const policyGuard = (route) => {
|
|
236
|
-
const
|
|
247
|
+
const routePolicy = route.data;
|
|
237
248
|
const authStore = inject(AuthStore);
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
249
|
+
return waitForAuthInitialization$1(authStore.initialized).pipe(map(() => canAttemptRoutePolicy(routePolicy, authStore.rbac())));
|
|
250
|
+
};
|
|
251
|
+
|
|
252
|
+
const waitForAuthInitialization = (initialized) => {
|
|
253
|
+
if (initialized()) {
|
|
254
|
+
return of(true);
|
|
255
|
+
}
|
|
256
|
+
return toObservable(initialized).pipe(filter(Boolean), take(1));
|
|
257
|
+
};
|
|
258
|
+
const stripTrailingSlashes = (value) => {
|
|
259
|
+
let end = value.length;
|
|
260
|
+
while (end > 0 && value[end - 1] === '/') {
|
|
261
|
+
end -= 1;
|
|
262
|
+
}
|
|
263
|
+
return value.slice(0, end);
|
|
264
|
+
};
|
|
265
|
+
const buildParentUrl = (state) => {
|
|
266
|
+
const path = stripTrailingSlashes(state.url.split('?')[0]);
|
|
267
|
+
const segments = path.split('/').filter(Boolean);
|
|
268
|
+
if (segments.length <= 1) {
|
|
269
|
+
return '/';
|
|
270
|
+
}
|
|
271
|
+
return `/${segments.slice(0, -1).join('/')}`;
|
|
272
|
+
};
|
|
273
|
+
const resolveUnauthorizedRedirect = (routeData, router, state) => {
|
|
274
|
+
if (Array.isArray(routeData.unauthorizedRedirectTo)) {
|
|
275
|
+
return router.createUrlTree(routeData.unauthorizedRedirectTo);
|
|
276
|
+
}
|
|
277
|
+
if (typeof routeData.unauthorizedRedirectTo === 'string') {
|
|
278
|
+
return router.parseUrl(routeData.unauthorizedRedirectTo);
|
|
241
279
|
}
|
|
242
|
-
return
|
|
280
|
+
return router.parseUrl(buildParentUrl(state));
|
|
281
|
+
};
|
|
282
|
+
const resourcePolicyGuard = (route, state) => {
|
|
283
|
+
const routeData = route.data;
|
|
284
|
+
const authStore = inject(AuthStore);
|
|
285
|
+
const router = inject(Router);
|
|
286
|
+
return waitForAuthInitialization(authStore.initialized).pipe(map(() => {
|
|
287
|
+
const ability = authStore.ability();
|
|
288
|
+
const resource = route.data?.[routeData.resourceKey];
|
|
289
|
+
if (!ability ||
|
|
290
|
+
!resource ||
|
|
291
|
+
typeof resource !== 'object' ||
|
|
292
|
+
Array.isArray(resource) ||
|
|
293
|
+
!canAccessResource(ability, routeData.action, routeData.subject, resource)) {
|
|
294
|
+
return resolveUnauthorizedRedirect(routeData, router, state);
|
|
295
|
+
}
|
|
296
|
+
return true;
|
|
297
|
+
}));
|
|
243
298
|
};
|
|
244
299
|
|
|
245
300
|
class AnarchitectsFeatureLogin {
|
|
@@ -261,5 +316,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImpor
|
|
|
261
316
|
* Generated bundle index. Do not edit.
|
|
262
317
|
*/
|
|
263
318
|
|
|
264
|
-
export { AnarchitectsFeatureActivateUser, AnarchitectsFeatureChangePassword, AnarchitectsFeatureForgotPassword, AnarchitectsFeatureLogin, AnarchitectsFeatureLogout, AnarchitectsFeatureRefreshTokens, AnarchitectsFeatureRegister, AnarchitectsFeatureResetPassword, AnarchitectsFeatureUpdateEmail, AnarchitectsFeatureVerifyEmail, policyGuard };
|
|
319
|
+
export { AnarchitectsFeatureActivateUser, AnarchitectsFeatureChangePassword, AnarchitectsFeatureForgotPassword, AnarchitectsFeatureLogin, AnarchitectsFeatureLogout, AnarchitectsFeatureRefreshTokens, AnarchitectsFeatureRegister, AnarchitectsFeatureResetPassword, AnarchitectsFeatureUpdateEmail, AnarchitectsFeatureVerifyEmail, policyGuard, resourcePolicyGuard };
|
|
265
320
|
//# sourceMappingURL=anarchitects-auth-angular-feature.mjs.map
|