frontend: refactor code to always remove room secret from URL instead of switching moderator secret with publisher secret

This commit is contained in:
juancarmore 2025-07-25 21:55:50 +02:00
parent 51a379af33
commit ed6af7a7ff
9 changed files with 80 additions and 85 deletions

View File

@ -8,13 +8,16 @@ export const extractRoomQueryParamsGuard: CanActivateFn = (route: ActivatedRoute
const navigationService = inject(NavigationService); const navigationService = inject(NavigationService);
const roomService = inject(RoomService); const roomService = inject(RoomService);
const participantService = inject(ParticipantTokenService); const participantService = inject(ParticipantTokenService);
const sessionStorageService = inject(SessionStorageService);
const { roomId, participantName, secret, leaveRedirectUrl, showOnlyRecordings } = extractParams(route); const { roomId, participantName, secret, leaveRedirectUrl, showOnlyRecordings } = extractParams(route);
const storedSecret = sessionStorageService.getRoomSecret(roomId);
if (isValidUrl(leaveRedirectUrl)) { if (isValidUrl(leaveRedirectUrl)) {
navigationService.setLeaveRedirectUrl(leaveRedirectUrl); navigationService.setLeaveRedirectUrl(leaveRedirectUrl);
} }
if (!secret) { if (!secret && !storedSecret) {
// If no secret is provided, redirect to the error page // If no secret is provided, redirect to the error page
return navigationService.redirectToErrorPage(ErrorReason.MISSING_ROOM_SECRET); return navigationService.redirectToErrorPage(ErrorReason.MISSING_ROOM_SECRET);
} }
@ -40,7 +43,7 @@ export const extractRecordingQueryParamsGuard: CanActivateFn = (route: Activated
const sessionStorageService = inject(SessionStorageService); const sessionStorageService = inject(SessionStorageService);
const { roomId, secret } = extractParams(route); const { roomId, secret } = extractParams(route);
const storedSecret = sessionStorageService.getModeratorSecret(roomId); const storedSecret = sessionStorageService.getRoomSecret(roomId);
if (!secret && !storedSecret) { if (!secret && !storedSecret) {
// If no secret is provided, redirect to the error page // If no secret is provided, redirect to the error page

View File

@ -1,5 +1,5 @@
export * from './auth.guard'; export * from './auth.guard';
export * from './extract-query-params.guard'; export * from './extract-query-params.guard';
export * from './moderator-secret.guard'; export * from './remove-secret.guard';
export * from './run-serially.guard'; export * from './run-serially.guard';
export * from './validate-recording-access.guard'; export * from './validate-recording-access.guard';

View File

@ -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;
};

View File

@ -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;
};

View File

@ -229,7 +229,7 @@ export class VideoRoomComponent implements OnInit {
try { try {
await this.generateParticipantToken(); await this.generateParticipantToken();
await this.replaceUrlQueryParams(); await this.addParticipantNameToUrl();
await this.roomService.loadPreferences(this.roomId); await this.roomService.loadPreferences(this.roomId);
this.showRoom = true; this.showRoom = true;
} catch (error) { } catch (error) {
@ -277,24 +277,11 @@ export class VideoRoomComponent implements OnInit {
} }
} }
private async replaceUrlQueryParams() { /**
let secretQueryParam = this.roomSecret; * Add participant name as a query parameter to the URL
*/
// If participant is moderator, store the moderator secret in session storage private async addParticipantNameToUrl() {
// and replace the secret in the URL with the publisher secret await this.navigationService.updateQueryParamsFromUrl(this.route.snapshot.queryParams, {
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,
'participant-name': this.participantName 'participant-name': this.participantName
}); });
} }
@ -302,7 +289,7 @@ export class VideoRoomComponent implements OnInit {
onRoomCreated(room: Room) { onRoomCreated(room: Room) {
room.on( room.on(
RoomEvent.DataReceived, 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)); const event = JSON.parse(new TextDecoder().decode(payload));
if (topic === MeetSignalType.MEET_ROOM_PREFERENCES_UPDATED) { if (topic === MeetSignalType.MEET_ROOM_PREFERENCES_UPDATED) {
const roomPreferences: MeetRoomPreferences = event.preferences; 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 // Remove the moderator secret from session storage if the participant left for a reason other than browser unload
if (event.reason !== ParticipantLeftReason.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 // Navigate to the disconnected page with the reason

View File

@ -6,7 +6,7 @@ import {
checkUserNotAuthenticatedGuard, checkUserNotAuthenticatedGuard,
extractRecordingQueryParamsGuard, extractRecordingQueryParamsGuard,
extractRoomQueryParamsGuard, extractRoomQueryParamsGuard,
removeModeratorSecretGuard, removeRoomSecretGuard,
runGuardsSerially, runGuardsSerially,
validateRecordingAccessGuard validateRecordingAccessGuard
} from '@lib/guards'; } from '@lib/guards';
@ -35,7 +35,9 @@ export const baseRoutes: Routes = [
{ {
path: 'room/:room-id', path: 'room/:room-id',
component: VideoRoomComponent, component: VideoRoomComponent,
canActivate: [runGuardsSerially(extractRoomQueryParamsGuard, checkParticipantRoleAndAuthGuard)] canActivate: [
runGuardsSerially(extractRoomQueryParamsGuard, checkParticipantRoleAndAuthGuard, removeRoomSecretGuard)
]
}, },
{ {
path: 'room/:room-id/recordings', path: 'room/:room-id/recordings',
@ -45,7 +47,7 @@ export const baseRoutes: Routes = [
extractRecordingQueryParamsGuard, extractRecordingQueryParamsGuard,
checkParticipantRoleAndAuthGuard, checkParticipantRoleAndAuthGuard,
validateRecordingAccessGuard, validateRecordingAccessGuard,
removeModeratorSecretGuard removeRoomSecretGuard
) )
] ]
}, },

View File

@ -148,17 +148,17 @@ export class NavigationService {
* @param oldParams - The existing query parameters * @param oldParams - The existing query parameters
* @param newParams - The new query parameters to merge with the existing ones * @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<void> {
const queryParams = { const queryParams = {
...oldParams, ...oldParams,
...newParams ...newParams
}; };
const urlTree = this.router.createUrlTree([], {
await this.router.navigate([], {
queryParams, queryParams,
replaceUrl: true,
queryParamsHandling: 'merge' 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 queryParams - The current query parameters
* @param param - The parameter to remove * @param param - The parameter to remove
*/ */
removeQueryParamFromUrl(queryParams: Params, param: string): void { async removeQueryParamFromUrl(queryParams: Params, param: string): Promise<void> {
const updatedParams = { ...queryParams }; const updatedParams = { ...queryParams };
delete updatedParams[param]; delete updatedParams[param];
const urlTree = this.router.createUrlTree([], { queryParams: updatedParams });
const newUrl = this.router.serializeUrl(urlTree); await this.router.navigate([], {
this.location.replaceState(newUrl); queryParams: updatedParams,
replaceUrl: true,
queryParamsHandling: 'replace'
});
} }
} }

View File

@ -46,10 +46,14 @@ export class RoomService {
return this.roomId; return this.roomId;
} }
setRoomSecret(secret: string) { setRoomSecret(secret?: string) {
// If a secret is stored in session storage for the current room, use it instead of the provided secret // If no secret is provided, check session storage for the current room's secret
const storedSecret = this.sessionStorageService.getModeratorSecret(this.roomId); if (!secret) {
this.roomSecret = storedSecret || secret; const storedSecret = this.sessionStorageService.getRoomSecret(this.roomId);
this.roomSecret = storedSecret || '';
} else {
this.roomSecret = secret;
}
} }
getRoomSecret(): string { getRoomSecret(): string {

View File

@ -11,32 +11,32 @@ export class SessionStorageService {
constructor() {} 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 roomId The room ID.
* @param secret The secret string. * @param secret The secret to store.
*/ */
public setModeratorSecret(roomId: string, secret: string): void { public setRoomSecret(roomId: string, secret: string): void {
this.set(`moderator_secret_${roomId}`, secret); 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. * @param roomId The room ID.
* @returns The stored secret or null if not found. * @returns The stored secret or null if not found.
*/ */
public getModeratorSecret(roomId: string): string | null { public getRoomSecret(roomId: string): string | null {
return this.get<string>(`moderator_secret_${roomId}`) ?? null; return this.get<string>(`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. * @param roomId The room ID.
*/ */
public removeModeratorSecret(roomId: string): void { public removeRoomSecret(roomId: string): void {
this.remove(`moderator_secret_${roomId}`); this.remove(`room_secret_${roomId}`);
} }
/** /**