Picsur/frontend/src/app/guards/permission.guard.ts

87 lines
2.7 KiB
TypeScript

import { Injectable } from '@angular/core';
import {
ActivatedRouteSnapshot,
CanActivate,
CanActivateChild,
Router,
RouterStateSnapshot,
} from '@angular/router';
import { isPermissionsArray } from 'picsur-shared/dist/validators/permissions.validator';
import { PRouteData } from '../models/dto/picsur-routes.dto';
import { PermissionService } from '../services/api/permission.service';
import { StaticInfoService } from '../services/api/static-info.service';
import { Logger } from '../services/logger/logger.service';
@Injectable({
providedIn: 'root',
})
export class PermissionGuard implements CanActivate, CanActivateChild {
private readonly logger = new Logger(PermissionGuard.name);
private allPermissionsArray: string[] | null = null;
constructor(
private readonly permissionService: PermissionService,
private readonly staticInfo: StaticInfoService,
private readonly router: Router,
) {
this.setupAllPermissions().catch(this.logger.error);
}
private async setupAllPermissions() {
this.allPermissionsArray = await this.staticInfo.getAllPermissions();
}
async canActivateChild(
childRoute: ActivatedRouteSnapshot,
state: RouterStateSnapshot,
) {
return await this.can(childRoute, state);
}
async canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
return await this.can(route, state);
}
private async can(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
const requiredPermissions: string[] = this.nestedPermissions(route);
// Check if permissions array is valid
// But only if we actually have the data
if (
this.allPermissionsArray !== null &&
!isPermissionsArray(requiredPermissions, this.allPermissionsArray)
) {
this.logger.error(
`Permissions array is invalid: "${requiredPermissions}" (available: ${this.allPermissionsArray})`,
);
return false;
}
const ourPermissions = await this.permissionService.getLoadedSnapshot();
const weHavePermission = requiredPermissions.every((permission) =>
ourPermissions.includes(permission),
);
if (!weHavePermission)
this.router.navigate(['/error/401'], { replaceUrl: true });
return weHavePermission;
}
// This aggregates nested permission for deep routes
private nestedPermissions(route: ActivatedRouteSnapshot): string[] {
const data: PRouteData = route.data;
let permissions: string[] = [];
if (data?.permissions) {
permissions = permissions.concat(data.permissions);
}
if (route.firstChild) {
permissions = permissions.concat(
this.nestedPermissions(route.firstChild),
);
}
return permissions;
}
}