diff --git a/apps/isa-app/src/shared/services/navigation/customer-create.navigation.ts b/apps/isa-app/src/shared/services/navigation/customer-create.navigation.ts
index 415382a31..db414c276 100644
--- a/apps/isa-app/src/shared/services/navigation/customer-create.navigation.ts
+++ b/apps/isa-app/src/shared/services/navigation/customer-create.navigation.ts
@@ -6,7 +6,7 @@ import { NavigationRoute } from './defs/navigation-route';
import {
encodeFormData,
mapCustomerInfoDtoToCustomerCreateFormData,
-} from 'apps/isa-app/src/page/customer';
+} from '@page/customer';
@Injectable({ providedIn: 'root' })
export class CustomerCreateNavigation {
@@ -58,7 +58,7 @@ export class CustomerCreateNavigation {
},
];
- let formData = params?.customerInfo
+ const formData = params?.customerInfo
? encodeFormData(
mapCustomerInfoDtoToCustomerCreateFormData(params.customerInfo),
)
diff --git a/docs/library-reference.md b/docs/library-reference.md
index fbcc1046e..09161b223 100644
--- a/docs/library-reference.md
+++ b/docs/library-reference.md
@@ -1,11 +1,11 @@
# Library Reference Guide
-> **Last Updated:** 2025-10-27
+> **Last Updated:** 2025-01-10
> **Angular Version:** 20.1.2
> **Nx Version:** 21.3.2
-> **Total Libraries:** 62
+> **Total Libraries:** 63
-All 62 libraries in the monorepo have comprehensive README.md documentation located at `libs/[domain]/[layer]/[feature]/README.md`.
+All 63 libraries in the monorepo have comprehensive README.md documentation located at `libs/[domain]/[layer]/[feature]/README.md`.
**IMPORTANT: Always use the `docs-researcher` subagent** to retrieve and analyze library documentation. This keeps the main context clean and prevents pollution.
@@ -82,7 +82,14 @@ A comprehensive print management library for Angular applications providing prin
---
-## Core Libraries (5 libraries)
+## Core Libraries (6 libraries)
+
+### `@isa/core/auth`
+Type-safe role-based authorization utilities with Angular signals integration for the ISA Frontend application. Provides Role enum, RoleService for programmatic checks, and IfRoleDirective for declarative template rendering with automatic JWT token parsing via OAuthService.
+
+**Location:** `libs/core/auth/`
+**Testing:** Vitest (18 passing tests)
+**Features:** Signal-based reactivity, type-safe Role enum, zero-configuration OAuth2 integration
### `@isa/core/config`
A lightweight, type-safe configuration management system for Angular applications with runtime validation and nested object access.
diff --git a/libs/checkout/feature/reward-order-confirmation/src/lib/order-confirmation-item-list/order-confirmation-item-list-item/confirmation-list-item-action-card/confirmation-list-item-action-card.component.html b/libs/checkout/feature/reward-order-confirmation/src/lib/order-confirmation-item-list/order-confirmation-item-list-item/confirmation-list-item-action-card/confirmation-list-item-action-card.component.html
index be459e369..f06aa3e67 100644
--- a/libs/checkout/feature/reward-order-confirmation/src/lib/order-confirmation-item-list/order-confirmation-item-list-item/confirmation-list-item-action-card/confirmation-list-item-action-card.component.html
+++ b/libs/checkout/feature/reward-order-confirmation/src/lib/order-confirmation-item-list/order-confirmation-item-list-item/confirmation-list-item-action-card/confirmation-list-item-action-card.component.html
@@ -16,6 +16,7 @@
+
+
Store Dashboard
+
+
+
+
+
+
CallCenter Dashboard
+
+
+
+
+
+
+
+ `
+})
+export class DashboardComponent {
+ protected readonly Role = Role; // Expose to template
+}
+```
+
+**2. Use RoleService programmatically:**
+
+```typescript
+import { Component, inject } from '@angular/core';
+import { RoleService, Role } from '@isa/core/auth';
+
+@Component({
+ selector: 'app-nav',
+ template: `...`
+})
+export class NavComponent {
+ private readonly roleService = inject(RoleService);
+
+ ngOnInit() {
+ if (this.roleService.hasRole(Role.Store)) {
+ // Enable store-specific navigation
+ }
+ }
+}
+```
+
+**3. No configuration needed!** The library automatically uses `OAuthService` to parse JWT tokens.
+
+## Core Concepts
+
+### Role Enum
+
+Roles are defined as a const object with TypeScript type safety:
+
+```typescript
+export const Role = {
+ CallCenter: 'CallCenter', // HSC (Hugendubel Service Center)
+ Store: 'Store', // Store/Branch users
+} as const;
+
+export type Role = (typeof Role)[keyof typeof Role];
+```
+
+**Benefits:**
+- Autocomplete in IDEs
+- Compile-time checking prevents invalid roles
+- Easy to extend with new roles
+
+### Token Provider Pattern
+
+The library uses an injectable `TokenProvider` abstraction to decouple from specific authentication implementations:
+
+```typescript
+export interface TokenProvider {
+ getClaimByKey(key: string): unknown;
+}
+```
+
+**Default Implementation:**
+- Automatically provided via `InjectionToken` factory
+- Uses `OAuthService.getAccessToken()` to fetch JWT
+- Parses token using `parseJwt()` utility
+- No manual configuration required
+
+### Signal-Based Reactivity
+
+The `IfRoleDirective` uses Angular effects for automatic re-rendering when roles change:
+
+```typescript
+constructor() {
+ effect(() => {
+ this.render(); // Re-render when ifRole/ifNotRole inputs change
+ });
+}
+```
+
+## API Reference
+
+### Role (Enum)
+
+Type-safe role definitions for the application.
+
+```typescript
+export const Role = {
+ CallCenter: 'CallCenter', // HSC users
+ Store: 'Store', // Store users
+} as const;
+```
+
+**Usage:**
+```typescript
+import { Role } from '@isa/core/auth';
+
+if (roleService.hasRole(Role.Store)) {
+ // Type-safe!
+}
+```
+
+---
+
+### RoleService
+
+Service for programmatic role checks.
+
+#### Methods
+
+##### `hasRole(role: Role | Role[]): boolean`
+
+Check if the authenticated user has specific role(s).
+
+**Parameters:**
+- `role` - Single role or array of roles to check (AND logic for arrays)
+
+**Returns:** `true` if user has all specified roles, `false` otherwise
+
+**Examples:**
+
+```typescript
+import { inject } from '@angular/core';
+import { RoleService, Role } from '@isa/core/auth';
+
+export class ExampleComponent {
+ private readonly roleService = inject(RoleService);
+
+ checkAccess() {
+ // Single role check
+ if (this.roleService.hasRole(Role.Store)) {
+ console.log('User is a store employee');
+ }
+
+ // Multiple roles (AND logic)
+ if (this.roleService.hasRole([Role.Store, Role.CallCenter])) {
+ console.log('User has BOTH store AND call center access');
+ }
+
+ // Multiple checks
+ const isStore = this.roleService.hasRole(Role.Store);
+ const isCallCenter = this.roleService.hasRole(Role.CallCenter);
+
+ if (isStore || isCallCenter) {
+ console.log('User has at least one role (OR logic)');
+ }
+ }
+}
+```
+
+**Logging:**
+
+The service logs all role checks at `debug` level:
+```
+[RoleService] Role check: Store => true
+[RoleService] Role check: Store, CallCenter => false
+```
+
+---
+
+### IfRoleDirective
+
+Structural directive for declarative role-based rendering.
+
+**Selector:** `[ifRole]`, `[ifRoleElse]`, `[ifNotRole]`, `[ifNotRoleElse]`
+
+#### Inputs
+
+| Input | Type | Description |
+|-------|------|-------------|
+| `ifRole` | `Role \| Role[]` | Role(s) required to show template |
+| `ifRoleElse` | `TemplateRef` | Alternative template if user lacks role |
+| `ifNotRole` | `Role \| Role[]` | Role(s) that should NOT be present |
+| `ifNotRoleElse` | `TemplateRef` | Alternative template if user has role |
+
+#### Examples
+
+**Basic Usage:**
+
+```html
+
+
+ Store-specific content
+
+
+
+
+ CallCenter-specific content
+
+```
+
+**With Else Template:**
+
+```html
+
+
+
+
+
+ You don't have permission to complete orders
+
+```
+
+**Negation (`ifNotRole`):**
+
+```html
+
+
+
+
+
+
+```
+
+**Multiple Roles (AND logic):**
+
+```html
+
+
+ Advanced features requiring both roles
+
+```
+
+**Component Integration:**
+
+```typescript
+import { Component } from '@angular/core';
+import { IfRoleDirective, Role } from '@isa/core/auth';
+
+@Component({
+ selector: 'app-actions',
+ standalone: true,
+ imports: [IfRoleDirective],
+ template: `
+
+
+
+ `
+})
+export class ActionsComponent {
+ // Expose Role to template
+ protected readonly Role = Role;
+
+ completeOrder() {
+ // ...
+ }
+}
+```
+
+---
+
+### TokenProvider
+
+Injectable abstraction for JWT token parsing.
+
+```typescript
+export interface TokenProvider {
+ getClaimByKey(key: string): unknown;
+}
+```
+
+**Default Implementation:**
+
+Automatically provided via `InjectionToken` factory:
+
+```typescript
+export const TOKEN_PROVIDER = new InjectionToken(
+ 'TOKEN_PROVIDER',
+ {
+ providedIn: 'root',
+ factory: () => {
+ const oAuthService = inject(OAuthService);
+ return {
+ getClaimByKey: (key: string) => {
+ const claims = parseJwt(oAuthService.getAccessToken());
+ return claims?.[key] ?? null;
+ },
+ };
+ },
+ },
+);
+```
+
+**Custom Provider (Advanced):**
+
+Override the default implementation:
+
+```typescript
+import { TOKEN_PROVIDER, TokenProvider } from '@isa/core/auth';
+
+providers: [
+ {
+ provide: TOKEN_PROVIDER,
+ useValue: {
+ getClaimByKey: (key: string) => {
+ // Custom token parsing logic
+ return myCustomAuthService.getClaim(key);
+ }
+ } as TokenProvider
+ }
+]
+```
+
+---
+
+### parseJwt()
+
+Utility function to parse JWT tokens.
+
+```typescript
+export function parseJwt(
+ token: string | null
+): Record | null
+```
+
+**Parameters:**
+- `token` - JWT token string or null
+
+**Returns:** Parsed claims object or null
+
+**Example:**
+
+```typescript
+import { parseJwt } from '@isa/core/auth';
+
+const token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...';
+const claims = parseJwt(token);
+
+console.log(claims?.['role']); // ['Store']
+console.log(claims?.['sub']); // User ID
+```
+
+## Usage Examples
+
+### Example 1: Conditional Navigation
+
+```typescript
+import { Component, inject } from '@angular/core';
+import { RouterLink } from '@angular/router';
+import { IfRoleDirective, Role } from '@isa/core/auth';
+
+@Component({
+ selector: 'app-side-menu',
+ standalone: true,
+ imports: [RouterLink, IfRoleDirective],
+ template: `
+
+ `
+})
+export class SideMenuComponent {
+ protected readonly Role = Role;
+}
+```
+
+### Example 2: Guard with RoleService
+
+```typescript
+import { inject } from '@angular/core';
+import { CanActivateFn, Router } from '@angular/router';
+import { RoleService, Role } from '@isa/core/auth';
+
+export const storeGuard: CanActivateFn = () => {
+ const roleService = inject(RoleService);
+ const router = inject(Router);
+
+ if (roleService.hasRole(Role.Store)) {
+ return true;
+ }
+
+ // Redirect to unauthorized page
+ return router.createUrlTree(['/unauthorized']);
+};
+
+// Route configuration
+export const routes = [
+ {
+ path: 'inventory',
+ component: InventoryComponent,
+ canActivate: [storeGuard]
+ }
+];
+```
+
+### Example 3: Computed Signals with Roles
+
+```typescript
+import { Component, inject, computed } from '@angular/core';
+import { RoleService, Role } from '@isa/core/auth';
+import { toSignal } from '@angular/core/rxjs-interop';
+
+@Component({
+ selector: 'app-dashboard',
+ template: `
+ @if (canManageInventory()) {
+
+ }
+
+ @if (canProcessReturns()) {
+
+ }
+ `
+})
+export class DashboardComponent {
+ private readonly roleService = inject(RoleService);
+
+ // Computed permissions
+ canManageInventory = computed(() =>
+ this.roleService.hasRole(Role.Store)
+ );
+
+ canProcessReturns = computed(() =>
+ this.roleService.hasRole([Role.Store, Role.CallCenter])
+ );
+
+ openInventory() { /* ... */ }
+ openReturns() { /* ... */ }
+}
+```
+
+### Example 4: Real-World Component (Reward Order Confirmation)
+
+```typescript
+import { Component } from '@angular/core';
+import { IfRoleDirective, Role } from '@isa/core/auth';
+import { ButtonComponent } from '@isa/ui/buttons';
+
+@Component({
+ selector: 'checkout-confirmation-actions',
+ standalone: true,
+ imports: [IfRoleDirective, ButtonComponent],
+ template: `
+
+
+ Please complete the order or select an action.
+
+
+
+
+
+
+
+
+
+ `
+})
+export class ConfirmationActionsComponent {
+ protected readonly Role = Role;
+ selectedAction = 'collect';
+
+ complete() {
+ // Complete order logic
+ }
+}
+```
+
+## Configuration
+
+### Default Configuration (Recommended)
+
+No configuration needed! The library automatically uses `OAuthService`:
+
+```typescript
+import { Component } from '@angular/core';
+import { IfRoleDirective, Role } from '@isa/core/auth';
+
+@Component({
+ standalone: true,
+ imports: [IfRoleDirective],
+ // Works out of the box!
+})
+export class MyComponent {}
+```
+
+### Custom TokenProvider (Advanced)
+
+Override the default token provider:
+
+```typescript
+import { ApplicationConfig } from '@angular/core';
+import { TOKEN_PROVIDER, TokenProvider } from '@isa/core/auth';
+
+export const appConfig: ApplicationConfig = {
+ providers: [
+ {
+ provide: TOKEN_PROVIDER,
+ useFactory: () => {
+ const customAuth = inject(CustomAuthService);
+ return {
+ getClaimByKey: (key: string) => customAuth.getClaim(key)
+ } as TokenProvider;
+ }
+ }
+ ]
+};
+```
+
+### JWT Token Structure
+
+The library expects JWT tokens with a `role` claim:
+
+```json
+{
+ "sub": "user123",
+ "role": ["Store"],
+ "exp": 1234567890
+}
+```
+
+**Supported formats:**
+- Single role: `"role": "Store"`
+- Multiple roles: `"role": ["Store", "CallCenter"]`
+
+## Testing
+
+### Run Tests
+
+```bash
+# Run all tests
+npx nx test core-auth
+
+# Run with coverage
+npx nx test core-auth --coverage.enabled=true
+
+# Skip cache (fresh run)
+npx nx test core-auth --skip-nx-cache
+```
+
+### Test Results
+
+```
+✓ src/lib/role.service.spec.ts (11 tests)
+✓ src/lib/if-role.directive.spec.ts (7 tests)
+
+Test Files 2 passed (2)
+Tests 18 passed (18)
+```
+
+### Testing in Your App
+
+**Mock RoleService:**
+
+```typescript
+import { describe, it, expect, vi } from 'vitest';
+import { TestBed } from '@angular/core/testing';
+import { RoleService, Role } from '@isa/core/auth';
+
+describe('MyComponent', () => {
+ let roleService: RoleService;
+
+ beforeEach(() => {
+ roleService = {
+ hasRole: vi.fn().mockReturnValue(true)
+ } as any;
+
+ TestBed.configureTestingModule({
+ providers: [
+ { provide: RoleService, useValue: roleService }
+ ]
+ });
+ });
+
+ it('should show store content for store users', () => {
+ vi.spyOn(roleService, 'hasRole').mockReturnValue(true);
+
+ const fixture = TestBed.createComponent(MyComponent);
+ fixture.detectChanges();
+
+ expect(roleService.hasRole).toHaveBeenCalledWith(Role.Store);
+ // Assert UI changes
+ });
+});
+```
+
+**Mock TokenProvider:**
+
+```typescript
+import { TOKEN_PROVIDER, TokenProvider, Role } from '@isa/core/auth';
+
+const mockTokenProvider: TokenProvider = {
+ getClaimByKey: vi.fn().mockReturnValue([Role.Store])
+};
+
+TestBed.configureTestingModule({
+ providers: [
+ { provide: TOKEN_PROVIDER, useValue: mockTokenProvider }
+ ]
+});
+```
+
+## Architecture
+
+### Design Patterns
+
+**1. Token Provider Pattern**
+- Abstracts JWT parsing behind injectable interface
+- Allows custom implementations without changing consumers
+- Default factory provides OAuthService integration
+
+**2. Signal-Based Reactivity**
+- Uses Angular signals for reactive role checks
+- Effect-driven template updates
+- Minimal re-renders with fine-grained reactivity
+
+**3. Type-Safe Enum Pattern**
+- Const object with `as const` assertion
+- Provides autocomplete and compile-time safety
+- Prevents typos and invalid role strings
+
+### Architecture Diagram
+
+```
+┌─────────────────────────────────────────────────────┐
+│ Application Layer │
+│ ┌─────────────────┐ ┌──────────────────┐ │
+│ │ Components │ │ Route Guards │ │
+│ │ (Templates) │ │ │ │
+│ └────────┬────────┘ └────────┬─────────┘ │
+│ │ │ │
+│ │ *ifRole │ hasRole() │
+│ ▼ ▼ │
+├───────────────────────────────────────────────────┤
+│ @isa/core/auth Library │
+│ ┌──────────────────┐ ┌─────────────────┐ │
+│ │ IfRoleDirective │ │ RoleService │ │
+│ │ (Signals) │──────▶│ (Injectable) │ │
+│ └──────────────────┘ └────────┬────────┘ │
+│ │ │
+│ hasRole(Role[]) │
+│ │ │
+│ ▼ │
+│ ┌────────────────────┐ │
+│ │ TokenProvider │ │
+│ │ (InjectionToken) │ │
+│ └────────┬───────────┘ │
+│ │ │
+│ getClaimByKey('role') │
+│ │ │
+├───────────────────────────────────┼──────────────┤
+│ ▼ │
+│ ┌──────────────────────────┐ │
+│ │ OAuthService │ │
+│ │ (angular-oauth2-oidc) │ │
+│ └──────────┬───────────────┘ │
+│ │ │
+│ getAccessToken() │
+│ │ │
+└──────────────────────────┼────────────────────────┘
+ ▼
+ ┌───────────────┐
+ │ JWT Token │
+ │ { role: ... }│
+ └───────────────┘
+```
+
+### Role Claim Handling
+
+The library handles both single and multiple role formats:
+
+```typescript
+// Single role (string)
+{ "role": "Store" }
+
+// Multiple roles (array)
+{ "role": ["Store", "CallCenter"] }
+
+// Internal normalization using coerceArray()
+const userRolesArray = coerceArray(userRoles);
+```
+
+## Dependencies
+
+### External Dependencies
+
+- **`@angular/core`** - Angular framework
+- **`@angular/cdk/coercion`** - Array coercion utility
+- **`angular-oauth2-oidc`** - OAuth2/OIDC authentication
+- **`@isa/core/logging`** - Logging integration
+
+### Internal Dependencies
+
+No other ISA libraries required beyond `@isa/core/logging`.
+
+### Import Path
+
+```typescript
+import {
+ RoleService,
+ IfRoleDirective,
+ Role,
+ TokenProvider,
+ TOKEN_PROVIDER,
+ parseJwt
+} from '@isa/core/auth';
+```
+
+**Path Alias:** `@isa/core/auth` → `libs/core/auth/src/index.ts`
+
+## Related Documentation
+
+- [CLAUDE.md](../../../CLAUDE.md) - Project guidelines
+- [Testing Guidelines](../../../docs/guidelines/testing.md) - Vitest setup
+- [Library Reference](../../../docs/library-reference.md) - All libraries
+
+## Related Libraries
+
+- [`@isa/core/logging`](../logging/README.md) - Structured logging
+- [`@isa/core/config`](../config/README.md) - Configuration management
+- [`@isa/core/storage`](../storage/README.md) - State persistence
+
+---
+
+**License:** ISC
+**Version:** 1.0.0
+**Last Updated:** 2025-01-10
diff --git a/libs/core/auth/eslint.config.cjs b/libs/core/auth/eslint.config.cjs
new file mode 100644
index 000000000..d36ea77f0
--- /dev/null
+++ b/libs/core/auth/eslint.config.cjs
@@ -0,0 +1,34 @@
+const nx = require('@nx/eslint-plugin');
+const baseConfig = require('../../../eslint.config.js');
+
+module.exports = [
+ ...baseConfig,
+ ...nx.configs['flat/angular'],
+ ...nx.configs['flat/angular-template'],
+ {
+ files: ['**/*.ts'],
+ rules: {
+ '@angular-eslint/directive-selector': [
+ 'error',
+ {
+ type: 'attribute',
+ prefix: 'lib',
+ style: 'camelCase',
+ },
+ ],
+ '@angular-eslint/component-selector': [
+ 'error',
+ {
+ type: 'element',
+ prefix: 'lib',
+ style: 'kebab-case',
+ },
+ ],
+ },
+ },
+ {
+ files: ['**/*.html'],
+ // Override or add rules here
+ rules: {},
+ },
+];
diff --git a/libs/core/auth/project.json b/libs/core/auth/project.json
new file mode 100644
index 000000000..d1949f8ec
--- /dev/null
+++ b/libs/core/auth/project.json
@@ -0,0 +1,20 @@
+{
+ "name": "core-auth",
+ "$schema": "../../../node_modules/nx/schemas/project-schema.json",
+ "sourceRoot": "libs/core/auth/src",
+ "prefix": "lib",
+ "projectType": "library",
+ "tags": [],
+ "targets": {
+ "test": {
+ "executor": "@nx/vite:test",
+ "outputs": ["{options.reportsDirectory}"],
+ "options": {
+ "reportsDirectory": "../../../coverage/libs/core/auth"
+ }
+ },
+ "lint": {
+ "executor": "@nx/eslint:lint"
+ }
+ }
+}
diff --git a/libs/core/auth/src/index.ts b/libs/core/auth/src/index.ts
new file mode 100644
index 000000000..5743f94e9
--- /dev/null
+++ b/libs/core/auth/src/index.ts
@@ -0,0 +1,10 @@
+/**
+ * Core Auth Library
+ *
+ * Provides role-based authorization utilities for the ISA Frontend application.
+ */
+
+export { RoleService } from './lib/role.service';
+export { IfRoleDirective } from './lib/if-role.directive';
+export { TokenProvider, TOKEN_PROVIDER, parseJwt } from './lib/token-provider';
+export { Role } from './lib/role';
diff --git a/libs/core/auth/src/lib/if-role.directive.spec.ts b/libs/core/auth/src/lib/if-role.directive.spec.ts
new file mode 100644
index 000000000..0746222b8
--- /dev/null
+++ b/libs/core/auth/src/lib/if-role.directive.spec.ts
@@ -0,0 +1,157 @@
+import { Component } from '@angular/core';
+import { TestBed } from '@angular/core/testing';
+import { describe, it, expect, beforeEach, vi } from 'vitest';
+import { IfRoleDirective } from './if-role.directive';
+import { RoleService } from './role.service';
+import { Role } from './role';
+
+@Component({
+ standalone: true,
+ imports: [IfRoleDirective],
+ template: `
+ Store Content
+ `,
+})
+class TestIfRoleComponent {
+ role = Role.Store;
+}
+
+@Component({
+ standalone: true,
+ imports: [IfRoleDirective],
+ template: `
+ Store Content
+
+ No Access
+
+ `,
+})
+class TestIfRoleElseComponent {
+ role = Role.Store;
+}
+
+@Component({
+ standalone: true,
+ imports: [IfRoleDirective],
+ template: `
+ Non-Store Content
+ `,
+})
+class TestIfNotRoleComponent {
+ role = Role.Store;
+}
+
+describe('IfRoleDirective', () => {
+ let roleService: { hasRole: ReturnType };
+
+ beforeEach(() => {
+ roleService = {
+ hasRole: vi.fn(),
+ };
+
+ TestBed.configureTestingModule({
+ providers: [{ provide: RoleService, useValue: roleService }],
+ });
+ });
+
+ describe('ifRole', () => {
+ it('should render content when user has role', () => {
+ roleService.hasRole.mockReturnValue(true);
+
+ const fixture = TestBed.createComponent(TestIfRoleComponent);
+ fixture.detectChanges();
+
+ const content = fixture.nativeElement.querySelector('[data-test="content"]');
+ expect(content).toBeTruthy();
+ expect(content?.textContent).toContain('Store Content');
+ });
+
+ it('should not render content when user does not have role', () => {
+ roleService.hasRole.mockReturnValue(false);
+
+ const fixture = TestBed.createComponent(TestIfRoleComponent);
+ fixture.detectChanges();
+
+ const content = fixture.nativeElement.querySelector('[data-test="content"]');
+ expect(content).toBeFalsy();
+ });
+
+ it('should render else template when user does not have role', () => {
+ roleService.hasRole.mockReturnValue(false);
+
+ const fixture = TestBed.createComponent(TestIfRoleElseComponent);
+ fixture.detectChanges();
+
+ const content = fixture.nativeElement.querySelector('[data-test="content"]');
+ const elseContent = fixture.nativeElement.querySelector('[data-test="else"]');
+
+ expect(content).toBeFalsy();
+ expect(elseContent).toBeTruthy();
+ expect(elseContent?.textContent).toContain('No Access');
+ });
+
+ it('should update when role input changes', () => {
+ roleService.hasRole.mockReturnValue(true);
+
+ const fixture = TestBed.createComponent(TestIfRoleComponent);
+ fixture.detectChanges();
+
+ let content = fixture.nativeElement.querySelector('[data-test="content"]');
+ expect(content).toBeTruthy();
+
+ // Change role and mock to return false
+ roleService.hasRole.mockReturnValue(false);
+ fixture.componentInstance.role = Role.CallCenter;
+ fixture.detectChanges();
+
+ content = fixture.nativeElement.querySelector('[data-test="content"]');
+ expect(content).toBeFalsy();
+ });
+ });
+
+ describe('ifNotRole', () => {
+ it('should render content when user does NOT have role', () => {
+ roleService.hasRole.mockReturnValue(false);
+
+ const fixture = TestBed.createComponent(TestIfNotRoleComponent);
+ fixture.detectChanges();
+
+ const content = fixture.nativeElement.querySelector('[data-test="content"]');
+ expect(content).toBeTruthy();
+ expect(content?.textContent).toContain('Non-Store Content');
+ });
+
+ it('should not render content when user has role', () => {
+ roleService.hasRole.mockReturnValue(true);
+
+ const fixture = TestBed.createComponent(TestIfNotRoleComponent);
+ fixture.detectChanges();
+
+ const content = fixture.nativeElement.querySelector('[data-test="content"]');
+ expect(content).toBeFalsy();
+ });
+ });
+
+ describe('multiple roles', () => {
+ it('should handle array of roles', () => {
+ roleService.hasRole.mockReturnValue(true);
+
+ @Component({
+ standalone: true,
+ imports: [IfRoleDirective],
+ template: `Content
`,
+ })
+ class TestMultipleRolesComponent {
+ roles = [Role.Store, Role.CallCenter];
+ }
+
+ const fixture = TestBed.createComponent(TestMultipleRolesComponent);
+ fixture.detectChanges();
+
+ expect(roleService.hasRole).toHaveBeenCalledWith([Role.Store, Role.CallCenter]);
+
+ const content = fixture.nativeElement.querySelector('[data-test="content"]');
+ expect(content).toBeTruthy();
+ });
+ });
+});
diff --git a/libs/core/auth/src/lib/if-role.directive.ts b/libs/core/auth/src/lib/if-role.directive.ts
new file mode 100644
index 000000000..8902944c9
--- /dev/null
+++ b/libs/core/auth/src/lib/if-role.directive.ts
@@ -0,0 +1,105 @@
+import {
+ Directive,
+ effect,
+ inject,
+ input,
+ TemplateRef,
+ ViewContainerRef,
+} from '@angular/core';
+import { RoleService } from './role.service';
+import { Role } from './role';
+
+/**
+ * Structural directive for role-based conditional rendering using Angular signals
+ *
+ * @example
+ * ```html
+ *
+ * Store content
+ *
+ *
+ * Multiple roles
+ *
+ *
+ * Store content
+ * No access
+ *
+ *
+ * Non-CallCenter content
+ * ```
+ */
+@Directive({
+ selector: '[ifRole],[ifRoleElse],[ifNotRole],[ifNotRoleElse]',
+ standalone: true,
+})
+export class IfRoleDirective {
+ private readonly _templateRef = inject(TemplateRef<{ $implicit: Role | Role[] }>);
+ private readonly _viewContainer = inject(ViewContainerRef);
+ private readonly _roleService = inject(RoleService);
+
+ /**
+ * Role(s) required to show the template
+ */
+ readonly ifRole = input();
+
+ /**
+ * Alternative template to show if user doesn't have ifRole
+ */
+ readonly ifRoleElse = input>();
+
+ /**
+ * Role(s) that should NOT be present to show the template
+ */
+ readonly ifNotRole = input();
+
+ /**
+ * Alternative template to show if user has ifNotRole
+ */
+ readonly ifNotRoleElse = input>();
+
+ constructor() {
+ // Use effect to reactively update the view when inputs change
+ effect(() => {
+ this.render();
+ });
+ }
+
+ private get renderTemplateRef(): boolean {
+ const role = this.ifRole();
+ const notRole = this.ifNotRole();
+
+ if (role) {
+ return this._roleService.hasRole(role);
+ }
+ if (notRole) {
+ return !this._roleService.hasRole(notRole);
+ }
+ return false;
+ }
+
+ private get elseTemplateRef(): TemplateRef | undefined {
+ return this.ifRoleElse() || this.ifNotRoleElse();
+ }
+
+ private render() {
+ if (this.renderTemplateRef) {
+ this._viewContainer.clear();
+ this._viewContainer.createEmbeddedView(this._templateRef, this.getContext());
+ return;
+ }
+
+ if (this.elseTemplateRef) {
+ this._viewContainer.clear();
+ this._viewContainer.createEmbeddedView(this.elseTemplateRef, this.getContext());
+ return;
+ }
+
+ this._viewContainer.clear();
+ }
+
+ private getContext(): { $implicit: Role | Role[] | undefined } {
+ return {
+ $implicit: this.ifRole() || this.ifNotRole(),
+ };
+ }
+}
diff --git a/libs/core/auth/src/lib/role.service.spec.ts b/libs/core/auth/src/lib/role.service.spec.ts
new file mode 100644
index 000000000..ac78a7d78
--- /dev/null
+++ b/libs/core/auth/src/lib/role.service.spec.ts
@@ -0,0 +1,95 @@
+import { TestBed } from '@angular/core/testing';
+import { describe, it, expect, beforeEach, vi } from 'vitest';
+import { RoleService } from './role.service';
+import { TOKEN_PROVIDER, TokenProvider } from './token-provider';
+import { Role } from './role';
+
+describe('RoleService', () => {
+ let service: RoleService;
+ let tokenProvider: TokenProvider;
+
+ beforeEach(() => {
+ tokenProvider = {
+ getClaimByKey: vi.fn(),
+ };
+
+ TestBed.configureTestingModule({
+ providers: [RoleService, { provide: TOKEN_PROVIDER, useValue: tokenProvider }],
+ });
+
+ service = TestBed.inject(RoleService);
+ });
+
+ it('should be created', () => {
+ expect(service).toBeTruthy();
+ });
+
+ describe('hasRole', () => {
+ it('should return true when user has single required role', () => {
+ vi.spyOn(tokenProvider, 'getClaimByKey').mockReturnValue([Role.Store, Role.CallCenter]);
+
+ expect(service.hasRole(Role.Store)).toBe(true);
+ });
+
+ it('should return false when user does not have required role', () => {
+ vi.spyOn(tokenProvider, 'getClaimByKey').mockReturnValue([Role.CallCenter]);
+
+ expect(service.hasRole(Role.Store)).toBe(false);
+ });
+
+ it('should return true when user has all required roles (array)', () => {
+ vi.spyOn(tokenProvider, 'getClaimByKey').mockReturnValue([
+ Role.Store,
+ Role.CallCenter,
+ ]);
+
+ expect(service.hasRole([Role.Store, Role.CallCenter])).toBe(true);
+ });
+
+ it('should return false when user is missing one of required roles', () => {
+ vi.spyOn(tokenProvider, 'getClaimByKey').mockReturnValue([Role.CallCenter]);
+
+ expect(service.hasRole([Role.Store, Role.CallCenter])).toBe(false);
+ });
+
+ it('should return false when user has no roles in token', () => {
+ vi.spyOn(tokenProvider, 'getClaimByKey').mockReturnValue(null);
+
+ expect(service.hasRole(Role.Store)).toBe(false);
+ });
+
+ it('should return false when user has undefined roles', () => {
+ vi.spyOn(tokenProvider, 'getClaimByKey').mockReturnValue(undefined);
+
+ expect(service.hasRole(Role.Store)).toBe(false);
+ });
+
+ it('should handle errors gracefully', () => {
+ vi.spyOn(tokenProvider, 'getClaimByKey').mockImplementation(() => {
+ throw new Error('Token parsing error');
+ });
+
+ expect(service.hasRole(Role.Store)).toBe(false);
+ });
+
+ it('should handle empty role array', () => {
+ vi.spyOn(tokenProvider, 'getClaimByKey').mockReturnValue([Role.Store]);
+
+ expect(service.hasRole([])).toBe(true); // empty array means no requirements
+ });
+
+ it('should handle single role as string (not array)', () => {
+ // JWT might return a single string instead of array for single role
+ vi.spyOn(tokenProvider, 'getClaimByKey').mockReturnValue(Role.Store);
+
+ expect(service.hasRole(Role.Store)).toBe(true);
+ expect(service.hasRole(Role.CallCenter)).toBe(false);
+ });
+
+ it('should handle single role string when checking multiple roles', () => {
+ vi.spyOn(tokenProvider, 'getClaimByKey').mockReturnValue(Role.Store);
+
+ expect(service.hasRole([Role.Store, Role.CallCenter])).toBe(false);
+ });
+ });
+});
diff --git a/libs/core/auth/src/lib/role.service.ts b/libs/core/auth/src/lib/role.service.ts
new file mode 100644
index 000000000..a9e861e01
--- /dev/null
+++ b/libs/core/auth/src/lib/role.service.ts
@@ -0,0 +1,71 @@
+import { coerceArray } from '@angular/cdk/coercion';
+import { inject, Injectable } from '@angular/core';
+import { logger } from '@isa/core/logging';
+import { TOKEN_PROVIDER } from './token-provider';
+import { Role } from './role';
+
+/**
+ * Service for role-based authorization checks
+ *
+ * @example
+ * ```typescript
+ * import { Role } from '@isa/core/auth';
+ *
+ * const roleService = inject(RoleService);
+ * if (roleService.hasRole(Role.Store)) {
+ * // Show store features
+ * }
+ * ```
+ */
+@Injectable({
+ providedIn: 'root',
+})
+export class RoleService {
+ private readonly _log = logger({ service: 'RoleService' });
+ private readonly _tokenProvider = inject(TOKEN_PROVIDER);
+
+ /**
+ * Check if the authenticated user has specific role(s)
+ *
+ * @param role Single role or array of roles to check
+ * @returns true if user has all specified roles, false otherwise
+ *
+ * @example
+ * ```typescript
+ * import { Role } from '@isa/core/auth';
+ *
+ * // Check single role
+ * hasRole(Role.Store) // true if user has Store role
+ *
+ * // Check multiple roles (AND logic)
+ * hasRole([Role.Store, Role.CallCenter]) // true only if user has BOTH roles
+ * ```
+ */
+ hasRole(role: Role | Role[]): boolean {
+ const roles = coerceArray(role);
+
+ try {
+ const userRoles = this._tokenProvider.getClaimByKey('role');
+
+ if (!userRoles) {
+ this._log.debug('No roles found in token claims');
+ return false;
+ }
+
+ // Coerce userRoles to array in case it's a single string
+ const userRolesArray = coerceArray(userRoles);
+
+ const hasAllRoles = roles.every((r) => userRolesArray.includes(r));
+
+ this._log.debug(`Role check: ${roles.join(', ')} => ${hasAllRoles}`, () => ({
+ requiredRoles: roles,
+ userRoles: userRolesArray,
+ }));
+
+ return hasAllRoles;
+ } catch (error) {
+ this._log.error('Error checking roles', error as Error, () => ({ requiredRoles: roles }));
+ return false;
+ }
+ }
+}
diff --git a/libs/core/auth/src/lib/role.ts b/libs/core/auth/src/lib/role.ts
new file mode 100644
index 000000000..cc89c43c4
--- /dev/null
+++ b/libs/core/auth/src/lib/role.ts
@@ -0,0 +1,13 @@
+export const Role = {
+ /**
+ * HSC
+ */
+ CallCenter: 'CallCenter',
+
+ /**
+ * Filiale
+ */
+ Store: 'Store',
+} as const;
+
+export type Role = (typeof Role)[keyof typeof Role];
diff --git a/libs/core/auth/src/lib/token-provider.ts b/libs/core/auth/src/lib/token-provider.ts
new file mode 100644
index 000000000..de064acf1
--- /dev/null
+++ b/libs/core/auth/src/lib/token-provider.ts
@@ -0,0 +1,67 @@
+import { inject, InjectionToken } from '@angular/core';
+import { OAuthService } from 'angular-oauth2-oidc';
+
+/**
+ * Token provider interface for role checking
+ * The app can provide a custom implementation that returns user roles from the auth token
+ */
+export interface TokenProvider {
+ /**
+ * Get a claim value from the authentication token
+ * @param key The claim key (e.g., 'role')
+ * @returns The claim value or null if not found
+ */
+ getClaimByKey(key: string): unknown;
+}
+
+/**
+ * Parse JWT token to extract claims
+ */
+export function parseJwt(token: string | null): Record | null {
+ if (!token) {
+ return null;
+ }
+
+ try {
+ const base64Url = token.split('.')[1];
+ const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/');
+ const encoded = window.atob(base64);
+ return JSON.parse(encoded);
+ } catch {
+ return null;
+ }
+}
+
+/**
+ * Injection token for TokenProvider with default OAuthService implementation
+ *
+ * By default, this uses OAuthService to extract claims from the access token.
+ * You can override this by providing your own implementation:
+ *
+ * @example
+ * ```typescript
+ * providers: [
+ * {
+ * provide: TOKEN_PROVIDER,
+ * useValue: {
+ * getClaimByKey: (key) => customTokenService.getClaim(key)
+ * }
+ * }
+ * ]
+ * ```
+ */
+export const TOKEN_PROVIDER = new InjectionToken(
+ 'TOKEN_PROVIDER',
+ {
+ providedIn: 'root',
+ factory: () => {
+ const oAuthService = inject(OAuthService);
+ return {
+ getClaimByKey: (key: string) => {
+ const claims = parseJwt(oAuthService.getAccessToken());
+ return claims?.[key] ?? null;
+ },
+ };
+ },
+ },
+);
diff --git a/libs/core/auth/src/test-setup.ts b/libs/core/auth/src/test-setup.ts
new file mode 100644
index 000000000..cebf5ae72
--- /dev/null
+++ b/libs/core/auth/src/test-setup.ts
@@ -0,0 +1,13 @@
+import '@angular/compiler';
+import '@analogjs/vitest-angular/setup-zone';
+
+import {
+ BrowserTestingModule,
+ platformBrowserTesting,
+} from '@angular/platform-browser/testing';
+import { getTestBed } from '@angular/core/testing';
+
+getTestBed().initTestEnvironment(
+ BrowserTestingModule,
+ platformBrowserTesting(),
+);
diff --git a/libs/core/auth/tsconfig.json b/libs/core/auth/tsconfig.json
new file mode 100644
index 000000000..3268ed4dc
--- /dev/null
+++ b/libs/core/auth/tsconfig.json
@@ -0,0 +1,30 @@
+{
+ "extends": "../../../tsconfig.base.json",
+ "compilerOptions": {
+ "importHelpers": true,
+ "moduleResolution": "bundler",
+ "strict": true,
+ "noImplicitOverride": true,
+ "noPropertyAccessFromIndexSignature": true,
+ "noImplicitReturns": true,
+ "noFallthroughCasesInSwitch": true,
+ "module": "preserve"
+ },
+ "angularCompilerOptions": {
+ "enableI18nLegacyMessageIdFormat": false,
+ "strictInjectionParameters": true,
+ "strictInputAccessModifiers": true,
+ "typeCheckHostBindings": true,
+ "strictTemplates": true
+ },
+ "files": [],
+ "include": [],
+ "references": [
+ {
+ "path": "./tsconfig.lib.json"
+ },
+ {
+ "path": "./tsconfig.spec.json"
+ }
+ ]
+}
diff --git a/libs/core/auth/tsconfig.lib.json b/libs/core/auth/tsconfig.lib.json
new file mode 100644
index 000000000..312ee86bb
--- /dev/null
+++ b/libs/core/auth/tsconfig.lib.json
@@ -0,0 +1,27 @@
+{
+ "extends": "./tsconfig.json",
+ "compilerOptions": {
+ "outDir": "../../../dist/out-tsc",
+ "declaration": true,
+ "declarationMap": true,
+ "inlineSources": true,
+ "types": []
+ },
+ "exclude": [
+ "src/**/*.spec.ts",
+ "src/test-setup.ts",
+ "jest.config.ts",
+ "src/**/*.test.ts",
+ "vite.config.ts",
+ "vite.config.mts",
+ "vitest.config.ts",
+ "vitest.config.mts",
+ "src/**/*.test.tsx",
+ "src/**/*.spec.tsx",
+ "src/**/*.test.js",
+ "src/**/*.spec.js",
+ "src/**/*.test.jsx",
+ "src/**/*.spec.jsx"
+ ],
+ "include": ["src/**/*.ts"]
+}
diff --git a/libs/core/auth/tsconfig.spec.json b/libs/core/auth/tsconfig.spec.json
new file mode 100644
index 000000000..5785a8a5f
--- /dev/null
+++ b/libs/core/auth/tsconfig.spec.json
@@ -0,0 +1,29 @@
+{
+ "extends": "./tsconfig.json",
+ "compilerOptions": {
+ "outDir": "../../../dist/out-tsc",
+ "types": [
+ "vitest/globals",
+ "vitest/importMeta",
+ "vite/client",
+ "node",
+ "vitest"
+ ]
+ },
+ "include": [
+ "vite.config.ts",
+ "vite.config.mts",
+ "vitest.config.ts",
+ "vitest.config.mts",
+ "src/**/*.test.ts",
+ "src/**/*.spec.ts",
+ "src/**/*.test.tsx",
+ "src/**/*.spec.tsx",
+ "src/**/*.test.js",
+ "src/**/*.spec.js",
+ "src/**/*.test.jsx",
+ "src/**/*.spec.jsx",
+ "src/**/*.d.ts"
+ ],
+ "files": ["src/test-setup.ts"]
+}
diff --git a/libs/core/auth/vite.config.mts b/libs/core/auth/vite.config.mts
new file mode 100644
index 000000000..0c9889cf2
--- /dev/null
+++ b/libs/core/auth/vite.config.mts
@@ -0,0 +1,33 @@
+///
+import { defineConfig } from 'vite';
+import angular from '@analogjs/vite-plugin-angular';
+import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin';
+import { nxCopyAssetsPlugin } from '@nx/vite/plugins/nx-copy-assets.plugin';
+
+export default
+// @ts-expect-error - Vitest reporter tuple types have complex inference issues
+defineConfig(() => ({
+ root: __dirname,
+ cacheDir: '../../../node_modules/.vite/libs/core/auth',
+ plugins: [angular(), nxViteTsPaths(), nxCopyAssetsPlugin(['*.md'])],
+ // Uncomment this if you are using workers.
+ // worker: {
+ // plugins: [ nxViteTsPaths() ],
+ // },
+ test: {
+ watch: false,
+ globals: true,
+ environment: 'jsdom',
+ include: ['{src,tests}/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
+ setupFiles: ['src/test-setup.ts'],
+ reporters: [
+ 'default',
+ ['junit', { outputFile: '../../../testresults/junit-core-auth.xml' }],
+ ],
+ coverage: {
+ reportsDirectory: '../../../coverage/libs/core/auth',
+ provider: 'v8' as const,
+ reporter: ['text', 'cobertura'],
+ },
+ },
+}));
diff --git a/package-lock.json b/package-lock.json
index af58aeb00..040359615 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -6563,110 +6563,6 @@
"win32"
]
},
- "node_modules/@mapbox/node-pre-gyp": {
- "version": "1.0.11",
- "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.11.tgz",
- "integrity": "sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ==",
- "dev": true,
- "license": "BSD-3-Clause",
- "optional": true,
- "dependencies": {
- "detect-libc": "^2.0.0",
- "https-proxy-agent": "^5.0.0",
- "make-dir": "^3.1.0",
- "node-fetch": "^2.6.7",
- "nopt": "^5.0.0",
- "npmlog": "^5.0.1",
- "rimraf": "^3.0.2",
- "semver": "^7.3.5",
- "tar": "^6.1.11"
- },
- "bin": {
- "node-pre-gyp": "bin/node-pre-gyp"
- }
- },
- "node_modules/@mapbox/node-pre-gyp/node_modules/abbrev": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz",
- "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==",
- "dev": true,
- "license": "ISC",
- "optional": true
- },
- "node_modules/@mapbox/node-pre-gyp/node_modules/agent-base": {
- "version": "6.0.2",
- "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz",
- "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==",
- "dev": true,
- "license": "MIT",
- "optional": true,
- "dependencies": {
- "debug": "4"
- },
- "engines": {
- "node": ">= 6.0.0"
- }
- },
- "node_modules/@mapbox/node-pre-gyp/node_modules/https-proxy-agent": {
- "version": "5.0.1",
- "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz",
- "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==",
- "dev": true,
- "license": "MIT",
- "optional": true,
- "dependencies": {
- "agent-base": "6",
- "debug": "4"
- },
- "engines": {
- "node": ">= 6"
- }
- },
- "node_modules/@mapbox/node-pre-gyp/node_modules/make-dir": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz",
- "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==",
- "dev": true,
- "license": "MIT",
- "optional": true,
- "dependencies": {
- "semver": "^6.0.0"
- },
- "engines": {
- "node": ">=8"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/@mapbox/node-pre-gyp/node_modules/make-dir/node_modules/semver": {
- "version": "6.3.1",
- "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
- "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
- "dev": true,
- "license": "ISC",
- "optional": true,
- "bin": {
- "semver": "bin/semver.js"
- }
- },
- "node_modules/@mapbox/node-pre-gyp/node_modules/nopt": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz",
- "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==",
- "dev": true,
- "license": "ISC",
- "optional": true,
- "dependencies": {
- "abbrev": "1"
- },
- "bin": {
- "nopt": "bin/nopt.js"
- },
- "engines": {
- "node": ">=6"
- }
- },
"node_modules/@mdx-js/react": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/@mdx-js/react/-/react-3.1.1.tgz",
@@ -15100,30 +14996,6 @@
"url": "https://github.com/sponsors/jonschlinkert"
}
},
- "node_modules/aproba": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.1.0.tgz",
- "integrity": "sha512-tLIEcj5GuR2RSTnxNKdkK0dJ/GrC7P38sUkiDmDuHfsHmbagTFAxDVIBltoklXEVIQ/f14IL8IMJ5pn9Hez1Ew==",
- "dev": true,
- "license": "ISC",
- "optional": true
- },
- "node_modules/are-we-there-yet": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz",
- "integrity": "sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==",
- "deprecated": "This package is no longer supported.",
- "dev": true,
- "license": "ISC",
- "optional": true,
- "dependencies": {
- "delegates": "^1.0.0",
- "readable-stream": "^3.6.0"
- },
- "engines": {
- "node": ">=10"
- }
- },
"node_modules/arg": {
"version": "5.0.2",
"resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz",
@@ -16623,17 +16495,6 @@
"dev": true,
"license": "MIT"
},
- "node_modules/color-support": {
- "version": "1.1.3",
- "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz",
- "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==",
- "dev": true,
- "license": "ISC",
- "optional": true,
- "bin": {
- "color-support": "bin.js"
- }
- },
"node_modules/colord": {
"version": "2.9.3",
"resolved": "https://registry.npmjs.org/colord/-/colord-2.9.3.tgz",
@@ -16800,14 +16661,6 @@
"node": ">=0.8"
}
},
- "node_modules/console-control-strings": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz",
- "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==",
- "dev": true,
- "license": "ISC",
- "optional": true
- },
"node_modules/content-disposition": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.0.tgz",
@@ -18288,20 +18141,6 @@
"dev": true,
"license": "MIT"
},
- "node_modules/decompress-response": {
- "version": "4.2.1",
- "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-4.2.1.tgz",
- "integrity": "sha512-jOSne2qbyE+/r8G1VU+G/82LBs2Fs4LAsTiLSHOCOMZQl2OKZ6i8i4IyHemTe+/yIXOtTcRQMzPcgyhoFlqPkw==",
- "dev": true,
- "license": "MIT",
- "optional": true,
- "dependencies": {
- "mimic-response": "^2.0.0"
- },
- "engines": {
- "node": ">=8"
- }
- },
"node_modules/dedent": {
"version": "1.7.0",
"resolved": "https://registry.npmjs.org/dedent/-/dedent-1.7.0.tgz",
@@ -18843,6 +18682,7 @@
"integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==",
"devOptional": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"iconv-lite": "^0.6.2"
}
@@ -20561,72 +20401,6 @@
"url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/gauge": {
- "version": "3.0.2",
- "resolved": "https://registry.npmjs.org/gauge/-/gauge-3.0.2.tgz",
- "integrity": "sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==",
- "deprecated": "This package is no longer supported.",
- "dev": true,
- "license": "ISC",
- "optional": true,
- "dependencies": {
- "aproba": "^1.0.3 || ^2.0.0",
- "color-support": "^1.1.2",
- "console-control-strings": "^1.0.0",
- "has-unicode": "^2.0.1",
- "object-assign": "^4.1.1",
- "signal-exit": "^3.0.0",
- "string-width": "^4.2.3",
- "strip-ansi": "^6.0.1",
- "wide-align": "^1.1.2"
- },
- "engines": {
- "node": ">=10"
- }
- },
- "node_modules/gauge/node_modules/emoji-regex": {
- "version": "8.0.0",
- "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
- "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
- "dev": true,
- "license": "MIT",
- "optional": true
- },
- "node_modules/gauge/node_modules/is-fullwidth-code-point": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
- "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
- "dev": true,
- "license": "MIT",
- "optional": true,
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/gauge/node_modules/signal-exit": {
- "version": "3.0.7",
- "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
- "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==",
- "dev": true,
- "license": "ISC",
- "optional": true
- },
- "node_modules/gauge/node_modules/string-width": {
- "version": "4.2.3",
- "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
- "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
- "dev": true,
- "license": "MIT",
- "optional": true,
- "dependencies": {
- "emoji-regex": "^8.0.0",
- "is-fullwidth-code-point": "^3.0.0",
- "strip-ansi": "^6.0.1"
- },
- "engines": {
- "node": ">=8"
- }
- },
"node_modules/generator-function": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/generator-function/-/generator-function-2.0.1.tgz",
@@ -21096,14 +20870,6 @@
"url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/has-unicode": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz",
- "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==",
- "dev": true,
- "license": "ISC",
- "optional": true
- },
"node_modules/hasown": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
@@ -28676,20 +28442,6 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/mimic-response": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-2.1.0.tgz",
- "integrity": "sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA==",
- "dev": true,
- "license": "MIT",
- "optional": true,
- "engines": {
- "node": ">=8"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
"node_modules/min-indent": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz",
@@ -29045,14 +28797,6 @@
"thenify-all": "^1.0.0"
}
},
- "node_modules/nan": {
- "version": "2.23.0",
- "resolved": "https://registry.npmjs.org/nan/-/nan-2.23.0.tgz",
- "integrity": "sha512-1UxuyYGdoQHcGg87Lkqm3FzefucTa0NAiOcuRsDmysep3c1LVCRK2krrUDafMWtjSG04htvAmvg96+SDknOmgQ==",
- "dev": true,
- "license": "MIT",
- "optional": true
- },
"node_modules/nanoid": {
"version": "3.3.11",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
@@ -29767,21 +29511,6 @@
"node": ">=8"
}
},
- "node_modules/npmlog": {
- "version": "5.0.1",
- "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-5.0.1.tgz",
- "integrity": "sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==",
- "deprecated": "This package is no longer supported.",
- "dev": true,
- "license": "ISC",
- "optional": true,
- "dependencies": {
- "are-we-there-yet": "^2.0.0",
- "console-control-strings": "^1.1.0",
- "gauge": "^3.0.0",
- "set-blocking": "^2.0.0"
- }
- },
"node_modules/nth-check": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz",
@@ -32830,73 +32559,6 @@
"dev": true,
"license": "MIT"
},
- "node_modules/rimraf": {
- "version": "3.0.2",
- "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
- "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
- "deprecated": "Rimraf versions prior to v4 are no longer supported",
- "dev": true,
- "license": "ISC",
- "optional": true,
- "dependencies": {
- "glob": "^7.1.3"
- },
- "bin": {
- "rimraf": "bin.js"
- },
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
- }
- },
- "node_modules/rimraf/node_modules/brace-expansion": {
- "version": "1.1.12",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
- "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
- "dev": true,
- "license": "MIT",
- "optional": true,
- "dependencies": {
- "balanced-match": "^1.0.0",
- "concat-map": "0.0.1"
- }
- },
- "node_modules/rimraf/node_modules/glob": {
- "version": "7.2.3",
- "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
- "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
- "deprecated": "Glob versions prior to v9 are no longer supported",
- "dev": true,
- "license": "ISC",
- "optional": true,
- "dependencies": {
- "fs.realpath": "^1.0.0",
- "inflight": "^1.0.4",
- "inherits": "2",
- "minimatch": "^3.1.1",
- "once": "^1.3.0",
- "path-is-absolute": "^1.0.0"
- },
- "engines": {
- "node": "*"
- },
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
- }
- },
- "node_modules/rimraf/node_modules/minimatch": {
- "version": "3.1.2",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
- "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
- "dev": true,
- "license": "ISC",
- "optional": true,
- "dependencies": {
- "brace-expansion": "^1.1.7"
- },
- "engines": {
- "node": "*"
- }
- },
"node_modules/roarr": {
"version": "2.15.4",
"resolved": "https://registry.npmjs.org/roarr/-/roarr-2.15.4.tgz",
@@ -33947,14 +33609,6 @@
"node": ">= 18"
}
},
- "node_modules/set-blocking": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
- "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==",
- "dev": true,
- "license": "ISC",
- "optional": true
- },
"node_modules/set-cookie-parser": {
"version": "2.7.2",
"resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz",
@@ -34152,19 +33806,6 @@
"license": "MIT",
"optional": true
},
- "node_modules/simple-get": {
- "version": "3.1.1",
- "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-3.1.1.tgz",
- "integrity": "sha512-CQ5LTKGfCpvE1K0n2us+kuMPbk/q0EKl82s4aheV9oXjFEz6W/Y7oQFVJuU6QG77hRT4Ghb5RURteF5vnWjupA==",
- "dev": true,
- "license": "MIT",
- "optional": true,
- "dependencies": {
- "decompress-response": "^4.2.0",
- "once": "^1.3.1",
- "simple-concat": "^1.0.0"
- }
- },
"node_modules/sirv": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/sirv/-/sirv-3.0.2.tgz",
@@ -37853,52 +37494,6 @@
"node": ">=8"
}
},
- "node_modules/wide-align": {
- "version": "1.1.5",
- "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz",
- "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==",
- "dev": true,
- "license": "ISC",
- "optional": true,
- "dependencies": {
- "string-width": "^1.0.2 || 2 || 3 || 4"
- }
- },
- "node_modules/wide-align/node_modules/emoji-regex": {
- "version": "8.0.0",
- "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
- "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
- "dev": true,
- "license": "MIT",
- "optional": true
- },
- "node_modules/wide-align/node_modules/is-fullwidth-code-point": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
- "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
- "dev": true,
- "license": "MIT",
- "optional": true,
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/wide-align/node_modules/string-width": {
- "version": "4.2.3",
- "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
- "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
- "dev": true,
- "license": "MIT",
- "optional": true,
- "dependencies": {
- "emoji-regex": "^8.0.0",
- "is-fullwidth-code-point": "^3.0.0",
- "strip-ansi": "^6.0.1"
- },
- "engines": {
- "node": ">=8"
- }
- },
"node_modules/wildcard": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.1.tgz",
diff --git a/tsconfig.base.json b/tsconfig.base.json
index 68e64acdf..b6a004658 100644
--- a/tsconfig.base.json
+++ b/tsconfig.base.json
@@ -62,6 +62,7 @@
"@isa/common/data-access": ["libs/common/data-access/src/index.ts"],
"@isa/common/decorators": ["libs/common/decorators/src/index.ts"],
"@isa/common/print": ["libs/common/print/src/index.ts"],
+ "@isa/core/auth": ["libs/core/auth/src/index.ts"],
"@isa/core/config": ["libs/core/config/src/index.ts"],
"@isa/core/logging": ["libs/core/logging/src/index.ts"],
"@isa/core/navigation": ["libs/core/navigation/src/index.ts"],