part of frontend permission guard
This commit is contained in:
parent
7026c8cb67
commit
ac72035f76
|
@ -8,13 +8,12 @@ import {
|
|||
import { Reflector } from '@nestjs/core';
|
||||
import { AuthGuard } from '@nestjs/passport';
|
||||
import { plainToClass } from 'class-transformer';
|
||||
import { isArray, isEnum, isString, validate } from 'class-validator';
|
||||
import { validate } from 'class-validator';
|
||||
import {
|
||||
Permissions,
|
||||
PermissionsList
|
||||
Permissions
|
||||
} from 'picsur-shared/dist/dto/permissions';
|
||||
import { Roles } from 'picsur-shared/dist/dto/roles.dto';
|
||||
import { Fail, Failable, HasFailed } from 'picsur-shared/dist/types';
|
||||
import { isPermissionsArray } from 'picsur-shared/dist/util/permissions';
|
||||
import { UsersService } from '../../../collections/userdb/userdb.service';
|
||||
import { EUserBackend } from '../../../models/entities/user.entity';
|
||||
|
||||
|
@ -42,13 +41,13 @@ export class MainAuthGuard extends AuthGuard(['jwt', 'guest']) {
|
|||
|
||||
const permissions = this.extractPermissions(context);
|
||||
if (HasFailed(permissions)) {
|
||||
this.logger.warn("222"+permissions.getReason());
|
||||
this.logger.warn('222' + permissions.getReason());
|
||||
throw new InternalServerErrorException();
|
||||
}
|
||||
|
||||
const userPermissions = await this.usersService.getPermissions(user);
|
||||
if (HasFailed(userPermissions)) {
|
||||
this.logger.warn("111"+userPermissions.getReason());
|
||||
this.logger.warn('111' + userPermissions.getReason());
|
||||
throw new InternalServerErrorException();
|
||||
}
|
||||
|
||||
|
@ -69,21 +68,13 @@ export class MainAuthGuard extends AuthGuard(['jwt', 'guest']) {
|
|||
);
|
||||
}
|
||||
|
||||
if (!this.isPermissionsArray(permissions)) {
|
||||
if (!isPermissionsArray(permissions)) {
|
||||
return Fail(`Permissions for ${handlerName} is not a string array`);
|
||||
}
|
||||
|
||||
return permissions;
|
||||
}
|
||||
|
||||
private isPermissionsArray(value: any): value is Roles {
|
||||
if (!isArray(value)) return false;
|
||||
if (!value.every((item: unknown) => isString(item))) return false;
|
||||
if (!value.every((item: string) => isEnum(item, PermissionsList)))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
private async validateUser(user: EUserBackend): Promise<EUserBackend> {
|
||||
const userClass = plainToClass(EUserBackend, user);
|
||||
const errors = await validate(userClass, {
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { Injectable, Logger } from '@nestjs/common';
|
||||
import { Permission } from 'picsur-shared/dist/dto/permissions';
|
||||
import { ImageDBService } from '../../collections/imagedb/imagedb.service';
|
||||
import { RolesService } from '../../collections/roledb/roledb.service';
|
||||
|
||||
|
@ -15,7 +16,7 @@ export class DemoManagerService {
|
|||
this.logger.warn(
|
||||
'Modifying roles for demo mode, this will not be reverted automatically',
|
||||
);
|
||||
this.rolesService.addPermissions('guest', ['image-upload']);
|
||||
this.rolesService.addPermissions('guest', [Permission.ImageUpload]);
|
||||
}
|
||||
|
||||
public execute() {
|
||||
|
|
|
@ -21,6 +21,7 @@ import {
|
|||
UserUpdateRolesRequest,
|
||||
UserUpdateRolesResponse
|
||||
} from 'picsur-shared/dist/dto/api/user.dto';
|
||||
import { Permission } from 'picsur-shared/dist/dto/permissions';
|
||||
import { HasFailed } from 'picsur-shared/dist/types';
|
||||
import { UsersService } from '../../../collections/userdb/userdb.service';
|
||||
import {
|
||||
|
@ -41,7 +42,7 @@ export class UserController {
|
|||
) {}
|
||||
|
||||
@Post('login')
|
||||
@UseLocalAuth('user-login')
|
||||
@UseLocalAuth(Permission.UserLogin)
|
||||
async login(@Request() req: AuthFasityRequest): Promise<UserLoginResponse> {
|
||||
return {
|
||||
jwt_token: await this.authService.createToken(req.user),
|
||||
|
@ -49,7 +50,7 @@ export class UserController {
|
|||
}
|
||||
|
||||
@Post('register')
|
||||
@RequiredPermissions('user-register')
|
||||
@RequiredPermissions(Permission.UserRegister)
|
||||
async register(
|
||||
@Body() register: UserRegisterRequest,
|
||||
): Promise<UserRegisterResponse> {
|
||||
|
@ -74,7 +75,7 @@ export class UserController {
|
|||
}
|
||||
|
||||
@Post('delete')
|
||||
@RequiredPermissions('user-manage')
|
||||
@RequiredPermissions(Permission.UserManage)
|
||||
async delete(
|
||||
@Body() deleteData: UserDeleteRequest,
|
||||
): Promise<UserDeleteResponse> {
|
||||
|
@ -88,7 +89,7 @@ export class UserController {
|
|||
}
|
||||
|
||||
@Post('roles')
|
||||
@RequiredPermissions('user-manage')
|
||||
@RequiredPermissions(Permission.UserManage)
|
||||
async setPermissions(
|
||||
@Body() body: UserUpdateRolesRequest,
|
||||
): Promise<UserUpdateRolesResponse> {
|
||||
|
@ -106,7 +107,7 @@ export class UserController {
|
|||
}
|
||||
|
||||
@Post('info')
|
||||
@RequiredPermissions('user-manage')
|
||||
@RequiredPermissions(Permission.UserManage)
|
||||
async getUser(@Body() body: UserInfoRequest): Promise<UserInfoResponse> {
|
||||
const user = await this.usersService.findOne(body.username);
|
||||
if (HasFailed(user)) {
|
||||
|
@ -118,7 +119,7 @@ export class UserController {
|
|||
}
|
||||
|
||||
@Get('list')
|
||||
@RequiredPermissions('user-manage')
|
||||
@RequiredPermissions(Permission.UserManage)
|
||||
async listUsers(): Promise<UserListResponse> {
|
||||
const users = await this.usersService.findAll();
|
||||
if (HasFailed(users)) {
|
||||
|
@ -133,7 +134,7 @@ export class UserController {
|
|||
}
|
||||
|
||||
@Get('me')
|
||||
@RequiredPermissions('user-view')
|
||||
@RequiredPermissions(Permission.UserView)
|
||||
async me(@Request() req: AuthFasityRequest): Promise<UserMeResponse> {
|
||||
return {
|
||||
user: req.user,
|
||||
|
|
|
@ -11,13 +11,14 @@ import {
|
|||
SysPreferenceResponse,
|
||||
UpdateSysPreferenceRequest
|
||||
} from 'picsur-shared/dist/dto/api/pref.dto';
|
||||
import { Permission } from 'picsur-shared/dist/dto/permissions';
|
||||
import { SysPreferences } from 'picsur-shared/dist/dto/syspreferences.dto';
|
||||
import { HasFailed } from 'picsur-shared/dist/types';
|
||||
import { SysPreferenceService } from '../../../collections/syspreferencesdb/syspreferencedb.service';
|
||||
import { RequiredPermissions } from '../../../decorators/permissions.decorator';
|
||||
|
||||
@Controller('api/pref')
|
||||
@RequiredPermissions('syspref-manage')
|
||||
@RequiredPermissions(Permission.SysPrefManage)
|
||||
export class PrefController {
|
||||
private readonly logger = new Logger('PrefController');
|
||||
|
||||
|
|
|
@ -17,12 +17,13 @@ import {
|
|||
RoleUpdateRequest,
|
||||
RoleUpdateResponse
|
||||
} from 'picsur-shared/dist/dto/api/roles.dto';
|
||||
import { Permission } from 'picsur-shared/dist/dto/permissions';
|
||||
import { HasFailed } from 'picsur-shared/dist/types';
|
||||
import { RolesService } from '../../../collections/roledb/roledb.service';
|
||||
import { RequiredPermissions } from '../../../decorators/permissions.decorator';
|
||||
|
||||
@Controller('api/roles')
|
||||
@RequiredPermissions('role-manage')
|
||||
@RequiredPermissions(Permission.RoleManage)
|
||||
export class RolesController {
|
||||
private readonly logger = new Logger('RolesController');
|
||||
|
||||
|
|
|
@ -13,6 +13,7 @@ import {
|
|||
import { isHash } from 'class-validator';
|
||||
import { FastifyReply, FastifyRequest } from 'fastify';
|
||||
import { ImageMetaResponse } from 'picsur-shared/dist/dto/api/image.dto';
|
||||
import { Permission } from 'picsur-shared/dist/dto/permissions';
|
||||
import { HasFailed } from 'picsur-shared/dist/types';
|
||||
import { MultiPart } from '../../decorators/multipart.decorator';
|
||||
import { RequiredPermissions } from '../../decorators/permissions.decorator';
|
||||
|
@ -20,7 +21,7 @@ import { ImageManagerService } from '../../managers/imagemanager/imagemanager.se
|
|||
import { ImageUploadDto } from '../../models/dto/imageroute.dto';
|
||||
|
||||
@Controller('i')
|
||||
@RequiredPermissions('image-view')
|
||||
@RequiredPermissions(Permission.ImageView)
|
||||
export class ImageController {
|
||||
private readonly logger = new Logger('ImageController');
|
||||
|
||||
|
@ -57,7 +58,7 @@ export class ImageController {
|
|||
}
|
||||
|
||||
@Post()
|
||||
@RequiredPermissions('image-upload')
|
||||
@RequiredPermissions(Permission.ImageUpload)
|
||||
async uploadImage(
|
||||
@Req() req: FastifyRequest,
|
||||
@MultiPart(ImageUploadDto) multipart: ImageUploadDto,
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { Component, OnInit } from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
import { AutoUnsubscribe } from 'ngx-auto-unsubscribe-decorator';
|
||||
import { Permissions } from 'picsur-shared/dist/dto/permissions';
|
||||
import { Permission, Permissions } from 'picsur-shared/dist/dto/permissions';
|
||||
import { EUser } from 'picsur-shared/dist/entities/user.entity';
|
||||
import { HasFailed } from 'picsur-shared/dist/types';
|
||||
import { PermissionService } from 'src/app/api/permission.service';
|
||||
|
@ -29,7 +29,7 @@ export class HeaderComponent implements OnInit {
|
|||
}
|
||||
|
||||
public get canLogIn() {
|
||||
return this.permissions.includes('user-login');
|
||||
return this.permissions.includes(Permission.UserLogin);
|
||||
}
|
||||
|
||||
constructor(
|
||||
|
|
11
frontend/src/app/guards/guards.module.ts
Normal file
11
frontend/src/app/guards/guards.module.ts
Normal file
|
@ -0,0 +1,11 @@
|
|||
import { CommonModule } from '@angular/common';
|
||||
import { NgModule } from '@angular/core';
|
||||
import { ApiModule } from '../api/api.module';
|
||||
import { PermissionGuard } from './permission.guard';
|
||||
|
||||
@NgModule({
|
||||
imports: [CommonModule, ApiModule],
|
||||
providers: [PermissionGuard],
|
||||
exports: [],
|
||||
})
|
||||
export class GuardsModule {}
|
37
frontend/src/app/guards/permission.guard.ts
Normal file
37
frontend/src/app/guards/permission.guard.ts
Normal file
|
@ -0,0 +1,37 @@
|
|||
import { Injectable } from '@angular/core';
|
||||
import {
|
||||
ActivatedRouteSnapshot,
|
||||
CanActivate,
|
||||
RouterStateSnapshot
|
||||
} from '@angular/router';
|
||||
import { Permissions } from 'picsur-shared/dist/dto/permissions';
|
||||
import { isPermissionsArray } from 'picsur-shared/dist/util/permissions';
|
||||
import { PermissionService } from '../api/permission.service';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class PermissionGuard implements CanActivate {
|
||||
constructor(private permissionService: PermissionService) {}
|
||||
|
||||
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
|
||||
const requiredPermissions: Permissions = route.data['permissions'];
|
||||
if (!isPermissionsArray(requiredPermissions)) {
|
||||
throw new Error(
|
||||
`PermissionGuard: route data 'permissions' must be an array of Permission values`
|
||||
);
|
||||
}
|
||||
|
||||
const ourPermissions = this.permissionService.snapshot;
|
||||
|
||||
const isOk = requiredPermissions.every((permission) =>
|
||||
ourPermissions.includes(permission)
|
||||
);
|
||||
|
||||
console.log(
|
||||
`PermissionGuard: requiredPermissions=${requiredPermissions} ourPermissions=${ourPermissions} isOk=${isOk}`
|
||||
);
|
||||
|
||||
return isOk;
|
||||
}
|
||||
}
|
|
@ -6,10 +6,13 @@ import { MatInputModule } from '@angular/material/input';
|
|||
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
|
||||
import { RouterModule, Routes } from '@angular/router';
|
||||
import { NgxDropzoneModule } from 'ngx-dropzone';
|
||||
import { Permission } from 'picsur-shared/dist/dto/permissions';
|
||||
import { ApiModule } from '../api/api.module';
|
||||
import { CopyFieldModule } from '../components/copyfield/copyfield.module';
|
||||
import { PageNotFoundComponent } from '../components/pagenotfound/pagenotfound.component';
|
||||
import { PageNotFoundModule } from '../components/pagenotfound/pagenotfound.module';
|
||||
import { GuardsModule } from '../guards/guards.module';
|
||||
import { PermissionGuard } from '../guards/permission.guard';
|
||||
import { LoginComponent } from '../routes/login/login.component';
|
||||
import { ProcessingComponent } from '../routes/processing/processing.component';
|
||||
import { UploadComponent } from '../routes/upload/upload.component';
|
||||
|
@ -25,13 +28,19 @@ const routes: Routes = [
|
|||
component: ProcessingComponent,
|
||||
},
|
||||
{ path: 'view/:hash', component: ViewComponent },
|
||||
{ path: 'login', component: LoginComponent },
|
||||
{
|
||||
path: 'login',
|
||||
component: LoginComponent,
|
||||
canActivate: [PermissionGuard],
|
||||
data: { permissions: [Permission.UserLogin] },
|
||||
},
|
||||
{ path: '**', component: PageNotFoundComponent },
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
CommonModule,
|
||||
GuardsModule,
|
||||
NgxDropzoneModule,
|
||||
UtilModule,
|
||||
MatProgressSpinnerModule,
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { Component, OnInit } from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
import { AutoUnsubscribe } from 'ngx-auto-unsubscribe-decorator';
|
||||
import { Permissions } from 'picsur-shared/dist/dto/permissions';
|
||||
import { Permission, Permissions } from 'picsur-shared/dist/dto/permissions';
|
||||
import { HasFailed } from 'picsur-shared/dist/types';
|
||||
import { PermissionService } from 'src/app/api/permission.service';
|
||||
import { UserService } from 'src/app/api/user.service';
|
||||
|
@ -20,7 +20,7 @@ export class LoginComponent implements OnInit {
|
|||
private permissions: Permissions = [];
|
||||
|
||||
public get showRegister() {
|
||||
return this.permissions.includes('user-register');
|
||||
return this.permissions.includes(Permission.UserRegister);
|
||||
}
|
||||
|
||||
model = new LoginControl();
|
||||
|
|
|
@ -2,7 +2,7 @@ import { Component, OnInit } from '@angular/core';
|
|||
import { Router } from '@angular/router';
|
||||
import { AutoUnsubscribe } from 'ngx-auto-unsubscribe-decorator';
|
||||
import { NgxDropzoneChangeEvent } from 'ngx-dropzone';
|
||||
import { Permissions } from 'picsur-shared/dist/dto/permissions';
|
||||
import { Permission, Permissions } from 'picsur-shared/dist/dto/permissions';
|
||||
import { PermissionService } from 'src/app/api/permission.service';
|
||||
import { UtilService } from 'src/app/util/util.service';
|
||||
import { ProcessingViewMetadata } from '../../models/processing-view-metadata';
|
||||
|
@ -16,7 +16,7 @@ export class UploadComponent implements OnInit {
|
|||
|
||||
// Lets be optimistic here, this makes for a better ux
|
||||
public get hasUploadPermission() {
|
||||
return this.permissions.includes('image-upload');
|
||||
return this.permissions.includes(Permission.ImageUpload);
|
||||
}
|
||||
|
||||
constructor(
|
||||
|
|
|
@ -1,21 +1,18 @@
|
|||
import tuple from '../types/tuple';
|
||||
|
||||
// Config
|
||||
|
||||
const PermissionsTuple = tuple(
|
||||
'image-view',
|
||||
'image-upload',
|
||||
'user-login', // Ability to log in
|
||||
'user-register', // Ability to register
|
||||
'user-manage',
|
||||
'user-view', // Ability to view user details and refresh token
|
||||
'role-manage',
|
||||
'syspref-manage',
|
||||
);
|
||||
export enum Permission {
|
||||
ImageView = 'image-view',
|
||||
ImageUpload = 'image-upload',
|
||||
UserLogin = 'user-login', // Ability to log in
|
||||
UserRegister = 'user-register', // Ability to register
|
||||
UserManage = 'user-manage',
|
||||
UserView = 'user-view', // Ability to view user details and refresh token
|
||||
RoleManage = 'role-manage',
|
||||
SysPrefManage = 'syspref-manage',
|
||||
}
|
||||
|
||||
// Derivatives
|
||||
|
||||
export const PermissionsList: string[] = PermissionsTuple;
|
||||
export const PermissionsList: Permission[] = Object.values(Permission);
|
||||
|
||||
export type Permission = typeof PermissionsTuple[number];
|
||||
export type Permissions = Permission[];
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import tuple from '../types/tuple';
|
||||
import { Permissions, PermissionsList } from './permissions';
|
||||
import { Permission, Permissions, PermissionsList } from './permissions';
|
||||
|
||||
// Config
|
||||
|
||||
|
@ -24,10 +24,15 @@ export type SystemRoles = SystemRole[];
|
|||
export const SystemRoleDefaults: {
|
||||
[key in SystemRole]: Permissions;
|
||||
} = {
|
||||
guest: ['image-view', 'user-login'],
|
||||
user: ['image-view', 'user-view', 'user-login', 'image-upload'],
|
||||
guest: [Permission.ImageView, Permission.UserLogin],
|
||||
user: [
|
||||
Permission.ImageView,
|
||||
Permission.UserView,
|
||||
Permission.UserLogin,
|
||||
Permission.ImageUpload,
|
||||
],
|
||||
// Grant all permissions to admin
|
||||
admin: PermissionsList as Permissions,
|
||||
admin: PermissionsList,
|
||||
};
|
||||
|
||||
// Normal roles types
|
||||
|
|
10
shared/src/util/permissions.ts
Normal file
10
shared/src/util/permissions.ts
Normal file
|
@ -0,0 +1,10 @@
|
|||
import { isArray, isEnum, isString } from 'class-validator';
|
||||
import { Permissions, PermissionsList } from '../dto/permissions';
|
||||
|
||||
export function isPermissionsArray(value: any): value is Permissions {
|
||||
if (!isArray(value)) return false;
|
||||
if (!value.every((item: unknown) => isString(item))) return false;
|
||||
if (!value.every((item: string) => isEnum(item, PermissionsList)))
|
||||
return false;
|
||||
return true;
|
||||
}
|
Loading…
Reference in a new issue