add role api

This commit is contained in:
rubikscraft
2022-03-12 16:25:15 +01:00
parent 0ad444f43b
commit 1febcd8147
14 changed files with 212 additions and 19 deletions

View File

@@ -35,8 +35,8 @@ export class RolesModule implements OnModuleInit {
} }
private async nukeRoles() { private async nukeRoles() {
this.logger.error('Nuking all roles'); this.logger.error('Nuking system roles');
const result = this.rolesService.nuke(true); const result = this.rolesService.nukeSystemRoles(true);
if (HasFailed(result)) { if (HasFailed(result)) {
this.logger.error(`Failed to nuke roles because: ${result.getReason()}`); this.logger.error(`Failed to nuke roles because: ${result.getReason()}`);
} }

View File

@@ -14,7 +14,7 @@ import {
HasFailed, HasFailed,
HasSuccess HasSuccess
} from 'picsur-shared/dist/types'; } from 'picsur-shared/dist/types';
import { Repository } from 'typeorm'; import { In, Repository } from 'typeorm';
import { ERoleBackend } from '../../models/entities/role.entity'; import { ERoleBackend } from '../../models/entities/role.entity';
@Injectable() @Injectable()
@@ -79,7 +79,7 @@ export class RolesService {
public async addPermissions( public async addPermissions(
role: string | ERoleBackend, role: string | ERoleBackend,
permissions: Permissions, permissions: Permissions,
): AsyncFailable<true> { ): AsyncFailable<ERoleBackend> {
const roleToModify = await this.resolve(role); const roleToModify = await this.resolve(role);
if (HasFailed(roleToModify)) return roleToModify; if (HasFailed(roleToModify)) return roleToModify;
@@ -94,7 +94,7 @@ export class RolesService {
public async removePermissions( public async removePermissions(
role: string | ERoleBackend, role: string | ERoleBackend,
permissions: Permissions, permissions: Permissions,
): AsyncFailable<true> { ): AsyncFailable<ERoleBackend> {
const roleToModify = await this.resolve(role); const roleToModify = await this.resolve(role);
if (HasFailed(roleToModify)) return roleToModify; if (HasFailed(roleToModify)) return roleToModify;
@@ -109,7 +109,7 @@ export class RolesService {
role: string | ERoleBackend, role: string | ERoleBackend,
permissions: Permissions, permissions: Permissions,
allowImmutable: boolean = false, allowImmutable: boolean = false,
): AsyncFailable<true> { ): AsyncFailable<ERoleBackend> {
const roleToModify = await this.resolve(role); const roleToModify = await this.resolve(role);
if (HasFailed(roleToModify)) return roleToModify; if (HasFailed(roleToModify)) return roleToModify;
@@ -120,12 +120,10 @@ export class RolesService {
roleToModify.permissions = permissions; roleToModify.permissions = permissions;
try { try {
await this.rolesRepository.save(roleToModify); return await this.rolesRepository.save(roleToModify);
} catch (e: any) { } catch (e: any) {
return Fail(e?.message); return Fail(e?.message);
} }
return true;
} }
public async findOne(name: string): AsyncFailable<ERoleBackend> { public async findOne(name: string): AsyncFailable<ERoleBackend> {
@@ -141,14 +139,26 @@ export class RolesService {
} }
} }
public async findAll(): AsyncFailable<ERoleBackend[]> {
try {
const found = await this.rolesRepository.find();
if (!found) return Fail('No roles found');
return found as ERoleBackend[];
} catch (e: any) {
return Fail(e?.message);
}
}
public async exists(username: string): Promise<boolean> { public async exists(username: string): Promise<boolean> {
return HasSuccess(await this.findOne(username)); return HasSuccess(await this.findOne(username));
} }
public async nuke(iamsure: boolean = false): AsyncFailable<true> { public async nukeSystemRoles(iamsure: boolean = false): AsyncFailable<true> {
if (!iamsure) return Fail('Nuke aborted'); if (!iamsure) return Fail('Nuke aborted');
try { try {
await this.rolesRepository.delete({}); await this.rolesRepository.delete({
name: In(SystemRolesList),
});
} catch (e: any) { } catch (e: any) {
return Fail(e?.message); return Fail(e?.message);
} }

View File

@@ -1,8 +1,9 @@
import { Module } from '@nestjs/common'; import { Module } from '@nestjs/common';
import { AuthModule } from './auth/auth.module'; import { AuthModule } from './auth/auth.module';
import { ExperimentModule } from './experiment/experiment.module'; import { ExperimentModule } from './experiment/experiment.module';
import { PrefModule } from './pref/pref.module';
import { InfoModule } from './info/info.module'; import { InfoModule } from './info/info.module';
import { PrefModule } from './pref/pref.module';
import { RolesApiModule } from './roles/roles.module';
@Module({ @Module({
imports: [ imports: [
@@ -10,6 +11,7 @@ import { InfoModule } from './info/info.module';
PrefModule, PrefModule,
ExperimentModule, ExperimentModule,
InfoModule, InfoModule,
RolesApiModule,
] ]
}) })
export class PicsurApiModule {} export class PicsurApiModule {}

View File

@@ -7,6 +7,7 @@ import {
Post, Post,
Request Request
} from '@nestjs/common'; } from '@nestjs/common';
import { AuthUserInfoRequest } from 'picsur-shared/dist/dto/api/auth.dto';
import { import {
AuthDeleteRequest, AuthDeleteRequest,
AuthLoginResponse, AuthLoginResponse,
@@ -15,7 +16,10 @@ import {
} from 'picsur-shared/dist/dto/auth.dto'; } from 'picsur-shared/dist/dto/auth.dto';
import { HasFailed } from 'picsur-shared/dist/types'; import { HasFailed } from 'picsur-shared/dist/types';
import { UsersService } from '../../../collections/userdb/userdb.service'; import { UsersService } from '../../../collections/userdb/userdb.service';
import { RequiredPermissions, UseLocalAuth } from '../../../decorators/permissions.decorator'; import {
RequiredPermissions,
UseLocalAuth
} from '../../../decorators/permissions.decorator';
import { AuthManagerService } from '../../../managers/auth/auth.service'; import { AuthManagerService } from '../../../managers/auth/auth.service';
import AuthFasityRequest from '../../../models/dto/authrequest.dto'; import AuthFasityRequest from '../../../models/dto/authrequest.dto';
@@ -75,6 +79,18 @@ export class AuthController {
return user; return user;
} }
@Post('info')
@RequiredPermissions('user-manage')
async getUser(@Body() body: AuthUserInfoRequest) {
const user = await this.usersService.findOne(body.username);
if (HasFailed(user)) {
this.logger.warn(user.getReason());
throw new InternalServerErrorException('Could not find user');
}
return user;
}
@Get('list') @Get('list')
@RequiredPermissions('user-manage') @RequiredPermissions('user-manage')
async listUsers(@Request() req: AuthFasityRequest) { async listUsers(@Request() req: AuthFasityRequest) {

View File

@@ -0,0 +1,113 @@
import {
Body,
Controller,
Get,
InternalServerErrorException,
Logger,
Post
} from '@nestjs/common';
import {
RoleCreateRequest,
RoleDeleteRequest,
RoleInfoRequest,
RoleUpdateRequest
} from 'picsur-shared/dist/dto/api/roles.dto';
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')
export class RolesController {
private readonly logger = new Logger('RolesController');
constructor(private rolesService: RolesService) {}
@Get('/list')
getRoles() {
const roles = this.rolesService.findAll();
if (HasFailed(roles)) {
this.logger.warn(roles.getReason());
throw new InternalServerErrorException('Could not list roles');
}
return roles;
}
@Post('/info')
getRole(@Body() body: RoleInfoRequest) {
const role = this.rolesService.findOne(body.name);
if (HasFailed(role)) {
this.logger.warn(role.getReason());
throw new InternalServerErrorException('Could not find role');
}
return role;
}
@Post('/permissions/set')
updateRole(@Body() body: RoleUpdateRequest) {
const updatedRole = this.rolesService.setPermissions(
body.name,
body.permissions,
);
if (HasFailed(updatedRole)) {
this.logger.warn(updatedRole.getReason());
throw new InternalServerErrorException('Could not set role permissions');
}
return updatedRole;
}
@Post('/permissions/add')
addPermissions(@Body() body: RoleUpdateRequest) {
const updatedRole = this.rolesService.addPermissions(
body.name,
body.permissions,
);
if (HasFailed(updatedRole)) {
this.logger.warn(updatedRole.getReason());
throw new InternalServerErrorException('Could not add role permissions');
}
return updatedRole;
}
@Post('/permissions/remove')
removePermissions(@Body() body: RoleUpdateRequest) {
const updatedRole = this.rolesService.removePermissions(
body.name,
body.permissions,
);
if (HasFailed(updatedRole)) {
this.logger.warn(updatedRole.getReason());
throw new InternalServerErrorException(
'Could not remove role permissions',
);
}
return updatedRole;
}
@Post('/create')
createRole(@Body() role: RoleCreateRequest) {
const newRole = this.rolesService.create(role.name, role.permissions);
if (HasFailed(newRole)) {
this.logger.warn(newRole.getReason());
throw new InternalServerErrorException('Could not create role');
}
return newRole;
}
@Post('/delete')
deleteRole(@Body() role: RoleDeleteRequest) {
const deletedRole = this.rolesService.delete(role.name);
if (HasFailed(deletedRole)) {
this.logger.warn(deletedRole.getReason());
throw new InternalServerErrorException('Could not delete role');
}
return deletedRole;
}
}

View File

@@ -0,0 +1,9 @@
import { Module } from '@nestjs/common';
import { RolesModule } from '../../../collections/roledb/roledb.module';
import { RolesController } from './roles.controller';
@Module({
imports: [RolesModule],
controllers: [RolesController],
})
export class RolesApiModule {}

View File

@@ -66,3 +66,9 @@ export class AuthMeResponse {
@IsDefined() @IsDefined()
newJwtToken: string; newJwtToken: string;
} }
export class AuthUserInfoRequest {
@IsString()
@IsNotEmpty()
username: string;
}

View File

@@ -1,9 +1,11 @@
import { IsDefined } from 'class-validator'; import { IsBoolean, IsDefined } from 'class-validator';
export class InfoResponse { export class InfoResponse {
@IsBoolean()
@IsDefined() @IsDefined()
production: boolean; production: boolean;
@IsBoolean()
@IsDefined() @IsDefined()
demo: boolean; demo: boolean;
} }

View File

@@ -0,0 +1,26 @@
import { IsArray, IsEnum, IsNotEmpty, IsString } from 'class-validator';
import { ERole } from '../../entities/role.entity';
import { Permissions, PermissionsList } from '../permissions';
export class RoleInfoRequest {
@IsNotEmpty()
@IsString()
name: string;
}
export class RoleUpdateRequest {
@IsNotEmpty()
@IsString()
name: string;
@IsArray()
@IsEnum(PermissionsList, { each: true })
permissions: Permissions;
}
export class RoleCreateRequest extends ERole {}
export class RoleDeleteRequest {
@IsNotEmpty()
name: string;
}

View File

@@ -9,6 +9,7 @@ const PermissionsTuple = tuple(
'user-register', // Ability to register 'user-register', // Ability to register
'user-view', // Ability to view user info, only granted if logged in 'user-view', // Ability to view user info, only granted if logged in
'user-manage', 'user-manage',
'role-manage',
'syspref-manage', 'syspref-manage',
); );

View File

@@ -1,9 +1,10 @@
import { Exclude } from 'class-transformer'; import { Exclude } from 'class-transformer';
import { IsDefined, IsEnum, IsHash, IsOptional } from 'class-validator'; import { IsDefined, IsEnum, IsHash, IsInt, IsOptional } from 'class-validator';
import { SupportedMime, SupportedMimes } from '../dto/mimes.dto'; import { SupportedMime, SupportedMimes } from '../dto/mimes.dto';
export class EImage { export class EImage {
@IsOptional() @IsOptional()
@IsInt()
id?: number; id?: number;
@IsHash('sha256') @IsHash('sha256')

View File

@@ -1,11 +1,13 @@
import { IsArray, IsEnum, IsNotEmpty, IsOptional } from 'class-validator'; import { IsArray, IsEnum, IsInt, IsNotEmpty, IsOptional, IsString } from 'class-validator';
import { Permissions, PermissionsList } from '../dto/permissions'; import { Permissions, PermissionsList } from '../dto/permissions';
export class ERole { export class ERole {
@IsOptional() @IsOptional()
@IsInt()
id?: number; id?: number;
@IsNotEmpty() @IsNotEmpty()
@IsString()
name: string; name: string;
@IsArray() @IsArray()

View File

@@ -1,8 +1,9 @@
import { IsEnum, IsNotEmpty, IsOptional } from 'class-validator'; import { IsEnum, IsInt, IsNotEmpty, IsOptional, IsString } from 'class-validator';
import { SysPreferences } from '../dto/syspreferences.dto'; import { SysPreferences } from '../dto/syspreferences.dto';
export class ESysPreference { export class ESysPreference {
@IsOptional() @IsOptional()
@IsInt()
id?: number; id?: number;
@IsNotEmpty() @IsNotEmpty()
@@ -10,5 +11,6 @@ export class ESysPreference {
key: SysPreferences; key: SysPreferences;
@IsNotEmpty() @IsNotEmpty()
@IsString()
value: string; value: string;
} }

View File

@@ -1,6 +1,6 @@
import { Exclude } from 'class-transformer'; import { Exclude } from 'class-transformer';
import { import {
IsArray, IsNotEmpty, IsArray, IsInt, IsNotEmpty,
IsOptional, IsOptional,
IsString IsString
} from 'class-validator'; } from 'class-validator';
@@ -8,9 +8,11 @@ import { Roles } from '../dto/roles.dto';
export class EUser { export class EUser {
@IsOptional() @IsOptional()
@IsInt()
id?: number; id?: number;
@IsNotEmpty() @IsNotEmpty()
@IsString()
username: string; username: string;
@IsArray() @IsArray()
@@ -19,5 +21,6 @@ export class EUser {
@IsOptional() @IsOptional()
@Exclude() @Exclude()
@IsString()
password?: string; password?: string;
} }