frontend: Add new route to access RoomRecordingsComponent and associated guards
This commit is contained in:
parent
b4292e8ca1
commit
6a9cd04a74
@ -25,8 +25,8 @@ export const checkUserAuthenticatedGuard: CanActivateFn = async (
|
||||
const isAuthenticated = await authService.isUserAuthenticated();
|
||||
if (!isAuthenticated) {
|
||||
// Redirect to the login page specified in the route data when user is not authenticated
|
||||
const { redirectToUnauthorized } = route.data;
|
||||
return router.createUrlTree([redirectToUnauthorized]);
|
||||
const { redirectToWhenUnauthorized } = route.data;
|
||||
return router.createUrlTree([redirectToWhenUnauthorized]);
|
||||
}
|
||||
|
||||
// Check if the user has the expected roles
|
||||
@ -35,8 +35,8 @@ export const checkUserAuthenticatedGuard: CanActivateFn = async (
|
||||
|
||||
if (!expectedRoles.includes(userRole)) {
|
||||
// Redirect to the page specified in the route data when user has an invalid role
|
||||
const { redirectToInvalidRole } = route.data;
|
||||
return router.createUrlTree([redirectToInvalidRole]);
|
||||
const { redirectToWhenInvalidRole } = route.data;
|
||||
return router.createUrlTree([redirectToWhenInvalidRole]);
|
||||
}
|
||||
|
||||
// Allow access to the requested page
|
||||
@ -63,6 +63,7 @@ export const checkParticipantRoleAndAuthGuard: CanActivateFn = async (
|
||||
|
||||
const roomRoleAndPermissions = await httpService.getRoomRoleAndPermissions(roomId, storageSecret || secret);
|
||||
participantRole = roomRoleAndPermissions.role;
|
||||
contextService.setParticipantRole(participantRole);
|
||||
} catch (error) {
|
||||
console.error('Error getting participant role:', error);
|
||||
return router.createUrlTree(['unauthorized'], { queryParams: { reason: 'unauthorized-participant' } });
|
||||
|
||||
@ -1,10 +1,11 @@
|
||||
import { inject } from '@angular/core';
|
||||
import { ActivatedRouteSnapshot, CanActivateFn } from '@angular/router';
|
||||
import { ActivatedRouteSnapshot, CanActivateFn, Router } from '@angular/router';
|
||||
import { ContextService } from '../services';
|
||||
|
||||
export const extractQueryParamsGuard: CanActivateFn = (route: ActivatedRouteSnapshot) => {
|
||||
export const extractRoomQueryParamsGuard: CanActivateFn = (route: ActivatedRouteSnapshot) => {
|
||||
const router = inject(Router);
|
||||
const contextService = inject(ContextService);
|
||||
const { roomId, participantName, secret, leaveRedirectUrl } = extractParams(route);
|
||||
const { roomId, participantName, secret, leaveRedirectUrl, viewRecordings } = extractParams(route);
|
||||
|
||||
if (isValidUrl(leaveRedirectUrl)) {
|
||||
contextService.setLeaveRedirectUrl(leaveRedirectUrl);
|
||||
@ -14,6 +15,23 @@ export const extractQueryParamsGuard: CanActivateFn = (route: ActivatedRouteSnap
|
||||
contextService.setParticipantName(participantName);
|
||||
contextService.setSecret(secret);
|
||||
|
||||
if (viewRecordings === 'true') {
|
||||
// Redirect to the room recordings page
|
||||
return router.createUrlTree([`room/${roomId}/recordings`], {
|
||||
queryParams: { secret }
|
||||
});
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
export const extractRecordingQueryParamsGuard: CanActivateFn = (route: ActivatedRouteSnapshot) => {
|
||||
const contextService = inject(ContextService);
|
||||
const { roomId, secret } = extractParams(route);
|
||||
|
||||
contextService.setRoomId(roomId);
|
||||
contextService.setSecret(secret);
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
@ -21,7 +39,8 @@ const extractParams = (route: ActivatedRouteSnapshot) => ({
|
||||
roomId: route.params['room-id'],
|
||||
participantName: route.queryParams['participant-name'],
|
||||
secret: route.queryParams['secret'],
|
||||
leaveRedirectUrl: route.queryParams['leave-redirect-url']
|
||||
leaveRedirectUrl: route.queryParams['leave-redirect-url'],
|
||||
viewRecordings: route.queryParams['view-recordings']
|
||||
});
|
||||
|
||||
const isValidUrl = (url: string) => {
|
||||
|
||||
@ -1,8 +1,9 @@
|
||||
export * from './auth.guard';
|
||||
export * from './extract-query-params.guard';
|
||||
export * from './validate-room-access.guard';
|
||||
export * from './validate-recording-access.guard';
|
||||
export * from './application-mode.guard';
|
||||
export * from './participant-name.guard';
|
||||
export * from './replace-moderator-secret.guard';
|
||||
export * from './moderator-secret.guard';
|
||||
export * from './room-creator.guard';
|
||||
export * from './run-serially.guard';
|
||||
|
||||
@ -53,6 +53,44 @@ export const replaceModeratorSecretGuard: CanActivateFn = (route, _state) => {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 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 httpService = inject(HttpService);
|
||||
const contextService = inject(ContextService);
|
||||
const router = inject(Router);
|
||||
const location: Location = inject(Location);
|
||||
const sessionStorageService = inject(SessionStorageService);
|
||||
|
||||
router.events
|
||||
.pipe(
|
||||
filter((event) => event instanceof NavigationEnd),
|
||||
take(1)
|
||||
)
|
||||
.subscribe(async () => {
|
||||
if (contextService.isModeratorParticipant()) {
|
||||
const roomId = contextService.getRoomId();
|
||||
const storedSecret = sessionStorageService.getModeratorSecret(roomId);
|
||||
const moderatorSecret = storedSecret || contextService.getSecret();
|
||||
sessionStorageService.setModeratorSecret(roomId, moderatorSecret);
|
||||
|
||||
// Remove secret from URL
|
||||
const queryParams = { ...route.queryParams };
|
||||
delete queryParams['secret'];
|
||||
const urlTree = router.createUrlTree([], { queryParams });
|
||||
const newUrl = router.serializeUrl(urlTree);
|
||||
|
||||
location.replaceState(newUrl);
|
||||
}
|
||||
});
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
const getUrlSecret = async (
|
||||
httpService: HttpService,
|
||||
roomId: string
|
||||
@ -3,10 +3,10 @@ import { CanActivateFn, RedirectCommand } from '@angular/router';
|
||||
import { Router } from '@angular/router';
|
||||
import { ContextService } from '../services';
|
||||
|
||||
export const checkParticipantNameGuard: CanActivateFn = (route, state) => {
|
||||
export const checkParticipantNameGuard: CanActivateFn = (_route, state) => {
|
||||
const router = inject(Router);
|
||||
const contextService = inject(ContextService);
|
||||
const roomId = route.params['room-id'];
|
||||
const roomId = contextService.getRoomId();
|
||||
const hasParticipantName = !!contextService.getParticipantName();
|
||||
|
||||
// Check if participant name exists in the service
|
||||
|
||||
@ -0,0 +1,56 @@
|
||||
import { inject } from '@angular/core';
|
||||
import {
|
||||
ActivatedRouteSnapshot,
|
||||
Router,
|
||||
RouterStateSnapshot,
|
||||
CanActivateFn,
|
||||
UrlTree,
|
||||
RedirectCommand
|
||||
} from '@angular/router';
|
||||
import { ContextService, HttpService, SessionStorageService } from '../services';
|
||||
|
||||
/**
|
||||
* Guard to validate the access to recordings.
|
||||
*/
|
||||
export const validateRecordingAccessGuard: CanActivateFn = async (
|
||||
_route: ActivatedRouteSnapshot,
|
||||
_state: RouterStateSnapshot
|
||||
) => {
|
||||
const httpService = inject(HttpService);
|
||||
const contextService = inject(ContextService);
|
||||
const router = inject(Router);
|
||||
const sessionStorageService = inject(SessionStorageService);
|
||||
|
||||
const roomId = contextService.getRoomId();
|
||||
const secret = contextService.getSecret();
|
||||
const storageSecret = sessionStorageService.getModeratorSecret(roomId);
|
||||
|
||||
try {
|
||||
// Generate a token to access recordings in the room
|
||||
const response = await httpService.generateRecordingToken(roomId, storageSecret || secret);
|
||||
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');
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch (error: any) {
|
||||
console.error('Error generating recording token:', error);
|
||||
switch (error.status) {
|
||||
case 403:
|
||||
// Recording access is configured for admins only
|
||||
return redirectToUnauthorized(router, 'unauthorized-recording-access');
|
||||
case 404:
|
||||
// There are no recordings in the room or the room does not exist
|
||||
return redirectToUnauthorized(router, 'no-recordings');
|
||||
default:
|
||||
return redirectToUnauthorized(router, 'invalid-room');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const redirectToUnauthorized = (router: Router, reason: string): UrlTree => {
|
||||
return router.createUrlTree(['unauthorized'], { queryParams: { reason } });
|
||||
};
|
||||
@ -1,5 +1,12 @@
|
||||
import { inject } from '@angular/core';
|
||||
import { ActivatedRouteSnapshot, Router, RouterStateSnapshot, CanActivateFn, UrlTree, RedirectCommand } from '@angular/router';
|
||||
import {
|
||||
ActivatedRouteSnapshot,
|
||||
Router,
|
||||
RouterStateSnapshot,
|
||||
CanActivateFn,
|
||||
UrlTree,
|
||||
RedirectCommand
|
||||
} from '@angular/router';
|
||||
import { ContextService, HttpService, SessionStorageService } from '../services';
|
||||
|
||||
/**
|
||||
@ -26,7 +33,7 @@ export const validateRoomAccessGuard: CanActivateFn = async (
|
||||
participantName,
|
||||
secret: storageSecret || secret
|
||||
});
|
||||
contextService.setToken(response.token);
|
||||
contextService.setParticipantToken(response.token);
|
||||
return true;
|
||||
} catch (error: any) {
|
||||
console.error('Error generating participant token:', error);
|
||||
|
||||
@ -82,9 +82,9 @@ export class RoomCreatorComponent implements OnInit {
|
||||
|
||||
const accessRoomUrl = new URL(room.moderatorRoomUrl);
|
||||
const secret = accessRoomUrl.searchParams.get('secret');
|
||||
const roomId = accessRoomUrl.pathname;
|
||||
const roomUrl = accessRoomUrl.pathname;
|
||||
|
||||
this.router.navigate([roomId], { queryParams: { secret } });
|
||||
this.router.navigate([roomUrl], { queryParams: { secret } });
|
||||
} catch (error) {
|
||||
console.error('Error creating room ', error);
|
||||
}
|
||||
|
||||
@ -105,7 +105,7 @@ export class VideoRoomComponent implements OnInit, OnDestroy {
|
||||
this.ctxService.setParticipantName(participantName);
|
||||
}
|
||||
|
||||
this.token = this.ctxService.getToken();
|
||||
this.token = this.ctxService.getParticipantToken();
|
||||
} catch (error: any) {
|
||||
console.error(error);
|
||||
this.serverError = error.error;
|
||||
|
||||
@ -1,35 +1,35 @@
|
||||
import { Routes } from '@angular/router';
|
||||
import { UnauthorizedComponent, RoomCreatorDisabledComponent } from '../components';
|
||||
import { UserRole } from '@lib/typings/ce';
|
||||
import { RoomCreatorDisabledComponent, UnauthorizedComponent } from '../components';
|
||||
import {
|
||||
applicationModeGuard,
|
||||
checkParticipantNameGuard,
|
||||
checkParticipantRoleAndAuthGuard,
|
||||
checkRoomCreatorEnabledGuard,
|
||||
checkUserAuthenticatedGuard,
|
||||
checkUserNotAuthenticatedGuard,
|
||||
validateRoomAccessGuard,
|
||||
applicationModeGuard,
|
||||
extractQueryParamsGuard,
|
||||
checkParticipantNameGuard,
|
||||
extractRecordingQueryParamsGuard,
|
||||
extractRoomQueryParamsGuard,
|
||||
removeModeratorSecretGuard,
|
||||
replaceModeratorSecretGuard,
|
||||
checkRoomCreatorEnabledGuard,
|
||||
checkParticipantRoleAndAuthGuard,
|
||||
runGuardsSerially
|
||||
runGuardsSerially,
|
||||
validateRecordingAccessGuard,
|
||||
validateRoomAccessGuard
|
||||
} from '../guards';
|
||||
import {
|
||||
AboutComponent,
|
||||
AccessPermissionsComponent,
|
||||
AppearanceComponent,
|
||||
ConsoleComponent,
|
||||
ConsoleLoginComponent,
|
||||
DisconnectedComponent,
|
||||
RoomCreatorComponent,
|
||||
LoginComponent,
|
||||
OverviewComponent,
|
||||
ParticipantNameFormComponent,
|
||||
RecordingsComponent,
|
||||
RoomCreatorComponent,
|
||||
RoomFormComponent,
|
||||
RoomRecordingsComponent,
|
||||
RoomsComponent,
|
||||
SecurityPreferencesComponent,
|
||||
VideoRoomComponent,
|
||||
RoomFormComponent
|
||||
VideoRoomComponent
|
||||
} from '../pages';
|
||||
import { UserRole } from '@lib/typings/ce';
|
||||
|
||||
export const baseRoutes: Routes = [
|
||||
{
|
||||
@ -39,8 +39,8 @@ export const baseRoutes: Routes = [
|
||||
data: {
|
||||
checkSkipAuth: true,
|
||||
expectedRoles: [UserRole.USER],
|
||||
redirectToUnauthorized: 'login',
|
||||
redirectToInvalidRole: 'console'
|
||||
redirectToWhenUnauthorized: 'login',
|
||||
redirectToWhenInvalidRole: 'console'
|
||||
}
|
||||
},
|
||||
{
|
||||
@ -64,8 +64,8 @@ export const baseRoutes: Routes = [
|
||||
canActivate: [checkUserAuthenticatedGuard],
|
||||
data: {
|
||||
expectedRoles: [UserRole.ADMIN],
|
||||
redirectToUnauthorized: 'console/login',
|
||||
redirectToInvalidRole: ''
|
||||
redirectToWhenUnauthorized: 'console/login',
|
||||
redirectToWhenInvalidRole: ''
|
||||
},
|
||||
children: [
|
||||
{
|
||||
@ -115,7 +115,7 @@ export const baseRoutes: Routes = [
|
||||
canActivate: [
|
||||
runGuardsSerially(
|
||||
applicationModeGuard,
|
||||
extractQueryParamsGuard,
|
||||
extractRoomQueryParamsGuard,
|
||||
checkParticipantRoleAndAuthGuard,
|
||||
checkParticipantNameGuard,
|
||||
validateRoomAccessGuard,
|
||||
@ -127,6 +127,19 @@ export const baseRoutes: Routes = [
|
||||
path: 'room/:room-id/participant-name',
|
||||
component: ParticipantNameFormComponent
|
||||
},
|
||||
{
|
||||
path: 'room/:room-id/recordings',
|
||||
component: RoomRecordingsComponent,
|
||||
canActivate: [
|
||||
runGuardsSerially(
|
||||
applicationModeGuard,
|
||||
extractRecordingQueryParamsGuard,
|
||||
checkParticipantRoleAndAuthGuard,
|
||||
validateRecordingAccessGuard,
|
||||
removeModeratorSecretGuard
|
||||
)
|
||||
]
|
||||
},
|
||||
|
||||
// Redirect all other routes to RoomCreatorComponent
|
||||
{ path: '**', redirectTo: '' }
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user