@flusys/ng-auth 1.0.0-beta → 1.0.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
@@ -324,7 +324,7 @@ provideHttpClient(
324
324
  Centralized endpoint definitions used by interceptors.
325
325
 
326
326
  ```typescript
327
- import { AUTH_ENDPOINTS, isAuthEndpoint, isEndpoint } from '@flusys/ng-auth';
327
+ import { AUTH_ENDPOINTS, isEndpoint } from '@flusys/ng-auth';
328
328
 
329
329
  // Endpoint constants
330
330
  AUTH_ENDPOINTS.LOGIN // '/auth/login'
@@ -334,8 +334,7 @@ AUTH_ENDPOINTS.REGISTER // '/auth/register'
334
334
  AUTH_ENDPOINTS.SELECT // '/auth/select'
335
335
  AUTH_ENDPOINTS.SWITCH_COMPANY // '/auth/switch-company'
336
336
 
337
- // Helper functions
338
- isAuthEndpoint(url); // Check if URL is any auth endpoint
337
+ // Helper function
339
338
  isEndpoint(url, endpoint); // Check if URL matches specific endpoint
340
339
  ```
341
340
 
@@ -393,41 +392,106 @@ getCompanyBranches(companyId); // User's permitted branches
393
392
 
394
393
  ## Pages
395
394
 
395
+ All page components follow Angular 21 best practices:
396
+ - `ChangeDetectionStrategy.OnPush` for optimal performance
397
+ - Signal Forms (`@angular/forms/signals`) with validation
398
+ - 100% responsive design (mobile-first with Tailwind)
399
+ - Dark mode compatible (PrimeNG + Tailwind CSS variables)
400
+ - `inject()` for dependency injection
401
+
396
402
  ### Authentication Pages
397
403
 
398
404
  **LoginPageComponent** (`/auth/login`)
399
405
  - Multi-step login: Step 1 = credentials, Step 2 = company/branch selection (if needed)
400
- - Visual step indicator
406
+ - Visual step indicator (when company feature enabled)
407
+ - Email verification step (when email not verified)
401
408
  - Auto-selects if only one company/branch available
402
409
  - Stores returnUrl for post-login redirect
410
+ - Responsive form layout with mobile padding
403
411
 
404
412
  **RegisterPageComponent** (`/auth/register`)
405
413
  - User registration with optional company creation
406
- - Email/password validation
407
- - Terms acceptance
414
+ - Two-column responsive grid layout
415
+ - Email/password validation with signal forms
416
+ - Company details section (when company feature enabled)
417
+
418
+ **ForgotPasswordPageComponent** (`/auth/forgot-password`)
419
+ - Email input with validation
420
+ - Success state showing email sent confirmation
421
+
422
+ **ResetPasswordPageComponent** (`/auth/reset-password`)
423
+ - Password reset with token validation
424
+ - Password confirmation matching
425
+ - Success/invalid token states
426
+
427
+ **VerifyEmailPageComponent** (`/auth/verify-email`)
428
+ - Token verification with SSR support (TransferState)
429
+ - Resend verification option
430
+ - Success/failure states
431
+
432
+ ### Profile Page
433
+
434
+ **ProfilePageComponent** (`/profile`)
435
+ - Page title with description
436
+ - Avatar section with initials fallback
437
+ - Update name and phone
438
+ - Upload profile picture (requires `PROFILE_UPLOAD_PROVIDER` from ng-storage)
439
+ - Change password section with strength indicator
440
+ - View company/branch info (if company feature enabled)
441
+ - View roles/actions (requires `PROFILE_PERMISSION_PROVIDER` from ng-iam)
442
+ - Responsive two-column layout on desktop
443
+
444
+ **Optional Provider Dependencies:**
445
+
446
+ | Provider | Source | Feature Enabled |
447
+ |----------|--------|-----------------|
448
+ | `PROFILE_UPLOAD_PROVIDER` | ng-storage via `provideStorageProviders()` | Profile picture upload |
449
+ | `PROFILE_PERMISSION_PROVIDER` | ng-iam via `provideIamProviders()` | Roles/actions display |
450
+
451
+ **Graceful Degradation:**
452
+ - If provider not registered, feature shows "not enabled" message
453
+ - If service disabled in config, section is hidden
454
+ - No errors thrown for missing optional providers
408
455
 
409
456
  ### Administration Pages
410
457
 
411
458
  **AdministrationPageComponent** (`/administration`)
412
- - Tabbed interface: Users, Companies, Branches
413
- - Company/Branch tabs require company feature enabled
459
+ - Page title "Administration" with dynamic subtitle
460
+ - Horizontal scrolling tabs (hidden scrollbar on mobile)
461
+ - Tabs: Users, Companies, Branches
462
+ - Company/Branch tabs only visible when company feature enabled
414
463
 
415
464
  **User Management:**
416
465
  - `UserListComponent` - LazyLoad datatable with Edit, Delete, Permission actions
417
- - `UserFormComponent` - Create/edit user form
466
+ - `UserFormComponent` - Create/edit user with auto company/branch assignment
467
+ - Permission dialogs for company/branch assignments
418
468
 
419
469
  **Company Management:**
420
- - `CompanyListComponent` - Company CRUD
421
- - `CompanyFormComponent` - Create/edit company
470
+ - `CompanyListComponent` - Company CRUD with View Branches action
471
+ - `CompanyFormComponent` - Create/edit company with auto slug generation
422
472
 
423
473
  **Branch Management:**
424
- - `BranchListComponent` - Branch CRUD filtered by company
425
- - `BranchFormComponent` - Create/edit branch
474
+ - `BranchListComponent` - Branch CRUD filtered by company dropdown
475
+ - `BranchFormComponent` - Create/edit branch with company selection
476
+
477
+ **Table Features (all list components):**
478
+ - Responsive header (stacked on mobile)
479
+ - Horizontal scroll wrapper (`overflow-x-auto`)
480
+ - Conditional pagination (`[paginator]="total() > 0"`)
481
+ - Striped rows (`p-datatable-striped`)
482
+ - Lazy loading for performance
426
483
 
427
484
  ---
428
485
 
429
486
  ## Dialog Components
430
487
 
488
+ Both dialog components use Angular 21 patterns:
489
+ - `ChangeDetectionStrategy.OnPush`
490
+ - `input.required<T>()` for required inputs
491
+ - `output<void>()` for events
492
+ - `effect()` for reactive data loading
493
+ - Responsive breakpoints (`{ '960px': '90vw', '640px': '95vw' }`)
494
+
431
495
  ### UserCompanyPermissionDialogComponent
432
496
 
433
497
  Manages user-to-company assignments with checkboxes.
@@ -447,11 +511,21 @@ import { UserCompanyPermissionDialogComponent } from '@flusys/ng-auth';
447
511
  })
448
512
  ```
449
513
 
514
+ **Inputs/Outputs:**
515
+ ```typescript
516
+ readonly visible = input.required<boolean>();
517
+ readonly user = input.required<IUser | null>();
518
+ readonly closed = output<void>();
519
+ readonly permissionsChanged = output<void>();
520
+ ```
521
+
450
522
  **Features:**
451
523
  - Shows all companies with checkboxes
452
524
  - Pre-checks assigned companies
453
525
  - Immediate save on toggle (no Save button)
454
526
  - Toast notifications for success/error
527
+ - Loading spinner during data fetch
528
+ - Disabled checkboxes while saving
455
529
 
456
530
  ### UserBranchPermissionDialogComponent
457
531
 
@@ -473,13 +547,14 @@ import { UserBranchPermissionDialogComponent } from '@flusys/ng-auth';
473
547
  ```
474
548
 
475
549
  **Features:**
476
- - Shows only companies where user has company-level permissions
477
- - User selects company → branches load
550
+ - Company dropdown shows only user's permitted companies
551
+ - Branch list loads on company selection
478
552
  - Immediate save on toggle
553
+ - Contextual empty messages (no permissions vs no branches)
479
554
  - Scoped to user's permitted companies
480
555
 
481
556
  **Workflow:**
482
- 1. Dialog opens → Loads user's permitted companies
557
+ 1. Dialog opens → Loads user's permitted companies via effect()
483
558
  2. User selects company → Loads branches for that company
484
559
  3. User toggles branch checkboxes → Immediately assigns/revokes
485
560
 
@@ -513,6 +588,21 @@ import { ADMINISTRATION_ROUTES } from '@flusys/ng-auth';
513
588
  }
514
589
  ```
515
590
 
591
+ ### PROFILE_ROUTES
592
+
593
+ User profile page routes.
594
+
595
+ ```typescript
596
+ import { PROFILE_ROUTES } from '@flusys/ng-auth';
597
+
598
+ // Route: /profile
599
+ {
600
+ path: 'profile',
601
+ canActivate: [authGuard],
602
+ children: PROFILE_ROUTES
603
+ }
604
+ ```
605
+
516
606
  ---
517
607
 
518
608
  ## Setup
@@ -529,6 +619,8 @@ import {
529
619
  provideAuthLayoutIntegration,
530
620
  provideAuthProviders
531
621
  } from '@flusys/ng-auth';
622
+ import { provideIamProviders } from '@flusys/ng-iam';
623
+ import { provideStorageProviders } from '@flusys/ng-storage';
532
624
 
533
625
  export const appConfig: ApplicationConfig = {
534
626
  providers: [
@@ -542,7 +634,9 @@ export const appConfig: ApplicationConfig = {
542
634
  ),
543
635
  { provide: APP_CONFIG, useValue: DEFAULT_APP_CONFIG },
544
636
  ...provideAuthLayoutIntegration(),
545
- ...provideAuthProviders()
637
+ ...provideAuthProviders(),
638
+ ...provideIamProviders(), // Enables permissions display on profile page
639
+ ...provideStorageProviders(), // Enables profile picture upload
546
640
  ]
547
641
  };
548
642
  ```
@@ -551,7 +645,7 @@ export const appConfig: ApplicationConfig = {
551
645
 
552
646
  ```typescript
553
647
  import { Routes } from '@angular/router';
554
- import { authGuard, guestGuard, AUTH_ROUTES, ADMINISTRATION_ROUTES } from '@flusys/ng-auth';
648
+ import { authGuard, guestGuard, AUTH_ROUTES, ADMINISTRATION_ROUTES, PROFILE_ROUTES } from '@flusys/ng-auth';
555
649
 
556
650
  export const routes: Routes = [
557
651
  { path: 'auth', children: AUTH_ROUTES },
@@ -560,7 +654,8 @@ export const routes: Routes = [
560
654
  canActivate: [authGuard],
561
655
  children: [
562
656
  { path: 'dashboard', loadComponent: () => import('./dashboard.component') },
563
- { path: 'administration', children: ADMINISTRATION_ROUTES }
657
+ { path: 'administration', children: ADMINISTRATION_ROUTES },
658
+ { path: 'profile', children: PROFILE_ROUTES }
564
659
  ]
565
660
  }
566
661
  ];
@@ -665,6 +760,67 @@ interface IUserBranchPermission { id: string; userId: string; branchId: string;
665
760
 
666
761
  ## Best Practices
667
762
 
763
+ ### Angular 21 Component Patterns
764
+
765
+ All components in ng-auth follow these patterns:
766
+
767
+ ```typescript
768
+ @Component({
769
+ selector: 'lib-example',
770
+ standalone: true,
771
+ changeDetection: ChangeDetectionStrategy.OnPush, // Always OnPush
772
+ imports: [AngularModule, PrimeModule, FormField],
773
+ template: `...`
774
+ })
775
+ export class ExampleComponent {
776
+ // Dependency injection via inject()
777
+ private readonly service = inject(MyService);
778
+
779
+ // Signal-based state
780
+ readonly isLoading = signal(false);
781
+ readonly data = signal<IData | null>(null);
782
+
783
+ // Computed signals
784
+ readonly isValid = computed(() => this.data() !== null);
785
+
786
+ // Signal Forms
787
+ readonly formModel = signal<IFormModel>({ name: '', email: '' });
788
+ readonly formTree = form(this.formModel, (f) => {
789
+ required(f.name, { message: 'Name is required' });
790
+ });
791
+
792
+ // Error handling with proper typing
793
+ async onSubmit(): Promise<void> {
794
+ try {
795
+ // ...
796
+ } catch (error: unknown) {
797
+ const err = error as { error?: { message?: string } };
798
+ // Handle error
799
+ }
800
+ }
801
+ }
802
+ ```
803
+
804
+ ### Responsive Design Patterns
805
+
806
+ ```html
807
+ <!-- Responsive container -->
808
+ <div class="flex flex-col sm:flex-row justify-between items-start sm:items-center gap-3 p-4">
809
+
810
+ <!-- Two-column form grid -->
811
+ <form class="grid grid-cols-1 md:grid-cols-2 gap-4">
812
+
813
+ <!-- Right-aligned buttons -->
814
+ <div class="flex justify-end gap-4 md:col-span-2 pt-4">
815
+
816
+ <!-- Horizontally scrollable tabs -->
817
+ <div class="scrollbar-hide flex gap-1 overflow-x-auto flex-nowrap">
818
+
819
+ <!-- Responsive table wrapper -->
820
+ <div class="overflow-x-auto">
821
+ <p-table [scrollable]="true" [paginator]="total() > 0">
822
+ ```
823
+
668
824
  ### Component Organization
669
825
 
670
826
  ```
@@ -675,7 +831,14 @@ projects/ng-auth/
675
831
  ├── pages/ ← Route-specific pages
676
832
  │ ├── login/
677
833
  │ ├── register/
678
- │ └── users/
834
+ │ ├── forgot-password/
835
+ │ ├── reset-password/
836
+ │ ├── verify-email/
837
+ │ ├── profile/
838
+ │ ├── administration/
839
+ │ ├── users/
840
+ │ ├── company/
841
+ │ └── branch/
679
842
  ├── services/
680
843
  ├── guards/
681
844
  ├── interceptors/
@@ -704,6 +867,27 @@ const allCompanies = await companyApi.fetchList(); // Security risk
704
867
  - TokenRefreshInterceptor handles automatic refresh
705
868
  - Auto-persistence for company/branch selection
706
869
 
870
+ ### Type Safety
871
+
872
+ Avoid `any` types - use explicit interfaces:
873
+
874
+ ```typescript
875
+ // ✅ CORRECT - Explicit response type
876
+ .subscribe({
877
+ next: (result: { message?: string }) => {
878
+ this.messageService.add({
879
+ severity: 'success',
880
+ detail: result.message,
881
+ });
882
+ },
883
+ });
884
+
885
+ // ❌ WRONG - Using any
886
+ .subscribe({
887
+ next: (result: any) => { ... },
888
+ });
889
+ ```
890
+
707
891
  ### Multi-Company Flow
708
892
 
709
893
  1. Login → Check if `requiresSelection: true`
@@ -760,6 +944,143 @@ providers: [
760
944
 
761
945
  ---
762
946
 
947
+ ## No-IAM Mode (Bypassing Permission Guards)
948
+
949
+ When you don't use the `@flusys/ng-iam` module but your routes use `permissionGuard`, the guard will fail because no permissions are loaded.
950
+
951
+ **Solution:** Set wildcard permissions in `PermissionValidatorService` after login.
952
+
953
+ ### How Permission Guard Works
954
+
955
+ ```
956
+ Route with permissionGuard('user.view')
957
+ ↓
958
+ permissionGuard() → inject(PermissionValidatorService)
959
+ ↓
960
+ permissionValidator.isPermissionsLoaded() → false
961
+ ↓
962
+ Redirect to '/' ❌
963
+ ```
964
+
965
+ ### Fix: Set Wildcard Permissions After Login
966
+
967
+ #### Option 1: In Login Flow (Recommended)
968
+
969
+ Update your login handling to set permissions after successful authentication:
970
+
971
+ ```typescript
972
+ // login.component.ts or auth.service.ts
973
+ import { PermissionValidatorService } from '@flusys/ng-shared';
974
+
975
+ @Component({ ... })
976
+ export class LoginComponent {
977
+ private readonly permissionValidator = inject(PermissionValidatorService);
978
+ private readonly authApi = inject(AuthApiService);
979
+ private readonly authState = inject(AuthStateService);
980
+
981
+ async onLogin(): Promise<void> {
982
+ const response = await firstValueFrom(this.authApi.login(credentials));
983
+ this.authState.setLoginState(response);
984
+
985
+ // When IAM is NOT used, grant all permissions via wildcard
986
+ this.permissionValidator.setPermissions(['*']);
987
+
988
+ // Navigate to dashboard
989
+ this.router.navigate(['/dashboard']);
990
+ }
991
+ }
992
+ ```
993
+
994
+ #### Option 2: In APP_INITIALIZER
995
+
996
+ Set permissions at app startup:
997
+
998
+ ```typescript
999
+ // app.config.ts
1000
+ import { APP_INITIALIZER } from '@angular/core';
1001
+ import { PermissionValidatorService } from '@flusys/ng-shared';
1002
+
1003
+ export const appConfig: ApplicationConfig = {
1004
+ providers: [
1005
+ {
1006
+ provide: APP_INITIALIZER,
1007
+ useFactory: (permissionValidator: PermissionValidatorService) => {
1008
+ return () => {
1009
+ // Set wildcard permissions when IAM is disabled
1010
+ permissionValidator.setPermissions(['*']);
1011
+ };
1012
+ },
1013
+ deps: [PermissionValidatorService],
1014
+ multi: true,
1015
+ },
1016
+ // ... other providers
1017
+ ],
1018
+ };
1019
+ ```
1020
+
1021
+ #### Option 3: In AuthInitService (After Session Restore)
1022
+
1023
+ If you restore sessions on page refresh, set permissions there:
1024
+
1025
+ ```typescript
1026
+ // After successful /auth/me response
1027
+ authInit.initialize().subscribe({
1028
+ next: (user) => {
1029
+ // Session restored, set wildcard permissions
1030
+ permissionValidator.setPermissions(['*']);
1031
+ },
1032
+ });
1033
+ ```
1034
+
1035
+ ### Wildcard Permission Syntax
1036
+
1037
+ The `permissionGuard` utilities support these patterns:
1038
+
1039
+ | Pattern | Meaning |
1040
+ |---------|---------|
1041
+ | `*` | Full access to everything |
1042
+ | `user.*` | All user permissions |
1043
+ | `branch.*` | All branch permissions |
1044
+
1045
+ ### For Jest Tests
1046
+
1047
+ Mock `PermissionValidatorService` in your test setup:
1048
+
1049
+ ```typescript
1050
+ import { PermissionValidatorService } from '@flusys/ng-shared';
1051
+
1052
+ describe('MyComponent', () => {
1053
+ beforeEach(async () => {
1054
+ await TestBed.configureTestingModule({
1055
+ imports: [MyComponent],
1056
+ providers: [
1057
+ {
1058
+ provide: PermissionValidatorService,
1059
+ useValue: {
1060
+ permissions: () => ['*'],
1061
+ isPermissionsLoaded: () => true,
1062
+ hasPermission: () => true,
1063
+ setPermissions: jest.fn(),
1064
+ clearPermissions: jest.fn(),
1065
+ },
1066
+ },
1067
+ ],
1068
+ }).compileComponents();
1069
+ });
1070
+ });
1071
+ ```
1072
+
1073
+ ### For Template Permission Checks
1074
+
1075
+ If using `*hasPermission` directive, it will also respect wildcard:
1076
+
1077
+ ```html
1078
+ <!-- This will show when permissions include '*' -->
1079
+ <button *hasPermission="'user.delete'">Delete User</button>
1080
+ ```
1081
+
1082
+ ---
1083
+
763
1084
  ## See Also
764
1085
 
765
1086
  - [CORE-GUIDE.md](./CORE-GUIDE.md) - APP_CONFIG configuration
@@ -770,5 +1091,25 @@ providers: [
770
1091
 
771
1092
  ---
772
1093
 
773
- **Last Updated:** 2026-02-15
1094
+ **Last Updated:** 2026-02-21
774
1095
  **Angular Version:** 21
1096
+
1097
+ ## Component Checklist
1098
+
1099
+ | Component | OnPush | Signal Forms | Responsive | Dark Mode |
1100
+ |-----------|--------|--------------|------------|-----------|
1101
+ | LoginPageComponent | ✅ | ✅ | ✅ | ✅ |
1102
+ | RegisterPageComponent | ✅ | ✅ | ✅ | ✅ |
1103
+ | ForgotPasswordPageComponent | ✅ | ✅ | ✅ | ✅ |
1104
+ | ResetPasswordPageComponent | ✅ | ✅ | ✅ | ✅ |
1105
+ | VerifyEmailPageComponent | ✅ | ✅ | ✅ | ✅ |
1106
+ | ProfilePageComponent | ✅ | ✅ | ✅ | ✅ |
1107
+ | AdministrationPageComponent | ✅ | N/A | ✅ | ✅ |
1108
+ | UserListComponent | ✅ | N/A | ✅ | ✅ |
1109
+ | UserFormComponent | ✅ | ✅ | ✅ | ✅ |
1110
+ | CompanyListComponent | ✅ | N/A | ✅ | ✅ |
1111
+ | CompanyFormComponent | ✅ | ✅ | ✅ | ✅ |
1112
+ | BranchListComponent | ✅ | N/A | ✅ | ✅ |
1113
+ | BranchFormComponent | ✅ | ✅ | ✅ | ✅ |
1114
+ | UserCompanyPermissionDialogComponent | ✅ | N/A | ✅ | ✅ |
1115
+ | UserBranchPermissionDialogComponent | ✅ | N/A | ✅ | ✅ |