frontend: refactor code to always remove room secret from URL instead of switching moderator secret with publisher secret
This commit is contained in:
parent
51a379af33
commit
ed6af7a7ff
@ -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
|
||||||
|
|||||||
@ -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';
|
||||||
|
|||||||
@ -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;
|
|
||||||
};
|
|
||||||
@ -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;
|
||||||
|
};
|
||||||
@ -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
|
||||||
|
|||||||
@ -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
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|||||||
@ -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'
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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 {
|
||||||
|
|||||||
@ -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}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user