From ab270239b516243a49f528c9a735c0439cfa55b2 Mon Sep 17 00:00:00 2001 From: juancarmore Date: Wed, 21 May 2025 21:41:42 +0200 Subject: [PATCH] frontend: replace UnauthorizedComponent by ErrorComponent and improve error handling in guards --- .../unauthorized/unauthorized.component.html | 4 -- .../unauthorized/unauthorized.component.scss | 0 .../unauthorized.component.spec.ts | 23 --------- .../unauthorized/unauthorized.component.ts | 38 -------------- .../src/lib/guards/auth.guard.ts | 28 +++++++++-- .../guards/validate-recording-access.guard.ts | 17 ++++--- .../lib/guards/validate-room-access.guard.ts | 14 ++++-- .../src/lib/pages/error/error.component.html | 10 ++++ .../src/lib/pages/error/error.component.scss | 33 ++++++++++++ .../lib/pages/error/error.component.spec.ts | 21 ++++++++ .../src/lib/pages/error/error.component.ts | 50 +++++++++++++++++++ .../src/lib/routes/base-routes.ts | 5 +- 12 files changed, 160 insertions(+), 83 deletions(-) delete mode 100644 frontend/projects/shared-meet-components/src/lib/components/errors/unauthorized/unauthorized.component.html delete mode 100644 frontend/projects/shared-meet-components/src/lib/components/errors/unauthorized/unauthorized.component.scss delete mode 100644 frontend/projects/shared-meet-components/src/lib/components/errors/unauthorized/unauthorized.component.spec.ts delete mode 100644 frontend/projects/shared-meet-components/src/lib/components/errors/unauthorized/unauthorized.component.ts create mode 100644 frontend/projects/shared-meet-components/src/lib/pages/error/error.component.html create mode 100644 frontend/projects/shared-meet-components/src/lib/pages/error/error.component.scss create mode 100644 frontend/projects/shared-meet-components/src/lib/pages/error/error.component.spec.ts create mode 100644 frontend/projects/shared-meet-components/src/lib/pages/error/error.component.ts diff --git a/frontend/projects/shared-meet-components/src/lib/components/errors/unauthorized/unauthorized.component.html b/frontend/projects/shared-meet-components/src/lib/components/errors/unauthorized/unauthorized.component.html deleted file mode 100644 index 74aecf8..0000000 --- a/frontend/projects/shared-meet-components/src/lib/components/errors/unauthorized/unauthorized.component.html +++ /dev/null @@ -1,4 +0,0 @@ -
-

The page you are trying to access is restricted.

-

Reason: {{ message }}

-
diff --git a/frontend/projects/shared-meet-components/src/lib/components/errors/unauthorized/unauthorized.component.scss b/frontend/projects/shared-meet-components/src/lib/components/errors/unauthorized/unauthorized.component.scss deleted file mode 100644 index e69de29..0000000 diff --git a/frontend/projects/shared-meet-components/src/lib/components/errors/unauthorized/unauthorized.component.spec.ts b/frontend/projects/shared-meet-components/src/lib/components/errors/unauthorized/unauthorized.component.spec.ts deleted file mode 100644 index bab5ad8..0000000 --- a/frontend/projects/shared-meet-components/src/lib/components/errors/unauthorized/unauthorized.component.spec.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; - -import { UnauthorizedComponent } from './unauthorized.component'; - -describe('UnauthorizedComponent', () => { - let component: UnauthorizedComponent; - let fixture: ComponentFixture; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - imports: [UnauthorizedComponent] - }) - .compileComponents(); - - fixture = TestBed.createComponent(UnauthorizedComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/frontend/projects/shared-meet-components/src/lib/components/errors/unauthorized/unauthorized.component.ts b/frontend/projects/shared-meet-components/src/lib/components/errors/unauthorized/unauthorized.component.ts deleted file mode 100644 index e165c8d..0000000 --- a/frontend/projects/shared-meet-components/src/lib/components/errors/unauthorized/unauthorized.component.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { Component, OnInit } from '@angular/core'; -import { ActivatedRoute } from '@angular/router'; - -@Component({ - selector: 'ov-unauthorized', - standalone: true, - imports: [], - templateUrl: './unauthorized.component.html', - styleUrl: './unauthorized.component.scss' -}) -export class UnauthorizedComponent implements OnInit { - message = 'Unauthorized access'; - constructor(private route: ActivatedRoute) {} - - ngOnInit(): void { - this.route.queryParams.subscribe((params) => { - const reason = params['reason']; - switch (reason) { - case 'invalid-token': - this.message = 'The token provided is not valid'; - break; - case 'invalid-room': - this.message = 'The room name is not valid'; - break; - case 'invalid-participant': - this.message = 'The participant name must be provided'; - break; - case 'unauthorized-participant': - this.message = 'You are not authorized to join this room'; - break; - - default: - this.message = 'Unauthorized access'; - break; - } - }); - } -} diff --git a/frontend/projects/shared-meet-components/src/lib/guards/auth.guard.ts b/frontend/projects/shared-meet-components/src/lib/guards/auth.guard.ts index 5a88c7c..b5a634f 100644 --- a/frontend/projects/shared-meet-components/src/lib/guards/auth.guard.ts +++ b/frontend/projects/shared-meet-components/src/lib/guards/auth.guard.ts @@ -1,7 +1,14 @@ import { inject } from '@angular/core'; -import { ActivatedRouteSnapshot, CanActivateFn, RedirectCommand, Router, RouterStateSnapshot } from '@angular/router'; -import { AuthService, ContextService, HttpService, SessionStorageService } from '../services'; +import { + ActivatedRouteSnapshot, + CanActivateFn, + RedirectCommand, + Router, + RouterStateSnapshot, + UrlTree +} from '@angular/router'; import { AuthMode, ParticipantRole } from '@lib/typings/ce'; +import { AuthService, ContextService, HttpService, SessionStorageService } from '../services'; export const checkUserAuthenticatedGuard: CanActivateFn = async ( route: ActivatedRouteSnapshot, @@ -64,9 +71,18 @@ export const checkParticipantRoleAndAuthGuard: CanActivateFn = async ( const roomRoleAndPermissions = await httpService.getRoomRoleAndPermissions(roomId, storageSecret || secret); participantRole = roomRoleAndPermissions.role; contextService.setParticipantRole(participantRole); - } catch (error) { + } catch (error: any) { console.error('Error getting participant role:', error); - return router.createUrlTree(['unauthorized'], { queryParams: { reason: 'unauthorized-participant' } }); + switch (error.status) { + case 400: + // Invalid secret + return redirectToErrorPage(router, 'invalid-secret'); + case 404: + // Room not found + return redirectToErrorPage(router, 'invalid-room'); + default: + return redirectToErrorPage(router, 'internal-error'); + } } const authMode = await contextService.getAuthModeToEnterRoom(); @@ -114,3 +130,7 @@ export const checkUserNotAuthenticatedGuard: CanActivateFn = async ( // Allow access to the requested page return true; }; + +const redirectToErrorPage = (router: Router, reason: string): UrlTree => { + return router.createUrlTree(['error'], { queryParams: { reason } }); +}; diff --git a/frontend/projects/shared-meet-components/src/lib/guards/validate-recording-access.guard.ts b/frontend/projects/shared-meet-components/src/lib/guards/validate-recording-access.guard.ts index ae215a0..89b988b 100644 --- a/frontend/projects/shared-meet-components/src/lib/guards/validate-recording-access.guard.ts +++ b/frontend/projects/shared-meet-components/src/lib/guards/validate-recording-access.guard.ts @@ -31,26 +31,29 @@ export const validateRecordingAccessGuard: CanActivateFn = async ( contextService.setRecordingPermissionsFromToken(response.token); if (!contextService.canRetrieveRecordings()) { - // If the user does not have permission to retrieve recordings, redirect to the unauthorized page - return redirectToUnauthorized(router, 'unauthorized-recording-access'); + // If the user does not have permission to retrieve recordings, redirect to the error page + return redirectToErrorPage(router, 'unauthorized-recording-access'); } return true; } catch (error: any) { console.error('Error generating recording token:', error); switch (error.status) { + case 400: + // Invalid secret + return redirectToErrorPage(router, 'invalid-secret'); case 403: // Recording access is configured for admins only - return redirectToUnauthorized(router, 'unauthorized-recording-access'); + return redirectToErrorPage(router, 'recordings-admin-only-access'); case 404: // There are no recordings in the room or the room does not exist - return redirectToUnauthorized(router, 'no-recordings'); + return redirectToErrorPage(router, 'no-recordings'); default: - return redirectToUnauthorized(router, 'invalid-room'); + return redirectToErrorPage(router, 'internal-error'); } } }; -const redirectToUnauthorized = (router: Router, reason: string): UrlTree => { - return router.createUrlTree(['unauthorized'], { queryParams: { reason } }); +const redirectToErrorPage = (router: Router, reason: string): UrlTree => { + return router.createUrlTree(['error'], { queryParams: { reason } }); }; diff --git a/frontend/projects/shared-meet-components/src/lib/guards/validate-room-access.guard.ts b/frontend/projects/shared-meet-components/src/lib/guards/validate-room-access.guard.ts index 39cf8f6..7a62eaf 100644 --- a/frontend/projects/shared-meet-components/src/lib/guards/validate-room-access.guard.ts +++ b/frontend/projects/shared-meet-components/src/lib/guards/validate-room-access.guard.ts @@ -38,6 +38,12 @@ export const validateRoomAccessGuard: CanActivateFn = async ( } catch (error: any) { console.error('Error generating participant token:', error); switch (error.status) { + case 400: + // Invalid secret + return redirectToErrorPage(router, 'invalid-secret'); + case 404: + // Room not found + return redirectToErrorPage(router, 'invalid-room'); case 409: // Participant already exists. // Send a timestamp to force update the query params and show the error message in participant name input form @@ -47,14 +53,12 @@ export const validateRoomAccessGuard: CanActivateFn = async ( return new RedirectCommand(participantNameRoute, { skipLocationChange: true }); - case 406: - return redirectToUnauthorized(router, 'unauthorized-participant'); default: - return redirectToUnauthorized(router, 'invalid-room'); + return redirectToErrorPage(router, 'internal-error'); } } }; -const redirectToUnauthorized = (router: Router, reason: string): UrlTree => { - return router.createUrlTree(['unauthorized'], { queryParams: { reason } }); +const redirectToErrorPage = (router: Router, reason: string): UrlTree => { + return router.createUrlTree(['error'], { queryParams: { reason } }); }; diff --git a/frontend/projects/shared-meet-components/src/lib/pages/error/error.component.html b/frontend/projects/shared-meet-components/src/lib/pages/error/error.component.html new file mode 100644 index 0000000..7204206 --- /dev/null +++ b/frontend/projects/shared-meet-components/src/lib/pages/error/error.component.html @@ -0,0 +1,10 @@ +
+ + + {{ errorName }} + + +

{{ message }}

+
+
+
diff --git a/frontend/projects/shared-meet-components/src/lib/pages/error/error.component.scss b/frontend/projects/shared-meet-components/src/lib/pages/error/error.component.scss new file mode 100644 index 0000000..2eb04f0 --- /dev/null +++ b/frontend/projects/shared-meet-components/src/lib/pages/error/error.component.scss @@ -0,0 +1,33 @@ +.container { + display: flex; + justify-content: center; + align-items: center; + height: 100vh; + background-color: var(--ov-background-color); +} + +.card { + width: 400px; + padding: 20px; + text-align: center; + background-color: var(--ov-surface-color); + border-radius: var(--ov-surface-radius); +} + +mat-card-header { + justify-content: center; + align-items: center; +} + +mat-card-title { + font-size: 1.5em; +} + +mat-card-content p { + font-size: 1em; + color: #555; +} + +mat-card-actions { + margin-top: 20px; +} diff --git a/frontend/projects/shared-meet-components/src/lib/pages/error/error.component.spec.ts b/frontend/projects/shared-meet-components/src/lib/pages/error/error.component.spec.ts new file mode 100644 index 0000000..9c94e46 --- /dev/null +++ b/frontend/projects/shared-meet-components/src/lib/pages/error/error.component.spec.ts @@ -0,0 +1,21 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { ErrorComponent } from './error.component'; + +describe('GenericErrorComponent', () => { + let component: ErrorComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [ErrorComponent] + }).compileComponents(); + + fixture = TestBed.createComponent(ErrorComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/frontend/projects/shared-meet-components/src/lib/pages/error/error.component.ts b/frontend/projects/shared-meet-components/src/lib/pages/error/error.component.ts new file mode 100644 index 0000000..80ce5e3 --- /dev/null +++ b/frontend/projects/shared-meet-components/src/lib/pages/error/error.component.ts @@ -0,0 +1,50 @@ +import { Component, OnInit } from '@angular/core'; +import { MatCardModule } from '@angular/material/card'; +import { ActivatedRoute } from '@angular/router'; + +@Component({ + selector: 'ov-error', + standalone: true, + imports: [MatCardModule], + templateUrl: './error.component.html', + styleUrl: './error.component.scss' +}) +export class ErrorComponent implements OnInit { + errorName = 'Error'; + message = ''; + + constructor(private route: ActivatedRoute) {} + + ngOnInit(): void { + this.route.queryParams.subscribe((params) => { + const reason = params['reason']; + switch (reason) { + case 'invalid-secret': + this.errorName = 'Invalid secret'; + this.message = + 'The secret provided to join the room is neither valid for moderators nor publishers'; + break; + case 'invalid-room': + this.errorName = 'Invalid room'; + this.message = 'The room you are trying to join does not exist or has been deleted'; + break; + case 'no-recordings': + this.errorName = 'No recordings'; + this.message = 'There are no recordings in this room or the room does not exist'; + break; + case 'unauthorized-recording-access': + this.errorName = 'Unauthorized recording access'; + this.message = 'You are not authorized to access the recordings in this room'; + break; + case 'recordings-admin-only-access': + this.errorName = 'Unauthorized recording access'; + this.message = 'Recordings access is configured for admins only in this room'; + break; + default: + this.errorName = 'Internal error'; + this.message = 'Something went wrong...'; + break; + } + }); + } +} diff --git a/frontend/projects/shared-meet-components/src/lib/routes/base-routes.ts b/frontend/projects/shared-meet-components/src/lib/routes/base-routes.ts index 7fdcf11..09fa944 100644 --- a/frontend/projects/shared-meet-components/src/lib/routes/base-routes.ts +++ b/frontend/projects/shared-meet-components/src/lib/routes/base-routes.ts @@ -1,6 +1,5 @@ import { Routes } from '@angular/router'; import { UserRole } from '@lib/typings/ce'; -import { RoomCreatorDisabledComponent, UnauthorizedComponent } from '../components'; import { applicationModeGuard, checkParticipantNameGuard, @@ -20,11 +19,13 @@ import { ConsoleComponent, ConsoleLoginComponent, DisconnectedComponent, + ErrorComponent, LoginComponent, OverviewComponent, ParticipantNameFormComponent, RecordingsComponent, RoomCreatorComponent, + RoomCreatorDisabledComponent, RoomFormComponent, RoomRecordingsComponent, RoomsComponent, @@ -51,7 +52,7 @@ export const baseRoutes: Routes = [ }, { path: 'room-creator-disabled', component: RoomCreatorDisabledComponent }, { path: 'disconnected', component: DisconnectedComponent }, - { path: 'unauthorized', component: UnauthorizedComponent }, + { path: 'error', component: ErrorComponent }, { path: 'console/login', component: ConsoleLoginComponent,