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();
|
const isAuthenticated = await authService.isUserAuthenticated();
|
||||||
if (!isAuthenticated) {
|
if (!isAuthenticated) {
|
||||||
// Redirect to the login page specified in the route data when user is not authenticated
|
// Redirect to the login page specified in the route data when user is not authenticated
|
||||||
const { redirectToUnauthorized } = route.data;
|
const { redirectToWhenUnauthorized } = route.data;
|
||||||
return router.createUrlTree([redirectToUnauthorized]);
|
return router.createUrlTree([redirectToWhenUnauthorized]);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if the user has the expected roles
|
// Check if the user has the expected roles
|
||||||
@ -35,8 +35,8 @@ export const checkUserAuthenticatedGuard: CanActivateFn = async (
|
|||||||
|
|
||||||
if (!expectedRoles.includes(userRole)) {
|
if (!expectedRoles.includes(userRole)) {
|
||||||
// Redirect to the page specified in the route data when user has an invalid role
|
// Redirect to the page specified in the route data when user has an invalid role
|
||||||
const { redirectToInvalidRole } = route.data;
|
const { redirectToWhenInvalidRole } = route.data;
|
||||||
return router.createUrlTree([redirectToInvalidRole]);
|
return router.createUrlTree([redirectToWhenInvalidRole]);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Allow access to the requested page
|
// Allow access to the requested page
|
||||||
@ -63,6 +63,7 @@ export const checkParticipantRoleAndAuthGuard: CanActivateFn = async (
|
|||||||
|
|
||||||
const roomRoleAndPermissions = await httpService.getRoomRoleAndPermissions(roomId, storageSecret || secret);
|
const roomRoleAndPermissions = await httpService.getRoomRoleAndPermissions(roomId, storageSecret || secret);
|
||||||
participantRole = roomRoleAndPermissions.role;
|
participantRole = roomRoleAndPermissions.role;
|
||||||
|
contextService.setParticipantRole(participantRole);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error getting participant role:', error);
|
console.error('Error getting participant role:', error);
|
||||||
return router.createUrlTree(['unauthorized'], { queryParams: { reason: 'unauthorized-participant' } });
|
return router.createUrlTree(['unauthorized'], { queryParams: { reason: 'unauthorized-participant' } });
|
||||||
|
|||||||
@ -1,10 +1,11 @@
|
|||||||
import { inject } from '@angular/core';
|
import { inject } from '@angular/core';
|
||||||
import { ActivatedRouteSnapshot, CanActivateFn } from '@angular/router';
|
import { ActivatedRouteSnapshot, CanActivateFn, Router } from '@angular/router';
|
||||||
import { ContextService } from '../services';
|
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 contextService = inject(ContextService);
|
||||||
const { roomId, participantName, secret, leaveRedirectUrl } = extractParams(route);
|
const { roomId, participantName, secret, leaveRedirectUrl, viewRecordings } = extractParams(route);
|
||||||
|
|
||||||
if (isValidUrl(leaveRedirectUrl)) {
|
if (isValidUrl(leaveRedirectUrl)) {
|
||||||
contextService.setLeaveRedirectUrl(leaveRedirectUrl);
|
contextService.setLeaveRedirectUrl(leaveRedirectUrl);
|
||||||
@ -14,6 +15,23 @@ export const extractQueryParamsGuard: CanActivateFn = (route: ActivatedRouteSnap
|
|||||||
contextService.setParticipantName(participantName);
|
contextService.setParticipantName(participantName);
|
||||||
contextService.setSecret(secret);
|
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;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -21,7 +39,8 @@ const extractParams = (route: ActivatedRouteSnapshot) => ({
|
|||||||
roomId: route.params['room-id'],
|
roomId: route.params['room-id'],
|
||||||
participantName: route.queryParams['participant-name'],
|
participantName: route.queryParams['participant-name'],
|
||||||
secret: route.queryParams['secret'],
|
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) => {
|
const isValidUrl = (url: string) => {
|
||||||
|
|||||||
@ -1,8 +1,9 @@
|
|||||||
export * from './auth.guard';
|
export * from './auth.guard';
|
||||||
export * from './extract-query-params.guard';
|
export * from './extract-query-params.guard';
|
||||||
export * from './validate-room-access.guard';
|
export * from './validate-room-access.guard';
|
||||||
|
export * from './validate-recording-access.guard';
|
||||||
export * from './application-mode.guard';
|
export * from './application-mode.guard';
|
||||||
export * from './participant-name.guard';
|
export * from './participant-name.guard';
|
||||||
export * from './replace-moderator-secret.guard';
|
export * from './moderator-secret.guard';
|
||||||
export * from './room-creator.guard';
|
export * from './room-creator.guard';
|
||||||
export * from './run-serially.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 (
|
const getUrlSecret = async (
|
||||||
httpService: HttpService,
|
httpService: HttpService,
|
||||||
roomId: string
|
roomId: string
|
||||||
@ -3,10 +3,10 @@ import { CanActivateFn, RedirectCommand } from '@angular/router';
|
|||||||
import { Router } from '@angular/router';
|
import { Router } from '@angular/router';
|
||||||
import { ContextService } from '../services';
|
import { ContextService } from '../services';
|
||||||
|
|
||||||
export const checkParticipantNameGuard: CanActivateFn = (route, state) => {
|
export const checkParticipantNameGuard: CanActivateFn = (_route, state) => {
|
||||||
const router = inject(Router);
|
const router = inject(Router);
|
||||||
const contextService = inject(ContextService);
|
const contextService = inject(ContextService);
|
||||||
const roomId = route.params['room-id'];
|
const roomId = contextService.getRoomId();
|
||||||
const hasParticipantName = !!contextService.getParticipantName();
|
const hasParticipantName = !!contextService.getParticipantName();
|
||||||
|
|
||||||
// Check if participant name exists in the service
|
// 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 { 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';
|
import { ContextService, HttpService, SessionStorageService } from '../services';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -26,7 +33,7 @@ export const validateRoomAccessGuard: CanActivateFn = async (
|
|||||||
participantName,
|
participantName,
|
||||||
secret: storageSecret || secret
|
secret: storageSecret || secret
|
||||||
});
|
});
|
||||||
contextService.setToken(response.token);
|
contextService.setParticipantToken(response.token);
|
||||||
return true;
|
return true;
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error('Error generating participant token:', error);
|
console.error('Error generating participant token:', error);
|
||||||
|
|||||||
@ -82,9 +82,9 @@ export class RoomCreatorComponent implements OnInit {
|
|||||||
|
|
||||||
const accessRoomUrl = new URL(room.moderatorRoomUrl);
|
const accessRoomUrl = new URL(room.moderatorRoomUrl);
|
||||||
const secret = accessRoomUrl.searchParams.get('secret');
|
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) {
|
} catch (error) {
|
||||||
console.error('Error creating room ', error);
|
console.error('Error creating room ', error);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -105,7 +105,7 @@ export class VideoRoomComponent implements OnInit, OnDestroy {
|
|||||||
this.ctxService.setParticipantName(participantName);
|
this.ctxService.setParticipantName(participantName);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.token = this.ctxService.getToken();
|
this.token = this.ctxService.getParticipantToken();
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
this.serverError = error.error;
|
this.serverError = error.error;
|
||||||
|
|||||||
@ -1,35 +1,35 @@
|
|||||||
import { Routes } from '@angular/router';
|
import { Routes } from '@angular/router';
|
||||||
import { UnauthorizedComponent, RoomCreatorDisabledComponent } from '../components';
|
import { UserRole } from '@lib/typings/ce';
|
||||||
|
import { RoomCreatorDisabledComponent, UnauthorizedComponent } from '../components';
|
||||||
import {
|
import {
|
||||||
|
applicationModeGuard,
|
||||||
|
checkParticipantNameGuard,
|
||||||
|
checkParticipantRoleAndAuthGuard,
|
||||||
|
checkRoomCreatorEnabledGuard,
|
||||||
checkUserAuthenticatedGuard,
|
checkUserAuthenticatedGuard,
|
||||||
checkUserNotAuthenticatedGuard,
|
checkUserNotAuthenticatedGuard,
|
||||||
validateRoomAccessGuard,
|
extractRecordingQueryParamsGuard,
|
||||||
applicationModeGuard,
|
extractRoomQueryParamsGuard,
|
||||||
extractQueryParamsGuard,
|
removeModeratorSecretGuard,
|
||||||
checkParticipantNameGuard,
|
|
||||||
replaceModeratorSecretGuard,
|
replaceModeratorSecretGuard,
|
||||||
checkRoomCreatorEnabledGuard,
|
runGuardsSerially,
|
||||||
checkParticipantRoleAndAuthGuard,
|
validateRecordingAccessGuard,
|
||||||
runGuardsSerially
|
validateRoomAccessGuard
|
||||||
} from '../guards';
|
} from '../guards';
|
||||||
import {
|
import {
|
||||||
AboutComponent,
|
|
||||||
AccessPermissionsComponent,
|
|
||||||
AppearanceComponent,
|
|
||||||
ConsoleComponent,
|
ConsoleComponent,
|
||||||
ConsoleLoginComponent,
|
ConsoleLoginComponent,
|
||||||
DisconnectedComponent,
|
DisconnectedComponent,
|
||||||
RoomCreatorComponent,
|
|
||||||
LoginComponent,
|
LoginComponent,
|
||||||
OverviewComponent,
|
OverviewComponent,
|
||||||
ParticipantNameFormComponent,
|
ParticipantNameFormComponent,
|
||||||
RecordingsComponent,
|
RecordingsComponent,
|
||||||
|
RoomCreatorComponent,
|
||||||
|
RoomFormComponent,
|
||||||
|
RoomRecordingsComponent,
|
||||||
RoomsComponent,
|
RoomsComponent,
|
||||||
SecurityPreferencesComponent,
|
VideoRoomComponent
|
||||||
VideoRoomComponent,
|
|
||||||
RoomFormComponent
|
|
||||||
} from '../pages';
|
} from '../pages';
|
||||||
import { UserRole } from '@lib/typings/ce';
|
|
||||||
|
|
||||||
export const baseRoutes: Routes = [
|
export const baseRoutes: Routes = [
|
||||||
{
|
{
|
||||||
@ -39,8 +39,8 @@ export const baseRoutes: Routes = [
|
|||||||
data: {
|
data: {
|
||||||
checkSkipAuth: true,
|
checkSkipAuth: true,
|
||||||
expectedRoles: [UserRole.USER],
|
expectedRoles: [UserRole.USER],
|
||||||
redirectToUnauthorized: 'login',
|
redirectToWhenUnauthorized: 'login',
|
||||||
redirectToInvalidRole: 'console'
|
redirectToWhenInvalidRole: 'console'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -64,8 +64,8 @@ export const baseRoutes: Routes = [
|
|||||||
canActivate: [checkUserAuthenticatedGuard],
|
canActivate: [checkUserAuthenticatedGuard],
|
||||||
data: {
|
data: {
|
||||||
expectedRoles: [UserRole.ADMIN],
|
expectedRoles: [UserRole.ADMIN],
|
||||||
redirectToUnauthorized: 'console/login',
|
redirectToWhenUnauthorized: 'console/login',
|
||||||
redirectToInvalidRole: ''
|
redirectToWhenInvalidRole: ''
|
||||||
},
|
},
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
@ -115,7 +115,7 @@ export const baseRoutes: Routes = [
|
|||||||
canActivate: [
|
canActivate: [
|
||||||
runGuardsSerially(
|
runGuardsSerially(
|
||||||
applicationModeGuard,
|
applicationModeGuard,
|
||||||
extractQueryParamsGuard,
|
extractRoomQueryParamsGuard,
|
||||||
checkParticipantRoleAndAuthGuard,
|
checkParticipantRoleAndAuthGuard,
|
||||||
checkParticipantNameGuard,
|
checkParticipantNameGuard,
|
||||||
validateRoomAccessGuard,
|
validateRoomAccessGuard,
|
||||||
@ -127,6 +127,19 @@ export const baseRoutes: Routes = [
|
|||||||
path: 'room/:room-id/participant-name',
|
path: 'room/:room-id/participant-name',
|
||||||
component: ParticipantNameFormComponent
|
component: ParticipantNameFormComponent
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: 'room/:room-id/recordings',
|
||||||
|
component: RoomRecordingsComponent,
|
||||||
|
canActivate: [
|
||||||
|
runGuardsSerially(
|
||||||
|
applicationModeGuard,
|
||||||
|
extractRecordingQueryParamsGuard,
|
||||||
|
checkParticipantRoleAndAuthGuard,
|
||||||
|
validateRecordingAccessGuard,
|
||||||
|
removeModeratorSecretGuard
|
||||||
|
)
|
||||||
|
]
|
||||||
|
},
|
||||||
|
|
||||||
// Redirect all other routes to RoomCreatorComponent
|
// Redirect all other routes to RoomCreatorComponent
|
||||||
{ path: '**', redirectTo: '' }
|
{ path: '**', redirectTo: '' }
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user