feat: add DCB User Service API - Authentication system with KEYCLOAK - Modular architecture with services for each feature

This commit is contained in:
diallolatoile 2025-11-03 21:07:19 +00:00
parent d586552944
commit 35f0bcd135
25 changed files with 183 additions and 168 deletions

View File

@ -34,7 +34,7 @@ export const routes: Routes = [
// ===== REDIRECTIONS POUR LES ERREURS =====
{ path: '404', redirectTo: '/error/404' },
{ path: '403', redirectTo: '/error/403' },
{ path: '401', redirectTo: '/auth/sign-in' },
{ path: '401', redirectTo: '/auth/login' },
{ path: 'unauthorized', redirectTo: '/error/403' },
// ===== CATCH-ALL =====

View File

@ -25,23 +25,32 @@ export class App implements OnInit {
async ngOnInit(): Promise<void> {
try {
// Initialiser l'authentification avec gestion d'erreur
const isAuthenticated = await this.authService.initialize();
console.log('Authentication initialized:', isAuthenticated);
if (!isAuthenticated) {
console.log('👤 User not authenticated, may redirect to login');
// Note: Votre AuthService gère déjà la redirection dans logout()
if (!isAuthenticated && this.router.url === '/') {
this.router.navigate(['/auth/login']);
} else if (isAuthenticated && this.router.url === '/') {
this.router.navigate(['/dcb-dashboard']);
}
} catch (error) {
console.error('Error during authentication initialization:', error);
this.router.navigate(['/auth/login']);
}
// Configurer le titre de la page
this.setupTitleListener();
}
private checkPublicRouteRedirection(): void {
const currentUrl = this.router.url;
const publicRoutes = ['/auth/login', '/auth/reset-password', '/auth/forgot-password'];
// Si l'utilisateur est authentifié et sur une route publique, rediriger vers la page d'accueil
if (publicRoutes.includes(currentUrl)) {
this.router.navigate(['/dcb-dashboard']);
}
}
private setupTitleListener(): void {
this.router.events
.pipe(

View File

@ -10,14 +10,11 @@ export const authGuard: CanActivateFn = (route: ActivatedRouteSnapshot, state: R
const roleService = inject(RoleService);
const router = inject(Router);
console.log('🔐 AuthGuard check for:', state.url);
// Attendre que l'initialisation soit terminée
return authService.getInitializedState().pipe(
switchMap(initialized => {
if (!initialized) {
console.log('⏳ AuthService pas encore initialisé, attente...');
return of(false); // Bloquer en attendant l'initialisation
return of(false);
}
// Vérifier l'authentification
@ -28,29 +25,24 @@ export const authGuard: CanActivateFn = (route: ActivatedRouteSnapshot, state: R
// Tentative de rafraîchissement du token
const refreshToken = authService.getRefreshToken();
if (refreshToken) {
console.log('🔄 Token expiré, tentative de rafraîchissement...');
return authService.refreshToken().pipe(
tap(() => {
console.log('✅ Token rafraîchi avec succès');
roleService.refreshRoles();
}),
map(() => checkRoleAccess(route, roleService, router, state.url)),
catchError((error) => {
console.error('❌ Échec du rafraîchissement du token:', error);
authService.logout().subscribe();
return of(redirectToLogin(router, state.url, 'session_expired'));
return of(redirectToLogin(router, state.url));
})
);
}
// Redirection vers login
console.log('🔒 Redirection vers login depuis:', state.url);
return of(redirectToLogin(router, state.url, 'not_authenticated'));
return of(redirectToLogin(router, state.url));
}),
catchError(error => {
console.error('❌ Erreur dans le guard d\'auth:', error);
return of(redirectToLogin(router, state.url, 'not_authenticated'));
return of(redirectToLogin(router, state.url));
})
);
}
@ -67,7 +59,6 @@ function checkRoleAccess(
const requiredRoles = route.data?.['roles'] as string[];
if (!requiredRoles || requiredRoles.length === 0) {
console.log('✅ Accès autorisé (aucun rôle requis):', currentUrl);
return true;
}
@ -75,17 +66,9 @@ function checkRoleAccess(
const currentUserRoles = roleService.getCurrentUserRoles();
if (hasRequiredRole) {
console.log('✅ Accès autorisé avec rôles:', currentUrl);
console.log(' Rôles requis:', requiredRoles);
console.log(' Rôles actuels:', currentUserRoles);
return true;
}
console.warn('❌ Accès refusé: rôles insuffisants');
console.warn(' URL demandée:', currentUrl);
console.warn(' Rôles requis:', requiredRoles);
console.warn(' Rôles actuels:', currentUserRoles);
// Rediriger vers la page non autorisée
router.navigate(['/unauthorized'], {
queryParams: {
@ -105,19 +88,13 @@ function checkRoleAccess(
function redirectToLogin(
router: Router,
returnUrl: string,
reason: 'not_authenticated' | 'session_expired'
): boolean {
const queryParams: any = {
returnUrl: returnUrl,
reason: reason
returnUrl: returnUrl
};
// Message spécifique selon la raison
if (reason === 'session_expired') {
queryParams.message = 'Votre session a expiré. Veuillez vous reconnecter.';
}
router.navigate(['/auth/sign-in'], {
router.navigate(['/auth/login'], {
queryParams,
replaceUrl: true
});

View File

@ -10,7 +10,6 @@ export const publicGuard: CanActivateFn = () => {
// Si l'utilisateur est déjà authentifié, le rediriger vers le dashboard
if (authService.isAuthenticated()) {
console.log('🔄 Utilisateur déjà authentifié, redirection vers le dashboard');
router.navigate(['/dcb-dashboard'], { replaceUrl: true });
return false;
}
@ -18,16 +17,12 @@ export const publicGuard: CanActivateFn = () => {
// Vérifier si un refresh token est disponible
const refreshToken = authService.getRefreshToken();
if (refreshToken) {
console.log('🔄 Token de rafraîchissement détecté, tentative de reconnexion...');
return authService.refreshToken().pipe(
map(() => {
console.log('✅ Reconnexion automatique réussie');
router.navigate(['/dcb-dashboard'], { replaceUrl: true });
return false;
}),
catchError((error) => {
console.error('❌ Échec de la reconnexion automatique:', error);
// En cas d'erreur, autoriser l'accès à la page publique
return of(true);
})
@ -35,6 +30,5 @@ export const publicGuard: CanActivateFn = () => {
}
// L'utilisateur n'est pas connecté, autoriser l'accès à la page publique
console.log('🌐 Accès public autorisé');
return true;
};

View File

@ -10,7 +10,6 @@ export const roleGuard: CanActivateFn = (route: ActivatedRouteSnapshot, state) =
// Vérifier d'abord l'authentification
if (!authService.isAuthenticated()) {
console.log('RoleGuard: User not authenticated, redirecting to login');
router.navigate(['/auth/login'], {
queryParams: {
returnUrl: state.url,
@ -24,26 +23,16 @@ export const roleGuard: CanActivateFn = (route: ActivatedRouteSnapshot, state) =
const userRoles = authService.getCurrentUserRoles();
if (!userRoles || userRoles.length === 0) {
console.warn('RoleGuard: User has no roles');
router.navigate(['/unauthorized'], {
queryParams: { reason: 'no_roles' }
});
router.navigate(['/unauthorized']);
return false;
}
const modulePath = getModulePath(route);
console.log('🔐 RoleGuard check:', {
module: modulePath,
userRoles: userRoles,
url: state.url
});
// Vérifier les permissions
const hasAccess = permissionsService.canAccessModule(modulePath, userRoles);
if (!hasAccess) {
console.warn('❌ RoleGuard: Access denied for', modulePath, 'User roles:', userRoles);
router.navigate(['/unauthorized'], {
queryParams: {
module: modulePath,
@ -54,7 +43,6 @@ export const roleGuard: CanActivateFn = (route: ActivatedRouteSnapshot, state) =
return false;
}
console.log('✅ RoleGuard: Access granted for', modulePath);
return true;
};
@ -92,8 +80,6 @@ function buildPathFromUrl(route: ActivatedRouteSnapshot): string {
}
function getRequiredRolesForModule(permissionsService: PermissionsService, modulePath: string): string[] {
// Cette fonction récupère les rôles requis pour un module donné
// Vous devrez peut-être l'adapter selon votre implémentation PermissionsService
const [mainModule] = modulePath.split('/');
const permission = (permissionsService as any).findPermission?.(mainModule);
return permission?.roles || [];

View File

@ -55,7 +55,7 @@ function handle401Error(
}),
catchError((refreshError) => {
authService.logout().subscribe();
router.navigate(['/auth/sign-in']);
router.navigate(['/auth/login']);
return throwError(() => refreshError);
})
);

View File

@ -38,7 +38,14 @@ export interface UserProfileDto {
firstName: string;
lastName: string;
roles: string[];
enabled: boolean;
emailVerified: boolean;
merchantPartnerId: string;
createdBy: string;
createdByUsername: string;
createdTimestamp: number;
lastLogin?: number;
userType: string;
}
export interface TokenValidationResponseDto {
@ -74,36 +81,29 @@ export class AuthService {
* Initialise l'authentification au démarrage de l'application
*/
async initialize(): Promise<boolean> {
console.log('🔄 Initialisation du service d\'authentification...');
try {
const token = this.getAccessToken();
if (!token) {
console.log('🔍 Aucun token trouvé, utilisateur non authentifié');
this.initialized$.next(true);
return false;
}
if (this.isTokenExpired(token)) {
console.log('⚠️ Token expiré, tentative de rafraîchissement...');
const refreshSuccess = await this.tryRefreshToken();
this.initialized$.next(true);
return refreshSuccess;
}
// Token valide, vérifier le profil utilisateur
console.log('✅ Token valide détecté, vérification du profil...');
await this.loadUserProfile().toPromise();
this.authState$.next(true);
this.initialized$.next(true);
console.log('🎯 Authentification initialisée avec succès');
return true;
} catch (error) {
console.error('❌ Erreur lors de l\'initialisation de l\'auth:', error);
this.clearAuthData();
this.initialized$.next(true);
return false;
@ -117,17 +117,14 @@ export class AuthService {
const refreshToken = this.getRefreshToken();
if (!refreshToken) {
console.log('🔍 Aucun refresh token disponible');
return false;
}
try {
// Convertir l'Observable en Promise pour l'initialisation
const response = await this.refreshToken().toPromise();
console.log('✅ Token rafraîchi avec succès lors de l\'initialisation');
return true;
} catch (error) {
console.error('❌ Échec du rafraîchissement du token:', error);
this.clearAuthData();
return false;
}
@ -174,10 +171,8 @@ export class AuthService {
).pipe(
tap(response => {
this.handleLoginSuccess(response);
console.log('🔄 Token rafraîchi avec succès');
}),
catchError(error => {
console.error('❌ Échec du rafraîchissement du token:', error);
this.clearAuthData();
return throwError(() => error);
})
@ -194,7 +189,6 @@ export class AuthService {
).pipe(
tap(() => {
this.clearAuthData();
console.log('👋 Déconnexion réussie');
}),
catchError(error => {
this.clearAuthData(); // Nettoyer même en cas d'erreur
@ -212,10 +206,8 @@ export class AuthService {
).pipe(
tap(profile => {
this.userProfile$.next(profile);
console.log('👤 Profil utilisateur chargé:', profile.username);
}),
catchError(error => {
console.error('❌ Erreur lors du chargement du profil:', error);
return throwError(() => error);
})
);
@ -233,7 +225,6 @@ export class AuthService {
}
this.authState$.next(true);
console.log('✅ Connexion réussie');
}
}
@ -245,7 +236,6 @@ export class AuthService {
localStorage.removeItem(this.refreshTokenKey);
this.authState$.next(false);
this.userProfile$.next(null);
console.log('🧹 Données d\'authentification nettoyées');
}
/**

View File

@ -124,6 +124,7 @@ export class MenuService {
];
}
// Mettez à jour votre méthode
private getFullUserDropdown(): UserDropdownItemType[] {
return [
{ label: 'Welcome back!', isHeader: true },
@ -132,9 +133,8 @@ export class MenuService {
{ label: 'Support Center', icon: 'tablerHeadset', url: '/support' },
{ isDivider: true },
{
label: 'Log Out',
label: 'Déconnexion',
icon: 'tablerLogout2',
url: '/auth/logout',
class: 'fw-semibold text-danger'
},
];

View File

@ -22,6 +22,18 @@ export class PermissionsService {
],
},
{
module: 'auth',
roles: [
'dcb-admin',
'dcb-partner',
'dcb-support',
'dcb-partner-admin',
'dcb-partner-manager',
'dcb-partner-support'
],
},
// Transactions
{
module: 'transactions',

View File

@ -303,6 +303,9 @@ export class RoleManagementService {
return 'bg-info';
case UserRole.DCB_PARTNER:
return 'bg-success';
case UserRole.DCB_PARTNER_ADMIN: return 'bg-danger';
case UserRole.DCB_PARTNER_MANAGER: return 'bg-success';
case UserRole.DCB_PARTNER_SUPPORT: return 'bg-info';
default:
return 'bg-secondary';
}
@ -319,6 +322,12 @@ export class RoleManagementService {
return 'lucideHeadphones';
case UserRole.DCB_PARTNER:
return 'lucideBuilding';
case UserRole.DCB_PARTNER_ADMIN:
return 'lucideShield';
case UserRole.DCB_PARTNER_MANAGER:
return 'lucideUserCog';
case UserRole.DCB_PARTNER_SUPPORT:
return 'lucideHeadphones';
default:
return 'lucideUser';
}

View File

@ -33,10 +33,9 @@ export const userDropdownItems: UserDropdownItemType[] = [
isDivider: true,
},
{
label: 'Log Out',
label: 'Déconnexion',
icon: 'tablerLogout2',
url: '/auth/logout',
class: 'fw-semibold',
},
]
@ -112,16 +111,6 @@ export const menuItems: MenuItemType[] = [
{ label: 'Liste des Utilisateurs', url: '/users' },
],
},
{
label: 'Authentification',
icon: 'lucideFingerprint',
isCollapsed: true,
children: [
{ label: 'Login / Logout', url: '/auth/login' },
{ label: 'Réinitialisation Mot de Passe', url: '/auth/reset-password' },
{ label: 'Gestion des Sessions', url: '/auth/sessions' },
],
},
// ---------------------------
// Paramètres & Intégrations

View File

@ -28,20 +28,6 @@
<!-- Élément normal -->
@if (!item.isHeader && !item.isDivider) {
@if (item.label === 'Log Out') {
<!-- Bouton Logout avec appel de méthode -->
<button
class="dropdown-item fw-semibold text-danger"
(click)="logout()">
<ng-icon
name="tablerLogout2"
size="17"
class="align-middle d-inline-flex align-items-center me-2"
/>
<span class="align-middle">Log Out</span>
</button>
} @else {
<!-- Autres items avec navigation normale -->
<a
[routerLink]="item.url"
class="dropdown-item"
@ -56,7 +42,6 @@
<span class="align-middle">{{ item.label }}</span>
</a>
}
}
</div>
}
</div>

View File

@ -45,14 +45,4 @@ export class UserProfile implements OnInit, OnDestroy {
private loadDropdownItems() {
this.menuItems = this.menuService.getUserDropdownItems()
}
logout() {
this.authService.logout()
}
handleItemClick(item: UserDropdownItemType) {
if (item.label === 'Log Out') {
this.logout()
}
}
}

View File

@ -1,13 +1,14 @@
import { Routes } from '@angular/router'
import { SignIn } from '@/app/modules/auth/sign-in'
import { Login } from '@/app/modules/auth/login'
import { Logout } from '@/app/modules/auth/logout'
import { ResetPassword } from '@/app/modules/auth/reset-password'
import { NewPassword } from '@/app/modules/auth/new-password'
import { publicGuard } from '@core/guards/public.guard';
export const AUTH_ROUTES: Routes = [
{
path: 'sign-in',
component: SignIn,
path: 'login',
component: Login,
canActivate: [publicGuard],
data: {
title: 'Connexion - DCB',
@ -35,7 +36,10 @@ export const AUTH_ROUTES: Routes = [
},
{
path: 'logout',
redirectTo: 'sign-in',
data: { module: 'auth' }
component: Logout,
data: {
title: 'Déconnexion - DCB',
module: 'auth'
}
}
];

View File

@ -1,4 +1,4 @@
// src/app/modules/auth/sign-in/sign-in.ts
// src/app/modules/auth/login/sign-in.ts
import { Component, inject, ChangeDetectorRef, OnInit, OnDestroy } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule, NgForm } from '@angular/forms';
@ -11,7 +11,7 @@ import { PasswordStrengthBar } from '@app/components/password-strength-bar';
import { appName, credits, currentYear } from '@/app/constants';
@Component({
selector: 'app-sign-in',
selector: 'app-login',
standalone: true,
imports: [FormsModule, CommonModule, RouterLink, AppLogo, PasswordStrengthBar],
template: `
@ -149,7 +149,7 @@ import { appName, credits, currentYear } from '@/app/constants';
`,
styles: []
})
export class SignIn implements OnInit, OnDestroy {
export class Login implements OnInit, OnDestroy {
protected readonly appName = appName;
protected readonly currentYear = currentYear;
protected readonly credits = credits;
@ -258,16 +258,18 @@ export class SignIn implements OnInit, OnDestroy {
const userRoles = this.roleService.getCurrentUserRoles();
// Logique de redirection selon les rôles
if (userRoles.includes('dcb-admin')) {
this.router.navigate(['/admin/dcb-dashboard']);
/*if (userRoles.includes('dcb-admin')) {
this.router.navigate(['/dcb-dashboard']);
} else if (userRoles.includes('dcb-partner-admin')) {
this.router.navigate(['/partner/dcb-dashboard']);
this.router.navigate(['/dcb-dashboard']);
} else if (userRoles.includes('dcb-support')) {
this.router.navigate(['/support/dcb-dashboard']);
} else {
// Route par défaut
this.router.navigate(['/dcb-dashboard']);
}
}*/
// Route par défaut
this.router.navigate(['/dcb-dashboard']);
}
/**

View File

@ -0,0 +1,51 @@
// app/modules/auth/logout/logout.component.ts
import { Component, OnInit } from '@angular/core';
import { Router } from '@angular/router';
import { AuthService } from '@core/services/auth.service';
@Component({
template: `
<div class="logout-container">
<div class="spinner-border text-primary" role="status">
<span class="visually-hidden">Déconnexion en cours...</span>
</div>
<p class="mt-2">Déconnexion en cours...</p>
</div>
`,
styles: [`
.logout-container {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100vh;
}
`]
})
export class Logout implements OnInit {
constructor(
private authService: AuthService,
private router: Router
) {}
ngOnInit() {
this.logout();
}
private logout(): void {
this.authService.logout().subscribe({
next: () => {
// Redirection vers la page de login après déconnexion
this.router.navigate(['/auth/login'], {
queryParams: { logout: 'success' }
});
},
error: (error) => {
console.error('Erreur lors de la déconnexion:', error);
// Rediriger même en cas d'erreur
this.router.navigate(['/auth/login']);
}
});
}
}

View File

@ -136,7 +136,7 @@ import { NgOtpInputModule } from 'ng-otp-input'
<p class="text-muted text-center mb-0">
Return to
<a
routerLink="/auth/sign-in"
routerLink="/auth/login"
class="text-decoration-underline link-offset-3 fw-semibold"
>Sign in</a
>

View File

@ -63,7 +63,7 @@ import { AppLogo } from '@app/components/app-logo'
<p class="text-muted text-center mt-4 mb-0">
Return to
<a
routerLink="/auth/sign-in"
routerLink="/auth/login"
class="text-decoration-underline link-offset-3 fw-semibold"
>Sign in</a
>

View File

@ -66,7 +66,7 @@ import { credits, currentYear } from '@/app/constants'
<p class="text-muted text-center mb-0">
Return to
<a
routerLink="/auth/sign-in"
routerLink="/auth/login"
class="text-decoration-underline link-offset-3 fw-semibold"
>Sign in</a
>

View File

@ -251,8 +251,4 @@ const routes: Routes = [
imports: [RouterModule.forChild(routes)],
exports: [RouterModule],
})
export class ModulesRoutes {
constructor() {
console.log('Modules routes loaded:', routes);
}
}
export class ModulesRoutes {}

View File

@ -22,7 +22,7 @@
@if (user) {
{{ getUserDisplayName() }}
} @else {
Profil
Profile
}
</li>
</ol>
@ -178,13 +178,29 @@
<div class="card-body">
<!-- Rôle actuel -->
<div class="text-center mb-3">
<span class="badge d-flex align-items-center justify-content-center" [ngClass]="getRoleBadgeClass(user.role)">
<ng-icon [name]="getRoleIcon(user.role)" class="me-2"></ng-icon>
{{ getRoleLabel(user.role) }}
<!-- Afficher le premier rôle ou tous les rôles -->
@if (user.roles && user.roles.length > 0) {
@for (role of user.roles; track role; let first = $first) {
@if (first) { <!-- Afficher seulement le premier rôle pour l'instant -->
<span class="badge d-flex align-items-center justify-content-center" [ngClass]="getRoleBadgeClass(role)">
<ng-icon [name]="getRoleIcon(role)" class="me-2"></ng-icon>
{{ getRoleLabel(role) }}
</span>
<small class="text-muted d-block mt-1">
{{ getRoleDescription(user.role) }}
{{ getRoleDescription(role) }}
</small>
}
}
<!-- Indicateur si plusieurs rôles -->
@if (user.roles.length > 1) {
<small class="text-muted">
+ {{ user.roles.length - 1 }} autre(s) rôle(s)
</small>
}
} @else {
<span class="badge bg-secondary">Aucun rôle</span>
}
</div>
<!-- Changement de rôle -->
@ -193,7 +209,7 @@
<label class="form-label fw-semibold">Changer le rôle</label>
<select
class="form-select"
[value]="user.role"
[value]="user.roles[0]"
(change)="updateUserRole($any($event.target).value)"
[disabled]="updatingRoles"
>
@ -201,12 +217,12 @@
@for (role of availableRoles; track role.value) {
<option
[value]="role.value"
[disabled]="!canAssignRole(role.value) || role.value === user.role"
[disabled]="!canAssignRole(role.value) || role.value === user.roles[0]"
>
{{ role.label }}
@if (!canAssignRole(role.value)) {
(Non autorisé)
} @else if (role.value === user.role) {
} @else if (role.value === user.roles[0]) {
(Actuel)
}
</option>

View File

@ -101,7 +101,10 @@ export class MyProfile implements OnInit, OnDestroy {
this.availableRoles = [
{ value: UserRole.DCB_ADMIN, label: 'DCB Admin', description: 'Administrateur système' },
{ value: UserRole.DCB_SUPPORT, label: 'DCB Support', description: 'Support technique' },
{ value: UserRole.DCB_PARTNER, label: 'DCB Partner', description: 'Partenaire commercial' }
{ value: UserRole.DCB_PARTNER, label: 'DCB Partner', description: 'Partenaire commercial' },
{ value: UserRole.DCB_PARTNER_ADMIN, label: 'DCB Partner Admin', description: 'Admin Partenaire commercial' },
{ value: UserRole.DCB_PARTNER_MANAGER, label: 'DCB Partner Manager', description: 'Manager Partenaire commercial' },
{ value: UserRole.DCB_PARTNER_SUPPORT, label: 'DCB Partner Support', description: 'Support Partenaire commercial' }
];
}
});
@ -111,11 +114,11 @@ export class MyProfile implements OnInit, OnDestroy {
this.loading = true;
this.error = '';
this.usersService.getUserById(this.user.id)
this.authService.getProfile()
.pipe(takeUntil(this.destroy$))
.subscribe({
next: (user) => {
this.user = user;
next: (profile) => {
this.user = profile;
this.loading = false;
this.cdRef.detectChanges();
},

View File

@ -55,7 +55,10 @@ export interface PaginatedUserResponse {
export enum UserRole {
DCB_ADMIN = 'dcb-admin',
DCB_SUPPORT = 'dcb-support',
DCB_PARTNER = 'dcb-partner'
DCB_PARTNER = 'dcb-partner',
DCB_PARTNER_ADMIN = 'dcb-partner-admin',
DCB_PARTNER_MANAGER = 'dcb-partner-manager',
DCB_PARTNER_SUPPORT = 'dcb-partner-support'
}
@Injectable({ providedIn: 'root' })

View File

@ -54,13 +54,13 @@ export type MenuItemType = {
isCollapsed?: boolean
}
// types/layout.ts - Ajoutez ce type
export type UserDropdownItemType = {
label?: string;
icon?: string;
url?: string;
isDivider?: boolean;
isHeader?: boolean;
isLogout?: boolean;
class?: string;
target?: string;
isDisabled?: boolean;

View File

@ -8,7 +8,6 @@ bootstrapApplication(App, {
...appConfig.providers,
]
}).then(async appRef => {
console.log('Application BO Admin completed.');
const authService = appRef.injector.get(AuthService);
await authService.initialize();
}).catch(err => console.error('BO Admin error', err));