199 lines
5.3 KiB
TypeScript
199 lines
5.3 KiB
TypeScript
import { Injectable, Logger } from '@nestjs/common';
|
|
import { InjectRepository } from '@nestjs/typeorm';
|
|
import { ERoleSchema } from 'picsur-shared/dist/entities/role.entity';
|
|
import {
|
|
AsyncFailable,
|
|
Fail,
|
|
FT,
|
|
HasFailed,
|
|
HasSuccess
|
|
} from 'picsur-shared/dist/types';
|
|
import { makeUnique } from 'picsur-shared/dist/util/unique';
|
|
import { In, Repository } from 'typeorm';
|
|
import { ERoleBackend } from '../../database/entities/role.entity';
|
|
import { Permissions } from '../../models/constants/permissions.const';
|
|
import {
|
|
ImmutableRolesList,
|
|
UndeletableRolesList
|
|
} from '../../models/constants/roles.const';
|
|
|
|
@Injectable()
|
|
export class RolesService {
|
|
private readonly logger = new Logger('UsersService');
|
|
|
|
constructor(
|
|
@InjectRepository(ERoleBackend)
|
|
private readonly rolesRepository: Repository<ERoleBackend>,
|
|
) {
|
|
}
|
|
|
|
public async create(
|
|
name: string,
|
|
permissions: Permissions,
|
|
): AsyncFailable<ERoleBackend> {
|
|
if (await this.exists(name))
|
|
return Fail(FT.Conflict, 'Role already exists');
|
|
|
|
let role = new ERoleBackend();
|
|
role.name = name;
|
|
role.permissions = permissions;
|
|
|
|
try {
|
|
return await this.rolesRepository.save(role, { reload: true });
|
|
} catch (e) {
|
|
return Fail(FT.Database, e);
|
|
}
|
|
}
|
|
|
|
public async delete(name: string): AsyncFailable<ERoleBackend> {
|
|
const roleToModify = await this.findOne(name);
|
|
if (HasFailed(roleToModify)) return roleToModify;
|
|
|
|
if (UndeletableRolesList.includes(roleToModify.name)) {
|
|
return Fail(FT.Permission, 'Cannot delete system role');
|
|
}
|
|
|
|
try {
|
|
return await this.rolesRepository.remove(roleToModify);
|
|
} catch (e) {
|
|
return Fail(FT.Database, e);
|
|
}
|
|
}
|
|
|
|
public async getPermissions(roles: string[]): AsyncFailable<Permissions> {
|
|
const foundRoles = await this.findMany(roles);
|
|
if (HasFailed(foundRoles)) return foundRoles;
|
|
|
|
const permissions = foundRoles.reduce(
|
|
(acc, role) => [...acc, ...role.permissions],
|
|
[] as Permissions,
|
|
);
|
|
|
|
return makeUnique(permissions);
|
|
}
|
|
|
|
public async addPermissions(
|
|
name: string,
|
|
permissions: Permissions,
|
|
): AsyncFailable<ERoleBackend> {
|
|
const roleToModify = await this.findOne(name);
|
|
if (HasFailed(roleToModify)) return roleToModify;
|
|
|
|
const newPermissions = makeUnique([
|
|
...roleToModify.permissions,
|
|
...permissions,
|
|
]);
|
|
|
|
return this.setPermissions(roleToModify, newPermissions);
|
|
}
|
|
|
|
public async removePermissions(
|
|
name: string,
|
|
permissions: Permissions,
|
|
): AsyncFailable<ERoleBackend> {
|
|
const roleToModify = await this.findOne(name);
|
|
if (HasFailed(roleToModify)) return roleToModify;
|
|
|
|
const newPermissions = roleToModify.permissions.filter(
|
|
(permission) => !permissions.includes(permission),
|
|
);
|
|
|
|
return this.setPermissions(roleToModify, newPermissions);
|
|
}
|
|
|
|
// Permission specific validation is done here
|
|
public async setPermissions(
|
|
role: string | ERoleBackend,
|
|
permissions: Permissions,
|
|
// Extra bypass for internal use
|
|
allowImmutable: boolean = false,
|
|
): AsyncFailable<ERoleBackend> {
|
|
const roleToModify = await this.resolve(role);
|
|
if (HasFailed(roleToModify)) return roleToModify;
|
|
|
|
if (!allowImmutable && ImmutableRolesList.includes(roleToModify.name)) {
|
|
return Fail(FT.Permission, 'Cannot modify immutable role');
|
|
}
|
|
|
|
roleToModify.permissions = makeUnique(permissions);
|
|
|
|
try {
|
|
return await this.rolesRepository.save(roleToModify);
|
|
} catch (e) {
|
|
return Fail(FT.Database, e);
|
|
}
|
|
}
|
|
|
|
public async findOne(name: string): AsyncFailable<ERoleBackend> {
|
|
try {
|
|
const found = await this.rolesRepository.findOne({
|
|
where: { name },
|
|
});
|
|
|
|
if (!found) return Fail(FT.NotFound, 'Role not found');
|
|
return found;
|
|
} catch (e) {
|
|
return Fail(FT.Database, e);
|
|
}
|
|
}
|
|
|
|
public async findMany(names: string[]): AsyncFailable<ERoleBackend[]> {
|
|
try {
|
|
const found = await this.rolesRepository.find({
|
|
where: { name: In(names) },
|
|
});
|
|
|
|
if (!found) return Fail(FT.NotFound, 'No roles found');
|
|
return found;
|
|
} catch (e) {
|
|
return Fail(FT.Database, e);
|
|
}
|
|
}
|
|
|
|
public async findAll(): AsyncFailable<ERoleBackend[]> {
|
|
try {
|
|
const found = await this.rolesRepository.find();
|
|
if (!found) return Fail(FT.NotFound, 'No roles found');
|
|
return found;
|
|
} catch (e) {
|
|
return Fail(FT.Database, e);
|
|
}
|
|
}
|
|
|
|
public async exists(name: string): Promise<boolean> {
|
|
return HasSuccess(await this.findOne(name));
|
|
}
|
|
|
|
public async nukeSystemRoles(IAmSure: boolean = false): AsyncFailable<true> {
|
|
if (!IAmSure)
|
|
return Fail(
|
|
FT.SysValidation,
|
|
'You must confirm that you want to delete all roles',
|
|
);
|
|
|
|
try {
|
|
await this.rolesRepository.delete({
|
|
name: In(UndeletableRolesList),
|
|
});
|
|
} catch (e) {
|
|
return Fail(FT.Database, e);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
private async resolve(
|
|
role: string | ERoleBackend,
|
|
): AsyncFailable<ERoleBackend> {
|
|
if (typeof role === 'string') {
|
|
return await this.findOne(role);
|
|
} else {
|
|
const result = ERoleSchema.safeParse(role);
|
|
if (!result.success) {
|
|
return Fail(FT.SysValidation, result.error);
|
|
}
|
|
// This is safe
|
|
return result.data as ERoleBackend;
|
|
}
|
|
}
|
|
}
|