mirror of
https://github.com/CaramelFur/Picsur.git
synced 2025-11-13 15:25:39 +01:00
relocate special roles to api request
This commit is contained in:
@@ -12,10 +12,11 @@
|
|||||||
"prebuild": "rimraf dist",
|
"prebuild": "rimraf dist",
|
||||||
"build": "nest build",
|
"build": "nest build",
|
||||||
"start": "nest start --exec \"node --experimental-specifier-resolution=node\"",
|
"start": "nest start --exec \"node --experimental-specifier-resolution=node\"",
|
||||||
"start:dev": "nest start --watch --exec \"node --experimental-specifier-resolution=node\"",
|
"start:dev": "yarn clean && nest start --watch --exec \"node --experimental-specifier-resolution=node\"",
|
||||||
"start:debug": "nest start --debug --watch --exec \"node --experimental-specifier-resolution=node\"",
|
"start:debug": "nest start --debug --watch --exec \"node --experimental-specifier-resolution=node\"",
|
||||||
"start:prod": "node --experimental-specifier-resolution=node dist/main",
|
"start:prod": "node --experimental-specifier-resolution=node dist/main",
|
||||||
"format": "prettier --write \"src/**/*.ts\"",
|
"format": "prettier --write \"src/**/*.ts\"",
|
||||||
|
"clean": "rimraf dist",
|
||||||
"lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix"
|
"lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|||||||
@@ -1,14 +1,9 @@
|
|||||||
import { Logger, Module, OnModuleInit } from '@nestjs/common';
|
import { Logger, Module, OnModuleInit } from '@nestjs/common';
|
||||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||||
import {
|
|
||||||
ImmuteableRolesList,
|
|
||||||
SystemRoleDefaults,
|
|
||||||
SystemRoles,
|
|
||||||
SystemRolesList
|
|
||||||
} from 'picsur-shared/dist/dto/roles.dto';
|
|
||||||
import { HasFailed } from 'picsur-shared/dist/types';
|
import { HasFailed } from 'picsur-shared/dist/types';
|
||||||
import { PicsurConfigModule } from '../../config/config.module';
|
import { PicsurConfigModule } from '../../config/config.module';
|
||||||
import { HostConfigService } from '../../config/host.config.service';
|
import { HostConfigService } from '../../config/host.config.service';
|
||||||
|
import { ImmutableRolesList, SystemRoleDefaults, UndeletableRolesList } from '../../models/dto/roles.dto';
|
||||||
import { ERoleBackend } from '../../models/entities/role.entity';
|
import { ERoleBackend } from '../../models/entities/role.entity';
|
||||||
import { RolesService } from './roledb.service';
|
import { RolesService } from './roledb.service';
|
||||||
|
|
||||||
@@ -43,7 +38,7 @@ export class RolesModule implements OnModuleInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async ensureSystemRolesExist() {
|
private async ensureSystemRolesExist() {
|
||||||
for (const systemRole of SystemRolesList as SystemRoles) {
|
for (const systemRole of UndeletableRolesList) {
|
||||||
this.logger.debug(`Ensuring system role "${systemRole}" exists`);
|
this.logger.debug(`Ensuring system role "${systemRole}" exists`);
|
||||||
|
|
||||||
const exists = await this.rolesService.exists(systemRole);
|
const exists = await this.rolesService.exists(systemRole);
|
||||||
@@ -63,7 +58,7 @@ export class RolesModule implements OnModuleInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async updateImmutableRoles() {
|
private async updateImmutableRoles() {
|
||||||
for (const immutableRole of ImmuteableRolesList as SystemRoles) {
|
for (const immutableRole of ImmutableRolesList) {
|
||||||
this.logger.debug(
|
this.logger.debug(
|
||||||
`Updating permissions for immutable role "${immutableRole}"`,
|
`Updating permissions for immutable role "${immutableRole}"`,
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -2,11 +2,6 @@ import { Injectable, Logger } from '@nestjs/common';
|
|||||||
import { InjectRepository } from '@nestjs/typeorm';
|
import { InjectRepository } from '@nestjs/typeorm';
|
||||||
import { plainToClass } from 'class-transformer';
|
import { plainToClass } from 'class-transformer';
|
||||||
import { Permissions } from 'picsur-shared/dist/dto/permissions';
|
import { Permissions } from 'picsur-shared/dist/dto/permissions';
|
||||||
import {
|
|
||||||
ImmuteableRolesList,
|
|
||||||
Roles,
|
|
||||||
SystemRolesList
|
|
||||||
} from 'picsur-shared/dist/dto/roles.dto';
|
|
||||||
import {
|
import {
|
||||||
AsyncFailable,
|
AsyncFailable,
|
||||||
Fail,
|
Fail,
|
||||||
@@ -15,6 +10,7 @@ import {
|
|||||||
} from 'picsur-shared/dist/types';
|
} from 'picsur-shared/dist/types';
|
||||||
import { strictValidate } from 'picsur-shared/dist/util/validate';
|
import { strictValidate } from 'picsur-shared/dist/util/validate';
|
||||||
import { In, Repository } from 'typeorm';
|
import { In, Repository } from 'typeorm';
|
||||||
|
import { ImmutableRolesList, UndeletableRolesList } from '../../models/dto/roles.dto';
|
||||||
import { ERoleBackend } from '../../models/entities/role.entity';
|
import { ERoleBackend } from '../../models/entities/role.entity';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
@@ -51,7 +47,7 @@ export class RolesService {
|
|||||||
const roleToModify = await this.resolve(role);
|
const roleToModify = await this.resolve(role);
|
||||||
if (HasFailed(roleToModify)) return roleToModify;
|
if (HasFailed(roleToModify)) return roleToModify;
|
||||||
|
|
||||||
if (SystemRolesList.includes(roleToModify.name)) {
|
if (UndeletableRolesList.includes(roleToModify.name)) {
|
||||||
return Fail('Cannot delete system role');
|
return Fail('Cannot delete system role');
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -62,7 +58,7 @@ export class RolesService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getPermissions(roles: Roles): AsyncFailable<Permissions> {
|
public async getPermissions(roles: string[]): AsyncFailable<Permissions> {
|
||||||
const permissions: Permissions = [];
|
const permissions: Permissions = [];
|
||||||
const foundRoles = await Promise.all(
|
const foundRoles = await Promise.all(
|
||||||
roles.map((role: string) => this.findOne(role)),
|
roles.map((role: string) => this.findOne(role)),
|
||||||
@@ -113,7 +109,7 @@ export class RolesService {
|
|||||||
const roleToModify = await this.resolve(role);
|
const roleToModify = await this.resolve(role);
|
||||||
if (HasFailed(roleToModify)) return roleToModify;
|
if (HasFailed(roleToModify)) return roleToModify;
|
||||||
|
|
||||||
if (!allowImmutable && ImmuteableRolesList.includes(roleToModify.name)) {
|
if (!allowImmutable && ImmutableRolesList.includes(roleToModify.name)) {
|
||||||
return Fail('Cannot modify immutable role');
|
return Fail('Cannot modify immutable role');
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -157,7 +153,7 @@ export class RolesService {
|
|||||||
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),
|
name: In(UndeletableRolesList),
|
||||||
});
|
});
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
return Fail(e?.message);
|
return Fail(e?.message);
|
||||||
|
|||||||
@@ -2,11 +2,6 @@ import { Injectable, Logger } from '@nestjs/common';
|
|||||||
import { InjectRepository } from '@nestjs/typeorm';
|
import { InjectRepository } from '@nestjs/typeorm';
|
||||||
import * as bcrypt from 'bcrypt';
|
import * as bcrypt from 'bcrypt';
|
||||||
import { plainToClass } from 'class-transformer';
|
import { plainToClass } from 'class-transformer';
|
||||||
import {
|
|
||||||
DefaultRolesList,
|
|
||||||
PermanentRolesList,
|
|
||||||
Roles
|
|
||||||
} from 'picsur-shared/dist/dto/roles.dto';
|
|
||||||
import {
|
import {
|
||||||
LockedLoginUsersList,
|
LockedLoginUsersList,
|
||||||
LockedPermsUsersList,
|
LockedPermsUsersList,
|
||||||
@@ -20,6 +15,10 @@ import {
|
|||||||
} from 'picsur-shared/dist/types';
|
} from 'picsur-shared/dist/types';
|
||||||
import { strictValidate } from 'picsur-shared/dist/util/validate';
|
import { strictValidate } from 'picsur-shared/dist/util/validate';
|
||||||
import { Repository } from 'typeorm';
|
import { Repository } from 'typeorm';
|
||||||
|
import {
|
||||||
|
DefaultRolesList,
|
||||||
|
SoulBoundRolesList
|
||||||
|
} from '../../models/dto/roles.dto';
|
||||||
import { EUserBackend } from '../../models/entities/user.entity';
|
import { EUserBackend } from '../../models/entities/user.entity';
|
||||||
import { GetCols } from '../collectionutils';
|
import { GetCols } from '../collectionutils';
|
||||||
import { RolesService } from '../roledb/roledb.service';
|
import { RolesService } from '../roledb/roledb.service';
|
||||||
@@ -41,7 +40,7 @@ export class UsersService {
|
|||||||
public async create(
|
public async create(
|
||||||
username: string,
|
username: string,
|
||||||
password: string,
|
password: string,
|
||||||
roles?: Roles,
|
roles?: string[],
|
||||||
byPassRoleCheck?: boolean,
|
byPassRoleCheck?: boolean,
|
||||||
): AsyncFailable<EUserBackend> {
|
): AsyncFailable<EUserBackend> {
|
||||||
if (await this.exists(username)) return Fail('User already exists');
|
if (await this.exists(username)) return Fail('User already exists');
|
||||||
@@ -90,7 +89,7 @@ export class UsersService {
|
|||||||
|
|
||||||
public async setRoles(
|
public async setRoles(
|
||||||
user: string | EUserBackend,
|
user: string | EUserBackend,
|
||||||
roles: Roles,
|
roles: string[],
|
||||||
): AsyncFailable<EUserBackend> {
|
): AsyncFailable<EUserBackend> {
|
||||||
const userToModify = await this.resolve(user);
|
const userToModify = await this.resolve(user);
|
||||||
if (HasFailed(userToModify)) return userToModify;
|
if (HasFailed(userToModify)) return userToModify;
|
||||||
@@ -101,7 +100,7 @@ export class UsersService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const rolesToKeep = userToModify.roles.filter((role) =>
|
const rolesToKeep = userToModify.roles.filter((role) =>
|
||||||
PermanentRolesList.includes(role),
|
SoulBoundRolesList.includes(role),
|
||||||
);
|
);
|
||||||
const rolesToAdd = this.filterAddedRoles(roles);
|
const rolesToAdd = this.filterAddedRoles(roles);
|
||||||
|
|
||||||
@@ -216,9 +215,9 @@ export class UsersService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private filterAddedRoles(roles: Roles): Roles {
|
private filterAddedRoles(roles: string[]): string[] {
|
||||||
const filteredRoles = roles.filter(
|
const filteredRoles = roles.filter(
|
||||||
(role) => !PermanentRolesList.includes(role),
|
(role) => !SoulBoundRolesList.includes(role),
|
||||||
);
|
);
|
||||||
|
|
||||||
return filteredRoles;
|
return filteredRoles;
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { Permissions } from 'picsur-shared/dist/dto/permissions';
|
import { Permissions } from 'picsur-shared/dist/dto/permissions';
|
||||||
import { Roles } from 'picsur-shared/dist/dto/roles.dto';
|
|
||||||
import { AsyncFailable, HasFailed } from 'picsur-shared/dist/types';
|
import { AsyncFailable, HasFailed } from 'picsur-shared/dist/types';
|
||||||
import { EUserBackend } from '../../models/entities/user.entity';
|
import { EUserBackend } from '../../models/entities/user.entity';
|
||||||
import { RolesService } from '../roledb/roledb.service';
|
import { RolesService } from '../roledb/roledb.service';
|
||||||
@@ -23,7 +22,7 @@ export class UserRolesService {
|
|||||||
|
|
||||||
public async addRoles(
|
public async addRoles(
|
||||||
user: string | EUserBackend,
|
user: string | EUserBackend,
|
||||||
roles: Roles,
|
roles: string[],
|
||||||
): AsyncFailable<EUserBackend> {
|
): AsyncFailable<EUserBackend> {
|
||||||
const userToModify = await this.usersService.resolve(user);
|
const userToModify = await this.usersService.resolve(user);
|
||||||
if (HasFailed(userToModify)) return userToModify;
|
if (HasFailed(userToModify)) return userToModify;
|
||||||
@@ -35,7 +34,7 @@ export class UserRolesService {
|
|||||||
|
|
||||||
public async removeRoles(
|
public async removeRoles(
|
||||||
user: string | EUserBackend,
|
user: string | EUserBackend,
|
||||||
roles: Roles,
|
roles: string[],
|
||||||
): AsyncFailable<EUserBackend> {
|
): AsyncFailable<EUserBackend> {
|
||||||
const userToModify = await this.usersService.resolve(user);
|
const userToModify = await this.usersService.resolve(user);
|
||||||
if (HasFailed(userToModify)) return userToModify;
|
if (HasFailed(userToModify)) return userToModify;
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import {
|
import {
|
||||||
BadRequestException,
|
BadRequestException,
|
||||||
Injectable,
|
Injectable,
|
||||||
Logger,
|
Logger,
|
||||||
PipeTransform,
|
PipeTransform,
|
||||||
Scope
|
Scope
|
||||||
} from '@nestjs/common';
|
} from '@nestjs/common';
|
||||||
import { FastifyRequest } from 'fastify';
|
import { FastifyRequest } from 'fastify';
|
||||||
import { MultipartFields, MultipartFile } from 'fastify-multipart';
|
import { MultipartFields, MultipartFile } from 'fastify-multipart';
|
||||||
@@ -11,9 +11,9 @@ import { Newable } from 'picsur-shared/dist/types';
|
|||||||
import { strictValidate } from 'picsur-shared/dist/util/validate';
|
import { strictValidate } from 'picsur-shared/dist/util/validate';
|
||||||
import { MultipartConfigService } from '../config/multipart.config.service';
|
import { MultipartConfigService } from '../config/multipart.config.service';
|
||||||
import {
|
import {
|
||||||
MultiPartFieldDto,
|
MultiPartFieldDto,
|
||||||
MultiPartFileDto
|
MultiPartFileDto
|
||||||
} from '../models/dto/multipart.dto';
|
} from '../models/requests/multipart.dto';
|
||||||
|
|
||||||
@Injectable({ scope: Scope.REQUEST })
|
@Injectable({ scope: Scope.REQUEST })
|
||||||
export class MultiPartPipe implements PipeTransform {
|
export class MultiPartPipe implements PipeTransform {
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import {
|
|||||||
ArgumentsHost, Catch, ExceptionFilter, HttpException
|
ArgumentsHost, Catch, ExceptionFilter, HttpException
|
||||||
} from '@nestjs/common';
|
} from '@nestjs/common';
|
||||||
import { FastifyReply, FastifyRequest } from 'fastify';
|
import { FastifyReply, FastifyRequest } from 'fastify';
|
||||||
import { ApiErrorResponse } from 'picsur-shared/dist/dto/api';
|
import { ApiErrorResponse } from 'picsur-shared/dist/dto/api/api.dto';
|
||||||
|
|
||||||
@Catch(HttpException)
|
@Catch(HttpException)
|
||||||
export class MainExceptionFilter implements ExceptionFilter {
|
export class MainExceptionFilter implements ExceptionFilter {
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import {
|
|||||||
CallHandler, ExecutionContext, Injectable,
|
CallHandler, ExecutionContext, Injectable,
|
||||||
NestInterceptor
|
NestInterceptor
|
||||||
} from '@nestjs/common';
|
} from '@nestjs/common';
|
||||||
import { ApiResponse } from 'picsur-shared/dist/dto/api';
|
import { ApiResponse } from 'picsur-shared/dist/dto/api/api.dto';
|
||||||
import { map, Observable } from 'rxjs';
|
import { map, Observable } from 'rxjs';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
|
|||||||
39
backend/src/models/dto/roles.dto.ts
Normal file
39
backend/src/models/dto/roles.dto.ts
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
import { Permission, Permissions, PermissionsList } from 'picsur-shared/dist/dto/permissions';
|
||||||
|
import tuple from 'picsur-shared/dist/types/tuple';
|
||||||
|
|
||||||
|
// Config
|
||||||
|
|
||||||
|
// These roles can never be removed or added to a user.
|
||||||
|
const SoulBoundRolesTuple = tuple('guest', 'user');
|
||||||
|
// These roles can never be modified
|
||||||
|
const ImmutableRolesTuple = tuple('admin');
|
||||||
|
// These roles can never be removed from the server
|
||||||
|
const UndeletableRolesTuple = tuple(...SoulBoundRolesTuple, ...ImmutableRolesTuple);
|
||||||
|
// These roles will be applied by default to new users
|
||||||
|
export const DefaultRolesList: string[] = ['user'];
|
||||||
|
|
||||||
|
// Derivatives
|
||||||
|
export const SoulBoundRolesList: string[] = SoulBoundRolesTuple;
|
||||||
|
export const ImmutableRolesList: string[] = ImmutableRolesTuple;
|
||||||
|
export const UndeletableRolesList: string[] = UndeletableRolesTuple;
|
||||||
|
|
||||||
|
// Defaults
|
||||||
|
type SystemRole = typeof UndeletableRolesTuple[number];
|
||||||
|
const SystemRoleDefaultsTyped: {
|
||||||
|
[key in SystemRole]: Permissions;
|
||||||
|
} = {
|
||||||
|
guest: [Permission.ImageView, Permission.UserLogin],
|
||||||
|
user: [
|
||||||
|
Permission.ImageView,
|
||||||
|
Permission.UserMe,
|
||||||
|
Permission.UserLogin,
|
||||||
|
Permission.Settings,
|
||||||
|
Permission.ImageUpload,
|
||||||
|
],
|
||||||
|
// Grant all permissions to admin
|
||||||
|
admin: PermissionsList,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const SystemRoleDefaults = SystemRoleDefaultsTyped as {
|
||||||
|
[key in string]: Permissions;
|
||||||
|
};
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
import { Roles } from 'picsur-shared/dist/dto/roles.dto';
|
|
||||||
import { EUser } from 'picsur-shared/dist/entities/user.entity';
|
import { EUser } from 'picsur-shared/dist/entities/user.entity';
|
||||||
import { Column, Entity, Index, PrimaryGeneratedColumn } from 'typeorm';
|
import { Column, Entity, Index, PrimaryGeneratedColumn } from 'typeorm';
|
||||||
|
|
||||||
@@ -14,7 +13,7 @@ export class EUserBackend extends EUser {
|
|||||||
override username: string;
|
override username: string;
|
||||||
|
|
||||||
@Column('text', { nullable: false, array: true })
|
@Column('text', { nullable: false, array: true })
|
||||||
override roles: Roles;
|
override roles: string[];
|
||||||
|
|
||||||
@Column({ nullable: false, select: false })
|
@Column({ nullable: false, select: false })
|
||||||
override password?: string;
|
override password?: string;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Controller, Get, Request } from '@nestjs/common';
|
import { Controller, Get, Request } from '@nestjs/common';
|
||||||
import AuthFasityRequest from '../../../models/dto/authrequest.dto';
|
import AuthFasityRequest from '../../../models/requests/authrequest.dto';
|
||||||
|
|
||||||
|
|
||||||
@Controller('api/experiment')
|
@Controller('api/experiment')
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import {
|
|||||||
Logger,
|
Logger,
|
||||||
Post
|
Post
|
||||||
} from '@nestjs/common';
|
} from '@nestjs/common';
|
||||||
|
import { plainToClass } from 'class-transformer';
|
||||||
import {
|
import {
|
||||||
RoleCreateRequest,
|
RoleCreateRequest,
|
||||||
RoleCreateResponse,
|
RoleCreateResponse,
|
||||||
@@ -15,12 +16,19 @@ import {
|
|||||||
RoleInfoResponse,
|
RoleInfoResponse,
|
||||||
RoleListResponse,
|
RoleListResponse,
|
||||||
RoleUpdateRequest,
|
RoleUpdateRequest,
|
||||||
RoleUpdateResponse
|
RoleUpdateResponse,
|
||||||
|
SpecialRolesResponse
|
||||||
} from 'picsur-shared/dist/dto/api/roles.dto';
|
} from 'picsur-shared/dist/dto/api/roles.dto';
|
||||||
import { Permission } from 'picsur-shared/dist/dto/permissions';
|
import { Permission } from 'picsur-shared/dist/dto/permissions';
|
||||||
import { HasFailed } from 'picsur-shared/dist/types';
|
import { HasFailed } from 'picsur-shared/dist/types';
|
||||||
import { RolesService } from '../../../collections/roledb/roledb.service';
|
import { RolesService } from '../../../collections/roledb/roledb.service';
|
||||||
import { RequiredPermissions } from '../../../decorators/permissions.decorator';
|
import { RequiredPermissions } from '../../../decorators/permissions.decorator';
|
||||||
|
import {
|
||||||
|
DefaultRolesList,
|
||||||
|
ImmutableRolesList,
|
||||||
|
SoulBoundRolesList,
|
||||||
|
UndeletableRolesList
|
||||||
|
} from '../../../models/dto/roles.dto';
|
||||||
|
|
||||||
@Controller('api/roles')
|
@Controller('api/roles')
|
||||||
@RequiredPermissions(Permission.RoleManage)
|
@RequiredPermissions(Permission.RoleManage)
|
||||||
@@ -95,4 +103,16 @@ export class RolesController {
|
|||||||
|
|
||||||
return deletedRole;
|
return deletedRole;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Get('special')
|
||||||
|
async getSpecialRoles(): Promise<SpecialRolesResponse> {
|
||||||
|
const result: SpecialRolesResponse = {
|
||||||
|
SoulBoundRoles: SoulBoundRolesList,
|
||||||
|
ImmutableRoles: ImmutableRolesList,
|
||||||
|
UndeletableRoles: UndeletableRolesList,
|
||||||
|
DefaultRoles: DefaultRolesList,
|
||||||
|
};
|
||||||
|
|
||||||
|
return plainToClass(SpecialRolesResponse, result);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,30 +1,30 @@
|
|||||||
import {
|
import {
|
||||||
Body,
|
Body,
|
||||||
Controller,
|
Controller,
|
||||||
Get,
|
Get,
|
||||||
InternalServerErrorException,
|
InternalServerErrorException,
|
||||||
Logger,
|
Logger,
|
||||||
Post,
|
Post,
|
||||||
Request
|
Request
|
||||||
} from '@nestjs/common';
|
} from '@nestjs/common';
|
||||||
import {
|
import {
|
||||||
UserLoginResponse,
|
UserLoginResponse,
|
||||||
UserMePermissionsResponse,
|
UserMePermissionsResponse,
|
||||||
UserMeResponse,
|
UserMeResponse,
|
||||||
UserRegisterRequest,
|
UserRegisterRequest,
|
||||||
UserRegisterResponse
|
UserRegisterResponse
|
||||||
} from 'picsur-shared/dist/dto/api/user.dto';
|
} from 'picsur-shared/dist/dto/api/user.dto';
|
||||||
import { Permission } from 'picsur-shared/dist/dto/permissions';
|
import { Permission } from 'picsur-shared/dist/dto/permissions';
|
||||||
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 { UserRolesService } from '../../../collections/userdb/userrolesdb.service';
|
import { UserRolesService } from '../../../collections/userdb/userrolesdb.service';
|
||||||
import {
|
import {
|
||||||
NoPermissions,
|
NoPermissions,
|
||||||
RequiredPermissions,
|
RequiredPermissions,
|
||||||
UseLocalAuth
|
UseLocalAuth
|
||||||
} from '../../../decorators/permissions.decorator';
|
} 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/requests/authrequest.dto';
|
||||||
|
|
||||||
@Controller('api/user')
|
@Controller('api/user')
|
||||||
export class UserController {
|
export class UserController {
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ import { HasFailed } from 'picsur-shared/dist/types';
|
|||||||
import { MultiPart } from '../../decorators/multipart.decorator';
|
import { MultiPart } from '../../decorators/multipart.decorator';
|
||||||
import { RequiredPermissions } from '../../decorators/permissions.decorator';
|
import { RequiredPermissions } from '../../decorators/permissions.decorator';
|
||||||
import { ImageManagerService } from '../../managers/imagemanager/imagemanager.service';
|
import { ImageManagerService } from '../../managers/imagemanager/imagemanager.service';
|
||||||
import { ImageUploadDto } from '../../models/dto/imageroute.dto';
|
import { ImageUploadDto } from '../../models/requests/imageroute.dto';
|
||||||
|
|
||||||
@Controller('i')
|
@Controller('i')
|
||||||
@RequiredPermissions(Permission.ImageView)
|
@RequiredPermissions(Permission.ImageView)
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
import { Roles } from 'picsur-shared/dist/dto/roles.dto';
|
|
||||||
|
|
||||||
export interface FullUserModel {
|
export interface FullUserModel {
|
||||||
username: string;
|
username: string;
|
||||||
password: string;
|
password: string;
|
||||||
roles: Roles;
|
roles: string[];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import { FormControl } from '@angular/forms';
|
import { FormControl } from '@angular/forms';
|
||||||
import Fuse from 'fuse.js';
|
import Fuse from 'fuse.js';
|
||||||
import { Permission, Permissions } from 'picsur-shared/dist/dto/permissions';
|
import { Permission, Permissions } from 'picsur-shared/dist/dto/permissions';
|
||||||
import { PermanentRolesList } from 'picsur-shared/dist/dto/roles.dto';
|
|
||||||
import { BehaviorSubject, Subscription } from 'rxjs';
|
import { BehaviorSubject, Subscription } from 'rxjs';
|
||||||
import { RoleNameValidators } from './role-validators';
|
import { RoleNameValidators } from './role-validators';
|
||||||
import { RoleModel } from './role.model';
|
import { RoleModel } from './role.model';
|
||||||
@@ -58,11 +57,6 @@ export class UpdateRoleControl {
|
|||||||
this.updateSelectablePermissions();
|
this.updateSelectablePermissions();
|
||||||
}
|
}
|
||||||
|
|
||||||
public isRemovable(role: Permission) {
|
|
||||||
if (PermanentRolesList.includes(role)) return false;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Data interaction
|
// Data interaction
|
||||||
|
|
||||||
public putAllPermissions(permissions: Permissions) {
|
public putAllPermissions(permissions: Permissions) {
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import { FormControl } from '@angular/forms';
|
import { FormControl } from '@angular/forms';
|
||||||
import Fuse from 'fuse.js';
|
import Fuse from 'fuse.js';
|
||||||
import { Permissions } from 'picsur-shared/dist/dto/permissions';
|
import { Permissions } from 'picsur-shared/dist/dto/permissions';
|
||||||
import { PermanentRolesList } from 'picsur-shared/dist/dto/roles.dto';
|
|
||||||
import { ERole } from 'picsur-shared/dist/entities/role.entity';
|
import { ERole } from 'picsur-shared/dist/entities/role.entity';
|
||||||
import { BehaviorSubject, Subscription } from 'rxjs';
|
import { BehaviorSubject, Subscription } from 'rxjs';
|
||||||
import { FullUserModel } from './fulluser.model';
|
import { FullUserModel } from './fulluser.model';
|
||||||
@@ -13,6 +12,9 @@ import {
|
|||||||
} from './user-validators';
|
} from './user-validators';
|
||||||
|
|
||||||
export class UpdateUserControl {
|
export class UpdateUserControl {
|
||||||
|
// Special roles
|
||||||
|
private SoulBoundRolesList: string[] = [];
|
||||||
|
|
||||||
// Set once
|
// Set once
|
||||||
private fullRoles: ERole[] = [];
|
private fullRoles: ERole[] = [];
|
||||||
private roles: string[] = [];
|
private roles: string[] = [];
|
||||||
@@ -68,7 +70,7 @@ export class UpdateUserControl {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public isRemovable(role: string) {
|
public isRemovable(role: string) {
|
||||||
if (PermanentRolesList.includes(role)) return false;
|
if (this.SoulBoundRolesList.includes(role)) return false;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -106,6 +108,10 @@ export class UpdateUserControl {
|
|||||||
this.updateSelectableRoles();
|
this.updateSelectableRoles();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public putSoulBoundRoles(roles: string[]) {
|
||||||
|
this.SoulBoundRolesList = roles;
|
||||||
|
}
|
||||||
|
|
||||||
public getData(): FullUserModel {
|
public getData(): FullUserModel {
|
||||||
return {
|
return {
|
||||||
username: this.username.value,
|
username: this.username.value,
|
||||||
@@ -119,7 +125,8 @@ export class UpdateUserControl {
|
|||||||
private updateSelectableRoles() {
|
private updateSelectableRoles() {
|
||||||
const availableRoles = this.roles.filter(
|
const availableRoles = this.roles.filter(
|
||||||
// Not available if either already selected, or the role is not addable/removable
|
// Not available if either already selected, or the role is not addable/removable
|
||||||
(r) => !(this.selectedRoles.includes(r) || PermanentRolesList.includes(r))
|
(r) =>
|
||||||
|
!(this.selectedRoles.includes(r) || this.SoulBoundRolesList.includes(r))
|
||||||
);
|
);
|
||||||
|
|
||||||
const searchValue = this.rolesControl.value;
|
const searchValue = this.rolesControl.value;
|
||||||
|
|||||||
@@ -33,12 +33,10 @@
|
|||||||
<mat-chip-list #chipList aria-label="Permissions Selection">
|
<mat-chip-list #chipList aria-label="Permissions Selection">
|
||||||
<mat-chip
|
<mat-chip
|
||||||
*ngFor="let permission of model.selectedPermissions"
|
*ngFor="let permission of model.selectedPermissions"
|
||||||
[removable]="model.isRemovable(permission)"
|
|
||||||
[disabled]="!model.isRemovable(permission)"
|
|
||||||
(removed)="removePermission(permission)"
|
(removed)="removePermission(permission)"
|
||||||
>
|
>
|
||||||
{{ uiFriendlyPermission(permission) }}
|
{{ uiFriendlyPermission(permission) }}
|
||||||
<button *ngIf="model.isRemovable(permission)" matChipRemove>
|
<button matChipRemove>
|
||||||
<mat-icon>cancel</mat-icon>
|
<mat-icon>cancel</mat-icon>
|
||||||
</button>
|
</button>
|
||||||
</mat-chip>
|
</mat-chip>
|
||||||
@@ -50,14 +48,14 @@
|
|||||||
[matAutocomplete]="auto"
|
[matAutocomplete]="auto"
|
||||||
[matChipInputFor]="chipList"
|
[matChipInputFor]="chipList"
|
||||||
[matChipInputSeparatorKeyCodes]="separatorKeysCodes"
|
[matChipInputSeparatorKeyCodes]="separatorKeysCodes"
|
||||||
(matChipInputTokenEnd)="addRole($event)"
|
(matChipInputTokenEnd)="addPermission($event)"
|
||||||
autocorrect="off"
|
autocorrect="off"
|
||||||
autocapitalize="none"
|
autocapitalize="none"
|
||||||
/>
|
/>
|
||||||
</mat-chip-list>
|
</mat-chip-list>
|
||||||
<mat-autocomplete
|
<mat-autocomplete
|
||||||
#auto="matAutocomplete"
|
#auto="matAutocomplete"
|
||||||
(optionSelected)="selectedRole($event)"
|
(optionSelected)="selectedPermission($event)"
|
||||||
>
|
>
|
||||||
<mat-option
|
<mat-option
|
||||||
*ngFor="let permission of model.selectablePermissions | async"
|
*ngFor="let permission of model.selectablePermissions | async"
|
||||||
|
|||||||
@@ -77,12 +77,12 @@ export class SettingsRolesEditComponent implements OnInit {
|
|||||||
this.model.removePermission(permission);
|
this.model.removePermission(permission);
|
||||||
}
|
}
|
||||||
|
|
||||||
addRole(event: MatChipInputEvent) {
|
addPermission(event: MatChipInputEvent) {
|
||||||
const value = (event.value ?? '').trim();
|
const value = (event.value ?? '').trim();
|
||||||
this.model.addPermission(value as Permission);
|
this.model.addPermission(value as Permission);
|
||||||
}
|
}
|
||||||
|
|
||||||
selectedRole(event: MatAutocompleteSelectedEvent): void {
|
selectedPermission(event: MatAutocompleteSelectedEvent): void {
|
||||||
this.model.addPermission(event.option.viewValue as Permission);
|
this.model.addPermission(event.option.viewValue as Permission);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import {
|
|||||||
Permission,
|
Permission,
|
||||||
UIFriendlyPermissions
|
UIFriendlyPermissions
|
||||||
} from 'picsur-shared/dist/dto/permissions';
|
} from 'picsur-shared/dist/dto/permissions';
|
||||||
import { ImmuteableRolesList, SystemRolesList } from 'picsur-shared/dist/dto/roles.dto';
|
|
||||||
import { ERole } from 'picsur-shared/dist/entities/role.entity';
|
import { ERole } from 'picsur-shared/dist/entities/role.entity';
|
||||||
import { HasFailed } from 'picsur-shared/dist/types';
|
import { HasFailed } from 'picsur-shared/dist/types';
|
||||||
import { SnackBarType } from 'src/app/models/snack-bar-type';
|
import { SnackBarType } from 'src/app/models/snack-bar-type';
|
||||||
@@ -29,6 +28,9 @@ export class SettingsRolesComponent implements OnInit, AfterViewInit {
|
|||||||
|
|
||||||
public dataSource = new MatTableDataSource<ERole>([]);
|
public dataSource = new MatTableDataSource<ERole>([]);
|
||||||
|
|
||||||
|
private UndeletableRolesList: string[] = [];
|
||||||
|
private ImmutableRolesList: string[] = [];
|
||||||
|
|
||||||
@ViewChild(MatPaginator) paginator: MatPaginator;
|
@ViewChild(MatPaginator) paginator: MatPaginator;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@@ -91,11 +93,11 @@ export class SettingsRolesComponent implements OnInit, AfterViewInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
isSystem(role: ERole) {
|
isSystem(role: ERole) {
|
||||||
return SystemRolesList.includes(role.name);
|
return this.UndeletableRolesList.includes(role.name);
|
||||||
}
|
}
|
||||||
|
|
||||||
isImmutable(role: ERole) {
|
isImmutable(role: ERole) {
|
||||||
return ImmuteableRolesList.includes(role.name);
|
return this.ImmutableRolesList.includes(role.name);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async fetchRoles() {
|
private async fetchRoles() {
|
||||||
@@ -106,5 +108,17 @@ export class SettingsRolesComponent implements OnInit, AfterViewInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.dataSource.data = roles;
|
this.dataSource.data = roles;
|
||||||
|
|
||||||
|
const specialRoles = await this.rolesService.getSpecialRoles();
|
||||||
|
if (HasFailed(specialRoles)) {
|
||||||
|
this.utilService.showSnackBar(
|
||||||
|
'Failed to load special roles',
|
||||||
|
SnackBarType.Error
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.UndeletableRolesList = specialRoles.UndeletableRoles;
|
||||||
|
this.ImmutableRolesList = specialRoles.ImmutableRoles;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import { MatAutocompleteSelectedEvent } from '@angular/material/autocomplete';
|
|||||||
import { MatChipInputEvent } from '@angular/material/chips';
|
import { MatChipInputEvent } from '@angular/material/chips';
|
||||||
import { ActivatedRoute, Router } from '@angular/router';
|
import { ActivatedRoute, Router } from '@angular/router';
|
||||||
import { UIFriendlyPermissions } from 'picsur-shared/dist/dto/permissions';
|
import { UIFriendlyPermissions } from 'picsur-shared/dist/dto/permissions';
|
||||||
import { DefaultRolesList } from 'picsur-shared/dist/dto/roles.dto';
|
|
||||||
import { LockedPermsUsersList } from 'picsur-shared/dist/dto/specialusers.dto';
|
import { LockedPermsUsersList } from 'picsur-shared/dist/dto/specialusers.dto';
|
||||||
import { HasFailed } from 'picsur-shared/dist/types';
|
import { HasFailed } from 'picsur-shared/dist/types';
|
||||||
import { UpdateUserControl } from 'src/app/models/forms/updateuser.control';
|
import { UpdateUserControl } from 'src/app/models/forms/updateuser.control';
|
||||||
@@ -51,9 +50,15 @@ export class SettingsUsersEditComponent implements OnInit {
|
|||||||
|
|
||||||
private async initUser() {
|
private async initUser() {
|
||||||
const username = this.route.snapshot.paramMap.get('username');
|
const username = this.route.snapshot.paramMap.get('username');
|
||||||
|
|
||||||
|
const { DefaultRoles, SoulBoundRoles } =
|
||||||
|
await this.rolesService.getSpecialRolesOptimistic();
|
||||||
|
this.model.putSoulBoundRoles(SoulBoundRoles);
|
||||||
|
|
||||||
if (!username) {
|
if (!username) {
|
||||||
this.mode = EditMode.add;
|
this.mode = EditMode.add;
|
||||||
this.model.putRoles(DefaultRolesList);
|
|
||||||
|
this.model.putRoles(DefaultRoles);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { ClassConstructor, plainToClass } from 'class-transformer';
|
import { ClassConstructor, plainToClass } from 'class-transformer';
|
||||||
import { ApiResponse, ApiSuccessResponse } from 'picsur-shared/dist/dto/api';
|
import { ApiResponse, ApiSuccessResponse } from 'picsur-shared/dist/dto/api/api.dto';
|
||||||
import { AsyncFailable, Fail, HasFailed } from 'picsur-shared/dist/types';
|
import { AsyncFailable, Fail, HasFailed } from 'picsur-shared/dist/types';
|
||||||
import { strictValidate } from 'picsur-shared/dist/util/validate';
|
import { strictValidate } from 'picsur-shared/dist/util/validate';
|
||||||
import { Subject } from 'rxjs';
|
import { Subject } from 'rxjs';
|
||||||
|
|||||||
44
frontend/src/app/services/api/cache.service.ts
Normal file
44
frontend/src/app/services/api/cache.service.ts
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
|
||||||
|
interface dataWrapper<T> {
|
||||||
|
data: T;
|
||||||
|
expires: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root',
|
||||||
|
})
|
||||||
|
export class CacheService {
|
||||||
|
private readonly cacheExpiresMS = 1000 * 60 * 60;
|
||||||
|
|
||||||
|
private storage: Storage;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
if (window.sessionStorage) {
|
||||||
|
this.storage = window.sessionStorage;
|
||||||
|
} else {
|
||||||
|
throw new Error('Session storage is not supported');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public get<T>(key: string): T | null {
|
||||||
|
try {
|
||||||
|
const data: dataWrapper<T> = JSON.parse(this.storage.getItem(key) ?? '');
|
||||||
|
if (data && data.data && data.expires > Date.now()) {
|
||||||
|
return data.data;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
} catch (e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public set<T>(key: string, value: T): void {
|
||||||
|
const data: dataWrapper<T> = {
|
||||||
|
data: value,
|
||||||
|
expires: Date.now() + this.cacheExpiresMS,
|
||||||
|
};
|
||||||
|
|
||||||
|
this.storage.setItem(key, JSON.stringify(data));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,18 +8,23 @@ import {
|
|||||||
RoleInfoResponse,
|
RoleInfoResponse,
|
||||||
RoleListResponse,
|
RoleListResponse,
|
||||||
RoleUpdateRequest,
|
RoleUpdateRequest,
|
||||||
RoleUpdateResponse
|
RoleUpdateResponse,
|
||||||
|
SpecialRolesResponse
|
||||||
} from 'picsur-shared/dist/dto/api/roles.dto';
|
} from 'picsur-shared/dist/dto/api/roles.dto';
|
||||||
import { ERole } from 'picsur-shared/dist/entities/role.entity';
|
import { ERole } from 'picsur-shared/dist/entities/role.entity';
|
||||||
import { AsyncFailable, HasFailed } from 'picsur-shared/dist/types';
|
import { AsyncFailable, HasFailed } from 'picsur-shared/dist/types';
|
||||||
import { RoleModel } from 'src/app/models/forms/role.model';
|
import { RoleModel } from 'src/app/models/forms/role.model';
|
||||||
import { ApiService } from './api.service';
|
import { ApiService } from './api.service';
|
||||||
|
import { CacheService } from './cache.service';
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root',
|
providedIn: 'root',
|
||||||
})
|
})
|
||||||
export class RolesService {
|
export class RolesService {
|
||||||
constructor(private apiService: ApiService) {}
|
constructor(
|
||||||
|
private apiService: ApiService,
|
||||||
|
private cacheService: CacheService
|
||||||
|
) {}
|
||||||
|
|
||||||
public async getRoles(): AsyncFailable<ERole[]> {
|
public async getRoles(): AsyncFailable<ERole[]> {
|
||||||
const result = await this.apiService.get(
|
const result = await this.apiService.get(
|
||||||
@@ -85,4 +90,38 @@ export class RolesService {
|
|||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async getSpecialRoles(): AsyncFailable<SpecialRolesResponse> {
|
||||||
|
const cached = this.cacheService.get<SpecialRolesResponse>('specialRoles');
|
||||||
|
if (cached !== null) {
|
||||||
|
return cached;
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await this.apiService.get(
|
||||||
|
SpecialRolesResponse,
|
||||||
|
'/api/roles/special'
|
||||||
|
);
|
||||||
|
|
||||||
|
if (HasFailed(result)) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.cacheService.set('specialRoles', result);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async getSpecialRolesOptimistic(): Promise<SpecialRolesResponse> {
|
||||||
|
const result = await this.getSpecialRoles();
|
||||||
|
if (HasFailed(result)) {
|
||||||
|
return {
|
||||||
|
DefaultRoles: [],
|
||||||
|
ImmutableRoles: [],
|
||||||
|
SoulBoundRoles: [],
|
||||||
|
UndeletableRoles: [],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,7 +18,8 @@
|
|||||||
"typescript": "4.5.5"
|
"typescript": "4.5.5"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "tsc-watch",
|
"clean": "rm -rf ./dist",
|
||||||
"build": "tsc"
|
"start": "yarn clean && tsc-watch",
|
||||||
|
"build": "yarn clean && tsc"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +1,12 @@
|
|||||||
import { Type } from 'class-transformer';
|
import { Type } from 'class-transformer';
|
||||||
import {
|
import { IsArray, IsDefined, ValidateNested } from 'class-validator';
|
||||||
IsArray,
|
|
||||||
IsDefined, ValidateNested
|
|
||||||
} from 'class-validator';
|
|
||||||
import {
|
import {
|
||||||
ERole,
|
ERole,
|
||||||
RoleNameObject,
|
RoleNameObject,
|
||||||
RoleNamePermsObject
|
RoleNamePermsObject
|
||||||
} from '../../entities/role.entity';
|
} from '../../entities/role.entity';
|
||||||
import { IsPosInt } from '../../validators/positive-int.validator';
|
import { IsPosInt } from '../../validators/positive-int.validator';
|
||||||
|
import { IsStringList } from '../../validators/string-list.validator';
|
||||||
|
|
||||||
// RoleInfo
|
// RoleInfo
|
||||||
export class RoleInfoRequest extends RoleNameObject {}
|
export class RoleInfoRequest extends RoleNameObject {}
|
||||||
@@ -37,3 +35,22 @@ export class RoleCreateResponse extends ERole {}
|
|||||||
// RoleDelete
|
// RoleDelete
|
||||||
export class RoleDeleteRequest extends RoleNameObject {}
|
export class RoleDeleteRequest extends RoleNameObject {}
|
||||||
export class RoleDeleteResponse extends ERole {}
|
export class RoleDeleteResponse extends ERole {}
|
||||||
|
|
||||||
|
// SpecialRoles
|
||||||
|
export class SpecialRolesResponse {
|
||||||
|
@IsDefined()
|
||||||
|
@IsStringList()
|
||||||
|
SoulBoundRoles: string[];
|
||||||
|
|
||||||
|
@IsDefined()
|
||||||
|
@IsStringList()
|
||||||
|
ImmutableRoles: string[];
|
||||||
|
|
||||||
|
@IsDefined()
|
||||||
|
@IsStringList()
|
||||||
|
UndeletableRoles: string[];
|
||||||
|
|
||||||
|
@IsDefined()
|
||||||
|
@IsStringList()
|
||||||
|
DefaultRoles: string[];
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
import { Type } from 'class-transformer';
|
import { Type } from 'class-transformer';
|
||||||
import {
|
import {
|
||||||
IsArray,
|
IsArray,
|
||||||
IsDefined, IsOptional,
|
IsDefined,
|
||||||
IsString, ValidateNested
|
IsOptional, ValidateNested
|
||||||
} from 'class-validator';
|
} from 'class-validator';
|
||||||
import { EUser, NamePassUser, UsernameUser } from '../../entities/user.entity';
|
import { EUser, NamePassUser, UsernameUser } from '../../entities/user.entity';
|
||||||
import { IsPosInt } from '../../validators/positive-int.validator';
|
import { IsPosInt } from '../../validators/positive-int.validator';
|
||||||
|
import { IsStringList } from '../../validators/string-list.validator';
|
||||||
import { IsPlainTextPwd } from '../../validators/user.validators';
|
import { IsPlainTextPwd } from '../../validators/user.validators';
|
||||||
import { Roles } from '../roles.dto';
|
|
||||||
|
|
||||||
// UserList
|
// UserList
|
||||||
export class UserListRequest {
|
export class UserListRequest {
|
||||||
@@ -35,9 +35,8 @@ export class UserListResponse {
|
|||||||
// UserCreate
|
// UserCreate
|
||||||
export class UserCreateRequest extends NamePassUser {
|
export class UserCreateRequest extends NamePassUser {
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
@IsArray()
|
@IsStringList()
|
||||||
@IsString({ each: true })
|
roles?: string[];
|
||||||
roles?: Roles;
|
|
||||||
}
|
}
|
||||||
export class UserCreateResponse extends EUser {}
|
export class UserCreateResponse extends EUser {}
|
||||||
|
|
||||||
@@ -52,9 +51,8 @@ export class UserInfoResponse extends EUser {}
|
|||||||
// UserUpdateRoles
|
// UserUpdateRoles
|
||||||
export class UserUpdateRequest extends UsernameUser {
|
export class UserUpdateRequest extends UsernameUser {
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
@IsArray()
|
@IsStringList()
|
||||||
@IsString({ each: true })
|
roles?: string[];
|
||||||
roles?: Roles;
|
|
||||||
|
|
||||||
@IsPlainTextPwd()
|
@IsPlainTextPwd()
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
|
|||||||
@@ -1,47 +0,0 @@
|
|||||||
import tuple from '../types/tuple';
|
|
||||||
import { Permission, Permissions, PermissionsList } from './permissions';
|
|
||||||
|
|
||||||
// Config
|
|
||||||
|
|
||||||
// These roles can never be removed or added to a user.
|
|
||||||
const PermanentRolesTuple = tuple('guest', 'user');
|
|
||||||
|
|
||||||
// These roles can never be modified
|
|
||||||
const ImmuteableRolesTuple = tuple('admin');
|
|
||||||
// These roles can never be removed from the server
|
|
||||||
const SystemRolesTuple = tuple(...PermanentRolesTuple, ...ImmuteableRolesTuple);
|
|
||||||
|
|
||||||
// These roles will be applied by default to new users
|
|
||||||
export const DefaultRolesList: string[] = ['user'];
|
|
||||||
|
|
||||||
// Derivatives
|
|
||||||
|
|
||||||
export const PermanentRolesList: string[] = PermanentRolesTuple;
|
|
||||||
export const ImmuteableRolesList: string[] = ImmuteableRolesTuple;
|
|
||||||
export const SystemRolesList: string[] = SystemRolesTuple;
|
|
||||||
|
|
||||||
|
|
||||||
export type SystemRole = typeof SystemRolesTuple[number];
|
|
||||||
export type SystemRoles = SystemRole[];
|
|
||||||
|
|
||||||
// Defaults
|
|
||||||
|
|
||||||
export const SystemRoleDefaults: {
|
|
||||||
[key in SystemRole]: Permissions;
|
|
||||||
} = {
|
|
||||||
guest: [Permission.ImageView, Permission.UserLogin],
|
|
||||||
user: [
|
|
||||||
Permission.ImageView,
|
|
||||||
Permission.UserMe,
|
|
||||||
Permission.UserLogin,
|
|
||||||
Permission.Settings,
|
|
||||||
Permission.ImageUpload,
|
|
||||||
],
|
|
||||||
// Grant all permissions to admin
|
|
||||||
admin: PermissionsList,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Normal roles types
|
|
||||||
|
|
||||||
export type Role = SystemRole | string;
|
|
||||||
export type Roles = Role[];
|
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import { Exclude } from 'class-transformer';
|
import { Exclude } from 'class-transformer';
|
||||||
import { IsArray, IsOptional, IsString } from 'class-validator';
|
import { IsDefined, IsOptional, IsString } from 'class-validator';
|
||||||
import { Roles } from '../dto/roles.dto';
|
|
||||||
import { EntityID } from '../validators/entity-id.validator';
|
import { EntityID } from '../validators/entity-id.validator';
|
||||||
|
import { IsStringList } from '../validators/string-list.validator';
|
||||||
import { IsPlainTextPwd, IsUsername } from '../validators/user.validators';
|
import { IsPlainTextPwd, IsUsername } from '../validators/user.validators';
|
||||||
|
|
||||||
export class UsernameUser {
|
export class UsernameUser {
|
||||||
@@ -17,9 +17,9 @@ export class NamePassUser extends UsernameUser {
|
|||||||
|
|
||||||
// Add a user object with just the username and roles for jwt
|
// Add a user object with just the username and roles for jwt
|
||||||
export class NameRolesUser extends UsernameUser {
|
export class NameRolesUser extends UsernameUser {
|
||||||
@IsArray()
|
@IsDefined()
|
||||||
@IsString({ each: true })
|
@IsStringList()
|
||||||
roles: Roles;
|
roles: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Actual entity that goes in the db
|
// Actual entity that goes in the db
|
||||||
|
|||||||
12
shared/src/validators/string-list.validator.ts
Normal file
12
shared/src/validators/string-list.validator.ts
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import {
|
||||||
|
IsArray,
|
||||||
|
IsNotEmpty,
|
||||||
|
IsString
|
||||||
|
} from 'class-validator';
|
||||||
|
import { ComposeValidators } from './compose.validator';
|
||||||
|
|
||||||
|
export const IsStringList = ComposeValidators(
|
||||||
|
IsArray(),
|
||||||
|
IsString({ each: true }),
|
||||||
|
IsNotEmpty({ each: true }),
|
||||||
|
);
|
||||||
Reference in New Issue
Block a user