mirror of
https://github.com/CaramelFur/Picsur.git
synced 2025-11-12 14:55:39 +01:00
clean up validation, still not happy
This commit is contained in:
@@ -1,6 +1,5 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { InjectRepository } from '@nestjs/typeorm';
|
import { InjectRepository } from '@nestjs/typeorm';
|
||||||
import { plainToClass } from 'class-transformer';
|
|
||||||
import Crypto from 'crypto';
|
import Crypto from 'crypto';
|
||||||
import {
|
import {
|
||||||
AsyncFailable,
|
AsyncFailable,
|
||||||
@@ -36,8 +35,7 @@ export class ImageDBService {
|
|||||||
return Fail(e?.message);
|
return Fail(e?.message);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Strips unwanted data
|
return imageEntity;
|
||||||
return plainToClass(EImageBackend, imageEntity);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async findOne<B extends true | undefined = undefined>(
|
public async findOne<B extends true | undefined = undefined>(
|
||||||
|
|||||||
@@ -52,10 +52,7 @@ export class RolesService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Makes sure we can return the id
|
return await this.rolesRepository.remove(roleToModify);
|
||||||
const cloned = plainToClass(ERoleBackend, roleToModify);
|
|
||||||
await this.rolesRepository.remove(roleToModify);
|
|
||||||
return cloned;
|
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
return Fail(e?.message);
|
return Fail(e?.message);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
import { Injectable, Logger } from '@nestjs/common';
|
import { Injectable, Logger } from '@nestjs/common';
|
||||||
import { InjectRepository } from '@nestjs/typeorm';
|
import { InjectRepository } from '@nestjs/typeorm';
|
||||||
import { PrefValueType, PrefValueTypeStrings } from 'picsur-shared/dist/dto/preferences.dto';
|
|
||||||
import {
|
import {
|
||||||
InternalSysPrefRepresentation,
|
DecodedSysPref,
|
||||||
SysPreference
|
PrefValueType,
|
||||||
} from 'picsur-shared/dist/dto/syspreferences.dto';
|
PrefValueTypeStrings
|
||||||
|
} from 'picsur-shared/dist/dto/preferences.dto';
|
||||||
|
import { SysPreference } from 'picsur-shared/dist/dto/syspreferences.dto';
|
||||||
import {
|
import {
|
||||||
AsyncFailable,
|
AsyncFailable,
|
||||||
Fail,
|
Fail,
|
||||||
@@ -33,7 +34,7 @@ export class SysPreferenceService {
|
|||||||
public async setPreference(
|
public async setPreference(
|
||||||
key: string,
|
key: string,
|
||||||
value: PrefValueType,
|
value: PrefValueType,
|
||||||
): AsyncFailable<InternalSysPrefRepresentation> {
|
): AsyncFailable<DecodedSysPref> {
|
||||||
// Validate
|
// Validate
|
||||||
let sysPreference = await this.validatePref(key, value);
|
let sysPreference = await this.validatePref(key, value);
|
||||||
if (HasFailed(sysPreference)) return sysPreference;
|
if (HasFailed(sysPreference)) return sysPreference;
|
||||||
@@ -58,9 +59,7 @@ export class SysPreferenceService {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getPreference(
|
public async getPreference(key: string): AsyncFailable<DecodedSysPref> {
|
||||||
key: string,
|
|
||||||
): AsyncFailable<InternalSysPrefRepresentation> {
|
|
||||||
// Validate
|
// Validate
|
||||||
let validatedKey = this.validatePrefKey(key);
|
let validatedKey = this.validatePrefKey(key);
|
||||||
if (HasFailed(validatedKey)) return validatedKey;
|
if (HasFailed(validatedKey)) return validatedKey;
|
||||||
@@ -116,9 +115,7 @@ export class SysPreferenceService {
|
|||||||
return pref.value;
|
return pref.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getAllPreferences(): AsyncFailable<
|
public async getAllPreferences(): AsyncFailable<DecodedSysPref[]> {
|
||||||
InternalSysPrefRepresentation[]
|
|
||||||
> {
|
|
||||||
// TODO: We are fetching each value invidually, we should fetch all at once
|
// TODO: We are fetching each value invidually, we should fetch all at once
|
||||||
let internalSysPrefs = await Promise.all(
|
let internalSysPrefs = await Promise.all(
|
||||||
SysPreferenceList.map((key) => this.getPreference(key)),
|
SysPreferenceList.map((key) => this.getPreference(key)),
|
||||||
@@ -127,21 +124,21 @@ export class SysPreferenceService {
|
|||||||
return Fail('Could not get all preferences');
|
return Fail('Could not get all preferences');
|
||||||
}
|
}
|
||||||
|
|
||||||
return internalSysPrefs as InternalSysPrefRepresentation[];
|
return internalSysPrefs as DecodedSysPref[];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Private
|
// Private
|
||||||
|
|
||||||
private async saveDefault(
|
private async saveDefault(
|
||||||
key: SysPreference, // Force enum here because we dont validate
|
key: SysPreference, // Force enum here because we dont validate
|
||||||
): AsyncFailable<InternalSysPrefRepresentation> {
|
): AsyncFailable<DecodedSysPref> {
|
||||||
return this.setPreference(key, this.defaultsService.sysDefaults[key]());
|
return this.setPreference(key, this.defaultsService.sysDefaults[key]());
|
||||||
}
|
}
|
||||||
|
|
||||||
// This converts the raw string representation of the value to the correct type
|
// This converts the raw string representation of the value to the correct type
|
||||||
private retrieveConvertedValue(
|
private retrieveConvertedValue(
|
||||||
preference: ESysPreferenceBackend,
|
preference: ESysPreferenceBackend,
|
||||||
): Failable<InternalSysPrefRepresentation> {
|
): Failable<DecodedSysPref> {
|
||||||
const key = this.validatePrefKey(preference.key);
|
const key = this.validatePrefKey(preference.key);
|
||||||
if (HasFailed(key)) return key;
|
if (HasFailed(key)) return key;
|
||||||
|
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ export class UsersService {
|
|||||||
|
|
||||||
let user = new EUserBackend();
|
let user = new EUserBackend();
|
||||||
user.username = username;
|
user.username = username;
|
||||||
user.password = hashedPassword;
|
user.hashedPassword = hashedPassword;
|
||||||
if (byPassRoleCheck) {
|
if (byPassRoleCheck) {
|
||||||
const rolesToAdd = roles ?? [];
|
const rolesToAdd = roles ?? [];
|
||||||
user.roles = makeUnique(rolesToAdd);
|
user.roles = makeUnique(rolesToAdd);
|
||||||
@@ -64,13 +64,10 @@ export class UsersService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
user = await this.usersRepository.save(user, { reload: true });
|
return await this.usersRepository.save(user);
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
return Fail(e?.message);
|
return Fail(e?.message);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Strips unwanted data
|
|
||||||
return plainToClass(EUserBackend, user);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async delete(uuid: string): AsyncFailable<EUserBackend> {
|
public async delete(uuid: string): AsyncFailable<EUserBackend> {
|
||||||
@@ -153,8 +150,7 @@ export class UsersService {
|
|||||||
if (HasFailed(userToModify)) return userToModify;
|
if (HasFailed(userToModify)) return userToModify;
|
||||||
|
|
||||||
const strength = await this.getBCryptStrength();
|
const strength = await this.getBCryptStrength();
|
||||||
const hashedPassword = await bcrypt.hash(password, strength);
|
userToModify.hashedPassword = await bcrypt.hash(password, strength);
|
||||||
userToModify.password = hashedPassword;
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
userToModify = await this.usersRepository.save(userToModify);
|
userToModify = await this.usersRepository.save(userToModify);
|
||||||
@@ -180,7 +176,7 @@ export class UsersService {
|
|||||||
return Fail('Wrong username');
|
return Fail('Wrong username');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!(await bcrypt.compare(password, user.password)))
|
if (!(await bcrypt.compare(password, user.hashedPassword)))
|
||||||
return Fail('Wrong password');
|
return Fail('Wrong password');
|
||||||
|
|
||||||
return await this.findOne(user.id);
|
return await this.findOne(user.id);
|
||||||
@@ -199,7 +195,11 @@ export class UsersService {
|
|||||||
try {
|
try {
|
||||||
const found = await this.usersRepository.findOne({
|
const found = await this.usersRepository.findOne({
|
||||||
where: { username },
|
where: { username },
|
||||||
select: getPrivate ? GetCols(this.usersRepository) : undefined,
|
...(getPrivate
|
||||||
|
? {
|
||||||
|
select: GetCols(this.usersRepository),
|
||||||
|
}
|
||||||
|
: {}),
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!found) return Fail('User not found');
|
if (!found) return Fail('User not found');
|
||||||
|
|||||||
@@ -41,6 +41,10 @@ export class MainAuthGuard extends AuthGuard(['jwt', 'guest']) {
|
|||||||
const user = await this.validateUser(
|
const user = await this.validateUser(
|
||||||
context.switchToHttp().getRequest().user,
|
context.switchToHttp().getRequest().user,
|
||||||
);
|
);
|
||||||
|
if (!user.id) {
|
||||||
|
this.logger.error('User has no id, this should not happen');
|
||||||
|
throw new InternalServerErrorException();
|
||||||
|
}
|
||||||
|
|
||||||
// These are the permissions required to access the route
|
// These are the permissions required to access the route
|
||||||
const permissions = this.extractPermissions(context);
|
const permissions = this.extractPermissions(context);
|
||||||
|
|||||||
@@ -1,19 +1,23 @@
|
|||||||
|
import { IsNotEmpty, IsOptional } from 'class-validator';
|
||||||
import { EImage } from 'picsur-shared/dist/entities/image.entity';
|
import { EImage } from 'picsur-shared/dist/entities/image.entity';
|
||||||
import { Column, Entity, Index, PrimaryGeneratedColumn } from 'typeorm';
|
import { Column, Entity, Index, PrimaryGeneratedColumn } from 'typeorm';
|
||||||
|
|
||||||
@Entity()
|
@Entity()
|
||||||
export class EImageBackend extends EImage {
|
export class EImageBackend extends EImage {
|
||||||
@PrimaryGeneratedColumn("uuid")
|
@PrimaryGeneratedColumn('uuid')
|
||||||
override id: string;
|
override id?: string;
|
||||||
|
|
||||||
@Index()
|
@Index()
|
||||||
@Column({ unique: true, nullable: false })
|
@Column({ unique: true, nullable: false })
|
||||||
override hash: string;
|
override hash: string;
|
||||||
|
|
||||||
// Binary data
|
|
||||||
@Column({ type: 'bytea', nullable: false, select: false })
|
|
||||||
override data?: Buffer;
|
|
||||||
|
|
||||||
@Column({ nullable: false })
|
@Column({ nullable: false })
|
||||||
override mime: string;
|
override mime: string;
|
||||||
|
|
||||||
|
// Binary data
|
||||||
|
@Column({ type: 'bytea', nullable: false, select: false })
|
||||||
|
@IsOptional()
|
||||||
|
@IsNotEmpty()
|
||||||
|
// @ts-ignore
|
||||||
|
override data?: Buffer;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { Permissions } from '../dto/permissions.dto';
|
|||||||
@Entity()
|
@Entity()
|
||||||
export class ERoleBackend extends ERole {
|
export class ERoleBackend extends ERole {
|
||||||
@PrimaryGeneratedColumn("uuid")
|
@PrimaryGeneratedColumn("uuid")
|
||||||
override id: string;
|
override id?: string;
|
||||||
|
|
||||||
@Index()
|
@Index()
|
||||||
@Column({ nullable: false, unique: true })
|
@Column({ nullable: false, unique: true })
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { IsOptional, IsString } from 'class-validator';
|
||||||
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';
|
||||||
|
|
||||||
@@ -6,7 +7,7 @@ import { Column, Entity, Index, PrimaryGeneratedColumn } from 'typeorm';
|
|||||||
@Entity()
|
@Entity()
|
||||||
export class EUserBackend extends EUser {
|
export class EUserBackend extends EUser {
|
||||||
@PrimaryGeneratedColumn("uuid")
|
@PrimaryGeneratedColumn("uuid")
|
||||||
override id: string;
|
override id?: string;
|
||||||
|
|
||||||
@Index()
|
@Index()
|
||||||
@Column({ nullable: false, unique: true })
|
@Column({ nullable: false, unique: true })
|
||||||
@@ -16,5 +17,9 @@ export class EUserBackend extends EUser {
|
|||||||
override roles: string[];
|
override roles: string[];
|
||||||
|
|
||||||
@Column({ nullable: false, select: false })
|
@Column({ nullable: false, select: false })
|
||||||
override password?: string;
|
@IsOptional()
|
||||||
|
@IsString()
|
||||||
|
// @ts-ignore
|
||||||
|
override hashedPassword?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
14
backend/src/models/transformers/image.transformer.ts
Normal file
14
backend/src/models/transformers/image.transformer.ts
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import { EImage } from 'picsur-shared/dist/entities/image.entity';
|
||||||
|
import { EImageBackend } from '../entities/image.entity';
|
||||||
|
|
||||||
|
export function EImageBackend2EImage(
|
||||||
|
eImage: EImageBackend,
|
||||||
|
): EImage {
|
||||||
|
if (eImage.data === undefined)
|
||||||
|
return eImage as EImage;
|
||||||
|
|
||||||
|
return {
|
||||||
|
...eImage,
|
||||||
|
data: undefined,
|
||||||
|
};
|
||||||
|
}
|
||||||
14
backend/src/models/transformers/user.transformer.ts
Normal file
14
backend/src/models/transformers/user.transformer.ts
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import { EUser } from 'picsur-shared/dist/entities/user.entity';
|
||||||
|
import { EUserBackend } from '../entities/user.entity';
|
||||||
|
|
||||||
|
export function EUserBackend2EUser(
|
||||||
|
eUser: EUserBackend,
|
||||||
|
): EUser {
|
||||||
|
if (eUser.hashedPassword === undefined)
|
||||||
|
return eUser as EUser;
|
||||||
|
|
||||||
|
return {
|
||||||
|
...eUser,
|
||||||
|
hashedPassword: undefined,
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -24,6 +24,7 @@ import {
|
|||||||
import { AuthManagerService } from '../../../managers/auth/auth.service';
|
import { AuthManagerService } from '../../../managers/auth/auth.service';
|
||||||
import { Permission } from '../../../models/dto/permissions.dto';
|
import { Permission } from '../../../models/dto/permissions.dto';
|
||||||
import AuthFasityRequest from '../../../models/requests/authrequest.dto';
|
import AuthFasityRequest from '../../../models/requests/authrequest.dto';
|
||||||
|
import { EUserBackend2EUser } from '../../../models/transformers/user.transformer';
|
||||||
|
|
||||||
@Controller('api/user')
|
@Controller('api/user')
|
||||||
export class UserController {
|
export class UserController {
|
||||||
@@ -60,12 +61,14 @@ export class UserController {
|
|||||||
throw new InternalServerErrorException('Could not register user');
|
throw new InternalServerErrorException('Could not register user');
|
||||||
}
|
}
|
||||||
|
|
||||||
return user;
|
return EUserBackend2EUser(user);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get('me')
|
@Get('me')
|
||||||
@RequiredPermissions(Permission.UserKeepLogin)
|
@RequiredPermissions(Permission.UserKeepLogin)
|
||||||
async me(@Request() req: AuthFasityRequest): Promise<UserMeResponse> {
|
async me(@Request() req: AuthFasityRequest): Promise<UserMeResponse> {
|
||||||
|
if (!req.user.id) throw new InternalServerErrorException('User is corrupt');
|
||||||
|
|
||||||
const user = await this.usersService.findOne(req.user.id);
|
const user = await this.usersService.findOne(req.user.id);
|
||||||
|
|
||||||
if (HasFailed(user)) {
|
if (HasFailed(user)) {
|
||||||
@@ -79,7 +82,7 @@ export class UserController {
|
|||||||
throw new InternalServerErrorException('Could not get new token');
|
throw new InternalServerErrorException('Could not get new token');
|
||||||
}
|
}
|
||||||
|
|
||||||
return { user, token };
|
return { user: EUserBackend2EUser(user), token };
|
||||||
}
|
}
|
||||||
|
|
||||||
// You can always check your permissions
|
// You can always check your permissions
|
||||||
@@ -88,6 +91,8 @@ export class UserController {
|
|||||||
async refresh(
|
async refresh(
|
||||||
@Request() req: AuthFasityRequest,
|
@Request() req: AuthFasityRequest,
|
||||||
): Promise<UserMePermissionsResponse> {
|
): Promise<UserMePermissionsResponse> {
|
||||||
|
if (!req.user.id) throw new InternalServerErrorException('User is corrupt');
|
||||||
|
|
||||||
const permissions = await this.usersService.getPermissions(req.user.id);
|
const permissions = await this.usersService.getPermissions(req.user.id);
|
||||||
if (HasFailed(permissions)) {
|
if (HasFailed(permissions)) {
|
||||||
this.logger.warn(permissions.getReason());
|
this.logger.warn(permissions.getReason());
|
||||||
|
|||||||
@@ -24,7 +24,12 @@ import { HasFailed } from 'picsur-shared/dist/types';
|
|||||||
import { UsersService } from '../../../collections/userdb/userdb.service';
|
import { UsersService } from '../../../collections/userdb/userdb.service';
|
||||||
import { RequiredPermissions } from '../../../decorators/permissions.decorator';
|
import { RequiredPermissions } from '../../../decorators/permissions.decorator';
|
||||||
import { Permission } from '../../../models/dto/permissions.dto';
|
import { Permission } from '../../../models/dto/permissions.dto';
|
||||||
import { ImmutableUsersList, LockedLoginUsersList, UndeletableUsersList } from '../../../models/dto/specialusers.dto';
|
import {
|
||||||
|
ImmutableUsersList,
|
||||||
|
LockedLoginUsersList,
|
||||||
|
UndeletableUsersList
|
||||||
|
} from '../../../models/dto/specialusers.dto';
|
||||||
|
import { EUserBackend2EUser } from '../../../models/transformers/user.transformer';
|
||||||
|
|
||||||
@Controller('api/user')
|
@Controller('api/user')
|
||||||
@RequiredPermissions(Permission.UserManage)
|
@RequiredPermissions(Permission.UserManage)
|
||||||
@@ -53,7 +58,7 @@ export class UserManageController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
users,
|
users: users.map(EUserBackend2EUser),
|
||||||
count: users.length,
|
count: users.length,
|
||||||
page: body.page,
|
page: body.page,
|
||||||
};
|
};
|
||||||
@@ -73,20 +78,18 @@ export class UserManageController {
|
|||||||
throw new InternalServerErrorException('Could not create user');
|
throw new InternalServerErrorException('Could not create user');
|
||||||
}
|
}
|
||||||
|
|
||||||
return user;
|
return EUserBackend2EUser(user);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Post('delete')
|
@Post('delete')
|
||||||
async delete(
|
async delete(@Body() body: UserDeleteRequest): Promise<UserDeleteResponse> {
|
||||||
@Body() body: UserDeleteRequest,
|
|
||||||
): Promise<UserDeleteResponse> {
|
|
||||||
const user = await this.usersService.delete(body.id);
|
const user = await this.usersService.delete(body.id);
|
||||||
if (HasFailed(user)) {
|
if (HasFailed(user)) {
|
||||||
this.logger.warn(user.getReason());
|
this.logger.warn(user.getReason());
|
||||||
throw new InternalServerErrorException('Could not delete user');
|
throw new InternalServerErrorException('Could not delete user');
|
||||||
}
|
}
|
||||||
|
|
||||||
return user;
|
return EUserBackend2EUser(user);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Post('info')
|
@Post('info')
|
||||||
@@ -97,7 +100,7 @@ export class UserManageController {
|
|||||||
throw new InternalServerErrorException('Could not find user');
|
throw new InternalServerErrorException('Could not find user');
|
||||||
}
|
}
|
||||||
|
|
||||||
return user;
|
return EUserBackend2EUser(user);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Post('update')
|
@Post('update')
|
||||||
@@ -111,7 +114,7 @@ export class UserManageController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (body.roles) {
|
if (body.roles) {
|
||||||
user = await this.usersService.setRoles(user.id, body.roles);
|
user = await this.usersService.setRoles(body.id, body.roles);
|
||||||
if (HasFailed(user)) {
|
if (HasFailed(user)) {
|
||||||
this.logger.warn(user.getReason());
|
this.logger.warn(user.getReason());
|
||||||
throw new InternalServerErrorException('Could not update user');
|
throw new InternalServerErrorException('Could not update user');
|
||||||
@@ -119,14 +122,14 @@ export class UserManageController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (body.password) {
|
if (body.password) {
|
||||||
user = await this.usersService.updatePassword(user.id, body.password);
|
user = await this.usersService.updatePassword(body.id, body.password);
|
||||||
if (HasFailed(user)) {
|
if (HasFailed(user)) {
|
||||||
this.logger.warn(user.getReason());
|
this.logger.warn(user.getReason());
|
||||||
throw new InternalServerErrorException('Could not update user');
|
throw new InternalServerErrorException('Could not update user');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return user;
|
return EUserBackend2EUser(user);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get('special')
|
@Get('special')
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
import {
|
import {
|
||||||
Controller,
|
Controller,
|
||||||
Get,
|
Get,
|
||||||
InternalServerErrorException,
|
InternalServerErrorException,
|
||||||
Logger,
|
Logger,
|
||||||
NotFoundException,
|
NotFoundException,
|
||||||
Param,
|
Param,
|
||||||
Post, Res
|
Post,
|
||||||
|
Res
|
||||||
} from '@nestjs/common';
|
} from '@nestjs/common';
|
||||||
import { FastifyReply } from 'fastify';
|
import { FastifyReply } from 'fastify';
|
||||||
import { ImageMetaResponse } from 'picsur-shared/dist/dto/api/image.dto';
|
import { ImageMetaResponse } from 'picsur-shared/dist/dto/api/image.dto';
|
||||||
@@ -15,6 +16,7 @@ import { RequiredPermissions } from '../../decorators/permissions.decorator';
|
|||||||
import { ImageManagerService } from '../../managers/imagemanager/imagemanager.service';
|
import { ImageManagerService } from '../../managers/imagemanager/imagemanager.service';
|
||||||
import { Permission } from '../../models/dto/permissions.dto';
|
import { Permission } from '../../models/dto/permissions.dto';
|
||||||
import { ImageUploadDto } from '../../models/requests/imageroute.dto';
|
import { ImageUploadDto } from '../../models/requests/imageroute.dto';
|
||||||
|
import { EImageBackend2EImage } from '../../models/transformers/image.transformer';
|
||||||
import { ImageIdValidator } from './imageid.validator';
|
import { ImageIdValidator } from './imageid.validator';
|
||||||
|
|
||||||
// This is the only controller with CORS enabled
|
// This is the only controller with CORS enabled
|
||||||
@@ -52,7 +54,7 @@ export class ImageController {
|
|||||||
throw new NotFoundException('Could not find image');
|
throw new NotFoundException('Could not find image');
|
||||||
}
|
}
|
||||||
|
|
||||||
return image;
|
return EImageBackend2EImage(image);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Post()
|
@Post()
|
||||||
@@ -67,6 +69,6 @@ export class ImageController {
|
|||||||
throw new InternalServerErrorException('Could not upload image');
|
throw new InternalServerErrorException('Could not upload image');
|
||||||
}
|
}
|
||||||
|
|
||||||
return image;
|
return EImageBackend2EImage(image);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Component } from '@angular/core';
|
import { Component } from '@angular/core';
|
||||||
import { SysPreferenceBaseResponse } from 'picsur-shared/dist/dto/api/syspref.dto';
|
import { DecodedSysPref } from 'picsur-shared/dist/dto/preferences.dto';
|
||||||
import { Observable } from 'rxjs';
|
import { Observable } from 'rxjs';
|
||||||
import { SysprefService as SysPrefService } from 'src/app/services/api/syspref.service';
|
import { SysprefService as SysPrefService } from 'src/app/services/api/syspref.service';
|
||||||
|
|
||||||
@@ -7,7 +7,7 @@ import { SysprefService as SysPrefService } from 'src/app/services/api/syspref.s
|
|||||||
templateUrl: './settings-syspref.component.html',
|
templateUrl: './settings-syspref.component.html',
|
||||||
})
|
})
|
||||||
export class SettingsSysprefComponent {
|
export class SettingsSysprefComponent {
|
||||||
preferences: Observable<SysPreferenceBaseResponse[]>;
|
preferences: Observable<DecodedSysPref[]>;
|
||||||
|
|
||||||
constructor(sysprefService: SysPrefService) {
|
constructor(sysprefService: SysPrefService) {
|
||||||
this.preferences = sysprefService.live;
|
this.preferences = sysprefService.live;
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import { Component, Input, OnInit } from '@angular/core';
|
import { Component, Input, OnInit } from '@angular/core';
|
||||||
import { AutoUnsubscribe } from 'ngx-auto-unsubscribe-decorator';
|
import { AutoUnsubscribe } from 'ngx-auto-unsubscribe-decorator';
|
||||||
import { SysPreferenceBaseResponse } from 'picsur-shared/dist/dto/api/syspref.dto';
|
import { DecodedSysPref, PrefValueType } from 'picsur-shared/dist/dto/preferences.dto';
|
||||||
import { PrefValueType } from 'picsur-shared/dist/dto/preferences.dto';
|
|
||||||
import { SysPreference } from 'picsur-shared/dist/dto/syspreferences.dto';
|
import { SysPreference } from 'picsur-shared/dist/dto/syspreferences.dto';
|
||||||
import { HasFailed } from 'picsur-shared/dist/types';
|
import { HasFailed } from 'picsur-shared/dist/types';
|
||||||
import { Subject, throttleTime } from 'rxjs';
|
import { Subject, throttleTime } from 'rxjs';
|
||||||
@@ -16,7 +15,7 @@ import { UtilService } from 'src/app/util/util.service';
|
|||||||
styleUrls: ['./settings-syspref-option.component.scss'],
|
styleUrls: ['./settings-syspref-option.component.scss'],
|
||||||
})
|
})
|
||||||
export class SettingsSysprefOptionComponent implements OnInit {
|
export class SettingsSysprefOptionComponent implements OnInit {
|
||||||
@Input() pref: SysPreferenceBaseResponse;
|
@Input() pref: DecodedSysPref;
|
||||||
|
|
||||||
private updateSubject = new Subject<PrefValueType>();
|
private updateSubject = new Subject<PrefValueType>();
|
||||||
|
|
||||||
|
|||||||
@@ -73,7 +73,7 @@ export class SettingsUsersComponent implements OnInit {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (pressedButton === 'delete') {
|
if (pressedButton === 'delete') {
|
||||||
const result = await this.userManageService.deleteUser(user.id);
|
const result = await this.userManageService.deleteUser(user.id ?? '');
|
||||||
if (HasFailed(result)) {
|
if (HasFailed(result)) {
|
||||||
this.utilService.showSnackBar(
|
this.utilService.showSnackBar(
|
||||||
'Failed to delete user',
|
'Failed to delete user',
|
||||||
|
|||||||
@@ -3,12 +3,11 @@ import { AutoUnsubscribe } from 'ngx-auto-unsubscribe-decorator';
|
|||||||
import {
|
import {
|
||||||
GetSyspreferenceResponse,
|
GetSyspreferenceResponse,
|
||||||
MultipleSysPreferencesResponse,
|
MultipleSysPreferencesResponse,
|
||||||
SysPreferenceBaseResponse,
|
|
||||||
UpdateSysPreferenceRequest,
|
UpdateSysPreferenceRequest,
|
||||||
UpdateSysPreferenceResponse
|
UpdateSysPreferenceResponse
|
||||||
} from 'picsur-shared/dist/dto/api/syspref.dto';
|
} from 'picsur-shared/dist/dto/api/syspref.dto';
|
||||||
import { Permission } from 'picsur-shared/dist/dto/permissions.dto';
|
import { Permission } from 'picsur-shared/dist/dto/permissions.dto';
|
||||||
import { PrefValueType } from 'picsur-shared/dist/dto/preferences.dto';
|
import { DecodedSysPref, PrefValueType } from 'picsur-shared/dist/dto/preferences.dto';
|
||||||
import { AsyncFailable, Fail, HasFailed, Map } from 'picsur-shared/dist/types';
|
import { AsyncFailable, Fail, HasFailed, Map } from 'picsur-shared/dist/types';
|
||||||
import { BehaviorSubject } from 'rxjs';
|
import { BehaviorSubject } from 'rxjs';
|
||||||
import { SnackBarType } from 'src/app/models/dto/snack-bar-type.dto';
|
import { SnackBarType } from 'src/app/models/dto/snack-bar-type.dto';
|
||||||
@@ -25,7 +24,7 @@ export class SysprefService {
|
|||||||
|
|
||||||
private hasPermission = false;
|
private hasPermission = false;
|
||||||
|
|
||||||
private sysprefObservable = new BehaviorSubject<SysPreferenceBaseResponse[]>(
|
private sysprefObservable = new BehaviorSubject<DecodedSysPref[]>(
|
||||||
[]
|
[]
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -57,7 +56,7 @@ export class SysprefService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getPreferences(): AsyncFailable<SysPreferenceBaseResponse[]> {
|
public async getPreferences(): AsyncFailable<DecodedSysPref[]> {
|
||||||
if (!this.hasPermission)
|
if (!this.hasPermission)
|
||||||
return Fail('You do not have permission to edit system preferences');
|
return Fail('You do not have permission to edit system preferences');
|
||||||
|
|
||||||
@@ -105,7 +104,7 @@ export class SysprefService {
|
|||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
private updatePrefArray(pref: SysPreferenceBaseResponse) {
|
private updatePrefArray(pref: DecodedSysPref) {
|
||||||
const prefArray = this.snapshot;
|
const prefArray = this.snapshot;
|
||||||
// Replace the old pref with the new one
|
// Replace the old pref with the new one
|
||||||
const index = prefArray.findIndex((i) => pref.key === i.key);
|
const index = prefArray.findIndex((i) => pref.key === i.key);
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
import {
|
import {
|
||||||
IsBoolean,
|
IsBoolean, IsInt,
|
||||||
IsDefined,
|
|
||||||
IsInt,
|
|
||||||
IsNotEmpty,
|
IsNotEmpty,
|
||||||
IsString,
|
IsString,
|
||||||
Max,
|
Max,
|
||||||
@@ -10,21 +8,17 @@ import {
|
|||||||
|
|
||||||
class BaseApiResponse<T extends Object, W extends boolean> {
|
class BaseApiResponse<T extends Object, W extends boolean> {
|
||||||
@IsBoolean()
|
@IsBoolean()
|
||||||
@IsDefined()
|
|
||||||
success: W;
|
success: W;
|
||||||
|
|
||||||
@IsInt()
|
@IsInt()
|
||||||
@Min(0)
|
@Min(0)
|
||||||
@Max(1000)
|
@Max(1000)
|
||||||
@IsDefined()
|
|
||||||
statusCode: number;
|
statusCode: number;
|
||||||
|
|
||||||
@IsString()
|
@IsString()
|
||||||
@IsNotEmpty()
|
|
||||||
timestamp: string;
|
timestamp: string;
|
||||||
|
|
||||||
//@ValidateNested()
|
@IsNotEmpty()
|
||||||
@IsDefined()
|
|
||||||
data: T;
|
data: T;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -35,7 +29,6 @@ export class ApiSuccessResponse<T extends Object> extends BaseApiResponse<
|
|||||||
|
|
||||||
export class ApiErrorData {
|
export class ApiErrorData {
|
||||||
@IsString()
|
@IsString()
|
||||||
@IsNotEmpty()
|
|
||||||
message: string;
|
message: string;
|
||||||
}
|
}
|
||||||
export class ApiErrorResponse extends BaseApiResponse<ApiErrorData, false> {}
|
export class ApiErrorResponse extends BaseApiResponse<ApiErrorData, false> {}
|
||||||
|
|||||||
@@ -1,24 +1,19 @@
|
|||||||
import { IsBoolean, IsDefined, IsSemVer, IsString } from 'class-validator';
|
import { IsBoolean, IsSemVer } from 'class-validator';
|
||||||
import { IsStringList } from '../../validators/string-list.validator';
|
import { IsStringList } from '../../validators/string-list.validator';
|
||||||
|
|
||||||
export class InfoResponse {
|
export class InfoResponse {
|
||||||
@IsBoolean()
|
@IsBoolean()
|
||||||
@IsDefined()
|
|
||||||
production: boolean;
|
production: boolean;
|
||||||
|
|
||||||
@IsBoolean()
|
@IsBoolean()
|
||||||
@IsDefined()
|
|
||||||
demo: boolean;
|
demo: boolean;
|
||||||
|
|
||||||
@IsDefined()
|
|
||||||
@IsString()
|
|
||||||
@IsSemVer()
|
@IsSemVer()
|
||||||
version: string;
|
version: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
// AllPermissions
|
// AllPermissions
|
||||||
export class AllPermissionsResponse {
|
export class AllPermissionsResponse {
|
||||||
@IsDefined()
|
|
||||||
@IsStringList()
|
@IsStringList()
|
||||||
permissions: string[];
|
permissions: string[];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,23 +1,21 @@
|
|||||||
import { Type } from 'class-transformer';
|
import { IsArray } from 'class-validator';
|
||||||
import { IsArray, IsDefined, ValidateNested } from 'class-validator';
|
import { ERole, SimpleRole } from '../../entities/role.entity';
|
||||||
import {
|
import { IsNested } from '../../validators/nested.validator';
|
||||||
ERole,
|
|
||||||
RoleNameObject,
|
|
||||||
RoleNamePermsObject
|
|
||||||
} from '../../entities/role.entity';
|
|
||||||
import { IsPosInt } from '../../validators/positive-int.validator';
|
import { IsPosInt } from '../../validators/positive-int.validator';
|
||||||
|
import { IsRoleName } from '../../validators/role.validators';
|
||||||
import { IsStringList } from '../../validators/string-list.validator';
|
import { IsStringList } from '../../validators/string-list.validator';
|
||||||
|
|
||||||
// RoleInfo
|
// RoleInfo
|
||||||
export class RoleInfoRequest extends RoleNameObject {}
|
export class RoleInfoRequest {
|
||||||
|
@IsRoleName()
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
export class RoleInfoResponse extends ERole {}
|
export class RoleInfoResponse extends ERole {}
|
||||||
|
|
||||||
// RoleList
|
// RoleList
|
||||||
export class RoleListResponse {
|
export class RoleListResponse {
|
||||||
@IsArray()
|
@IsArray()
|
||||||
@IsDefined()
|
@IsNested(ERole)
|
||||||
@ValidateNested()
|
|
||||||
@Type(() => ERole)
|
|
||||||
roles: ERole[];
|
roles: ERole[];
|
||||||
|
|
||||||
@IsPosInt()
|
@IsPosInt()
|
||||||
@@ -25,32 +23,31 @@ export class RoleListResponse {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// RoleUpdate
|
// RoleUpdate
|
||||||
export class RoleUpdateRequest extends RoleNamePermsObject {}
|
export class RoleUpdateRequest extends SimpleRole {}
|
||||||
export class RoleUpdateResponse extends ERole {}
|
export class RoleUpdateResponse extends ERole {}
|
||||||
|
|
||||||
// RoleCreate
|
// RoleCreate
|
||||||
export class RoleCreateRequest extends RoleNamePermsObject {}
|
export class RoleCreateRequest extends SimpleRole {}
|
||||||
export class RoleCreateResponse extends ERole {}
|
export class RoleCreateResponse extends ERole {}
|
||||||
|
|
||||||
// RoleDelete
|
// RoleDelete
|
||||||
export class RoleDeleteRequest extends RoleNameObject {}
|
export class RoleDeleteRequest {
|
||||||
|
@IsRoleName()
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
export class RoleDeleteResponse extends ERole {}
|
export class RoleDeleteResponse extends ERole {}
|
||||||
|
|
||||||
// SpecialRoles
|
// SpecialRoles
|
||||||
export class SpecialRolesResponse {
|
export class SpecialRolesResponse {
|
||||||
@IsDefined()
|
|
||||||
@IsStringList()
|
@IsStringList()
|
||||||
SoulBoundRoles: string[];
|
SoulBoundRoles: string[];
|
||||||
|
|
||||||
@IsDefined()
|
|
||||||
@IsStringList()
|
@IsStringList()
|
||||||
ImmutableRoles: string[];
|
ImmutableRoles: string[];
|
||||||
|
|
||||||
@IsDefined()
|
|
||||||
@IsStringList()
|
@IsStringList()
|
||||||
UndeletableRoles: string[];
|
UndeletableRoles: string[];
|
||||||
|
|
||||||
@IsDefined()
|
|
||||||
@IsStringList()
|
@IsStringList()
|
||||||
DefaultRoles: string[];
|
DefaultRoles: string[];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,36 +1,21 @@
|
|||||||
import { Type } from 'class-transformer';
|
|
||||||
import {
|
import {
|
||||||
IsArray, IsEnum, IsNotEmpty, IsString, ValidateNested
|
IsArray
|
||||||
} from 'class-validator';
|
} from 'class-validator';
|
||||||
|
import { IsNested } from '../../validators/nested.validator';
|
||||||
import { IsPosInt } from '../../validators/positive-int.validator';
|
import { IsPosInt } from '../../validators/positive-int.validator';
|
||||||
import { IsSysPrefValue } from '../../validators/syspref.validator';
|
import { IsPrefValue } from '../../validators/pref-value.validator';
|
||||||
import { PrefValueType, PrefValueTypes, PrefValueTypeStrings } from '../preferences.dto';
|
import { DecodedSysPref, PrefValueType } from '../preferences.dto';
|
||||||
|
|
||||||
export class SysPreferenceBaseResponse {
|
|
||||||
@IsNotEmpty()
|
|
||||||
@IsString()
|
|
||||||
key: string;
|
|
||||||
|
|
||||||
@IsNotEmpty()
|
|
||||||
@IsSysPrefValue()
|
|
||||||
value: PrefValueType;
|
|
||||||
|
|
||||||
@IsNotEmpty()
|
|
||||||
@IsEnum(PrefValueTypes)
|
|
||||||
type: PrefValueTypeStrings;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get Syspreference
|
// Get Syspreference
|
||||||
// Request is done via url parameters
|
// Request is done via url parameters
|
||||||
export class GetSyspreferenceResponse extends SysPreferenceBaseResponse {}
|
export class GetSyspreferenceResponse extends DecodedSysPref {}
|
||||||
|
|
||||||
// Get syspreferences
|
// Get syspreferences
|
||||||
export class MultipleSysPreferencesResponse {
|
export class MultipleSysPreferencesResponse {
|
||||||
@IsArray()
|
@IsArray()
|
||||||
@IsNotEmpty()
|
@IsNested(DecodedSysPref)
|
||||||
@ValidateNested({ each: true })
|
preferences: DecodedSysPref[];
|
||||||
@Type(() => SysPreferenceBaseResponse)
|
|
||||||
preferences: SysPreferenceBaseResponse[];
|
|
||||||
|
|
||||||
@IsPosInt()
|
@IsPosInt()
|
||||||
total: number;
|
total: number;
|
||||||
@@ -38,10 +23,9 @@ export class MultipleSysPreferencesResponse {
|
|||||||
|
|
||||||
// Update Syspreference
|
// Update Syspreference
|
||||||
export class UpdateSysPreferenceRequest {
|
export class UpdateSysPreferenceRequest {
|
||||||
@IsNotEmpty()
|
@IsPrefValue()
|
||||||
@IsSysPrefValue()
|
|
||||||
value: PrefValueType;
|
value: PrefValueType;
|
||||||
}
|
}
|
||||||
export class UpdateSysPreferenceResponse extends SysPreferenceBaseResponse {}
|
export class UpdateSysPreferenceResponse extends DecodedSysPref {}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,45 +1,47 @@
|
|||||||
import { Type } from 'class-transformer';
|
|
||||||
import {
|
import {
|
||||||
IsDefined, IsJWT,
|
IsJWT
|
||||||
IsString,
|
|
||||||
ValidateNested
|
|
||||||
} from 'class-validator';
|
} from 'class-validator';
|
||||||
import { EUser, NamePassUser } from '../../entities/user.entity';
|
import { EUser } from '../../entities/user.entity';
|
||||||
|
import { IsNested } from '../../validators/nested.validator';
|
||||||
import { IsStringList } from '../../validators/string-list.validator';
|
import { IsStringList } from '../../validators/string-list.validator';
|
||||||
|
import { IsPlainTextPwd, IsUsername } from '../../validators/user.validators';
|
||||||
|
|
||||||
// Api
|
// Api
|
||||||
|
|
||||||
// UserLogin
|
// UserLogin
|
||||||
export class UserLoginRequest extends NamePassUser {}
|
export class UserLoginRequest {
|
||||||
|
@IsUsername()
|
||||||
|
username: string;
|
||||||
|
|
||||||
|
@IsPlainTextPwd()
|
||||||
|
password: string;
|
||||||
|
}
|
||||||
export class UserLoginResponse {
|
export class UserLoginResponse {
|
||||||
@IsString()
|
|
||||||
@IsDefined()
|
|
||||||
@IsJWT()
|
@IsJWT()
|
||||||
jwt_token: string;
|
jwt_token: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
// UserRegister
|
// UserRegister
|
||||||
export class UserRegisterRequest extends NamePassUser {}
|
export class UserRegisterRequest {
|
||||||
|
@IsUsername()
|
||||||
|
username: string;
|
||||||
|
|
||||||
|
@IsPlainTextPwd()
|
||||||
|
password: string;
|
||||||
|
}
|
||||||
export class UserRegisterResponse extends EUser {}
|
export class UserRegisterResponse extends EUser {}
|
||||||
|
|
||||||
// UserMe
|
// UserMe
|
||||||
export class UserMeResponse {
|
export class UserMeResponse {
|
||||||
@IsDefined()
|
@IsNested(EUser)
|
||||||
@ValidateNested()
|
|
||||||
@Type(() => EUser)
|
|
||||||
user: EUser;
|
user: EUser;
|
||||||
|
|
||||||
@IsString()
|
|
||||||
@IsDefined()
|
|
||||||
@IsJWT()
|
@IsJWT()
|
||||||
token: string;
|
token: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
// UserMePermissions
|
// UserMePermissions
|
||||||
export class UserMePermissionsResponse {
|
export class UserMePermissionsResponse {
|
||||||
@IsDefined()
|
|
||||||
@IsStringList()
|
@IsStringList()
|
||||||
permissions: string[];
|
permissions: string[];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +1,10 @@
|
|||||||
import { Type } from 'class-transformer';
|
import { IsArray, IsOptional } from 'class-validator';
|
||||||
import {
|
import { EUser, SimpleUser } from '../../entities/user.entity';
|
||||||
IsArray,
|
import { Newable } from '../../types';
|
||||||
IsDefined,
|
import { IsEntityID } from '../../validators/entity-id.validator';
|
||||||
IsOptional,
|
import { IsNested } from '../../validators/nested.validator';
|
||||||
ValidateNested
|
|
||||||
} from 'class-validator';
|
|
||||||
import { EUser, NamePassUser } 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 { IsStringList } from '../../validators/string-list.validator';
|
||||||
import { IsPlainTextPwd, IsUsername } from '../../validators/user.validators';
|
|
||||||
import { EntityIDObject } from '../idobject.dto';
|
import { EntityIDObject } from '../idobject.dto';
|
||||||
|
|
||||||
// UserList
|
// UserList
|
||||||
@@ -22,9 +18,7 @@ export class UserListRequest {
|
|||||||
|
|
||||||
export class UserListResponse {
|
export class UserListResponse {
|
||||||
@IsArray()
|
@IsArray()
|
||||||
@IsDefined()
|
@IsNested(EUser)
|
||||||
@ValidateNested()
|
|
||||||
@Type(() => EUser)
|
|
||||||
users: EUser[];
|
users: EUser[];
|
||||||
|
|
||||||
@IsPosInt()
|
@IsPosInt()
|
||||||
@@ -35,11 +29,7 @@ export class UserListResponse {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// UserCreate
|
// UserCreate
|
||||||
export class UserCreateRequest extends NamePassUser {
|
export class UserCreateRequest extends SimpleUser {}
|
||||||
@IsOptional()
|
|
||||||
@IsStringList()
|
|
||||||
roles?: string[];
|
|
||||||
}
|
|
||||||
export class UserCreateResponse extends EUser {}
|
export class UserCreateResponse extends EUser {}
|
||||||
|
|
||||||
// UserDelete
|
// UserDelete
|
||||||
@@ -51,33 +41,31 @@ export class UserInfoRequest extends EntityIDObject {}
|
|||||||
export class UserInfoResponse extends EUser {}
|
export class UserInfoResponse extends EUser {}
|
||||||
|
|
||||||
// UserUpdate
|
// UserUpdate
|
||||||
export class UserUpdateRequest extends EntityIDObject {
|
export class UserUpdateRequest extends (SimpleUser as Newable<
|
||||||
@IsOptional()
|
Partial<SimpleUser>
|
||||||
@IsUsername()
|
>) {
|
||||||
username?: string;
|
@IsEntityID()
|
||||||
|
id: string;
|
||||||
|
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
@IsStringList()
|
override username?: string;
|
||||||
roles?: string[];
|
|
||||||
|
|
||||||
@IsPlainTextPwd()
|
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
password?: string;
|
override password?: string;
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
override roles?: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export class UserUpdateResponse extends EUser {}
|
export class UserUpdateResponse extends EUser {}
|
||||||
|
|
||||||
// GetSpecialUsers
|
// GetSpecialUsers
|
||||||
export class GetSpecialUsersResponse {
|
export class GetSpecialUsersResponse {
|
||||||
@IsDefined()
|
|
||||||
@IsStringList()
|
@IsStringList()
|
||||||
UndeletableUsersList: string[];
|
UndeletableUsersList: string[];
|
||||||
|
|
||||||
@IsDefined()
|
|
||||||
@IsStringList()
|
@IsStringList()
|
||||||
ImmutableUsersList: string[];
|
ImmutableUsersList: string[];
|
||||||
|
|
||||||
@IsDefined()
|
|
||||||
@IsStringList()
|
@IsStringList()
|
||||||
LockedLoginUsersList: string[];
|
LockedLoginUsersList: string[];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { EntityID } from '../validators/entity-id.validator';
|
import { IsEntityID } from '../validators/entity-id.validator';
|
||||||
|
|
||||||
export class EntityIDObject {
|
export class EntityIDObject {
|
||||||
@EntityID()
|
@IsEntityID()
|
||||||
id: string;
|
id: string;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,9 @@
|
|||||||
import { Type } from 'class-transformer';
|
import { IsInt, IsOptional } from 'class-validator';
|
||||||
import { IsDefined, IsInt, IsOptional, ValidateNested } from 'class-validator';
|
|
||||||
import { EUser } from '../entities/user.entity';
|
import { EUser } from '../entities/user.entity';
|
||||||
|
import { IsNested } from '../validators/nested.validator';
|
||||||
|
|
||||||
export class JwtDataDto {
|
export class JwtDataDto {
|
||||||
@IsDefined()
|
@IsNested(EUser)
|
||||||
@ValidateNested()
|
|
||||||
@Type(() => EUser)
|
|
||||||
user: EUser;
|
user: EUser;
|
||||||
|
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
|
|||||||
@@ -1,4 +1,26 @@
|
|||||||
|
import { IsEnum, IsString } from 'class-validator';
|
||||||
|
import { IsEntityID } from '../validators/entity-id.validator';
|
||||||
|
import { IsPrefValue } from '../validators/pref-value.validator';
|
||||||
|
|
||||||
// Variable value type
|
// Variable value type
|
||||||
export type PrefValueType = string | number | boolean;
|
export type PrefValueType = string | number | boolean;
|
||||||
export type PrefValueTypeStrings = 'string' | 'number' | 'boolean';
|
export type PrefValueTypeStrings = 'string' | 'number' | 'boolean';
|
||||||
export const PrefValueTypes = ['string', 'number', 'boolean'];
|
export const PrefValueTypes = ['string', 'number', 'boolean'];
|
||||||
|
|
||||||
|
// Decoded Representations
|
||||||
|
|
||||||
|
export class DecodedSysPref {
|
||||||
|
@IsString()
|
||||||
|
key: string;
|
||||||
|
|
||||||
|
@IsPrefValue()
|
||||||
|
value: PrefValueType;
|
||||||
|
|
||||||
|
@IsEnum(PrefValueTypes)
|
||||||
|
type: PrefValueTypeStrings;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class DecodedUsrPref extends DecodedSysPref {
|
||||||
|
@IsEntityID()
|
||||||
|
user: string;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import { PrefValueType, PrefValueTypeStrings } from './preferences.dto';
|
|
||||||
|
|
||||||
// This enum is only here to make accessing the values easier, and type checking in the backend
|
// This enum is only here to make accessing the values easier, and type checking in the backend
|
||||||
export enum SysPreference {
|
export enum SysPreference {
|
||||||
@@ -9,10 +8,3 @@ export enum SysPreference {
|
|||||||
TestNumber = 'test_number',
|
TestNumber = 'test_number',
|
||||||
TestBoolean = 'test_boolean',
|
TestBoolean = 'test_boolean',
|
||||||
}
|
}
|
||||||
|
|
||||||
// Interfaces
|
|
||||||
export interface InternalSysPrefRepresentation {
|
|
||||||
key: string;
|
|
||||||
value: PrefValueType;
|
|
||||||
type: PrefValueTypeStrings;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import { PrefValueType, PrefValueTypeStrings } from './preferences.dto';
|
|
||||||
|
|
||||||
// This enum is only here to make accessing the values easier, and type checking in the backend
|
// This enum is only here to make accessing the values easier, and type checking in the backend
|
||||||
export enum UsrPreference {
|
export enum UsrPreference {
|
||||||
@@ -7,10 +6,3 @@ export enum UsrPreference {
|
|||||||
TestBoolean = 'test_boolean',
|
TestBoolean = 'test_boolean',
|
||||||
}
|
}
|
||||||
|
|
||||||
// Interfaces
|
|
||||||
export interface InternalUsrPrefRepresentation {
|
|
||||||
key: string;
|
|
||||||
value: PrefValueType;
|
|
||||||
type: PrefValueTypeStrings;
|
|
||||||
user: number;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,20 +1,19 @@
|
|||||||
import { Exclude } from 'class-transformer';
|
import { IsHash, IsOptional, IsString } from 'class-validator';
|
||||||
import { IsHash, IsNotEmpty, IsOptional, IsString } from 'class-validator';
|
import { IsEntityID } from '../validators/entity-id.validator';
|
||||||
import { EntityID } from '../validators/entity-id.validator';
|
import { IsNotDefined } from '../validators/not-defined.validator';
|
||||||
|
|
||||||
export class EImage {
|
export class EImage {
|
||||||
@EntityID()
|
@IsOptional()
|
||||||
id: string;
|
@IsEntityID()
|
||||||
|
id?: string;
|
||||||
|
|
||||||
@IsHash('sha256')
|
@IsHash('sha256')
|
||||||
hash: string;
|
hash: string;
|
||||||
|
|
||||||
// Binary data
|
// Because typescript does not support exact types, we have to do this stupidness
|
||||||
@IsOptional()
|
@IsNotDefined()
|
||||||
@Exclude() // Dont send this by default
|
data: undefined;
|
||||||
data?: object;
|
|
||||||
|
|
||||||
@IsNotEmpty()
|
|
||||||
@IsString()
|
@IsString()
|
||||||
mime: string;
|
mime: string;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,23 +1,24 @@
|
|||||||
import { IsDefined } from 'class-validator';
|
import { IsOptional } from 'class-validator';
|
||||||
import { EntityID } from '../validators/entity-id.validator';
|
import { IsEntityID } from '../validators/entity-id.validator';
|
||||||
import { IsRoleName } from '../validators/role.validators';
|
import { IsRoleName } from '../validators/role.validators';
|
||||||
import { IsStringList } from '../validators/string-list.validator';
|
import { IsStringList } from '../validators/string-list.validator';
|
||||||
|
|
||||||
// This entity is build from multiple smaller enitities
|
export class SimpleRole {
|
||||||
// Theses smaller entities are used in other places
|
|
||||||
|
|
||||||
export class RoleNameObject {
|
|
||||||
@IsRoleName()
|
@IsRoleName()
|
||||||
name: string;
|
name: string;
|
||||||
}
|
|
||||||
|
|
||||||
export class RoleNamePermsObject extends RoleNameObject {
|
|
||||||
@IsDefined()
|
|
||||||
@IsStringList()
|
@IsStringList()
|
||||||
permissions: string[];
|
permissions: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ERole extends RoleNamePermsObject {
|
export class ERole {
|
||||||
@EntityID()
|
@IsOptional()
|
||||||
id: string;
|
@IsEntityID()
|
||||||
|
id?: string;
|
||||||
|
|
||||||
|
@IsRoleName()
|
||||||
|
name: string;
|
||||||
|
|
||||||
|
@IsStringList()
|
||||||
|
permissions: string[];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,15 +1,14 @@
|
|||||||
import { IsNotEmpty, IsString } from 'class-validator';
|
import { IsOptional, IsString } from 'class-validator';
|
||||||
import { EntityIDOptional } from '../validators/entity-id.validator';
|
import { IsEntityID } from '../validators/entity-id.validator';
|
||||||
|
|
||||||
export class ESysPreference {
|
export class ESysPreference {
|
||||||
@EntityIDOptional()
|
@IsOptional()
|
||||||
|
@IsEntityID()
|
||||||
id?: string;
|
id?: string;
|
||||||
|
|
||||||
@IsNotEmpty()
|
|
||||||
@IsString()
|
@IsString()
|
||||||
key: string;
|
key: string;
|
||||||
|
|
||||||
@IsNotEmpty()
|
|
||||||
@IsString()
|
@IsString()
|
||||||
value: string;
|
value: string;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,37 +1,32 @@
|
|||||||
import { Exclude } from 'class-transformer';
|
import { IsOptional } from 'class-validator';
|
||||||
import { IsDefined, IsOptional, IsString } from 'class-validator';
|
import { IsEntityID } from '../validators/entity-id.validator';
|
||||||
import { EntityID } from '../validators/entity-id.validator';
|
import { IsNotDefined } from '../validators/not-defined.validator';
|
||||||
import { IsStringList } from '../validators/string-list.validator';
|
import { IsStringList } from '../validators/string-list.validator';
|
||||||
import { IsPlainTextPwd, IsUsername } from '../validators/user.validators';
|
import { IsPlainTextPwd, IsUsername } from '../validators/user.validators';
|
||||||
|
|
||||||
// This entity is build from multiple smaller enitities
|
export class SimpleUser {
|
||||||
// Theses smaller entities are used in other places
|
|
||||||
|
|
||||||
export class UsernameUser {
|
|
||||||
@IsUsername()
|
@IsUsername()
|
||||||
username: string;
|
username: string;
|
||||||
}
|
|
||||||
|
|
||||||
// This is a simple user object with just the username and unhashed password
|
|
||||||
export class NamePassUser extends UsernameUser {
|
|
||||||
@IsPlainTextPwd()
|
@IsPlainTextPwd()
|
||||||
password: string;
|
password: string;
|
||||||
}
|
|
||||||
|
|
||||||
// Add a user object with just the username and roles for jwt
|
|
||||||
export class NameRolesUser extends UsernameUser {
|
|
||||||
@IsDefined()
|
|
||||||
@IsStringList()
|
@IsStringList()
|
||||||
roles: string[];
|
roles: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Actual entity that goes in the db
|
export class EUser {
|
||||||
export class EUser extends NameRolesUser {
|
|
||||||
@EntityID()
|
|
||||||
id: string;
|
|
||||||
|
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
@Exclude()
|
@IsEntityID()
|
||||||
@IsString()
|
id?: string;
|
||||||
password?: string;
|
|
||||||
|
@IsUsername()
|
||||||
|
username: string;
|
||||||
|
|
||||||
|
@IsStringList()
|
||||||
|
roles: string[];
|
||||||
|
|
||||||
|
// Because typescript does not support exact types, we have to do this stupidness
|
||||||
|
@IsNotDefined()
|
||||||
|
hashedPassword: undefined;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,20 +1,18 @@
|
|||||||
import { IsDefined, IsNotEmpty, IsString } from 'class-validator';
|
import { IsOptional, IsString } from 'class-validator';
|
||||||
import { EntityIDOptional } from '../validators/entity-id.validator';
|
import { IsEntityID } from '../validators/entity-id.validator';
|
||||||
import { IsPosInt } from '../validators/positive-int.validator';
|
import { IsPosInt } from '../validators/positive-int.validator';
|
||||||
|
|
||||||
export class EUsrPreference {
|
export class EUsrPreference {
|
||||||
@EntityIDOptional()
|
@IsOptional()
|
||||||
|
@IsEntityID()
|
||||||
id?: string;
|
id?: string;
|
||||||
|
|
||||||
@IsNotEmpty()
|
|
||||||
@IsString()
|
@IsString()
|
||||||
key: string;
|
key: string;
|
||||||
|
|
||||||
@IsNotEmpty()
|
|
||||||
@IsString()
|
@IsString()
|
||||||
value: string;
|
value: string;
|
||||||
|
|
||||||
@IsDefined()
|
|
||||||
@IsPosInt()
|
@IsPosInt()
|
||||||
userId: number;
|
userId: number;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ type FCDecorator = MethodDecorator & ClassDecorator;
|
|||||||
export function CombineFCDecorators(...decorators: FCDecorator[]) {
|
export function CombineFCDecorators(...decorators: FCDecorator[]) {
|
||||||
return (target: any, key: string, descriptor: PropertyDescriptor) => {
|
return (target: any, key: string, descriptor: PropertyDescriptor) => {
|
||||||
decorators.forEach(decorator => {
|
decorators.forEach(decorator => {
|
||||||
decorator(target, key, descriptor);
|
decorator(target, key, descriptor as any);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
import { validate } from 'class-validator';
|
import { validate, ValidatorOptions } from 'class-validator';
|
||||||
|
|
||||||
// For some stupid reason, the class-validator library does not have a way to set global defaults
|
// For some stupid reason, the class-validator library does not have a way to set global defaults
|
||||||
// So now we have to do it this way
|
// So now we have to do it this way
|
||||||
|
|
||||||
export const ValidateOptions = {
|
export const ValidateOptions: ValidatorOptions = {
|
||||||
disableErrorMessages: true,
|
|
||||||
forbidNonWhitelisted: true,
|
forbidNonWhitelisted: true,
|
||||||
forbidUnknownValues: true,
|
forbidUnknownValues: true,
|
||||||
stopAtFirstError: true,
|
stopAtFirstError: true,
|
||||||
whitelist: true,
|
whitelist: true,
|
||||||
|
strictGroups: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const strictValidate = (object: object) =>
|
export const strictValidate = (object: object) =>
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { IsNotEmpty, IsOptional, IsUUID } from 'class-validator';
|
import { IsUUID } from 'class-validator';
|
||||||
import { CombinePDecorators } from '../util/decorator';
|
import { CombinePDecorators } from '../util/decorator';
|
||||||
|
|
||||||
export const EntityID = CombinePDecorators(IsNotEmpty(), IsUUID('4'));
|
export const IsEntityID = CombinePDecorators(IsUUID('4'));
|
||||||
export const EntityIDOptional = CombinePDecorators(IsOptional(), IsUUID('4'));
|
|
||||||
|
|||||||
15
shared/src/validators/nested.validator.ts
Normal file
15
shared/src/validators/nested.validator.ts
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import { Type } from 'class-transformer';
|
||||||
|
import { IsNotEmpty, ValidateNested } from 'class-validator';
|
||||||
|
import { Newable } from '../types';
|
||||||
|
|
||||||
|
export const IsNested = (nestedClass: Newable<any>) => {
|
||||||
|
const nestedValidator = ValidateNested();
|
||||||
|
const isNotEmptyValidator = IsNotEmpty();
|
||||||
|
const typeValidator = Type(() => nestedClass);
|
||||||
|
|
||||||
|
return (target: Object, propertyKey: string | symbol): void => {
|
||||||
|
nestedValidator(target, propertyKey);
|
||||||
|
isNotEmptyValidator(target, propertyKey);
|
||||||
|
typeValidator(target, propertyKey);
|
||||||
|
};
|
||||||
|
};
|
||||||
26
shared/src/validators/not-defined.validator.ts
Normal file
26
shared/src/validators/not-defined.validator.ts
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
import {
|
||||||
|
IsOptional,
|
||||||
|
registerDecorator,
|
||||||
|
ValidationArguments,
|
||||||
|
ValidationOptions
|
||||||
|
} from 'class-validator';
|
||||||
|
|
||||||
|
export function isNotDefined(value: any, args: ValidationArguments) {
|
||||||
|
return value === undefined || value === null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function IsNotDefined(validationOptions?: ValidationOptions) {
|
||||||
|
const optional = IsOptional();
|
||||||
|
return function (object: Object, propertyName: string) {
|
||||||
|
registerDecorator({
|
||||||
|
name: 'isNotDefined',
|
||||||
|
target: object.constructor,
|
||||||
|
propertyName: propertyName,
|
||||||
|
options: validationOptions ?? {},
|
||||||
|
validator: {
|
||||||
|
validate: isNotDefined,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
optional(object, propertyName);
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { IsDefined, IsInt, Min } from 'class-validator';
|
import { IsInt, Min } from 'class-validator';
|
||||||
import { CombinePDecorators } from '../util/decorator';
|
import { CombinePDecorators } from '../util/decorator';
|
||||||
|
|
||||||
export const IsPosInt = CombinePDecorators(IsInt(), Min(0), IsDefined());
|
export const IsPosInt = CombinePDecorators(IsInt(), Min(0));
|
||||||
|
|||||||
@@ -1,20 +1,20 @@
|
|||||||
import { registerDecorator, ValidationArguments, ValidationOptions } from 'class-validator';
|
import { registerDecorator, ValidationArguments, ValidationOptions } from 'class-validator';
|
||||||
import { PrefValueTypes } from '../dto/preferences.dto';
|
import { PrefValueTypes } from '../dto/preferences.dto';
|
||||||
|
|
||||||
export function isSysPrefValue(value: any, args: ValidationArguments) {
|
export function isPrefValue(value: any, args: ValidationArguments) {
|
||||||
const type = typeof value;
|
const type = typeof value;
|
||||||
return PrefValueTypes.includes(type);
|
return PrefValueTypes.includes(type);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function IsSysPrefValue(validationOptions?: ValidationOptions) {
|
export function IsPrefValue(validationOptions?: ValidationOptions) {
|
||||||
return function (object: Object, propertyName: string) {
|
return function (object: Object, propertyName: string) {
|
||||||
registerDecorator({
|
registerDecorator({
|
||||||
name: 'isSysPrefValue',
|
name: 'isPrefValue',
|
||||||
target: object.constructor,
|
target: object.constructor,
|
||||||
propertyName: propertyName,
|
propertyName: propertyName,
|
||||||
options: validationOptions,
|
options: validationOptions ?? {},
|
||||||
validator: {
|
validator: {
|
||||||
validate: isSysPrefValue,
|
validate: isPrefValue,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@@ -1,8 +1,7 @@
|
|||||||
import { IsAlphanumeric, IsNotEmpty, IsString, Length } from 'class-validator';
|
import { IsAlphanumeric, IsString, Length } from 'class-validator';
|
||||||
import { CombinePDecorators } from '../util/decorator';
|
import { CombinePDecorators } from '../util/decorator';
|
||||||
|
|
||||||
export const IsRoleName = CombinePDecorators(
|
export const IsRoleName = CombinePDecorators(
|
||||||
IsNotEmpty(),
|
|
||||||
IsString(),
|
IsString(),
|
||||||
Length(4, 32),
|
Length(4, 32),
|
||||||
IsAlphanumeric(),
|
IsAlphanumeric(),
|
||||||
|
|||||||
@@ -1,12 +1,9 @@
|
|||||||
import {
|
import {
|
||||||
IsArray,
|
IsArray, IsString
|
||||||
IsNotEmpty,
|
|
||||||
IsString
|
|
||||||
} from 'class-validator';
|
} from 'class-validator';
|
||||||
import { CombinePDecorators } from '../util/decorator';
|
import { CombinePDecorators } from '../util/decorator';
|
||||||
|
|
||||||
export const IsStringList = CombinePDecorators(
|
export const IsStringList = CombinePDecorators(
|
||||||
IsArray(),
|
IsArray(),
|
||||||
IsString({ each: true }),
|
IsString({ each: true }),
|
||||||
IsNotEmpty({ each: true }),
|
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,18 +1,16 @@
|
|||||||
import { IsAlphanumeric, IsNotEmpty, IsString, Length } from 'class-validator';
|
import { IsAlphanumeric, IsString, Length } from 'class-validator';
|
||||||
import { CombinePDecorators } from '../util/decorator';
|
import { CombinePDecorators } from '../util/decorator';
|
||||||
|
|
||||||
// Match this with user validators in frontend
|
// Match this with user validators in frontend
|
||||||
// (Frontend is not security focused, but it tells the user what is wrong)
|
// (Frontend is not security focused, but it tells the user what is wrong)
|
||||||
|
|
||||||
export const IsUsername = CombinePDecorators(
|
export const IsUsername = CombinePDecorators(
|
||||||
IsNotEmpty(),
|
|
||||||
IsString(),
|
IsString(),
|
||||||
Length(4, 32),
|
Length(4, 32),
|
||||||
IsAlphanumeric(),
|
IsAlphanumeric(),
|
||||||
);
|
);
|
||||||
|
|
||||||
export const IsPlainTextPwd = CombinePDecorators(
|
export const IsPlainTextPwd = CombinePDecorators(
|
||||||
IsNotEmpty(),
|
|
||||||
IsString(),
|
IsString(),
|
||||||
Length(4, 1024),
|
Length(4, 1024),
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user