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 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

View File

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

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 {
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

View File

@ -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
)
]
},

View File

@ -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<void> {
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<void> {
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'
});
}
}

View File

@ -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 {

View File

@ -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<string>(`moderator_secret_${roomId}`) ?? null;
public getRoomSecret(roomId: string): string | 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.
*/
public removeModeratorSecret(roomId: string): void {
this.remove(`moderator_secret_${roomId}`);
public removeRoomSecret(roomId: string): void {
this.remove(`room_secret_${roomId}`);
}
/**