@anarchitects/auth-angular 0.3.0 → 0.5.0

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 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`: router policy guard and orchestration components that delegate rendering to auth UI components
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 { policyGuard } from '@anarchitects/auth-angular/feature';
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 and CASL ability sync |
103
- | `@anarchitects/auth-angular/feature` | Router policy guard |
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 factory and typings |
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 and caches the CASL ability returned by the API.
118
- - Ability creation is centralised in `@anarchitects/auth-angular/util`; import `createAppAbility` instead of instantiating CASL directly.
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
@@ -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` that denies routes unless the logged-in ability can perform the configured action on the configured subject.
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 { policyGuard } from '@anarchitects/auth-angular/feature';
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
- The guard reads the `AuthStore` ability snapshot. Ensure the state layer is explicitly provided in your app/route providers by wiring `provideAuthState()` from `@anarchitects/auth-angular/state`.
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 { HttpClient, HttpContextToken, HttpErrorResponse, HttpStatusCode, HttpBackend, withInterceptors } from '@angular/common/http';
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 { jwtDecode } from 'jwt-decode';
6
+ import { tap, finalize, shareReplay, catchError, throwError, switchMap, map } from 'rxjs';
6
7
  import { Router } from '@angular/router';
7
- import { tap, finalize, shareReplay, catchError, throwError, switchMap } from 'rxjs';
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
- redirectToLogin(router ?? null);
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
- redirectToLogin(router ?? null);
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
- redirectToLogin(router ?? null);
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":"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"}
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 { action, subject } = route.data;
247
+ const routePolicy = route.data;
237
248
  const authStore = inject(AuthStore);
238
- const ability = authStore.ability?.();
239
- if (!ability) {
240
- return false;
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 ability.can(action, subject);
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