From ed6af7a7fff7442938d8a307a2b495547fadfc94 Mon Sep 17 00:00:00 2001 From: juancarmore Date: Fri, 25 Jul 2025 21:55:50 +0200 Subject: [PATCH] frontend: refactor code to always remove room secret from URL instead of switching moderator secret with publisher secret --- .../lib/guards/extract-query-params.guard.ts | 7 ++-- .../src/lib/guards/index.ts | 2 +- .../src/lib/guards/moderator-secret.guard.ts | 36 ------------------- .../src/lib/guards/remove-secret.guard.ts | 32 +++++++++++++++++ .../pages/video-room/video-room.component.ts | 29 +++++---------- .../src/lib/routes/base-routes.ts | 8 +++-- .../src/lib/services/navigation.service.ts | 19 +++++----- .../src/lib/services/room.service.ts | 12 ++++--- .../lib/services/session-storage.service.ts | 20 +++++------ 9 files changed, 80 insertions(+), 85 deletions(-) delete mode 100644 frontend/projects/shared-meet-components/src/lib/guards/moderator-secret.guard.ts create mode 100644 frontend/projects/shared-meet-components/src/lib/guards/remove-secret.guard.ts diff --git a/frontend/projects/shared-meet-components/src/lib/guards/extract-query-params.guard.ts b/frontend/projects/shared-meet-components/src/lib/guards/extract-query-params.guard.ts index a5af6f4..99738e2 100644 --- a/frontend/projects/shared-meet-components/src/lib/guards/extract-query-params.guard.ts +++ b/frontend/projects/shared-meet-components/src/lib/guards/extract-query-params.guard.ts @@ -8,13 +8,16 @@ export const extractRoomQueryParamsGuard: CanActivateFn = (route: ActivatedRoute const navigationService = inject(NavigationService); const roomService = inject(RoomService); const participantService = inject(ParticipantTokenService); + const sessionStorageService = inject(SessionStorageService); + const { roomId, participantName, secret, leaveRedirectUrl, showOnlyRecordings } = extractParams(route); + const storedSecret = sessionStorageService.getRoomSecret(roomId); if (isValidUrl(leaveRedirectUrl)) { navigationService.setLeaveRedirectUrl(leaveRedirectUrl); } - if (!secret) { + if (!secret && !storedSecret) { // If no secret is provided, redirect to the error page return navigationService.redirectToErrorPage(ErrorReason.MISSING_ROOM_SECRET); } @@ -40,7 +43,7 @@ export const extractRecordingQueryParamsGuard: CanActivateFn = (route: Activated const sessionStorageService = inject(SessionStorageService); const { roomId, secret } = extractParams(route); - const storedSecret = sessionStorageService.getModeratorSecret(roomId); + const storedSecret = sessionStorageService.getRoomSecret(roomId); if (!secret && !storedSecret) { // If no secret is provided, redirect to the error page diff --git a/frontend/projects/shared-meet-components/src/lib/guards/index.ts b/frontend/projects/shared-meet-components/src/lib/guards/index.ts index 1d125a2..6a41215 100644 --- a/frontend/projects/shared-meet-components/src/lib/guards/index.ts +++ b/frontend/projects/shared-meet-components/src/lib/guards/index.ts @@ -1,5 +1,5 @@ export * from './auth.guard'; export * from './extract-query-params.guard'; -export * from './moderator-secret.guard'; +export * from './remove-secret.guard'; export * from './run-serially.guard'; export * from './validate-recording-access.guard'; diff --git a/frontend/projects/shared-meet-components/src/lib/guards/moderator-secret.guard.ts b/frontend/projects/shared-meet-components/src/lib/guards/moderator-secret.guard.ts deleted file mode 100644 index b7afe37..0000000 --- a/frontend/projects/shared-meet-components/src/lib/guards/moderator-secret.guard.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { inject } from '@angular/core'; -import { CanActivateFn, NavigationEnd, Router } from '@angular/router'; -import { NavigationService, ParticipantTokenService, RoomService, SessionStorageService } from '@lib/services'; -import { filter, take } from 'rxjs'; - -/** - * Guard that intercepts navigation to remove the 'secret' query parameter from the URL - * when a moderator participant is detected. The secret is stored in session storage - * for the current room, and the URL is updated without the 'secret' parameter to - * enhance security. - */ -export const removeModeratorSecretGuard: CanActivateFn = (route, _state) => { - const roomService = inject(RoomService); - const participantService = inject(ParticipantTokenService); - const navigationService = inject(NavigationService); - const router = inject(Router); - const sessionStorageService = inject(SessionStorageService); - - router.events - .pipe( - filter((event) => event instanceof NavigationEnd), - take(1) - ) - .subscribe(async () => { - if (participantService.isModeratorParticipant()) { - const roomId = roomService.getRoomId(); - const moderatorSecret = roomService.getRoomSecret(); - - // Store the moderator secret in session storage for the current room and remove it from the URL - sessionStorageService.setModeratorSecret(roomId, moderatorSecret); - navigationService.removeQueryParamFromUrl(route.queryParams, 'secret'); - } - }); - - return true; -}; diff --git a/frontend/projects/shared-meet-components/src/lib/guards/remove-secret.guard.ts b/frontend/projects/shared-meet-components/src/lib/guards/remove-secret.guard.ts new file mode 100644 index 0000000..9b5721b --- /dev/null +++ b/frontend/projects/shared-meet-components/src/lib/guards/remove-secret.guard.ts @@ -0,0 +1,32 @@ +import { inject } from '@angular/core'; +import { CanActivateFn, NavigationEnd, Router } from '@angular/router'; +import { NavigationService, RoomService, SessionStorageService } from '@lib/services'; +import { filter, take } from 'rxjs'; + +/** + * Guard that intercepts navigation to remove the 'secret' query parameter from the URL + * when a participant joins a room. The secret is stored in session storage for the current room, + * and the URL is updated without the 'secret' parameter to enhance security. + */ +export const removeRoomSecretGuard: CanActivateFn = (route, _state) => { + const router = inject(Router); + const roomService = inject(RoomService); + const navigationService = inject(NavigationService); + const sessionStorageService = inject(SessionStorageService); + + router.events + .pipe( + filter((event) => event instanceof NavigationEnd), + take(1) + ) + .subscribe(async () => { + const roomId = roomService.getRoomId(); + const secret = roomService.getRoomSecret(); + + // Store the secret in session storage for the current room and remove it from the URL + sessionStorageService.setRoomSecret(roomId, secret); + await navigationService.removeQueryParamFromUrl(route.queryParams, 'secret'); + }); + + return true; +}; diff --git a/frontend/projects/shared-meet-components/src/lib/pages/video-room/video-room.component.ts b/frontend/projects/shared-meet-components/src/lib/pages/video-room/video-room.component.ts index 00abc20..5276dd0 100644 --- a/frontend/projects/shared-meet-components/src/lib/pages/video-room/video-room.component.ts +++ b/frontend/projects/shared-meet-components/src/lib/pages/video-room/video-room.component.ts @@ -229,7 +229,7 @@ export class VideoRoomComponent implements OnInit { try { await this.generateParticipantToken(); - await this.replaceUrlQueryParams(); + await this.addParticipantNameToUrl(); await this.roomService.loadPreferences(this.roomId); this.showRoom = true; } catch (error) { @@ -277,24 +277,11 @@ export class VideoRoomComponent implements OnInit { } } - private async replaceUrlQueryParams() { - let secretQueryParam = this.roomSecret; - - // If participant is moderator, store the moderator secret in session storage - // and replace the secret in the URL with the publisher secret - if (this.participantRole === ParticipantRole.MODERATOR) { - try { - const { moderatorSecret, publisherSecret } = await this.roomService.getSecrets(this.roomId); - this.sessionStorageService.setModeratorSecret(this.roomId, moderatorSecret); - secretQueryParam = publisherSecret; - } catch (error) { - console.error('error', error); - } - } - - // Replace secret and participant name in the URL query parameters - this.navigationService.updateQueryParamsFromUrl(this.route.snapshot.queryParams, { - secret: secretQueryParam, + /** + * Add participant name as a query parameter to the URL + */ + private async addParticipantNameToUrl() { + await this.navigationService.updateQueryParamsFromUrl(this.route.snapshot.queryParams, { 'participant-name': this.participantName }); } @@ -302,7 +289,7 @@ export class VideoRoomComponent implements OnInit { onRoomCreated(room: Room) { room.on( RoomEvent.DataReceived, - (payload: Uint8Array, participant?: RemoteParticipant, _?: DataPacket_Kind, topic?: string) => { + (payload: Uint8Array, _participant?: RemoteParticipant, _kind?: DataPacket_Kind, topic?: string) => { const event = JSON.parse(new TextDecoder().decode(payload)); if (topic === MeetSignalType.MEET_ROOM_PREFERENCES_UPDATED) { const roomPreferences: MeetRoomPreferences = event.preferences; @@ -342,7 +329,7 @@ export class VideoRoomComponent implements OnInit { // Remove the moderator secret from session storage if the participant left for a reason other than browser unload if (event.reason !== ParticipantLeftReason.BROWSER_UNLOAD) { - this.sessionStorageService.removeModeratorSecret(event.roomName); + this.sessionStorageService.removeRoomSecret(event.roomName); } // Navigate to the disconnected page with the reason 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 0b1136b..5da4afb 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 @@ -6,7 +6,7 @@ import { checkUserNotAuthenticatedGuard, extractRecordingQueryParamsGuard, extractRoomQueryParamsGuard, - removeModeratorSecretGuard, + removeRoomSecretGuard, runGuardsSerially, validateRecordingAccessGuard } from '@lib/guards'; @@ -35,7 +35,9 @@ export const baseRoutes: Routes = [ { path: 'room/:room-id', component: VideoRoomComponent, - canActivate: [runGuardsSerially(extractRoomQueryParamsGuard, checkParticipantRoleAndAuthGuard)] + canActivate: [ + runGuardsSerially(extractRoomQueryParamsGuard, checkParticipantRoleAndAuthGuard, removeRoomSecretGuard) + ] }, { path: 'room/:room-id/recordings', @@ -45,7 +47,7 @@ export const baseRoutes: Routes = [ extractRecordingQueryParamsGuard, checkParticipantRoleAndAuthGuard, validateRecordingAccessGuard, - removeModeratorSecretGuard + removeRoomSecretGuard ) ] }, diff --git a/frontend/projects/shared-meet-components/src/lib/services/navigation.service.ts b/frontend/projects/shared-meet-components/src/lib/services/navigation.service.ts index b9f568c..de877bc 100644 --- a/frontend/projects/shared-meet-components/src/lib/services/navigation.service.ts +++ b/frontend/projects/shared-meet-components/src/lib/services/navigation.service.ts @@ -148,17 +148,17 @@ export class NavigationService { * @param oldParams - The existing query parameters * @param newParams - The new query parameters to merge with the existing ones */ - updateQueryParamsFromUrl(oldParams: Params, newParams: Params): void { + async updateQueryParamsFromUrl(oldParams: Params, newParams: Params): Promise { const queryParams = { ...oldParams, ...newParams }; - const urlTree = this.router.createUrlTree([], { + + await this.router.navigate([], { queryParams, + replaceUrl: true, queryParamsHandling: 'merge' }); - const newUrl = this.router.serializeUrl(urlTree); - this.location.replaceState(newUrl); } /** @@ -167,11 +167,14 @@ export class NavigationService { * @param queryParams - The current query parameters * @param param - The parameter to remove */ - removeQueryParamFromUrl(queryParams: Params, param: string): void { + async removeQueryParamFromUrl(queryParams: Params, param: string): Promise { const updatedParams = { ...queryParams }; delete updatedParams[param]; - const urlTree = this.router.createUrlTree([], { queryParams: updatedParams }); - const newUrl = this.router.serializeUrl(urlTree); - this.location.replaceState(newUrl); + + await this.router.navigate([], { + queryParams: updatedParams, + replaceUrl: true, + queryParamsHandling: 'replace' + }); } } diff --git a/frontend/projects/shared-meet-components/src/lib/services/room.service.ts b/frontend/projects/shared-meet-components/src/lib/services/room.service.ts index 3888e4e..42d391b 100644 --- a/frontend/projects/shared-meet-components/src/lib/services/room.service.ts +++ b/frontend/projects/shared-meet-components/src/lib/services/room.service.ts @@ -46,10 +46,14 @@ export class RoomService { return this.roomId; } - setRoomSecret(secret: string) { - // If a secret is stored in session storage for the current room, use it instead of the provided secret - const storedSecret = this.sessionStorageService.getModeratorSecret(this.roomId); - this.roomSecret = storedSecret || secret; + setRoomSecret(secret?: string) { + // If no secret is provided, check session storage for the current room's secret + if (!secret) { + const storedSecret = this.sessionStorageService.getRoomSecret(this.roomId); + this.roomSecret = storedSecret || ''; + } else { + this.roomSecret = secret; + } } getRoomSecret(): string { diff --git a/frontend/projects/shared-meet-components/src/lib/services/session-storage.service.ts b/frontend/projects/shared-meet-components/src/lib/services/session-storage.service.ts index ad24f0d..73d7825 100644 --- a/frontend/projects/shared-meet-components/src/lib/services/session-storage.service.ts +++ b/frontend/projects/shared-meet-components/src/lib/services/session-storage.service.ts @@ -11,32 +11,32 @@ export class SessionStorageService { constructor() {} /** - * Stores a moderator secret for a specific room. + * Stores a secret associated with a participant role for a specific room. * * @param roomId The room ID. - * @param secret The secret string. + * @param secret The secret to store. */ - public setModeratorSecret(roomId: string, secret: string): void { - this.set(`moderator_secret_${roomId}`, secret); + public setRoomSecret(roomId: string, secret: string): void { + this.set(`room_secret_${roomId}`, secret); } /** - * Retrieves the moderator secret for a specific room. + * Retrieves the room secret for a specific room. * * @param roomId The room ID. * @returns The stored secret or null if not found. */ - public getModeratorSecret(roomId: string): string | null { - return this.get(`moderator_secret_${roomId}`) ?? null; + public getRoomSecret(roomId: string): string | null { + return this.get(`room_secret_${roomId}`) ?? null; } /** - * Removes the moderator secret for a specific room. + * Removes the room secret for a specific room. * * @param roomId The room ID. */ - public removeModeratorSecret(roomId: string): void { - this.remove(`moderator_secret_${roomId}`); + public removeRoomSecret(roomId: string): void { + this.remove(`room_secret_${roomId}`); } /**