frontend: Implement participant role retrieval and enhance authentication guards and http interceptor

This commit is contained in:
juancarmore 2025-03-28 12:15:11 +01:00
parent bc33e9c5d9
commit 28b65db651
5 changed files with 58 additions and 23 deletions

View File

@ -1,6 +1,6 @@
import { inject } from '@angular/core';
import { ActivatedRouteSnapshot, CanActivateFn, Router, RouterStateSnapshot } from '@angular/router';
import { AuthService, ContextService } from '../services';
import { ActivatedRouteSnapshot, CanActivateFn, RedirectCommand, Router, RouterStateSnapshot } from '@angular/router';
import { AuthService, ContextService, HttpService, SessionStorageService } from '../services';
import { AuthMode, ParticipantRole } from '@lib/typings/ce';
export const checkUserAuthenticatedGuard: CanActivateFn = async (
@ -47,11 +47,26 @@ export const checkParticipantRoleAndAuthGuard: CanActivateFn = async (
_route: ActivatedRouteSnapshot,
state: RouterStateSnapshot
) => {
const router = inject(Router);
const authService = inject(AuthService);
const contextService = inject(ContextService);
const router = inject(Router);
const sessionStorageService = inject(SessionStorageService);
const httpService = inject(HttpService);
// Get participant role by room secret
let participantRole: ParticipantRole;
try {
const roomName = contextService.getRoomName();
const secret = contextService.getSecret();
const storageSecret = sessionStorageService.getModeratorSecret(roomName);
participantRole = await httpService.getParticipantRole(roomName, storageSecret || secret);
} catch (error) {
console.error('Error getting participant role:', error);
return router.createUrlTree(['unauthorized'], { queryParams: { reason: 'unauthorized-participant' } });
}
const participantRole = contextService.getParticipantRole();
const authMode = await contextService.getAuthModeToEnterRoom();
// If the user is a moderator and the room requires authentication for moderators only,
@ -60,16 +75,18 @@ export const checkParticipantRoleAndAuthGuard: CanActivateFn = async (
const isAuthRequiredForModerators =
authMode === AuthMode.MODERATORS_ONLY && participantRole === ParticipantRole.MODERATOR;
const isAuthRequiredForAllUsers = authMode === AuthMode.ALL_USERS;
console.log('Participant role:', participantRole);
if (isAuthRequiredForModerators || isAuthRequiredForAllUsers) {
// Check if user is authenticated
const isAuthenticated = await authService.isUserAuthenticated();
if (!isAuthenticated) {
// Redirect to the login page with query param to redirect back to the room
return router.createUrlTree(['login'], {
const loginRoute = router.createUrlTree(['login'], {
queryParams: { redirectTo: state.url }
});
return new RedirectCommand(loginRoute, {
skipLocationChange: true
});
}
}

View File

@ -11,7 +11,8 @@ export const httpInterceptor: HttpInterceptorFn = (req: HttpRequest<unknown>, ne
const sessionStorageService = inject(SessionStorageService);
const httpService: HttpService = inject(HttpService);
const url = router.getCurrentNavigation()?.finalUrl?.toString() || router.url;
const pageUrl = router.getCurrentNavigation()?.finalUrl?.toString() || router.url;
const requestUrl = req.url;
req = req.clone({
withCredentials: true
@ -26,9 +27,15 @@ export const httpInterceptor: HttpInterceptorFn = (req: HttpRequest<unknown>, ne
}),
catchError((error: HttpErrorResponse) => {
if (error.url?.includes('/auth/refresh')) {
console.error('Error refreshing access token. Logging out...');
const redirectTo = url.startsWith('/console') ? 'console/login' : 'login';
authService.logout(redirectTo);
console.error('Error refreshing access token');
// If the original request was not to the profile endpoint, logout and redirect to the login page
if (!requestUrl.includes('/profile')) {
console.log('Logging out...');
const redirectTo = pageUrl.startsWith('/console') ? 'console/login' : 'login';
authService.logout(redirectTo);
}
throw firstError;
}
@ -79,9 +86,12 @@ export const httpInterceptor: HttpInterceptorFn = (req: HttpRequest<unknown>, ne
}
// Expired access/participant token
if (url.startsWith('/room')) {
if (pageUrl.startsWith('/room') && !requestUrl.includes('/profile')) {
// If the error occurred in a room page and the request is not to the profile endpoint,
// refresh the participant token
return refreshParticipantToken(error);
} else if (!url.startsWith('/console/login') && !url.startsWith('/login')) {
} else if (!pageUrl.startsWith('/console/login') && !pageUrl.startsWith('/login')) {
// If the error occurred in a page that is not the login page, refresh the access token
return refreshAccessToken(error);
}
}

View File

@ -63,7 +63,8 @@ export class LoginComponent {
return;
}
this.router.navigate([this.redirectTo]);
let urlTree = this.router.parseUrl(this.redirectTo);
this.router.navigateByUrl(urlTree);
} catch (error) {
if ((error as HttpErrorResponse).status === 429) {
this.loginErrorMessage = 'Too many login attempts. Please try again later';

View File

@ -35,12 +35,7 @@ export const baseRoutes: Routes = [
{
path: '',
component: RoomCreatorComponent,
canActivate: [
runGuardsSerially(
checkRoomCreatorEnabledGuard,
checkUserAuthenticatedGuard
)
],
canActivate: [runGuardsSerially(checkRoomCreatorEnabledGuard, checkUserAuthenticatedGuard)],
data: {
checkSkipAuth: true,
expectedRoles: [UserRole.USER],
@ -121,9 +116,9 @@ export const baseRoutes: Routes = [
runGuardsSerially(
applicationModeGuard,
extractQueryParamsGuard,
checkParticipantRoleAndAuthGuard,
checkParticipantNameGuard,
validateRoomAccessGuard,
checkParticipantRoleAndAuthGuard,
replaceModeratorSecretGuard
)
]

View File

@ -1,7 +1,7 @@
import { HttpClient, HttpHeaders } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { OpenViduMeetRoom, OpenViduMeetRoomOptions } from 'projects/shared-meet-components/src/lib/typings/ce/room';
import { GlobalPreferences, RoomPreferences, TokenOptions, User } from '@lib/typings/ce';
import { GlobalPreferences, ParticipantRole, RoomPreferences, TokenOptions, User } from '@lib/typings/ce';
import { RecordingInfo, Room } from 'openvidu-components-angular';
import { lastValueFrom } from 'rxjs';
@ -40,12 +40,24 @@ export class HttpService {
return this.getRequest(path);
}
getParticipantRole(roomName: string, secret: string): Promise<ParticipantRole> {
return this.getRequest(
`${this.INTERNAL_API_PATH_PREFIX}/${this.API_V1_VERSION}/rooms/${roomName}/participant-role?secret=${secret}`
);
}
generateParticipantToken(tokenOptions: TokenOptions): Promise<{ token: string }> {
return this.postRequest(`${this.INTERNAL_API_PATH_PREFIX}/${this.API_V1_VERSION}/participants/token`, tokenOptions);
return this.postRequest(
`${this.INTERNAL_API_PATH_PREFIX}/${this.API_V1_VERSION}/participants/token`,
tokenOptions
);
}
refreshParticipantToken(tokenOptions: TokenOptions): Promise<{ token: string }> {
return this.postRequest(`${this.INTERNAL_API_PATH_PREFIX}/${this.API_V1_VERSION}/participants/token/refresh`, tokenOptions);
return this.postRequest(
`${this.INTERNAL_API_PATH_PREFIX}/${this.API_V1_VERSION}/participants/token/refresh`,
tokenOptions
);
}
/**