refs #2089 and #2091 Implement AuthService for user authentication and role management
testing/ogGui-multibranch/pipeline/head There was a failure building this commit
Details
testing/ogGui-multibranch/pipeline/head There was a failure building this commit
Details
parent
4e6dbde59c
commit
c7ed41a1a5
|
@ -29,6 +29,7 @@ import { MatMenuTrigger } from '@angular/material/menu';
|
||||||
import { ClientDetailsComponent } from './shared/client-details/client-details.component';
|
import { ClientDetailsComponent } from './shared/client-details/client-details.component';
|
||||||
import { PartitionTypeOrganizatorComponent } from './shared/partition-type-organizator/partition-type-organizator.component';
|
import { PartitionTypeOrganizatorComponent } from './shared/partition-type-organizator/partition-type-organizator.component';
|
||||||
import { ClientTaskLogsComponent } from '../task-logs/client-task-logs/client-task-logs.component';
|
import { ClientTaskLogsComponent } from '../task-logs/client-task-logs/client-task-logs.component';
|
||||||
|
import { AuthService } from '@services/auth.service';
|
||||||
|
|
||||||
enum NodeType {
|
enum NodeType {
|
||||||
OrganizationalUnit = 'organizational-unit',
|
OrganizationalUnit = 'organizational-unit',
|
||||||
|
@ -114,6 +115,7 @@ export class GroupsComponent implements OnInit, OnDestroy {
|
||||||
private joyrideService: JoyrideService,
|
private joyrideService: JoyrideService,
|
||||||
private breakpointObserver: BreakpointObserver,
|
private breakpointObserver: BreakpointObserver,
|
||||||
private toastr: ToastrService,
|
private toastr: ToastrService,
|
||||||
|
private auth: AuthService,
|
||||||
private configService: ConfigService,
|
private configService: ConfigService,
|
||||||
private cd: ChangeDetectorRef,
|
private cd: ChangeDetectorRef,
|
||||||
) {
|
) {
|
||||||
|
@ -132,7 +134,7 @@ export class GroupsComponent implements OnInit, OnDestroy {
|
||||||
);
|
);
|
||||||
|
|
||||||
this.treeDataSource = new MatTreeFlatDataSource(this.treeControl, this.treeFlattener);
|
this.treeDataSource = new MatTreeFlatDataSource(this.treeControl, this.treeFlattener);
|
||||||
this.currentView = localStorage.getItem('groupsView') || 'list';
|
this.currentView = this.auth.groupsView || 'list';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -3,10 +3,10 @@ import { Component, signal } from '@angular/core';
|
||||||
import { Router } from '@angular/router';
|
import { Router } from '@angular/router';
|
||||||
import { TranslateService } from '@ngx-translate/core';
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
import { ToastrService } from "ngx-toastr";
|
import { ToastrService } from "ngx-toastr";
|
||||||
import { jwtDecode } from "jwt-decode";
|
|
||||||
import { ConfigService } from '@services/config.service';
|
import { ConfigService } from '@services/config.service';
|
||||||
import { MatDialog } from '@angular/material/dialog';
|
import { MatDialog } from '@angular/material/dialog';
|
||||||
import { GlobalStatusComponent } from '../global-status/global-status.component'
|
import { GlobalStatusComponent } from '../global-status/global-status.component'
|
||||||
|
import { AuthService } from '@services/auth.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-login',
|
selector: 'app-login',
|
||||||
|
@ -20,8 +20,6 @@ export class LoginComponent {
|
||||||
};
|
};
|
||||||
errorMessage: string = '';
|
errorMessage: string = '';
|
||||||
isLoading: boolean = false;
|
isLoading: boolean = false;
|
||||||
decodedToken: any;
|
|
||||||
|
|
||||||
baseUrl: string;
|
baseUrl: string;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
|
@ -29,6 +27,7 @@ export class LoginComponent {
|
||||||
private router: Router,
|
private router: Router,
|
||||||
private configService: ConfigService,
|
private configService: ConfigService,
|
||||||
private toastService: ToastrService,
|
private toastService: ToastrService,
|
||||||
|
private auth: AuthService,
|
||||||
private translateService: TranslateService,
|
private translateService: TranslateService,
|
||||||
private dialog: MatDialog
|
private dialog: MatDialog
|
||||||
) {
|
) {
|
||||||
|
@ -62,13 +61,8 @@ export class LoginComponent {
|
||||||
next: (res: any) => {
|
next: (res: any) => {
|
||||||
if (res.token) {
|
if (res.token) {
|
||||||
localStorage.setItem('loginToken', res.token);
|
localStorage.setItem('loginToken', res.token);
|
||||||
localStorage.setItem('refreshToken', res.refreshToken);
|
this.auth.refresh();
|
||||||
localStorage.setItem('username', this.loginObj.username);
|
this.openSnackBar(false, 'Bienvenido ' + this.auth.username);
|
||||||
|
|
||||||
this.decodedToken = jwtDecode(res.token);
|
|
||||||
localStorage.setItem('groupsView', this.decodedToken.groupsView);
|
|
||||||
|
|
||||||
this.openSnackBar(false, 'Bienvenido ' + this.loginObj.username);
|
|
||||||
this.router.navigateByUrl('/groups');
|
this.router.navigateByUrl('/groups');
|
||||||
this.dialog.open(GlobalStatusComponent, {
|
this.dialog.open(GlobalStatusComponent, {
|
||||||
width: '45vw',
|
width: '45vw',
|
||||||
|
|
|
@ -18,17 +18,17 @@
|
||||||
{{ 'GlobalStatus' | translate }}
|
{{ 'GlobalStatus' | translate }}
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button class="ordinary-button" *ngIf="isSuperAdmin" [matMenuTriggerFor]="menu"
|
<button class="ordinary-button" *ngIf="auth.userCategory === 'super-admin'" [matMenuTriggerFor]="menu"
|
||||||
matTooltip="Gestión de usuarios y roles de la aplicación" matTooltipShowDelay="1000">
|
matTooltip="Gestión de usuarios y roles de la aplicación" matTooltipShowDelay="1000">
|
||||||
{{ 'Administration' | translate }}
|
{{ 'Administration' | translate }}
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button class="ordinary-button" *ngIf="!isSuperAdmin" (click)="editUser()"
|
<button class="ordinary-button" *ngIf="auth.userCategory === 'super-admin'" (click)="editUser()"
|
||||||
matTooltip="Editar tu información de usuario" matTooltipShowDelay="1000">
|
matTooltip="Editar tu información de usuario" matTooltipShowDelay="1000">
|
||||||
{{ 'changePassword' | translate }}
|
{{ 'changePassword' | translate }}
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button class="logout-button" routerLink="/auth/login" matTooltip="Cerrar sesión y salir de la aplicación"
|
<button class="logout-button" (click)="logOut()" matTooltip="Cerrar sesión y salir de la aplicación"
|
||||||
matTooltipShowDelay="1000">
|
matTooltipShowDelay="1000">
|
||||||
{{ 'logout' | translate }}
|
{{ 'logout' | translate }}
|
||||||
</button>
|
</button>
|
||||||
|
@ -47,10 +47,10 @@
|
||||||
<button mat-menu-item (click)="showGlobalStatus()">
|
<button mat-menu-item (click)="showGlobalStatus()">
|
||||||
{{ 'GlobalStatus' | translate }}
|
{{ 'GlobalStatus' | translate }}
|
||||||
</button>
|
</button>
|
||||||
<button mat-menu-item *ngIf="isSuperAdmin" [matMenuTriggerFor]="menu">
|
<button mat-menu-item *ngIf="auth.userCategory === 'super-admin'" [matMenuTriggerFor]="menu">
|
||||||
{{ 'Administration' | translate }}
|
{{ 'Administration' | translate }}
|
||||||
</button>
|
</button>
|
||||||
<button mat-menu-item *ngIf="!isSuperAdmin" (click)="editUser()">
|
<button mat-menu-item *ngIf="auth.userCategory === 'super-admin'" (click)="editUser()">
|
||||||
{{ 'changePassword' | translate }}
|
{{ 'changePassword' | translate }}
|
||||||
</button>
|
</button>
|
||||||
</mat-menu>
|
</mat-menu>
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
import { Component, EventEmitter, OnInit, Output } from '@angular/core';
|
import { Component, EventEmitter, OnInit, Output } from '@angular/core';
|
||||||
import { jwtDecode } from 'jwt-decode';
|
|
||||||
import { ChangePasswordModalComponent } from '../../components/admin/users/users/change-password-modal/change-password-modal.component';
|
import { ChangePasswordModalComponent } from '../../components/admin/users/users/change-password-modal/change-password-modal.component';
|
||||||
import { MatDialog } from "@angular/material/dialog";
|
import { MatDialog } from "@angular/material/dialog";
|
||||||
import { GlobalStatusComponent } from 'src/app/components/global-status/global-status.component';
|
import { GlobalStatusComponent } from 'src/app/components/global-status/global-status.component';
|
||||||
import { BreakpointObserver, Breakpoints } from '@angular/cdk/layout';
|
import { BreakpointObserver } from '@angular/cdk/layout';
|
||||||
|
import { AuthService } from '@services/auth.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-header',
|
selector: 'app-header',
|
||||||
|
@ -16,39 +16,26 @@ export class HeaderComponent implements OnInit {
|
||||||
isSmallScreen: boolean = false;
|
isSmallScreen: boolean = false;
|
||||||
|
|
||||||
@Output() toggleSidebar = new EventEmitter<void>();
|
@Output() toggleSidebar = new EventEmitter<void>();
|
||||||
private decodedToken: any;
|
|
||||||
private username: any;
|
|
||||||
|
|
||||||
onToggleSidebar() {
|
onToggleSidebar() {
|
||||||
this.toggleSidebar.emit();
|
this.toggleSidebar.emit();
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(public dialog: MatDialog, private breakpointObserver: BreakpointObserver) { }
|
constructor(
|
||||||
|
public dialog: MatDialog,
|
||||||
|
public auth: AuthService,
|
||||||
|
private breakpointObserver: BreakpointObserver
|
||||||
|
) { }
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
const token = localStorage.getItem('loginToken');
|
|
||||||
if (token) {
|
|
||||||
try {
|
|
||||||
this.decodedToken = jwtDecode(token);
|
|
||||||
this.isSuperAdmin = this.decodedToken.roles.includes('ROLE_SUPER_ADMIN');
|
|
||||||
localStorage.setItem('isSuperAdmin', String(this.isSuperAdmin));
|
|
||||||
this.username = this.decodedToken.username;
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error decoding JWT:', error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.breakpointObserver.observe(['(max-width: 576px)']).subscribe((result) => {
|
this.breakpointObserver.observe(['(max-width: 576px)']).subscribe((result) => {
|
||||||
this.isSmallScreen = result.matches;
|
this.isSmallScreen = result.matches;
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
ngDoCheck(): void {
|
|
||||||
this.isSuperAdmin = localStorage.getItem('isSuperAdmin') === 'true';
|
|
||||||
}
|
|
||||||
|
|
||||||
editUser() {
|
editUser() {
|
||||||
const dialogRef = this.dialog.open(ChangePasswordModalComponent, {
|
const dialogRef = this.dialog.open(ChangePasswordModalComponent, {
|
||||||
data: { user: this.decodedToken.username, uuid: this.decodedToken.uuid },
|
data: { user: this.auth.username, uuid: this.auth.uuid },
|
||||||
width: '400px',
|
width: '400px',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -56,7 +43,11 @@ export class HeaderComponent implements OnInit {
|
||||||
showGlobalStatus() {
|
showGlobalStatus() {
|
||||||
this.dialog.open(GlobalStatusComponent, {
|
this.dialog.open(GlobalStatusComponent, {
|
||||||
width: '45vw',
|
width: '45vw',
|
||||||
height: '80vh',
|
height: '80vh',
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
logOut() {
|
||||||
|
this.auth.logout();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import { Component, Input, Output, EventEmitter } from '@angular/core';
|
import { Component, Input, Output, EventEmitter } from '@angular/core';
|
||||||
import { jwtDecode } from 'jwt-decode';
|
import { jwtDecode } from 'jwt-decode';
|
||||||
import { MatDialog } from '@angular/material/dialog';
|
import { MatDialog } from '@angular/material/dialog';
|
||||||
|
import { AuthService } from '@services/auth.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-sidebar',
|
selector: 'app-sidebar',
|
||||||
|
@ -12,9 +13,7 @@ export class SidebarComponent {
|
||||||
@Input() sidebarMode: 'side' | 'over' = 'side';
|
@Input() sidebarMode: 'side' | 'over' = 'side';
|
||||||
@Output() closeSidebar: EventEmitter<void> = new EventEmitter<void>();
|
@Output() closeSidebar: EventEmitter<void> = new EventEmitter<void>();
|
||||||
|
|
||||||
isSuperAdmin: boolean = false;
|
username: string | null = "";
|
||||||
username: string = "";
|
|
||||||
decodedToken: any = "";
|
|
||||||
showOgBootSub: boolean = false;
|
showOgBootSub: boolean = false;
|
||||||
showOgDhcpSub: boolean = false;
|
showOgDhcpSub: boolean = false;
|
||||||
showCommandSub: boolean = false;
|
showCommandSub: boolean = false;
|
||||||
|
@ -39,19 +38,9 @@ export class SidebarComponent {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(public dialog: MatDialog) {}
|
constructor(public dialog: MatDialog, public auth: AuthService,) {}
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
const token = localStorage.getItem('loginToken');
|
this.username = this.auth.username
|
||||||
if (token) {
|
|
||||||
try {
|
|
||||||
this.decodedToken = jwtDecode(token);
|
|
||||||
this.isSuperAdmin = this.decodedToken.roles.includes('ROLE_SUPER_ADMIN');
|
|
||||||
localStorage.setItem('isSuperAdmin', String(this.isSuperAdmin));
|
|
||||||
this.username = this.decodedToken.username;
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error decoding JWT:', error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -0,0 +1,108 @@
|
||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { Router } from '@angular/router';
|
||||||
|
import { jwtDecode } from 'jwt-decode';
|
||||||
|
|
||||||
|
export type UserCategory =
|
||||||
|
| 'super-admin'
|
||||||
|
| 'ou-admin'
|
||||||
|
| 'ou-operator'
|
||||||
|
| 'ou-minimal'
|
||||||
|
| 'user';
|
||||||
|
|
||||||
|
interface JwtPayload {
|
||||||
|
roles: string[];
|
||||||
|
username: string;
|
||||||
|
id: number;
|
||||||
|
uuid: string;
|
||||||
|
groupsView: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Injectable({ providedIn: 'root' })
|
||||||
|
export class AuthService {
|
||||||
|
private tokenPayload: JwtPayload | null = null;
|
||||||
|
|
||||||
|
constructor(private router: Router) {
|
||||||
|
this.loadToken();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Intenta leer y decodificar el token de localStorage */
|
||||||
|
private loadToken() {
|
||||||
|
const token = localStorage.getItem('loginToken');
|
||||||
|
if (!token) return;
|
||||||
|
try {
|
||||||
|
this.tokenPayload = jwtDecode<JwtPayload>(token);
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Error decoding JWT', e);
|
||||||
|
this.tokenPayload = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Fuerza recarga del token (por si cambió en localStorage) */
|
||||||
|
public refresh() {
|
||||||
|
this.loadToken();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Roles básicos que aparecen en el token */
|
||||||
|
private get roles(): string[] {
|
||||||
|
return this.tokenPayload?.roles || [];
|
||||||
|
}
|
||||||
|
|
||||||
|
get isSuperAdmin(): boolean {
|
||||||
|
return this.roles.includes('ROLE_SUPER_ADMIN');
|
||||||
|
}
|
||||||
|
|
||||||
|
get isOUAdmin(): boolean {
|
||||||
|
return this.roles.includes('ROLE_ORGANIZATIONAL_UNIT_ADMIN');
|
||||||
|
}
|
||||||
|
|
||||||
|
get isOUOperator(): boolean {
|
||||||
|
return this.roles.includes('ROLE_ORGANIZATIONAL_UNIT_OPERATOR');
|
||||||
|
}
|
||||||
|
|
||||||
|
get isOUMinimal(): boolean {
|
||||||
|
return this.roles.includes('ROLE_ORGANIZATIONAL_UNIT_MINIMAL');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Categoría única de usuario, según prioridades:
|
||||||
|
* - super-admin
|
||||||
|
* - ou-admin
|
||||||
|
* - ou-operator
|
||||||
|
* - ou-minimal
|
||||||
|
* - user (fallback)
|
||||||
|
*/
|
||||||
|
get userCategory(): UserCategory {
|
||||||
|
if (this.isSuperAdmin) return 'super-admin';
|
||||||
|
if (this.isOUAdmin) return 'ou-admin';
|
||||||
|
if (this.isOUOperator) return 'ou-operator';
|
||||||
|
if (this.isOUMinimal) return 'ou-minimal';
|
||||||
|
return 'user';
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Nombre de usuario */
|
||||||
|
get username(): string | null {
|
||||||
|
return this.tokenPayload?.username || null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Uuid del usuario*/
|
||||||
|
get uuid(): any | null {
|
||||||
|
return this.tokenPayload?.uuid || null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** groupsView del usuario */
|
||||||
|
get groupsView(): string | null {
|
||||||
|
return this.tokenPayload?.groupsView || null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Logout: limpia tokens y redirige al login */
|
||||||
|
logout(): void {
|
||||||
|
localStorage.removeItem('loginToken');
|
||||||
|
localStorage.removeItem('refreshToken');
|
||||||
|
localStorage.removeItem('isSuperAdmin');
|
||||||
|
localStorage.removeItem('username');
|
||||||
|
localStorage.removeItem('groupsView');
|
||||||
|
localStorage.removeItem('language');
|
||||||
|
this.tokenPayload = null;
|
||||||
|
this.router.navigate(['/auth/login']);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue