switch to zod
This commit is contained in:
parent
bcd427f5a7
commit
380b9d3456
|
@ -29,8 +29,6 @@
|
||||||
"@nestjs/serve-static": "^2.2.2",
|
"@nestjs/serve-static": "^2.2.2",
|
||||||
"@nestjs/typeorm": "^8.0.3",
|
"@nestjs/typeorm": "^8.0.3",
|
||||||
"bcrypt": "^5.0.1",
|
"bcrypt": "^5.0.1",
|
||||||
"class-transformer": "^0.5.1",
|
|
||||||
"class-validator": "^0.13.2",
|
|
||||||
"fastify-helmet": "^7.0.1",
|
"fastify-helmet": "^7.0.1",
|
||||||
"fastify-multipart": "^5.3.1",
|
"fastify-multipart": "^5.3.1",
|
||||||
"fastify-static": "^4.6.1",
|
"fastify-static": "^4.6.1",
|
||||||
|
@ -44,7 +42,8 @@
|
||||||
"reflect-metadata": "^0.1.13",
|
"reflect-metadata": "^0.1.13",
|
||||||
"rimraf": "^3.0.2",
|
"rimraf": "^3.0.2",
|
||||||
"rxjs": "^7.5.5",
|
"rxjs": "^7.5.5",
|
||||||
"typeorm": "0.3.4"
|
"typeorm": "0.3.4",
|
||||||
|
"zod": "^3.14.3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@nestjs/cli": "^8.2.4",
|
"@nestjs/cli": "^8.2.4",
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { Injectable, Logger } from '@nestjs/common';
|
import { Injectable, Logger } from '@nestjs/common';
|
||||||
import { InjectRepository } from '@nestjs/typeorm';
|
import { InjectRepository } from '@nestjs/typeorm';
|
||||||
import { plainToClass } from 'class-transformer';
|
import { ERoleSchema } from 'picsur-shared/dist/entities/role.entity';
|
||||||
import {
|
import {
|
||||||
AsyncFailable,
|
AsyncFailable,
|
||||||
Fail,
|
Fail,
|
||||||
|
@ -8,7 +8,6 @@ import {
|
||||||
HasSuccess
|
HasSuccess
|
||||||
} from 'picsur-shared/dist/types';
|
} from 'picsur-shared/dist/types';
|
||||||
import { makeUnique } from 'picsur-shared/dist/util/unique';
|
import { makeUnique } from 'picsur-shared/dist/util/unique';
|
||||||
import { strictValidate } from 'picsur-shared/dist/util/validate';
|
|
||||||
import { In, Repository } from 'typeorm';
|
import { In, Repository } from 'typeorm';
|
||||||
import { Permissions } from '../../models/dto/permissions.dto';
|
import { Permissions } from '../../models/dto/permissions.dto';
|
||||||
import {
|
import {
|
||||||
|
@ -171,13 +170,13 @@ export class RolesService {
|
||||||
if (typeof role === 'string') {
|
if (typeof role === 'string') {
|
||||||
return await this.findOne(role);
|
return await this.findOne(role);
|
||||||
} else {
|
} else {
|
||||||
role = plainToClass(ERoleBackend, role);
|
const result = ERoleSchema.safeParse(role);
|
||||||
const errors = await strictValidate(role);
|
if (!result.success) {
|
||||||
if (errors.length > 0) {
|
this.logger.warn(result.error);
|
||||||
this.logger.warn(errors);
|
|
||||||
return Fail('Invalid role');
|
return Fail('Invalid role');
|
||||||
}
|
}
|
||||||
return role;
|
// This is safe
|
||||||
|
return result.data as ERoleBackend;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,13 +6,13 @@ import {
|
||||||
PrefValueTypeStrings
|
PrefValueTypeStrings
|
||||||
} from 'picsur-shared/dist/dto/preferences.dto';
|
} 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 { ESysPreferenceSchema } from 'picsur-shared/dist/entities/syspreference.entity';
|
||||||
import {
|
import {
|
||||||
AsyncFailable,
|
AsyncFailable,
|
||||||
Fail,
|
Fail,
|
||||||
Failable,
|
Failable,
|
||||||
HasFailed
|
HasFailed
|
||||||
} from 'picsur-shared/dist/types';
|
} from 'picsur-shared/dist/types';
|
||||||
import { strictValidate } from 'picsur-shared/dist/util/validate';
|
|
||||||
import { Repository } from 'typeorm';
|
import { Repository } from 'typeorm';
|
||||||
import {
|
import {
|
||||||
SysPreferenceList,
|
SysPreferenceList,
|
||||||
|
@ -82,14 +82,14 @@ export class SysPreferenceService {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate
|
// Validate
|
||||||
const errors = await strictValidate(foundSysPreference);
|
const result = ESysPreferenceSchema.safeParse(foundSysPreference);
|
||||||
if (errors.length > 0) {
|
if (!result.success) {
|
||||||
this.logger.warn(errors);
|
this.logger.warn(result.error);
|
||||||
return Fail('Invalid preference');
|
return Fail('Invalid preference');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return
|
// Return
|
||||||
return this.retrieveConvertedValue(foundSysPreference);
|
return this.retrieveConvertedValue(result.data);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getStringPreference(key: string): AsyncFailable<string> {
|
public async getStringPreference(key: string): AsyncFailable<string> {
|
||||||
|
@ -182,13 +182,13 @@ export class SysPreferenceService {
|
||||||
verifySysPreference.value = validatedValue;
|
verifySysPreference.value = validatedValue;
|
||||||
|
|
||||||
// It should already be valid, but these two validators might go out of sync
|
// It should already be valid, but these two validators might go out of sync
|
||||||
const errors = await strictValidate(verifySysPreference);
|
const result = ESysPreferenceSchema.safeParse(verifySysPreference);
|
||||||
if (errors.length > 0) {
|
if (!result.success) {
|
||||||
this.logger.warn(errors);
|
this.logger.warn(result.error);
|
||||||
return Fail('Invalid preference');
|
return Fail('Invalid preference');
|
||||||
}
|
}
|
||||||
|
|
||||||
return verifySysPreference;
|
return result.data;
|
||||||
}
|
}
|
||||||
|
|
||||||
private validatePrefKey(key: string): Failable<SysPreference> {
|
private validatePrefKey(key: string): Failable<SysPreference> {
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
import { Injectable, Logger } from '@nestjs/common';
|
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 { SysPreference } from 'picsur-shared/dist/dto/syspreferences.dto';
|
import { SysPreference } from 'picsur-shared/dist/dto/syspreferences.dto';
|
||||||
import {
|
import {
|
||||||
AsyncFailable,
|
AsyncFailable,
|
||||||
|
@ -79,10 +78,7 @@ export class UsersService {
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Makes sure we can return the id
|
return await this.usersRepository.remove(userToModify);
|
||||||
const cloned = plainToClass(EUserBackend, userToModify);
|
|
||||||
await this.usersRepository.remove(userToModify);
|
|
||||||
return cloned;
|
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
return Fail(e?.message);
|
return Fail(e?.message);
|
||||||
}
|
}
|
||||||
|
@ -158,8 +154,7 @@ export class UsersService {
|
||||||
return Fail(e?.message);
|
return Fail(e?.message);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Strips unwanted data
|
return userToModify;
|
||||||
return plainToClass(EUserBackend, userToModify);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Authentication
|
// Authentication
|
||||||
|
@ -176,36 +171,28 @@ export class UsersService {
|
||||||
return Fail('Wrong username');
|
return Fail('Wrong username');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!(await bcrypt.compare(password, user.hashedPassword)))
|
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 ?? '');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Listing
|
// Listing
|
||||||
|
|
||||||
public async findByUsername<B extends true | undefined = undefined>(
|
public async findByUsername(
|
||||||
username: string,
|
username: string,
|
||||||
// Also fetch fields that aren't normally sent to the client
|
// Also fetch fields that aren't normally sent to the client
|
||||||
// (e.g. hashed password)
|
// (e.g. hashed password)
|
||||||
getPrivate?: B,
|
getPrivate: boolean = false,
|
||||||
): AsyncFailable<
|
): AsyncFailable<EUserBackend> {
|
||||||
B extends undefined ? EUserBackend : Required<EUserBackend>
|
|
||||||
> {
|
|
||||||
try {
|
try {
|
||||||
const found = await this.usersRepository.findOne({
|
const found = await this.usersRepository.findOne({
|
||||||
where: { username },
|
where: { username },
|
||||||
...(getPrivate
|
select: getPrivate ? GetCols(this.usersRepository) : undefined,
|
||||||
? {
|
|
||||||
select: GetCols(this.usersRepository),
|
|
||||||
}
|
|
||||||
: {}),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!found) return Fail('User not found');
|
if (!found) return Fail('User not found');
|
||||||
return found as B extends undefined
|
return found;
|
||||||
? EUserBackend
|
|
||||||
: Required<EUserBackend>;
|
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
return Fail(e?.message);
|
return Fail(e?.message);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,8 @@
|
||||||
import { createParamDecorator, ExecutionContext } from '@nestjs/common';
|
import { createParamDecorator, ExecutionContext } from '@nestjs/common';
|
||||||
import { Newable } from 'picsur-shared/dist/types';
|
|
||||||
|
|
||||||
// Since pipes dont have direct access to the request object, we need this decorator to inject it
|
// Since pipes dont have direct access to the request object, we need this decorator to inject it
|
||||||
export const InjectRequest = createParamDecorator(
|
export const InjectRequest = createParamDecorator(
|
||||||
async <T extends Object>(data: Newable<T>, ctx: ExecutionContext) => {
|
async (data: any, ctx: ExecutionContext) => {
|
||||||
return {
|
return ctx.switchToHttp().getRequest();
|
||||||
req: ctx.switchToHttp().getRequest(),
|
|
||||||
data,
|
|
||||||
};
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,9 +1,8 @@
|
||||||
import { Newable } from 'picsur-shared/dist/types';
|
|
||||||
import { InjectRequest } from './injectrequest.decorator';
|
import { InjectRequest } from './injectrequest.decorator';
|
||||||
import { MultiPartPipe } from './multipart.pipe';
|
import { MultiPartPipe } from './multipart.pipe';
|
||||||
import { PostFilePipe } from './postfile.pipe';
|
import { PostFilePipe } from './postfile.pipe';
|
||||||
|
|
||||||
export const PostFile = () => InjectRequest(PostFilePipe);
|
export const PostFile = () => InjectRequest(PostFilePipe);
|
||||||
|
|
||||||
export const MultiPart = <T extends Object>(data: Newable<T>) =>
|
export const MultiPart = () =>
|
||||||
InjectRequest(data, MultiPartPipe);
|
InjectRequest(MultiPartPipe);
|
||||||
|
|
|
@ -1,18 +1,19 @@
|
||||||
import {
|
import {
|
||||||
|
ArgumentMetadata,
|
||||||
BadRequestException,
|
BadRequestException,
|
||||||
Injectable,
|
Injectable,
|
||||||
|
InternalServerErrorException,
|
||||||
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';
|
||||||
import { Newable } from 'picsur-shared/dist/types';
|
import { ZodDtoStatic } from 'picsur-shared/dist/util/create-zod-dto';
|
||||||
import { strictValidate } from 'picsur-shared/dist/util/validate';
|
|
||||||
import { MultipartConfigService } from '../config/early/multipart.config.service';
|
import { MultipartConfigService } from '../config/early/multipart.config.service';
|
||||||
import {
|
import {
|
||||||
MultiPartFieldDto,
|
CreateMultiPartFieldDto,
|
||||||
MultiPartFileDto
|
CreateMultiPartFileDto
|
||||||
} from '../models/requests/multipart.dto';
|
} from '../models/requests/multipart.dto';
|
||||||
|
|
||||||
@Injectable({ scope: Scope.REQUEST })
|
@Injectable({ scope: Scope.REQUEST })
|
||||||
|
@ -21,16 +22,17 @@ export class MultiPartPipe implements PipeTransform {
|
||||||
|
|
||||||
constructor(private multipartConfigService: MultipartConfigService) {}
|
constructor(private multipartConfigService: MultipartConfigService) {}
|
||||||
|
|
||||||
async transform<T extends Object>({
|
async transform<T extends Object>(
|
||||||
req,
|
req: FastifyRequest,
|
||||||
data,
|
metadata: ArgumentMetadata,
|
||||||
}: {
|
) {
|
||||||
req: FastifyRequest;
|
let zodSchema = (metadata?.metatype as ZodDtoStatic)?.zodSchema;
|
||||||
data: Newable<T>;
|
if (!zodSchema) {
|
||||||
}) {
|
this.logger.warn('Invalid scheme on multipart body');
|
||||||
// Data should be a validatable class constructor
|
throw new InternalServerErrorException('Invalid scheme on backend');
|
||||||
const dtoClass = new data();
|
}
|
||||||
|
|
||||||
|
let multipartData = {};
|
||||||
if (!req.isMultipart()) throw new BadRequestException('Invalid file');
|
if (!req.isMultipart()) throw new BadRequestException('Invalid file');
|
||||||
|
|
||||||
// Fetch all fields from the request
|
// Fetch all fields from the request
|
||||||
|
@ -56,11 +58,11 @@ export class MultiPartPipe implements PipeTransform {
|
||||||
// Use the value property to differentiate between a field and a file
|
// Use the value property to differentiate between a field and a file
|
||||||
// And then put the value into the correct property on the validatable class
|
// And then put the value into the correct property on the validatable class
|
||||||
if ((fields[key] as any).value) {
|
if ((fields[key] as any).value) {
|
||||||
(dtoClass as any)[key] = new MultiPartFieldDto(
|
(multipartData as any)[key] = CreateMultiPartFieldDto(
|
||||||
fields[key] as MultipartFile,
|
fields[key] as MultipartFile,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
(dtoClass as any)[key] = new MultiPartFileDto(
|
(multipartData as any)[key] = CreateMultiPartFileDto(
|
||||||
fields[key] as MultipartFile,
|
fields[key] as MultipartFile,
|
||||||
new BadRequestException('Invalid file'),
|
new BadRequestException('Invalid file'),
|
||||||
);
|
);
|
||||||
|
@ -68,12 +70,12 @@ export class MultiPartPipe implements PipeTransform {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Now validate the class we made, if any properties were invalid, it will error here
|
// Now validate the class we made, if any properties were invalid, it will error here
|
||||||
const errors = await strictValidate(dtoClass);
|
const result = zodSchema.safeParse(multipartData);
|
||||||
if (errors.length > 0) {
|
if (!result.success) {
|
||||||
this.logger.warn(errors);
|
this.logger.warn(result.error);
|
||||||
throw new BadRequestException('Invalid file');
|
throw new BadRequestException('Invalid file');
|
||||||
}
|
}
|
||||||
|
|
||||||
return dtoClass;
|
return result.data;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,25 +2,82 @@ import {
|
||||||
CallHandler,
|
CallHandler,
|
||||||
ExecutionContext,
|
ExecutionContext,
|
||||||
Injectable,
|
Injectable,
|
||||||
NestInterceptor
|
InternalServerErrorException,
|
||||||
|
Logger,
|
||||||
|
NestInterceptor,
|
||||||
|
Optional
|
||||||
} from '@nestjs/common';
|
} from '@nestjs/common';
|
||||||
import { ApiResponse } from 'picsur-shared/dist/dto/api/api.dto';
|
import { Reflector } from '@nestjs/core';
|
||||||
|
import { ZodDtoStatic } from 'picsur-shared/dist/util/create-zod-dto';
|
||||||
import { map, Observable } from 'rxjs';
|
import { map, Observable } from 'rxjs';
|
||||||
|
|
||||||
// This interceptor will neatly wrap any json response made within nest
|
// This interceptor will neatly wrap any json response made within nest
|
||||||
|
|
||||||
|
export interface ZodValidationInterceptorOptions {
|
||||||
|
strict?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class SuccessInterceptor<T> implements NestInterceptor {
|
export class SuccessInterceptor<T> implements NestInterceptor {
|
||||||
|
private readonly logger = new Logger();
|
||||||
|
|
||||||
|
// TODO: make work
|
||||||
|
private strict: boolean;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private reflector: Reflector,
|
||||||
|
@Optional() options?: ZodValidationInterceptorOptions,
|
||||||
|
) {
|
||||||
|
this.strict = options?.strict ?? true;
|
||||||
|
}
|
||||||
|
|
||||||
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
|
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
|
||||||
return next.handle().pipe(
|
return next.handle().pipe(
|
||||||
map((data) => {
|
map((data) => {
|
||||||
if (data instanceof Buffer) {
|
if (data instanceof Buffer) {
|
||||||
return data;
|
return data;
|
||||||
} else if (typeof data === 'object') {
|
} else if (typeof data === 'object') {
|
||||||
// TODO: mabye validate outgoing requests?
|
const validated = this.validate(context, data);
|
||||||
|
|
||||||
|
return this.createResponse(context, validated);
|
||||||
|
} else {
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private validate(context: ExecutionContext, data: any): any {
|
||||||
|
const schemaStatic = this.reflector.get<ZodDtoStatic>(
|
||||||
|
'returns',
|
||||||
|
context.getHandler(),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!schemaStatic) {
|
||||||
|
this.logger.warn(
|
||||||
|
`No zodSchema found on handler ${context.getHandler().name}`,
|
||||||
|
);
|
||||||
|
throw new InternalServerErrorException("Couldn't find schema");
|
||||||
|
}
|
||||||
|
|
||||||
|
let schema = schemaStatic.zodSchema;
|
||||||
|
|
||||||
|
const parseResult = schema.safeParse(data);
|
||||||
|
if (!parseResult.success) {
|
||||||
|
this.logger.warn(
|
||||||
|
`Function ${context.getHandler().name} failed validation`,
|
||||||
|
);
|
||||||
|
throw new InternalServerErrorException(
|
||||||
|
'Server produced invalid response',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return parseResult.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
private createResponse(context: ExecutionContext, data: any): any {
|
||||||
const status = context.switchToHttp().getResponse().statusCode;
|
const status = context.switchToHttp().getResponse().statusCode;
|
||||||
const response: ApiResponse<any> = {
|
const response = {
|
||||||
success: true,
|
success: true,
|
||||||
statusCode: status,
|
statusCode: status,
|
||||||
timestamp: new Date().toISOString(),
|
timestamp: new Date().toISOString(),
|
||||||
|
@ -29,10 +86,5 @@ export class SuccessInterceptor<T> implements NestInterceptor {
|
||||||
};
|
};
|
||||||
|
|
||||||
return response;
|
return response;
|
||||||
} else {
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
47
backend/src/layers/validate/zod-validator.pipe.ts
Normal file
47
backend/src/layers/validate/zod-validator.pipe.ts
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
/**
|
||||||
|
* This file was originally taken directly from:
|
||||||
|
* https://github.com/anatine/zod-plugins/blob/main/libs/zod-nestjs/src/lib/zod-validation-pipe.ts
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {
|
||||||
|
ArgumentMetadata,
|
||||||
|
BadRequestException,
|
||||||
|
Injectable,
|
||||||
|
Optional,
|
||||||
|
PipeTransform
|
||||||
|
} from '@nestjs/common';
|
||||||
|
import { ZodDtoStatic } from 'picsur-shared/dist/util/create-zod-dto';
|
||||||
|
|
||||||
|
export interface ZodValidationPipeOptions {
|
||||||
|
strict?: boolean;
|
||||||
|
validateCustom?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class ZodValidationPipe implements PipeTransform {
|
||||||
|
private strict: boolean;
|
||||||
|
private validateCustom: boolean;
|
||||||
|
|
||||||
|
constructor(@Optional() options?: ZodValidationPipeOptions) {
|
||||||
|
this.strict = options?.strict ?? true;
|
||||||
|
this.validateCustom = options?.validateCustom ?? false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public transform(value: unknown, metadata: ArgumentMetadata): unknown {
|
||||||
|
if (!this.validateCustom && metadata.type === 'custom') return value;
|
||||||
|
|
||||||
|
let zodSchema = (metadata?.metatype as ZodDtoStatic)?.zodSchema;
|
||||||
|
|
||||||
|
if (zodSchema) {
|
||||||
|
const parseResult = zodSchema.safeParse(value);
|
||||||
|
|
||||||
|
if (!parseResult.success) {
|
||||||
|
throw new BadRequestException();
|
||||||
|
}
|
||||||
|
|
||||||
|
return parseResult.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +1,3 @@
|
||||||
import { ValidationPipe } from '@nestjs/common';
|
|
||||||
import { NestFactory, Reflector } from '@nestjs/core';
|
import { NestFactory, Reflector } from '@nestjs/core';
|
||||||
import {
|
import {
|
||||||
FastifyAdapter,
|
FastifyAdapter,
|
||||||
|
@ -6,12 +5,12 @@ import {
|
||||||
} from '@nestjs/platform-fastify';
|
} from '@nestjs/platform-fastify';
|
||||||
import fastifyHelmet from 'fastify-helmet';
|
import fastifyHelmet from 'fastify-helmet';
|
||||||
import * as multipart from 'fastify-multipart';
|
import * as multipart from 'fastify-multipart';
|
||||||
import { ValidateOptions } from 'picsur-shared/dist/util/validate';
|
|
||||||
import { AppModule } from './app.module';
|
import { AppModule } from './app.module';
|
||||||
import { UsersService } from './collections/userdb/userdb.service';
|
import { UsersService } from './collections/userdb/userdb.service';
|
||||||
import { HostConfigService } from './config/early/host.config.service';
|
import { HostConfigService } from './config/early/host.config.service';
|
||||||
import { MainExceptionFilter } from './layers/httpexception/httpexception.filter';
|
import { MainExceptionFilter } from './layers/httpexception/httpexception.filter';
|
||||||
import { SuccessInterceptor } from './layers/success/success.interceptor';
|
import { SuccessInterceptor } from './layers/success/success.interceptor';
|
||||||
|
import { ZodValidationPipe } from './layers/validate/zod-validator.pipe';
|
||||||
import { PicsurLoggerService } from './logger/logger.service';
|
import { PicsurLoggerService } from './logger/logger.service';
|
||||||
import { MainAuthGuard } from './managers/auth/guards/main.guard';
|
import { MainAuthGuard } from './managers/auth/guards/main.guard';
|
||||||
import { HelmetOptions } from './security';
|
import { HelmetOptions } from './security';
|
||||||
|
@ -31,14 +30,11 @@ async function bootstrap() {
|
||||||
bufferLogs: true,
|
bufferLogs: true,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
// app.enableCors({
|
|
||||||
// origin: 'self'
|
|
||||||
// });
|
|
||||||
|
|
||||||
// Configure nest app
|
// Configure nest app
|
||||||
app.useGlobalFilters(new MainExceptionFilter());
|
app.useGlobalFilters(new MainExceptionFilter());
|
||||||
app.useGlobalInterceptors(new SuccessInterceptor());
|
app.useGlobalInterceptors(new SuccessInterceptor(app.get(Reflector)));
|
||||||
app.useGlobalPipes(new ValidationPipe(ValidateOptions));
|
app.useGlobalPipes(new ZodValidationPipe());
|
||||||
app.useGlobalGuards(
|
app.useGlobalGuards(
|
||||||
new MainAuthGuard(app.get(Reflector), app.get(UsersService)),
|
new MainAuthGuard(app.get(Reflector), app.get(UsersService)),
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,10 +1,8 @@
|
||||||
import { Injectable, Logger } from '@nestjs/common';
|
import { Injectable, Logger } from '@nestjs/common';
|
||||||
import { JwtService } from '@nestjs/jwt';
|
import { JwtService } from '@nestjs/jwt';
|
||||||
import { instanceToPlain, plainToClass } from 'class-transformer';
|
import { JwtDataSchema } from 'picsur-shared/dist/dto/jwt.dto';
|
||||||
import { JwtDataDto } from 'picsur-shared/dist/dto/jwt.dto';
|
import { EUser } from 'picsur-shared/dist/entities/user.entity';
|
||||||
import { AsyncFailable, Fail } from 'picsur-shared/dist/types';
|
import { AsyncFailable, Fail } from 'picsur-shared/dist/types';
|
||||||
import { strictValidate } from 'picsur-shared/dist/util/validate';
|
|
||||||
import { EUserBackend } from '../../models/entities/user.entity';
|
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class AuthManagerService {
|
export class AuthManagerService {
|
||||||
|
@ -12,20 +10,20 @@ export class AuthManagerService {
|
||||||
|
|
||||||
constructor(private jwtService: JwtService) {}
|
constructor(private jwtService: JwtService) {}
|
||||||
|
|
||||||
async createToken(user: EUserBackend): AsyncFailable<string> {
|
async createToken(user: EUser): AsyncFailable<string> {
|
||||||
const jwtData: JwtDataDto = plainToClass(JwtDataDto, {
|
const jwtData = {
|
||||||
user,
|
user,
|
||||||
});
|
};
|
||||||
|
|
||||||
// Validate to be sure, this makes client experience better
|
// Validate to be sure, this makes client experience better
|
||||||
// in case of any failures
|
// in case of any failures
|
||||||
const errors = await strictValidate(jwtData);
|
const result = JwtDataSchema.safeParse(jwtData);
|
||||||
if (errors.length > 0) {
|
if (!result.success) {
|
||||||
return Fail('Invalid JWT: ' + errors);
|
return Fail('Invalid JWT: ' + result.error);
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
return await this.jwtService.signAsync(instanceToPlain(jwtData));
|
return await this.jwtService.signAsync(result.data);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return Fail("Couldn't create JWT: " + e);
|
return Fail("Couldn't create JWT: " + e);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,14 +1,8 @@
|
||||||
import {
|
import { Inject, Injectable, Logger } from '@nestjs/common';
|
||||||
Inject,
|
|
||||||
Injectable,
|
|
||||||
Logger
|
|
||||||
} from '@nestjs/common';
|
|
||||||
import { PassportStrategy } from '@nestjs/passport';
|
import { PassportStrategy } from '@nestjs/passport';
|
||||||
import { plainToClass } from 'class-transformer';
|
|
||||||
import { ExtractJwt, Strategy } from 'passport-jwt';
|
import { ExtractJwt, Strategy } from 'passport-jwt';
|
||||||
import { JwtDataDto } from 'picsur-shared/dist/dto/jwt.dto';
|
import { JwtDataSchema } from 'picsur-shared/dist/dto/jwt.dto';
|
||||||
import { strictValidate } from 'picsur-shared/dist/util/validate';
|
import { EUser } from 'picsur-shared/dist/entities/user.entity';
|
||||||
import { EUserBackend } from '../../../models/entities/user.entity';
|
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class JwtStrategy extends PassportStrategy(Strategy, 'jwt') {
|
export class JwtStrategy extends PassportStrategy(Strategy, 'jwt') {
|
||||||
|
@ -23,17 +17,14 @@ export class JwtStrategy extends PassportStrategy(Strategy, 'jwt') {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async validate(payload: any): Promise<EUserBackend | false> {
|
async validate(payload: any): Promise<EUser | false> {
|
||||||
const jwt = plainToClass(JwtDataDto, payload);
|
const result = JwtDataSchema.safeParse(payload);
|
||||||
|
if (!result.success) {
|
||||||
// This then validates the data inside the jwt token
|
this.logger.error('JWT could not be parsed: ' + result.error);
|
||||||
const errors = await strictValidate(jwt);
|
|
||||||
if (errors.length > 0) {
|
|
||||||
this.logger.warn(errors);
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// And return the user
|
// And return the user
|
||||||
return jwt.user;
|
return result.data.user;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
import { Injectable, UnauthorizedException } from '@nestjs/common';
|
import { Injectable, UnauthorizedException } from '@nestjs/common';
|
||||||
import { PassportStrategy } from '@nestjs/passport';
|
import { PassportStrategy } from '@nestjs/passport';
|
||||||
import { Strategy } from 'passport-local';
|
import { Strategy } from 'passport-local';
|
||||||
|
import { EUser } from 'picsur-shared/dist/entities/user.entity';
|
||||||
import { AsyncFailable, HasFailed } from 'picsur-shared/dist/types';
|
import { AsyncFailable, HasFailed } from 'picsur-shared/dist/types';
|
||||||
import { UsersService } from '../../../collections/userdb/userdb.service';
|
import { UsersService } from '../../../collections/userdb/userdb.service';
|
||||||
import { EUserBackend } from '../../../models/entities/user.entity';
|
import { EUserBackend2EUser } from '../../../models/transformers/user.transformer';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class LocalAuthStrategy extends PassportStrategy(Strategy, 'local') {
|
export class LocalAuthStrategy extends PassportStrategy(Strategy, 'local') {
|
||||||
|
@ -11,16 +12,13 @@ export class LocalAuthStrategy extends PassportStrategy(Strategy, 'local') {
|
||||||
super();
|
super();
|
||||||
}
|
}
|
||||||
|
|
||||||
async validate(
|
async validate(username: string, password: string): AsyncFailable<EUser> {
|
||||||
username: string,
|
|
||||||
password: string,
|
|
||||||
): AsyncFailable<EUserBackend> {
|
|
||||||
// All this does is call the usersservice authenticate for authentication
|
// All this does is call the usersservice authenticate for authentication
|
||||||
const user = await this.usersService.authenticate(username, password);
|
const user = await this.usersService.authenticate(username, password);
|
||||||
if (HasFailed(user)) {
|
if (HasFailed(user)) {
|
||||||
throw new UnauthorizedException();
|
throw new UnauthorizedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
return user;
|
return EUserBackend2EUser(user);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,12 +7,10 @@ import {
|
||||||
} from '@nestjs/common';
|
} from '@nestjs/common';
|
||||||
import { Reflector } from '@nestjs/core';
|
import { Reflector } from '@nestjs/core';
|
||||||
import { AuthGuard } from '@nestjs/passport';
|
import { AuthGuard } from '@nestjs/passport';
|
||||||
import { plainToClass } from 'class-transformer';
|
import { EUser, EUserSchema } from 'picsur-shared/dist/entities/user.entity';
|
||||||
import { Fail, Failable, HasFailed } from 'picsur-shared/dist/types';
|
import { Fail, Failable, HasFailed } from 'picsur-shared/dist/types';
|
||||||
import { strictValidate } from 'picsur-shared/dist/util/validate';
|
|
||||||
import { UsersService } from '../../../collections/userdb/userdb.service';
|
import { UsersService } from '../../../collections/userdb/userdb.service';
|
||||||
import { Permissions } from '../../../models/dto/permissions.dto';
|
import { Permissions } from '../../../models/dto/permissions.dto';
|
||||||
import { EUserBackend } from '../../../models/entities/user.entity';
|
|
||||||
import { isPermissionsArray } from '../../../models/validators/permissions.validator';
|
import { isPermissionsArray } from '../../../models/validators/permissions.validator';
|
||||||
|
|
||||||
// This guard extends both the jwt authenticator and the guest authenticator
|
// This guard extends both the jwt authenticator and the guest authenticator
|
||||||
|
@ -84,16 +82,15 @@ export class MainAuthGuard extends AuthGuard(['jwt', 'guest']) {
|
||||||
return permissions;
|
return permissions;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async validateUser(user: EUserBackend): Promise<EUserBackend> {
|
private async validateUser(user: EUser): Promise<EUser> {
|
||||||
const userClass = plainToClass(EUserBackend, user);
|
const result = EUserSchema.safeParse(user);
|
||||||
const errors = await strictValidate(userClass);
|
if (!result.success) {
|
||||||
|
this.logger.warn(
|
||||||
if (errors.length > 0) {
|
`Invalid user object, where it should always be valid: ${result.error}`,
|
||||||
this.logger.error(
|
|
||||||
'Invalid user object, where it should always be valid: ' + errors,
|
|
||||||
);
|
);
|
||||||
throw new InternalServerErrorException();
|
throw new InternalServerErrorException();
|
||||||
}
|
}
|
||||||
return userClass;
|
|
||||||
|
return result.data;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,23 +1,27 @@
|
||||||
import { IsNotEmpty, IsOptional } from 'class-validator';
|
import { EImageSchema } 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';
|
||||||
|
import { z } from 'zod';
|
||||||
|
|
||||||
|
const OverriddenEImageSchema = EImageSchema.omit({ data: true }).merge(
|
||||||
|
z.object({
|
||||||
|
data: z.any(),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
type OverriddenEImage = z.infer<typeof OverriddenEImageSchema>;
|
||||||
|
|
||||||
@Entity()
|
@Entity()
|
||||||
export class EImageBackend extends EImage {
|
export class EImageBackend implements OverriddenEImage {
|
||||||
@PrimaryGeneratedColumn('uuid')
|
@PrimaryGeneratedColumn('uuid')
|
||||||
override id?: string;
|
id?: string;
|
||||||
|
|
||||||
@Index()
|
@Index()
|
||||||
@Column({ unique: true, nullable: false })
|
@Column({ unique: true, nullable: false })
|
||||||
override hash: string;
|
hash: string;
|
||||||
|
|
||||||
@Column({ nullable: false })
|
@Column({ nullable: false })
|
||||||
override mime: string;
|
mime: string;
|
||||||
|
|
||||||
// Binary data
|
// Binary data
|
||||||
@Column({ type: 'bytea', nullable: false, select: false })
|
@Column({ type: 'bytea', nullable: false, select: false })
|
||||||
@IsOptional()
|
data?: Buffer;
|
||||||
@IsNotEmpty()
|
|
||||||
// @ts-ignore
|
|
||||||
override data?: Buffer;
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,14 +3,14 @@ import { Column, Entity, Index, PrimaryGeneratedColumn } from 'typeorm';
|
||||||
import { Permissions } from '../dto/permissions.dto';
|
import { Permissions } from '../dto/permissions.dto';
|
||||||
|
|
||||||
@Entity()
|
@Entity()
|
||||||
export class ERoleBackend extends ERole {
|
export class ERoleBackend implements ERole {
|
||||||
@PrimaryGeneratedColumn("uuid")
|
@PrimaryGeneratedColumn('uuid')
|
||||||
override id?: string;
|
id?: string;
|
||||||
|
|
||||||
@Index()
|
@Index()
|
||||||
@Column({ nullable: false, unique: true })
|
@Column({ nullable: false, unique: true })
|
||||||
override name: string;
|
name: string;
|
||||||
|
|
||||||
@Column('text', { nullable: false, array: true })
|
@Column('text', { nullable: false, array: true })
|
||||||
override permissions: Permissions;
|
permissions: Permissions;
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,14 +2,14 @@ import { ESysPreference } from 'picsur-shared/dist/entities/syspreference.entity
|
||||||
import { Column, Entity, Index, PrimaryGeneratedColumn } from 'typeorm';
|
import { Column, Entity, Index, PrimaryGeneratedColumn } from 'typeorm';
|
||||||
|
|
||||||
@Entity()
|
@Entity()
|
||||||
export class ESysPreferenceBackend extends ESysPreference {
|
export class ESysPreferenceBackend implements ESysPreference {
|
||||||
@PrimaryGeneratedColumn("uuid")
|
@PrimaryGeneratedColumn('uuid')
|
||||||
override id?: string;
|
id?: string;
|
||||||
|
|
||||||
@Index()
|
@Index()
|
||||||
@Column({ nullable: false, unique: true })
|
@Column({ nullable: false, unique: true })
|
||||||
override key: string;
|
key: string;
|
||||||
|
|
||||||
@Column({ nullable: false })
|
@Column({ nullable: false })
|
||||||
override value: string;
|
value: string;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,25 +1,27 @@
|
||||||
import { IsOptional, IsString } from 'class-validator';
|
import { EUserSchema } 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';
|
||||||
|
import { z } from 'zod';
|
||||||
|
|
||||||
// Different data for public and private
|
// Different data for public and private
|
||||||
|
const OverriddenEUserSchema = EUserSchema.omit({ hashedPassword: true }).merge(
|
||||||
|
z.object({
|
||||||
|
hashedPassword: z.string().optional(),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
type OverriddenEUser = z.infer<typeof OverriddenEUserSchema>;
|
||||||
|
|
||||||
@Entity()
|
@Entity()
|
||||||
export class EUserBackend extends EUser {
|
export class EUserBackend implements OverriddenEUser {
|
||||||
@PrimaryGeneratedColumn("uuid")
|
@PrimaryGeneratedColumn('uuid', {})
|
||||||
override id?: string;
|
id?: string;
|
||||||
|
|
||||||
@Index()
|
@Index()
|
||||||
@Column({ nullable: false, unique: true })
|
@Column({ nullable: false, unique: true })
|
||||||
override username: string;
|
username: string;
|
||||||
|
|
||||||
@Column('text', { nullable: false, array: true })
|
@Column('text', { nullable: false, array: true })
|
||||||
override roles: string[];
|
roles: string[];
|
||||||
|
|
||||||
@Column({ nullable: false, select: false })
|
@Column({ nullable: false, select: false })
|
||||||
@IsOptional()
|
hashedPassword?: string;
|
||||||
@IsString()
|
|
||||||
// @ts-ignore
|
|
||||||
override hashedPassword?: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,18 +1,18 @@
|
||||||
import { EUsrPreference } from 'picsur-shared/dist/entities/usrpreference';
|
import { EUsrPreference } from 'picsur-shared/dist/entities/usrpreference';
|
||||||
import { Column, Index, PrimaryGeneratedColumn } from 'typeorm';
|
import { Column, Index, PrimaryGeneratedColumn } from 'typeorm';
|
||||||
|
|
||||||
export class EUsrPreferenceBackend extends EUsrPreference {
|
export class EUsrPreferenceBackend implements EUsrPreference {
|
||||||
@PrimaryGeneratedColumn("uuid")
|
@PrimaryGeneratedColumn('uuid')
|
||||||
override id?: string;
|
id?: string;
|
||||||
|
|
||||||
@Index()
|
@Index()
|
||||||
@Column({ nullable: false, unique: true })
|
@Column({ nullable: false, unique: true })
|
||||||
override key: string;
|
key: string;
|
||||||
|
|
||||||
@Column({ nullable: false })
|
@Column({ nullable: false })
|
||||||
override value: string;
|
value: string;
|
||||||
|
|
||||||
@Index()
|
@Index()
|
||||||
@Column({ nullable: false })
|
@Column({ nullable: false })
|
||||||
override userId: number;
|
userId: number;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { FastifyRequest } from 'fastify';
|
import { FastifyRequest } from 'fastify';
|
||||||
import { EUserBackend } from '../entities/user.entity';
|
import { EUser } from 'picsur-shared/dist/entities/user.entity';
|
||||||
|
|
||||||
// Add typing to FastifyRequest to make using the user object easier
|
// Add typing to FastifyRequest to make using the user object easier
|
||||||
export default interface AuthFasityRequest extends FastifyRequest {
|
export default interface AuthFasityRequest extends FastifyRequest {
|
||||||
user: EUserBackend;
|
user: EUser;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
import { IsMultiPartFile } from '../validators/multipart.validator';
|
import { createZodDto } from 'picsur-shared/dist/util/create-zod-dto';
|
||||||
import { MultiPartFileDto } from './multipart.dto';
|
import { z } from 'zod';
|
||||||
|
import { MultiPartFileDtoSchema } from './multipart.dto';
|
||||||
|
|
||||||
// A validation class for form based file upload of an image
|
// A validation class for form based file upload of an image
|
||||||
export class ImageUploadDto {
|
export const ImageUploadDtoSchema = z.object({
|
||||||
@IsMultiPartFile()
|
image: MultiPartFileDtoSchema,
|
||||||
image: MultiPartFileDto
|
});
|
||||||
}
|
export class ImageUploadDto extends createZodDto(ImageUploadDtoSchema) {}
|
||||||
|
|
|
@ -1,64 +1,48 @@
|
||||||
import { BusboyFileStream } from '@fastify/busboy';
|
|
||||||
import { HttpException } from '@nestjs/common';
|
import { HttpException } from '@nestjs/common';
|
||||||
import { IsDefined, IsNotEmpty, IsString } from 'class-validator';
|
|
||||||
import { MultipartFile } from 'fastify-multipart';
|
import { MultipartFile } from 'fastify-multipart';
|
||||||
|
import { z } from 'zod';
|
||||||
|
|
||||||
export class MultiPartFileDto {
|
export const MultiPartFileDtoSchema = z.object({
|
||||||
@IsString()
|
fieldname: z.string(),
|
||||||
@IsNotEmpty()
|
encoding: z.string(),
|
||||||
fieldname: string;
|
filename: z.string(),
|
||||||
|
mimetype: z.string(),
|
||||||
|
toBuffer: z.function(undefined, z.any()),
|
||||||
|
file: z.any(),
|
||||||
|
});
|
||||||
|
export type MultiPartFileDto = z.infer<typeof MultiPartFileDtoSchema>;
|
||||||
|
|
||||||
@IsString()
|
export function CreateMultiPartFileDto(
|
||||||
@IsNotEmpty()
|
file: MultipartFile,
|
||||||
encoding: string;
|
exceptionOnFail: HttpException,
|
||||||
|
): MultiPartFileDto {
|
||||||
@IsString()
|
return {
|
||||||
@IsNotEmpty()
|
fieldname: file.fieldname,
|
||||||
filename: string;
|
encoding: file.encoding,
|
||||||
|
filename: file.filename,
|
||||||
@IsString()
|
mimetype: file.mimetype,
|
||||||
@IsNotEmpty()
|
toBuffer: async () => {
|
||||||
mimetype: string;
|
|
||||||
|
|
||||||
@IsDefined()
|
|
||||||
toBuffer: () => Promise<Buffer>;
|
|
||||||
|
|
||||||
@IsDefined()
|
|
||||||
file: BusboyFileStream;
|
|
||||||
|
|
||||||
constructor(file: MultipartFile, exceptionOnFail: HttpException) {
|
|
||||||
this.fieldname = file.fieldname;
|
|
||||||
this.encoding = file.encoding;
|
|
||||||
this.filename = file.filename;
|
|
||||||
this.mimetype = file.mimetype;
|
|
||||||
this.toBuffer = async () => {
|
|
||||||
try {
|
try {
|
||||||
return await file.toBuffer();
|
return await file.toBuffer();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
throw exceptionOnFail;
|
throw exceptionOnFail;
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
file: file.file,
|
||||||
};
|
};
|
||||||
|
|
||||||
this.file = file.file;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export class MultiPartFieldDto {
|
export const MultiPartFieldDtoSchema = z.object({
|
||||||
@IsString()
|
fieldname: z.string(),
|
||||||
@IsNotEmpty()
|
encoding: z.string(),
|
||||||
fieldname: string;
|
value: z.string(),
|
||||||
|
});
|
||||||
|
export type MultiPartFieldDto = z.infer<typeof MultiPartFieldDtoSchema>;
|
||||||
|
|
||||||
@IsString()
|
export function CreateMultiPartFieldDto(file: MultipartFile): MultiPartFieldDto {
|
||||||
@IsNotEmpty()
|
return {
|
||||||
encoding: string;
|
fieldname: file.fieldname,
|
||||||
|
encoding: file.encoding,
|
||||||
@IsString()
|
value: (file as any).value,
|
||||||
@IsNotEmpty()
|
};
|
||||||
value: string;
|
|
||||||
|
|
||||||
constructor(file: MultipartFile) {
|
|
||||||
this.fieldname = file.fieldname;
|
|
||||||
this.encoding = file.encoding;
|
|
||||||
this.value = (file as any).value;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,17 +0,0 @@
|
||||||
import { Type } from 'class-transformer';
|
|
||||||
import { IsDefined, ValidateNested } from 'class-validator';
|
|
||||||
import { CombinePDecorators } from 'picsur-shared/dist/util/decorator';
|
|
||||||
import { MultiPartFieldDto, MultiPartFileDto } from '../requests/multipart.dto';
|
|
||||||
|
|
||||||
|
|
||||||
export const IsMultiPartFile = CombinePDecorators(
|
|
||||||
IsDefined(),
|
|
||||||
ValidateNested(),
|
|
||||||
Type(() => MultiPartFileDto),
|
|
||||||
);
|
|
||||||
|
|
||||||
export const IsMultiPartField = CombinePDecorators(
|
|
||||||
IsDefined(),
|
|
||||||
ValidateNested(),
|
|
||||||
Type(() => MultiPartFieldDto),
|
|
||||||
);
|
|
|
@ -1,11 +1,11 @@
|
||||||
import { Controller, Get } from '@nestjs/common';
|
import { Controller, Get } from '@nestjs/common';
|
||||||
import { plainToClass } from 'class-transformer';
|
|
||||||
import {
|
import {
|
||||||
AllPermissionsResponse,
|
AllPermissionsResponse,
|
||||||
InfoResponse
|
InfoResponse
|
||||||
} from 'picsur-shared/dist/dto/api/info.dto';
|
} from 'picsur-shared/dist/dto/api/info.dto';
|
||||||
import { HostConfigService } from '../../../config/early/host.config.service';
|
import { HostConfigService } from '../../../config/early/host.config.service';
|
||||||
import { NoPermissions } from '../../../decorators/permissions.decorator';
|
import { NoPermissions } from '../../../decorators/permissions.decorator';
|
||||||
|
import { Returns } from '../../../decorators/returns.decorator';
|
||||||
import { PermissionsList } from '../../../models/dto/permissions.dto';
|
import { PermissionsList } from '../../../models/dto/permissions.dto';
|
||||||
|
|
||||||
@Controller('api/info')
|
@Controller('api/info')
|
||||||
|
@ -14,6 +14,7 @@ export class InfoController {
|
||||||
constructor(private hostConfig: HostConfigService) {}
|
constructor(private hostConfig: HostConfigService) {}
|
||||||
|
|
||||||
@Get()
|
@Get()
|
||||||
|
@Returns(InfoResponse)
|
||||||
async getInfo(): Promise<InfoResponse> {
|
async getInfo(): Promise<InfoResponse> {
|
||||||
return {
|
return {
|
||||||
demo: this.hostConfig.isDemo(),
|
demo: this.hostConfig.isDemo(),
|
||||||
|
@ -24,11 +25,10 @@ export class InfoController {
|
||||||
|
|
||||||
// List all available permissions
|
// List all available permissions
|
||||||
@Get('permissions')
|
@Get('permissions')
|
||||||
|
@Returns(AllPermissionsResponse)
|
||||||
async getPermissions(): Promise<AllPermissionsResponse> {
|
async getPermissions(): Promise<AllPermissionsResponse> {
|
||||||
const result: AllPermissionsResponse = {
|
return {
|
||||||
permissions: PermissionsList,
|
permissions: PermissionsList,
|
||||||
};
|
};
|
||||||
|
|
||||||
return plainToClass(AllPermissionsResponse, result);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,13 +8,15 @@ import {
|
||||||
Post
|
Post
|
||||||
} from '@nestjs/common';
|
} from '@nestjs/common';
|
||||||
import {
|
import {
|
||||||
GetSyspreferenceResponse,
|
GetSysPreferenceResponse,
|
||||||
MultipleSysPreferencesResponse, UpdateSysPreferenceRequest,
|
MultipleSysPreferencesResponse,
|
||||||
|
UpdateSysPreferenceRequest,
|
||||||
UpdateSysPreferenceResponse
|
UpdateSysPreferenceResponse
|
||||||
} from 'picsur-shared/dist/dto/api/syspref.dto';
|
} from 'picsur-shared/dist/dto/api/syspref.dto';
|
||||||
import { HasFailed } from 'picsur-shared/dist/types';
|
import { HasFailed } from 'picsur-shared/dist/types';
|
||||||
import { SysPreferenceService } from '../../../collections/syspreferencesdb/syspreferencedb.service';
|
import { SysPreferenceService } from '../../../collections/syspreferencesdb/syspreferencedb.service';
|
||||||
import { RequiredPermissions } from '../../../decorators/permissions.decorator';
|
import { RequiredPermissions } from '../../../decorators/permissions.decorator';
|
||||||
|
import { Returns } from '../../../decorators/returns.decorator';
|
||||||
import { Permission } from '../../../models/dto/permissions.dto';
|
import { Permission } from '../../../models/dto/permissions.dto';
|
||||||
|
|
||||||
@Controller('api/pref')
|
@Controller('api/pref')
|
||||||
|
@ -25,6 +27,7 @@ export class PrefController {
|
||||||
constructor(private prefService: SysPreferenceService) {}
|
constructor(private prefService: SysPreferenceService) {}
|
||||||
|
|
||||||
@Get('sys')
|
@Get('sys')
|
||||||
|
@Returns(MultipleSysPreferencesResponse)
|
||||||
async getAllSysPrefs(): Promise<MultipleSysPreferencesResponse> {
|
async getAllSysPrefs(): Promise<MultipleSysPreferencesResponse> {
|
||||||
const prefs = await this.prefService.getAllPreferences();
|
const prefs = await this.prefService.getAllPreferences();
|
||||||
if (HasFailed(prefs)) {
|
if (HasFailed(prefs)) {
|
||||||
|
@ -39,9 +42,10 @@ export class PrefController {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get('sys/:key')
|
@Get('sys/:key')
|
||||||
|
@Returns(GetSysPreferenceResponse)
|
||||||
async getSysPref(
|
async getSysPref(
|
||||||
@Param('key') key: string,
|
@Param('key') key: string,
|
||||||
): Promise<GetSyspreferenceResponse> {
|
): Promise<GetSysPreferenceResponse> {
|
||||||
const pref = await this.prefService.getPreference(key);
|
const pref = await this.prefService.getPreference(key);
|
||||||
if (HasFailed(pref)) {
|
if (HasFailed(pref)) {
|
||||||
this.logger.warn(pref.getReason());
|
this.logger.warn(pref.getReason());
|
||||||
|
@ -52,6 +56,7 @@ export class PrefController {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Post('sys/:key')
|
@Post('sys/:key')
|
||||||
|
@Returns(UpdateSysPreferenceResponse)
|
||||||
async setSysPref(
|
async setSysPref(
|
||||||
@Param('key') key: string,
|
@Param('key') key: string,
|
||||||
@Body() body: UpdateSysPreferenceRequest,
|
@Body() body: UpdateSysPreferenceRequest,
|
||||||
|
|
|
@ -22,6 +22,7 @@ import { HasFailed } from 'picsur-shared/dist/types';
|
||||||
import { RolesService } from '../../../collections/roledb/roledb.service';
|
import { RolesService } from '../../../collections/roledb/roledb.service';
|
||||||
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 { Returns } from '../../../decorators/returns.decorator';
|
||||||
import { Permission } from '../../../models/dto/permissions.dto';
|
import { Permission } from '../../../models/dto/permissions.dto';
|
||||||
import {
|
import {
|
||||||
DefaultRolesList,
|
DefaultRolesList,
|
||||||
|
@ -42,6 +43,7 @@ export class RolesController {
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
@Get('list')
|
@Get('list')
|
||||||
|
@Returns(RoleListResponse)
|
||||||
async getRoles(): Promise<RoleListResponse> {
|
async getRoles(): Promise<RoleListResponse> {
|
||||||
const roles = await this.rolesService.findAll();
|
const roles = await this.rolesService.findAll();
|
||||||
if (HasFailed(roles)) {
|
if (HasFailed(roles)) {
|
||||||
|
@ -56,6 +58,7 @@ export class RolesController {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Post('info')
|
@Post('info')
|
||||||
|
@Returns(RoleInfoResponse)
|
||||||
async getRole(@Body() body: RoleInfoRequest): Promise<RoleInfoResponse> {
|
async getRole(@Body() body: RoleInfoRequest): Promise<RoleInfoResponse> {
|
||||||
const role = await this.rolesService.findOne(body.name);
|
const role = await this.rolesService.findOne(body.name);
|
||||||
if (HasFailed(role)) {
|
if (HasFailed(role)) {
|
||||||
|
@ -67,6 +70,7 @@ export class RolesController {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Post('update')
|
@Post('update')
|
||||||
|
@Returns(RoleUpdateResponse)
|
||||||
async updateRole(
|
async updateRole(
|
||||||
@Body() body: RoleUpdateRequest,
|
@Body() body: RoleUpdateRequest,
|
||||||
): Promise<RoleUpdateResponse> {
|
): Promise<RoleUpdateResponse> {
|
||||||
|
@ -88,6 +92,7 @@ export class RolesController {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Post('create')
|
@Post('create')
|
||||||
|
@Returns(RoleCreateResponse)
|
||||||
async createRole(
|
async createRole(
|
||||||
@Body() role: RoleCreateRequest,
|
@Body() role: RoleCreateRequest,
|
||||||
): Promise<RoleCreateResponse> {
|
): Promise<RoleCreateResponse> {
|
||||||
|
@ -106,6 +111,7 @@ export class RolesController {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Post('delete')
|
@Post('delete')
|
||||||
|
@Returns(RoleDeleteResponse)
|
||||||
async deleteRole(
|
async deleteRole(
|
||||||
@Body() role: RoleDeleteRequest,
|
@Body() role: RoleDeleteRequest,
|
||||||
): Promise<RoleDeleteResponse> {
|
): Promise<RoleDeleteResponse> {
|
||||||
|
@ -126,6 +132,7 @@ export class RolesController {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get('special')
|
@Get('special')
|
||||||
|
@Returns(SpecialRolesResponse)
|
||||||
async getSpecialRoles(): Promise<SpecialRolesResponse> {
|
async getSpecialRoles(): Promise<SpecialRolesResponse> {
|
||||||
return {
|
return {
|
||||||
SoulBoundRoles: SoulBoundRolesList,
|
SoulBoundRoles: SoulBoundRolesList,
|
||||||
|
|
|
@ -8,8 +8,7 @@ import {
|
||||||
Request
|
Request
|
||||||
} from '@nestjs/common';
|
} from '@nestjs/common';
|
||||||
import {
|
import {
|
||||||
UserLoginResponse,
|
UserLoginResponse, UserMePermissionsResponse,
|
||||||
UserMePermissionsResponse,
|
|
||||||
UserMeResponse,
|
UserMeResponse,
|
||||||
UserRegisterRequest,
|
UserRegisterRequest,
|
||||||
UserRegisterResponse
|
UserRegisterResponse
|
||||||
|
@ -21,6 +20,7 @@ import {
|
||||||
RequiredPermissions,
|
RequiredPermissions,
|
||||||
UseLocalAuth
|
UseLocalAuth
|
||||||
} from '../../../decorators/permissions.decorator';
|
} from '../../../decorators/permissions.decorator';
|
||||||
|
import { Returns } from '../../../decorators/returns.decorator';
|
||||||
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';
|
||||||
|
@ -36,6 +36,7 @@ export class UserController {
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
@Post('login')
|
@Post('login')
|
||||||
|
@Returns(UserLoginResponse)
|
||||||
@UseLocalAuth(Permission.UserLogin)
|
@UseLocalAuth(Permission.UserLogin)
|
||||||
async login(@Request() req: AuthFasityRequest): Promise<UserLoginResponse> {
|
async login(@Request() req: AuthFasityRequest): Promise<UserLoginResponse> {
|
||||||
const jwt_token = await this.authService.createToken(req.user);
|
const jwt_token = await this.authService.createToken(req.user);
|
||||||
|
@ -48,6 +49,7 @@ export class UserController {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Post('register')
|
@Post('register')
|
||||||
|
@Returns(UserRegisterResponse)
|
||||||
@RequiredPermissions(Permission.UserRegister)
|
@RequiredPermissions(Permission.UserRegister)
|
||||||
async register(
|
async register(
|
||||||
@Body() register: UserRegisterRequest,
|
@Body() register: UserRegisterRequest,
|
||||||
|
@ -65,28 +67,32 @@ export class UserController {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get('me')
|
@Get('me')
|
||||||
|
@Returns(UserMeResponse)
|
||||||
@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');
|
if (!req.user.id) throw new InternalServerErrorException('User is corrupt');
|
||||||
|
|
||||||
const user = await this.usersService.findOne(req.user.id);
|
const backenduser = await this.usersService.findOne(req.user.id);
|
||||||
|
|
||||||
if (HasFailed(user)) {
|
if (HasFailed(backenduser)) {
|
||||||
this.logger.warn(user.getReason());
|
this.logger.warn(backenduser.getReason());
|
||||||
throw new InternalServerErrorException('Could not get user');
|
throw new InternalServerErrorException('Could not get user');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const user = EUserBackend2EUser(backenduser);
|
||||||
|
|
||||||
const token = await this.authService.createToken(user);
|
const token = await this.authService.createToken(user);
|
||||||
if (HasFailed(token)) {
|
if (HasFailed(token)) {
|
||||||
this.logger.warn(token.getReason());
|
this.logger.warn(token.getReason());
|
||||||
throw new InternalServerErrorException('Could not get new token');
|
throw new InternalServerErrorException('Could not get new token');
|
||||||
}
|
}
|
||||||
|
|
||||||
return { user: EUserBackend2EUser(user), token };
|
return { user, token };
|
||||||
}
|
}
|
||||||
|
|
||||||
// You can always check your permissions
|
// You can always check your permissions
|
||||||
@Get('me/permissions')
|
@Get('me/permissions')
|
||||||
|
@Returns(UserMePermissionsResponse)
|
||||||
@NoPermissions()
|
@NoPermissions()
|
||||||
async refresh(
|
async refresh(
|
||||||
@Request() req: AuthFasityRequest,
|
@Request() req: AuthFasityRequest,
|
||||||
|
|
|
@ -6,7 +6,6 @@ import {
|
||||||
Logger,
|
Logger,
|
||||||
Post
|
Post
|
||||||
} from '@nestjs/common';
|
} from '@nestjs/common';
|
||||||
import { plainToClass } from 'class-transformer';
|
|
||||||
import {
|
import {
|
||||||
GetSpecialUsersResponse,
|
GetSpecialUsersResponse,
|
||||||
UserCreateRequest,
|
UserCreateRequest,
|
||||||
|
@ -23,6 +22,7 @@ import {
|
||||||
import { HasFailed } from 'picsur-shared/dist/types';
|
import { HasFailed } from 'picsur-shared/dist/types';
|
||||||
import { UsersService } from '../../../collections/userdb/userdb.service';
|
import { UsersService } from '../../../collections/userdb/userdb.service';
|
||||||
import { RequiredPermissions } from '../../../decorators/permissions.decorator';
|
import { RequiredPermissions } from '../../../decorators/permissions.decorator';
|
||||||
|
import { Returns } from '../../../decorators/returns.decorator';
|
||||||
import { Permission } from '../../../models/dto/permissions.dto';
|
import { Permission } from '../../../models/dto/permissions.dto';
|
||||||
import {
|
import {
|
||||||
ImmutableUsersList,
|
ImmutableUsersList,
|
||||||
|
@ -39,15 +39,16 @@ export class UserManageController {
|
||||||
constructor(private usersService: UsersService) {}
|
constructor(private usersService: UsersService) {}
|
||||||
|
|
||||||
@Get('list')
|
@Get('list')
|
||||||
|
@Returns(UserListResponse)
|
||||||
async listUsers(): Promise<UserListResponse> {
|
async listUsers(): Promise<UserListResponse> {
|
||||||
const body = new UserListRequest();
|
return this.listUsersPaged({
|
||||||
body.count = 20;
|
count: 20,
|
||||||
body.page = 0;
|
page: 0,
|
||||||
|
});
|
||||||
return this.listUsersPaged(body);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Post('list')
|
@Post('list')
|
||||||
|
@Returns(UserListResponse)
|
||||||
async listUsersPaged(
|
async listUsersPaged(
|
||||||
@Body() body: UserListRequest,
|
@Body() body: UserListRequest,
|
||||||
): Promise<UserListResponse> {
|
): Promise<UserListResponse> {
|
||||||
|
@ -65,6 +66,7 @@ export class UserManageController {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Post('create')
|
@Post('create')
|
||||||
|
@Returns(UserCreateResponse)
|
||||||
async register(
|
async register(
|
||||||
@Body() create: UserCreateRequest,
|
@Body() create: UserCreateRequest,
|
||||||
): Promise<UserCreateResponse> {
|
): Promise<UserCreateResponse> {
|
||||||
|
@ -82,6 +84,7 @@ export class UserManageController {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Post('delete')
|
@Post('delete')
|
||||||
|
@Returns(UserDeleteResponse)
|
||||||
async delete(@Body() body: UserDeleteRequest): Promise<UserDeleteResponse> {
|
async delete(@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)) {
|
||||||
|
@ -93,6 +96,7 @@ export class UserManageController {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Post('info')
|
@Post('info')
|
||||||
|
@Returns(UserInfoResponse)
|
||||||
async getUser(@Body() body: UserInfoRequest): Promise<UserInfoResponse> {
|
async getUser(@Body() body: UserInfoRequest): Promise<UserInfoResponse> {
|
||||||
const user = await this.usersService.findOne(body.id);
|
const user = await this.usersService.findOne(body.id);
|
||||||
if (HasFailed(user)) {
|
if (HasFailed(user)) {
|
||||||
|
@ -104,6 +108,7 @@ export class UserManageController {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Post('update')
|
@Post('update')
|
||||||
|
@Returns(UserUpdateResponse)
|
||||||
async setPermissions(
|
async setPermissions(
|
||||||
@Body() body: UserUpdateRequest,
|
@Body() body: UserUpdateRequest,
|
||||||
): Promise<UserUpdateResponse> {
|
): Promise<UserUpdateResponse> {
|
||||||
|
@ -133,13 +138,12 @@ export class UserManageController {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get('special')
|
@Get('special')
|
||||||
|
@Returns(GetSpecialUsersResponse)
|
||||||
async getSpecial(): Promise<GetSpecialUsersResponse> {
|
async getSpecial(): Promise<GetSpecialUsersResponse> {
|
||||||
const result: GetSpecialUsersResponse = {
|
return {
|
||||||
ImmutableUsersList,
|
ImmutableUsersList,
|
||||||
LockedLoginUsersList,
|
LockedLoginUsersList,
|
||||||
UndeletableUsersList,
|
UndeletableUsersList,
|
||||||
};
|
};
|
||||||
|
|
||||||
return plainToClass(GetSpecialUsersResponse, result);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,14 +4,13 @@ import {
|
||||||
Injectable,
|
Injectable,
|
||||||
PipeTransform
|
PipeTransform
|
||||||
} from '@nestjs/common';
|
} from '@nestjs/common';
|
||||||
import { isHash } from 'class-validator';
|
import { SHA256 } from 'picsur-shared/dist/util/common-regex';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class ImageIdValidator implements PipeTransform<string, string> {
|
export class ImageIdValidator implements PipeTransform<string, string> {
|
||||||
transform(value: string, metadata: ArgumentMetadata): string {
|
transform(value: string, metadata: ArgumentMetadata): string {
|
||||||
if (isHash(value, 'sha256')) {
|
// Check regex for sha256
|
||||||
return value;
|
if (SHA256.test(value)) return value;
|
||||||
}
|
|
||||||
throw new BadRequestException('Invalid image id');
|
throw new BadRequestException('Invalid image id');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,9 +13,12 @@ import { ImageMetaResponse } from 'picsur-shared/dist/dto/api/image.dto';
|
||||||
import { HasFailed } from 'picsur-shared/dist/types';
|
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 { Returns } from '../../decorators/returns.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 { EImageBackend2EImage } from '../../models/transformers/image.transformer';
|
||||||
import { ImageIdValidator } from './imageid.validator';
|
import { ImageIdValidator } from './imageid.validator';
|
||||||
|
|
||||||
|
@ -45,6 +48,7 @@ export class ImageController {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get('meta/:hash')
|
@Get('meta/:hash')
|
||||||
|
@Returns(ImageMetaResponse)
|
||||||
async getImageMeta(
|
async getImageMeta(
|
||||||
@Param('hash', ImageIdValidator) hash: string,
|
@Param('hash', ImageIdValidator) hash: string,
|
||||||
): Promise<ImageMetaResponse> {
|
): Promise<ImageMetaResponse> {
|
||||||
|
@ -58,9 +62,10 @@ export class ImageController {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Post()
|
@Post()
|
||||||
|
@Returns(ImageMetaResponse)
|
||||||
@RequiredPermissions(Permission.ImageUpload)
|
@RequiredPermissions(Permission.ImageUpload)
|
||||||
async uploadImage(
|
async uploadImage(
|
||||||
@MultiPart(ImageUploadDto) multipart: ImageUploadDto,
|
@MultiPart() multipart: ImageUploadDto,
|
||||||
): Promise<ImageMetaResponse> {
|
): Promise<ImageMetaResponse> {
|
||||||
const fileBuffer = await multipart.image.toBuffer();
|
const fileBuffer = await multipart.image.toBuffer();
|
||||||
const image = await this.imagesService.upload(fileBuffer);
|
const image = await this.imagesService.upload(fileBuffer);
|
||||||
|
|
|
@ -24,8 +24,6 @@
|
||||||
"@angular/platform-browser-dynamic": "^14.0.0-next.10",
|
"@angular/platform-browser-dynamic": "^14.0.0-next.10",
|
||||||
"@angular/router": "^14.0.0-next.10",
|
"@angular/router": "^14.0.0-next.10",
|
||||||
"bootstrap": "^5.1.3",
|
"bootstrap": "^5.1.3",
|
||||||
"class-transformer": "^0.5.1",
|
|
||||||
"class-validator": "^0.13.2",
|
|
||||||
"fuse.js": "^6.5.3",
|
"fuse.js": "^6.5.3",
|
||||||
"jwt-decode": "^3.1.2",
|
"jwt-decode": "^3.1.2",
|
||||||
"ngx-auto-unsubscribe-decorator": "^0.1.0",
|
"ngx-auto-unsubscribe-decorator": "^0.1.0",
|
||||||
|
@ -34,6 +32,7 @@
|
||||||
"reflect-metadata": "^0.1.13",
|
"reflect-metadata": "^0.1.13",
|
||||||
"rxjs": "~7.5.5",
|
"rxjs": "~7.5.5",
|
||||||
"tslib": "^2.3.1",
|
"tslib": "^2.3.1",
|
||||||
|
"zod": "^3.14.3",
|
||||||
"zone.js": "~0.11.5"
|
"zone.js": "~0.11.5"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import { Component, OnInit } from '@angular/core';
|
import { Component, OnInit } from '@angular/core';
|
||||||
import { ActivatedRoute, Router } from '@angular/router';
|
import { ActivatedRoute, Router } from '@angular/router';
|
||||||
import { isHash } from 'class-validator';
|
|
||||||
import { ImageLinks } from 'picsur-shared/dist/dto/imagelinks.dto';
|
import { ImageLinks } from 'picsur-shared/dist/dto/imagelinks.dto';
|
||||||
import { HasFailed } from 'picsur-shared/dist/types';
|
import { HasFailed } from 'picsur-shared/dist/types';
|
||||||
|
import { SHA256 } from 'picsur-shared/dist/util/common-regex';
|
||||||
import { ImageService } from 'src/app/services/api/image.service';
|
import { ImageService } from 'src/app/services/api/image.service';
|
||||||
import { UtilService } from 'src/app/util/util.service';
|
import { UtilService } from 'src/app/util/util.service';
|
||||||
|
|
||||||
|
@ -24,7 +24,7 @@ export class ViewComponent implements OnInit {
|
||||||
async ngOnInit() {
|
async ngOnInit() {
|
||||||
const params = this.route.snapshot.paramMap;
|
const params = this.route.snapshot.paramMap;
|
||||||
const hash = params.get('hash') ?? '';
|
const hash = params.get('hash') ?? '';
|
||||||
if (!isHash(hash, 'sha256')) {
|
if (!SHA256.test(hash)) {
|
||||||
return this.utilService.quitError('Invalid image link');
|
return this.utilService.quitError('Invalid image link');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,13 +1,10 @@
|
||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { ClassConstructor, plainToClass } from 'class-transformer';
|
import { ApiResponseSchema } from 'picsur-shared/dist/dto/api/api.dto';
|
||||||
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 { ZodDtoStatic } from 'picsur-shared/dist/util/create-zod-dto';
|
||||||
import { Subject } from 'rxjs';
|
import { Subject } from 'rxjs';
|
||||||
import { ApiError } from 'src/app/models/dto/api-error.dto';
|
import { ApiError } from 'src/app/models/dto/api-error.dto';
|
||||||
|
import { z } from 'zod';
|
||||||
import { MultiPartRequest } from '../../models/dto/multi-part-request.dto';
|
import { MultiPartRequest } from '../../models/dto/multi-part-request.dto';
|
||||||
import { Logger } from '../logger/logger.service';
|
import { Logger } from '../logger/logger.service';
|
||||||
import { KeyService } from '../storage/key.service';
|
import { KeyService } from '../storage/key.service';
|
||||||
|
@ -30,72 +27,64 @@ export class ApiService {
|
||||||
|
|
||||||
constructor(private keyService: KeyService) {}
|
constructor(private keyService: KeyService) {}
|
||||||
|
|
||||||
public async get<T extends Object>(
|
public async get<T extends z.AnyZodObject>(
|
||||||
type: ClassConstructor<T>,
|
type: ZodDtoStatic<T>,
|
||||||
url: string
|
url: string
|
||||||
): AsyncFailable<T> {
|
): AsyncFailable<z.infer<T>> {
|
||||||
return this.fetchSafeJson(type, url, { method: 'GET' });
|
return this.fetchSafeJson(type, url, { method: 'GET' });
|
||||||
}
|
}
|
||||||
|
|
||||||
public async post<T extends Object, W extends Object>(
|
public async post<T extends z.AnyZodObject, W extends z.AnyZodObject>(
|
||||||
sendType: ClassConstructor<T>,
|
sendType: ZodDtoStatic<T>,
|
||||||
receiveType: ClassConstructor<W>,
|
receiveType: ZodDtoStatic<W>,
|
||||||
url: string,
|
url: string,
|
||||||
data: T
|
data: z.infer<T>
|
||||||
): AsyncFailable<W> {
|
): AsyncFailable<z.infer<W>> {
|
||||||
const sendClass = plainToClass(sendType, data);
|
const sendSchema = sendType.zodSchema;
|
||||||
const errors = await strictValidate(sendClass);
|
|
||||||
if (errors.length > 0) {
|
const validateResult = sendSchema.safeParse(data);
|
||||||
this.logger.warn(errors);
|
if (!validateResult.success) {
|
||||||
|
this.logger.warn(validateResult.error);
|
||||||
return Fail('Something went wrong');
|
return Fail('Something went wrong');
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.fetchSafeJson(receiveType, url, {
|
return this.fetchSafeJson(receiveType, url, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: JSON.stringify(sendClass),
|
body: JSON.stringify(validateResult.data),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public async postForm<T extends Object>(
|
public async postForm<T extends z.AnyZodObject>(
|
||||||
receiveType: ClassConstructor<T>,
|
receiveType: ZodDtoStatic<T>,
|
||||||
url: string,
|
url: string,
|
||||||
data: MultiPartRequest
|
data: MultiPartRequest
|
||||||
): AsyncFailable<T> {
|
): AsyncFailable<z.infer<T>> {
|
||||||
return this.fetchSafeJson(receiveType, url, {
|
return this.fetchSafeJson(receiveType, url, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: data.createFormData(),
|
body: data.createFormData(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private async fetchSafeJson<T extends Object>(
|
private async fetchSafeJson<T extends z.AnyZodObject>(
|
||||||
type: ClassConstructor<T>,
|
type: ZodDtoStatic<T>,
|
||||||
url: RequestInfo,
|
url: RequestInfo,
|
||||||
options: RequestInit
|
options: RequestInit
|
||||||
): AsyncFailable<T> {
|
): AsyncFailable<z.infer<T>> {
|
||||||
let result = await this.fetchJsonAs<ApiResponse<T>>(url, options);
|
const resultSchema = ApiResponseSchema(type.zodSchema as z.AnyZodObject);
|
||||||
|
type resultType = z.infer<typeof resultSchema>;
|
||||||
|
|
||||||
|
let result = await this.fetchJsonAs<resultType>(url, options);
|
||||||
if (HasFailed(result)) return result;
|
if (HasFailed(result)) return result;
|
||||||
|
|
||||||
if (result.success === false) return Fail(result.data.message);
|
const validateResult = resultSchema.safeParse(result);
|
||||||
|
if (!validateResult.success) {
|
||||||
const resultClass = plainToClass<
|
this.logger.warn('result', validateResult.error);
|
||||||
ApiSuccessResponse<T>,
|
|
||||||
ApiSuccessResponse<T>
|
|
||||||
>(ApiSuccessResponse, result);
|
|
||||||
|
|
||||||
const resultErrors = await strictValidate(resultClass);
|
|
||||||
if (resultErrors.length > 0) {
|
|
||||||
this.logger.warn('result', resultErrors);
|
|
||||||
return Fail('Something went wrong');
|
return Fail('Something went wrong');
|
||||||
}
|
}
|
||||||
|
|
||||||
const dataClass = plainToClass(type, result.data);
|
if (validateResult.data.success === false) return Fail(result.data.message);
|
||||||
const dataErrors = await strictValidate(dataClass);
|
|
||||||
if (dataErrors.length > 0) {
|
|
||||||
this.logger.warn('data', dataErrors);
|
|
||||||
return Fail('Something went wrong');
|
|
||||||
}
|
|
||||||
|
|
||||||
return result.data;
|
return validateResult.data.data;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async fetchJsonAs<T>(
|
private async fetchJsonAs<T>(
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { plainToClass } from 'class-transformer';
|
|
||||||
import { isSemVer } from 'class-validator';
|
|
||||||
import { InfoResponse } from 'picsur-shared/dist/dto/api/info.dto';
|
import { InfoResponse } from 'picsur-shared/dist/dto/api/info.dto';
|
||||||
import {
|
import {
|
||||||
AsyncFailable,
|
AsyncFailable,
|
||||||
Fail, HasFailed
|
Fail, HasFailed
|
||||||
} from 'picsur-shared/dist/types';
|
} from 'picsur-shared/dist/types';
|
||||||
|
import { SemVer } from 'picsur-shared/dist/util/common-regex';
|
||||||
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';
|
||||||
import { UtilService } from 'src/app/util/util.service';
|
import { UtilService } from 'src/app/util/util.service';
|
||||||
|
@ -34,10 +33,8 @@ export class InfoService {
|
||||||
const response = await this.api.get(InfoResponse, '/api/info');
|
const response = await this.api.get(InfoResponse, '/api/info');
|
||||||
if (HasFailed(response)) return response;
|
if (HasFailed(response)) return response;
|
||||||
|
|
||||||
const info = plainToClass(ServerInfo, response);
|
this.infoSubject.next(response);
|
||||||
|
return response;
|
||||||
this.infoSubject.next(info);
|
|
||||||
return info;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public getFrontendVersion(): string {
|
public getFrontendVersion(): string {
|
||||||
|
@ -53,7 +50,7 @@ export class InfoService {
|
||||||
const serverVersion = info.version;
|
const serverVersion = info.version;
|
||||||
const clientVersion = this.getFrontendVersion();
|
const clientVersion = this.getFrontendVersion();
|
||||||
|
|
||||||
if (!isSemVer(serverVersion) || !isSemVer(clientVersion)) {
|
if (!SemVer.test(serverVersion) || !SemVer.test(clientVersion)) {
|
||||||
return Fail(`Not a valid semver: ${serverVersion} or ${clientVersion}`);
|
return Fail(`Not a valid semver: ${serverVersion} or ${clientVersion}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { AutoUnsubscribe } from 'ngx-auto-unsubscribe-decorator';
|
import { AutoUnsubscribe } from 'ngx-auto-unsubscribe-decorator';
|
||||||
import {
|
import {
|
||||||
GetSyspreferenceResponse,
|
GetSysPreferenceResponse,
|
||||||
MultipleSysPreferencesResponse,
|
MultipleSysPreferencesResponse,
|
||||||
UpdateSysPreferenceRequest,
|
UpdateSysPreferenceRequest,
|
||||||
UpdateSysPreferenceResponse
|
UpdateSysPreferenceResponse
|
||||||
|
@ -73,12 +73,12 @@ export class SysprefService {
|
||||||
|
|
||||||
public async getPreference(
|
public async getPreference(
|
||||||
key: string
|
key: string
|
||||||
): AsyncFailable<GetSyspreferenceResponse> {
|
): AsyncFailable<GetSysPreferenceResponse> {
|
||||||
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');
|
||||||
|
|
||||||
const response = await this.api.get(
|
const response = await this.api.get(
|
||||||
GetSyspreferenceResponse,
|
GetSysPreferenceResponse,
|
||||||
`/api/pref/sys/${key}`
|
`/api/pref/sys/${key}`
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { plainToClass } from 'class-transformer';
|
|
||||||
import jwt_decode from 'jwt-decode';
|
import jwt_decode from 'jwt-decode';
|
||||||
import {
|
import {
|
||||||
UserLoginRequest,
|
UserLoginRequest,
|
||||||
|
@ -8,10 +7,9 @@ import {
|
||||||
UserRegisterRequest,
|
UserRegisterRequest,
|
||||||
UserRegisterResponse
|
UserRegisterResponse
|
||||||
} from 'picsur-shared/dist/dto/api/user.dto';
|
} from 'picsur-shared/dist/dto/api/user.dto';
|
||||||
import { JwtDataDto } from 'picsur-shared/dist/dto/jwt.dto';
|
import { JwtDataSchema } from 'picsur-shared/dist/dto/jwt.dto';
|
||||||
import { EUser } from 'picsur-shared/dist/entities/user.entity';
|
import { EUser } from 'picsur-shared/dist/entities/user.entity';
|
||||||
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 { BehaviorSubject } from 'rxjs';
|
import { BehaviorSubject } from 'rxjs';
|
||||||
import { Logger } from '../logger/logger.service';
|
import { Logger } from '../logger/logger.service';
|
||||||
import { KeyService } from '../storage/key.service';
|
import { KeyService } from '../storage/key.service';
|
||||||
|
@ -122,14 +120,13 @@ export class UserService {
|
||||||
return Fail('Invalid token');
|
return Fail('Invalid token');
|
||||||
}
|
}
|
||||||
|
|
||||||
const jwtData = plainToClass(JwtDataDto, decoded);
|
const result = JwtDataSchema.safeParse(decoded);
|
||||||
const errors = await strictValidate(jwtData);
|
if (!result.success) {
|
||||||
if (errors.length > 0) {
|
this.logger.warn(result.error);
|
||||||
this.logger.warn(errors);
|
|
||||||
return Fail('Invalid token data');
|
return Fail('Invalid token data');
|
||||||
}
|
}
|
||||||
|
|
||||||
return jwtData.user;
|
return result.data.user;
|
||||||
}
|
}
|
||||||
|
|
||||||
// This actually fetches up to date information from the server
|
// This actually fetches up to date information from the server
|
||||||
|
|
|
@ -9,9 +9,8 @@
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "./dist/index.js",
|
"main": "./dist/index.js",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"class-transformer": "^0.5.1",
|
"tsc-watch": "^4.6.2",
|
||||||
"class-validator": "^0.13.2",
|
"zod": "^3.14.3"
|
||||||
"tsc-watch": "^4.6.2"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^17.0.23",
|
"@types/node": "^17.0.23",
|
||||||
|
|
|
@ -1,36 +1,28 @@
|
||||||
import {
|
import z from 'zod';
|
||||||
IsBoolean, IsInt,
|
|
||||||
IsNotEmpty,
|
|
||||||
IsString,
|
|
||||||
Max,
|
|
||||||
Min
|
|
||||||
} from 'class-validator';
|
|
||||||
|
|
||||||
class BaseApiResponse<T extends Object, W extends boolean> {
|
const ApiResponseBase = z.object({
|
||||||
@IsBoolean()
|
statusCode: z.number().min(0).max(600),
|
||||||
success: W;
|
timestamp: z.string(),
|
||||||
|
});
|
||||||
|
|
||||||
@IsInt()
|
const ApiSuccessResponse = <T extends z.AnyZodObject>(data: T) =>
|
||||||
@Min(0)
|
ApiResponseBase.merge(
|
||||||
@Max(1000)
|
z.object({
|
||||||
statusCode: number;
|
success: z.literal(true),
|
||||||
|
data,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
@IsString()
|
const ApiErrorResponse = ApiResponseBase.merge(
|
||||||
timestamp: string;
|
z.object({
|
||||||
|
success: z.literal(false),
|
||||||
|
data: z.object({
|
||||||
|
message: z.string(),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
@IsNotEmpty()
|
export const ApiResponseSchema = <T extends z.AnyZodObject>(data: T) =>
|
||||||
data: T;
|
ApiErrorResponse.or(ApiSuccessResponse(data));
|
||||||
}
|
|
||||||
|
|
||||||
export class ApiSuccessResponse<T extends Object> extends BaseApiResponse<
|
export type ApiErrorResponse = z.infer<typeof ApiErrorResponse>;
|
||||||
T,
|
|
||||||
true
|
|
||||||
> {}
|
|
||||||
|
|
||||||
export class ApiErrorData {
|
|
||||||
@IsString()
|
|
||||||
message: string;
|
|
||||||
}
|
|
||||||
export class ApiErrorResponse extends BaseApiResponse<ApiErrorData, false> {}
|
|
||||||
|
|
||||||
export type ApiResponse<T> = ApiSuccessResponse<T> | ApiErrorResponse;
|
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
import { EImage } from '../../entities/image.entity';
|
import { EImageSchema } from '../../entities/image.entity';
|
||||||
|
import { createZodDto } from '../../util/create-zod-dto';
|
||||||
|
|
||||||
export class ImageMetaResponse extends EImage {}
|
export const ImageMetaResponseSchema = EImageSchema;
|
||||||
|
export class ImageMetaResponse extends createZodDto(ImageMetaResponseSchema) {}
|
||||||
|
|
|
@ -1,19 +1,17 @@
|
||||||
import { IsBoolean, IsSemVer } from 'class-validator';
|
import { string, z } from 'zod';
|
||||||
|
import { SemVer } from '../../util/common-regex';
|
||||||
|
import { createZodDto } from '../../util/create-zod-dto';
|
||||||
import { IsStringList } from '../../validators/string-list.validator';
|
import { IsStringList } from '../../validators/string-list.validator';
|
||||||
|
|
||||||
export class InfoResponse {
|
export const InfoResponseSchema = z.object({
|
||||||
@IsBoolean()
|
production: z.boolean(),
|
||||||
production: boolean;
|
demo: z.boolean(),
|
||||||
|
version: string().regex(SemVer),
|
||||||
|
});
|
||||||
|
export class InfoResponse extends createZodDto(InfoResponseSchema) {}
|
||||||
|
|
||||||
@IsBoolean()
|
// Allpermissions
|
||||||
demo: boolean;
|
export const AllPermissionsResponseSchema = z.object({
|
||||||
|
permissions: IsStringList(),
|
||||||
@IsSemVer()
|
});
|
||||||
version: string;
|
export class AllPermissionsResponse extends createZodDto(AllPermissionsResponseSchema) {}
|
||||||
}
|
|
||||||
|
|
||||||
// AllPermissions
|
|
||||||
export class AllPermissionsResponse {
|
|
||||||
@IsStringList()
|
|
||||||
permissions: string[];
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,53 +1,59 @@
|
||||||
import { IsArray } from 'class-validator';
|
import { z } from 'zod';
|
||||||
import { ERole, SimpleRole } from '../../entities/role.entity';
|
import {
|
||||||
import { IsNested } from '../../validators/nested.validator';
|
ERoleSchema, SimpleRoleSchema
|
||||||
|
} from '../../entities/role.entity';
|
||||||
|
import { createZodDto } from '../../util/create-zod-dto';
|
||||||
import { IsPosInt } from '../../validators/positive-int.validator';
|
import { IsPosInt } from '../../validators/positive-int.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';
|
||||||
|
|
||||||
// RoleInfo
|
// RoleInfo
|
||||||
export class RoleInfoRequest {
|
export const RoleInfoRequestSchema = z.object({
|
||||||
@IsRoleName()
|
name: IsRoleName(),
|
||||||
name: string;
|
});
|
||||||
}
|
export class RoleInfoRequest extends createZodDto(RoleInfoRequestSchema) {}
|
||||||
export class RoleInfoResponse extends ERole {}
|
|
||||||
|
export const RoleInfoResponseSchema = ERoleSchema;
|
||||||
|
export class RoleInfoResponse extends createZodDto(RoleInfoResponseSchema) {}
|
||||||
|
|
||||||
// RoleList
|
// RoleList
|
||||||
export class RoleListResponse {
|
export const RoleListResponseSchema = z.object({
|
||||||
@IsArray()
|
roles: z.array(ERoleSchema),
|
||||||
@IsNested(ERole)
|
total: IsPosInt(),
|
||||||
roles: ERole[];
|
});
|
||||||
|
export class RoleListResponse extends createZodDto(RoleListResponseSchema) {}
|
||||||
@IsPosInt()
|
|
||||||
total: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
// RoleUpdate
|
// RoleUpdate
|
||||||
export class RoleUpdateRequest extends SimpleRole {}
|
export const RoleUpdateRequestSchema = SimpleRoleSchema.partial({
|
||||||
export class RoleUpdateResponse extends ERole {}
|
permissions: true,
|
||||||
|
});
|
||||||
|
export class RoleUpdateRequest extends createZodDto(RoleUpdateRequestSchema) {}
|
||||||
|
|
||||||
|
export const RoleUpdateResponseSchema = ERoleSchema;
|
||||||
|
export class RoleUpdateResponse extends createZodDto(RoleUpdateResponseSchema) {}
|
||||||
|
|
||||||
// RoleCreate
|
// RoleCreate
|
||||||
export class RoleCreateRequest extends SimpleRole {}
|
export const RoleCreateRequestSchema = SimpleRoleSchema;
|
||||||
export class RoleCreateResponse extends ERole {}
|
export class RoleCreateRequest extends createZodDto(RoleCreateRequestSchema) {}
|
||||||
|
|
||||||
|
export const RoleCreateResponseSchema = ERoleSchema;
|
||||||
|
export class RoleCreateResponse extends createZodDto(RoleCreateResponseSchema) {}
|
||||||
|
|
||||||
// RoleDelete
|
// RoleDelete
|
||||||
export class RoleDeleteRequest {
|
export const RoleDeleteRequestSchema = z.object({
|
||||||
@IsRoleName()
|
name: IsRoleName(),
|
||||||
name: string;
|
});
|
||||||
}
|
export class RoleDeleteRequest extends createZodDto(RoleDeleteRequestSchema) {}
|
||||||
export class RoleDeleteResponse extends ERole {}
|
|
||||||
|
export const RoleDeleteResponseSchema = ERoleSchema;
|
||||||
|
export class RoleDeleteResponse extends createZodDto(RoleDeleteResponseSchema) {}
|
||||||
|
|
||||||
// SpecialRoles
|
// SpecialRoles
|
||||||
export class SpecialRolesResponse {
|
|
||||||
@IsStringList()
|
|
||||||
SoulBoundRoles: string[];
|
|
||||||
|
|
||||||
@IsStringList()
|
export const SpecialRolesResponseSchema = z.object({
|
||||||
ImmutableRoles: string[];
|
SoulBoundRoles: IsStringList(),
|
||||||
|
ImmutableRoles: IsStringList(),
|
||||||
@IsStringList()
|
UndeletableRoles: IsStringList(),
|
||||||
UndeletableRoles: string[];
|
DefaultRoles: IsStringList(),
|
||||||
|
});
|
||||||
@IsStringList()
|
export class SpecialRolesResponse extends createZodDto(SpecialRolesResponseSchema) {}
|
||||||
DefaultRoles: string[];
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,31 +1,29 @@
|
||||||
import {
|
import { z } from 'zod';
|
||||||
IsArray
|
import { createZodDto } from '../../util/create-zod-dto';
|
||||||
} from 'class-validator';
|
|
||||||
import { IsNested } from '../../validators/nested.validator';
|
|
||||||
import { IsPosInt } from '../../validators/positive-int.validator';
|
import { IsPosInt } from '../../validators/positive-int.validator';
|
||||||
import { IsPrefValue } from '../../validators/pref-value.validator';
|
import { IsPrefValue } from '../../validators/pref-value.validator';
|
||||||
import { DecodedSysPref, PrefValueType } from '../preferences.dto';
|
import { DecodedSysPrefSchema } from '../preferences.dto';
|
||||||
|
|
||||||
|
|
||||||
// Get Syspreference
|
// Get Syspreference
|
||||||
// Request is done via url parameters
|
// Request is done via url parameters
|
||||||
export class GetSyspreferenceResponse extends DecodedSysPref {}
|
export const GetSysPreferenceResponseSchema = DecodedSysPrefSchema;
|
||||||
|
export class GetSysPreferenceResponse extends createZodDto(GetSysPreferenceResponseSchema) {}
|
||||||
|
|
||||||
// Get syspreferences
|
// Get syspreferences
|
||||||
export class MultipleSysPreferencesResponse {
|
export const MultipleSysPreferencesResponseSchema = z.object({
|
||||||
@IsArray()
|
preferences: z.array(DecodedSysPrefSchema),
|
||||||
@IsNested(DecodedSysPref)
|
total: IsPosInt(),
|
||||||
preferences: DecodedSysPref[];
|
});
|
||||||
|
export class MultipleSysPreferencesResponse extends createZodDto(MultipleSysPreferencesResponseSchema) {}
|
||||||
@IsPosInt()
|
|
||||||
total: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update Syspreference
|
// Update Syspreference
|
||||||
export class UpdateSysPreferenceRequest {
|
export const UpdateSysPreferenceRequestSchema = z.object({
|
||||||
@IsPrefValue()
|
value: IsPrefValue(),
|
||||||
value: PrefValueType;
|
});
|
||||||
}
|
export class UpdateSysPreferenceRequest extends createZodDto(UpdateSysPreferenceRequestSchema) {}
|
||||||
export class UpdateSysPreferenceResponse extends DecodedSysPref {}
|
|
||||||
|
export const UpdateSysPreferenceResponseSchema = DecodedSysPrefSchema;
|
||||||
|
export class UpdateSysPreferenceResponse extends createZodDto(UpdateSysPreferenceResponseSchema) {}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,47 +1,40 @@
|
||||||
import {
|
import { z } from 'zod';
|
||||||
IsJWT
|
import { EUserSchema } from '../../entities/user.entity';
|
||||||
} from 'class-validator';
|
import { createZodDto } from '../../util/create-zod-dto';
|
||||||
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';
|
import { IsPlainTextPwd, IsUsername } from '../../validators/user.validators';
|
||||||
|
|
||||||
// Api
|
// Api
|
||||||
|
const UserPassSchema = z.object({
|
||||||
|
username: IsUsername(),
|
||||||
|
password: IsPlainTextPwd(),
|
||||||
|
});
|
||||||
|
|
||||||
// UserLogin
|
// UserLogin
|
||||||
export class UserLoginRequest {
|
export const UserLoginRequestSchema = UserPassSchema;
|
||||||
@IsUsername()
|
export class UserLoginRequest extends createZodDto(UserLoginRequestSchema) {}
|
||||||
username: string;
|
|
||||||
|
|
||||||
@IsPlainTextPwd()
|
export const UserLoginResponseSchema = z.object({
|
||||||
password: string;
|
jwt_token: z.string(),
|
||||||
}
|
});
|
||||||
export class UserLoginResponse {
|
export class UserLoginResponse extends createZodDto(UserLoginResponseSchema) {}
|
||||||
@IsJWT()
|
|
||||||
jwt_token: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
// UserRegister
|
// UserRegister
|
||||||
export class UserRegisterRequest {
|
export const UserRegisterRequestSchema = UserPassSchema;
|
||||||
@IsUsername()
|
export class UserRegisterRequest extends createZodDto(UserRegisterRequestSchema) {}
|
||||||
username: string;
|
|
||||||
|
|
||||||
@IsPlainTextPwd()
|
export const UserRegisterResponseSchema = EUserSchema;
|
||||||
password: string;
|
export class UserRegisterResponse extends createZodDto(UserRegisterResponseSchema) {}
|
||||||
}
|
|
||||||
export class UserRegisterResponse extends EUser {}
|
|
||||||
|
|
||||||
// UserMe
|
// UserMe
|
||||||
export class UserMeResponse {
|
export const UserMeResponseSchema = z.object({
|
||||||
@IsNested(EUser)
|
user: EUserSchema,
|
||||||
user: EUser;
|
token: z.string(),
|
||||||
|
});
|
||||||
@IsJWT()
|
export class UserMeResponse extends createZodDto(UserMeResponseSchema) {}
|
||||||
token: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
// UserMePermissions
|
// UserMePermissions
|
||||||
export class UserMePermissionsResponse {
|
export const UserMePermissionsResponseSchema = z.object({
|
||||||
@IsStringList()
|
permissions: IsStringList(),
|
||||||
permissions: string[];
|
});
|
||||||
}
|
export class UserMePermissionsResponse extends createZodDto(UserMePermissionsResponseSchema) {}
|
||||||
|
|
|
@ -1,71 +1,60 @@
|
||||||
import { IsArray, IsOptional } from 'class-validator';
|
import { z } from 'zod';
|
||||||
import { EUser, SimpleUser } from '../../entities/user.entity';
|
import {
|
||||||
import { Newable } from '../../types';
|
EUserSchema, SimpleUserSchema
|
||||||
import { IsEntityID } from '../../validators/entity-id.validator';
|
} from '../../entities/user.entity';
|
||||||
import { IsNested } from '../../validators/nested.validator';
|
import { createZodDto } from '../../util/create-zod-dto';
|
||||||
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 { EntityIDObject } from '../idobject.dto';
|
import { EntityIDObjectSchema } from '../idobject.dto';
|
||||||
|
|
||||||
// UserList
|
// UserList
|
||||||
export class UserListRequest {
|
export const UserListRequestSchema = z.object({
|
||||||
@IsPosInt()
|
count: IsPosInt(),
|
||||||
count: number;
|
page: IsPosInt(),
|
||||||
|
});
|
||||||
|
export class UserListRequest extends createZodDto(UserListRequestSchema) {}
|
||||||
|
|
||||||
@IsPosInt()
|
export const UserListResponseSchema = z.object({
|
||||||
page: number;
|
users: z.array(EUserSchema),
|
||||||
}
|
count: IsPosInt(),
|
||||||
|
page: IsPosInt(),
|
||||||
export class UserListResponse {
|
});
|
||||||
@IsArray()
|
export class UserListResponse extends createZodDto(UserListResponseSchema) {}
|
||||||
@IsNested(EUser)
|
|
||||||
users: EUser[];
|
|
||||||
|
|
||||||
@IsPosInt()
|
|
||||||
count: number;
|
|
||||||
|
|
||||||
@IsPosInt()
|
|
||||||
page: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
// UserCreate
|
// UserCreate
|
||||||
export class UserCreateRequest extends SimpleUser {}
|
export const UserCreateRequestSchema = SimpleUserSchema;
|
||||||
export class UserCreateResponse extends EUser {}
|
export class UserCreateRequest extends createZodDto(UserCreateRequestSchema) {}
|
||||||
|
|
||||||
|
export const UserCreateResponseSchema = EUserSchema;
|
||||||
|
export class UserCreateResponse extends createZodDto(UserCreateResponseSchema) {}
|
||||||
|
|
||||||
// UserDelete
|
// UserDelete
|
||||||
export class UserDeleteRequest extends EntityIDObject {}
|
export const UserDeleteRequestSchema = EntityIDObjectSchema;
|
||||||
export class UserDeleteResponse extends EUser {}
|
export class UserDeleteRequest extends createZodDto(UserDeleteRequestSchema) {}
|
||||||
|
|
||||||
|
export const UserDeleteResponseSchema = EUserSchema;
|
||||||
|
export class UserDeleteResponse extends createZodDto(UserDeleteResponseSchema) {}
|
||||||
|
|
||||||
// UserInfo
|
// UserInfo
|
||||||
export class UserInfoRequest extends EntityIDObject {}
|
export const UserInfoRequestSchema = EntityIDObjectSchema;
|
||||||
export class UserInfoResponse extends EUser {}
|
export class UserInfoRequest extends createZodDto(UserInfoRequestSchema) {}
|
||||||
|
|
||||||
|
export const UserInfoResponseSchema = EUserSchema;
|
||||||
|
export class UserInfoResponse extends createZodDto(UserInfoResponseSchema) {}
|
||||||
|
|
||||||
// UserUpdate
|
// UserUpdate
|
||||||
export class UserUpdateRequest extends (SimpleUser as Newable<
|
export const UserUpdateRequestSchema = EntityIDObjectSchema.merge(
|
||||||
Partial<SimpleUser>
|
SimpleUserSchema.partial(),
|
||||||
>) {
|
);
|
||||||
@IsEntityID()
|
export class UserUpdateRequest extends createZodDto(UserUpdateRequestSchema) {}
|
||||||
id: string;
|
|
||||||
|
|
||||||
@IsOptional()
|
export const UserUpdateResponseSchema = EUserSchema;
|
||||||
override username?: string;
|
export class UserUpdateResponse extends createZodDto(UserUpdateResponseSchema) {}
|
||||||
|
|
||||||
@IsOptional()
|
|
||||||
override password?: string;
|
|
||||||
|
|
||||||
@IsOptional()
|
|
||||||
override roles?: string[];
|
|
||||||
}
|
|
||||||
export class UserUpdateResponse extends EUser {}
|
|
||||||
|
|
||||||
// GetSpecialUsers
|
// GetSpecialUsers
|
||||||
export class GetSpecialUsersResponse {
|
export const GetSpecialUsersResponseSchema = z.object({
|
||||||
@IsStringList()
|
UndeletableUsersList: IsStringList(),
|
||||||
UndeletableUsersList: string[];
|
ImmutableUsersList: IsStringList(),
|
||||||
|
LockedLoginUsersList: IsStringList(),
|
||||||
@IsStringList()
|
});
|
||||||
ImmutableUsersList: string[];
|
export class GetSpecialUsersResponse extends createZodDto(GetSpecialUsersResponseSchema) {}
|
||||||
|
|
||||||
@IsStringList()
|
|
||||||
LockedLoginUsersList: string[];
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
|
import { z } from 'zod';
|
||||||
import { IsEntityID } from '../validators/entity-id.validator';
|
import { IsEntityID } from '../validators/entity-id.validator';
|
||||||
|
|
||||||
export class EntityIDObject {
|
export const EntityIDObjectSchema = z.object({
|
||||||
@IsEntityID()
|
id: IsEntityID(),
|
||||||
id: string;
|
});
|
||||||
}
|
|
||||||
|
|
|
@ -1,16 +1,9 @@
|
||||||
import { IsInt, IsOptional } from 'class-validator';
|
import { z } from 'zod';
|
||||||
import { EUser } from '../entities/user.entity';
|
import { EUserSchema } from '../entities/user.entity';
|
||||||
import { IsNested } from '../validators/nested.validator';
|
|
||||||
|
|
||||||
export class JwtDataDto {
|
export const JwtDataSchema = z.object({
|
||||||
@IsNested(EUser)
|
user: EUserSchema.required(),
|
||||||
user: EUser;
|
iat: z.number().int().optional(),
|
||||||
|
exp: z.number().int().optional(),
|
||||||
@IsOptional()
|
});
|
||||||
@IsInt()
|
export type JwtData = z.infer<typeof JwtDataSchema>;
|
||||||
iat?: number;
|
|
||||||
|
|
||||||
@IsOptional()
|
|
||||||
@IsInt()
|
|
||||||
exp?: number;
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,26 +1,24 @@
|
||||||
import { IsEnum, IsString } from 'class-validator';
|
import { z } from 'zod';
|
||||||
|
import tuple from '../types/tuple';
|
||||||
import { IsEntityID } from '../validators/entity-id.validator';
|
import { IsEntityID } from '../validators/entity-id.validator';
|
||||||
import { IsPrefValue } from '../validators/pref-value.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 = tuple('string', 'number', 'boolean');
|
||||||
|
|
||||||
// Decoded Representations
|
// Decoded Representations
|
||||||
|
|
||||||
export class DecodedSysPref {
|
export const DecodedSysPrefSchema = z.object({
|
||||||
@IsString()
|
key: z.string(),
|
||||||
key: string;
|
value: IsPrefValue(),
|
||||||
|
type: z.enum(PrefValueTypes),
|
||||||
|
})
|
||||||
|
export type DecodedSysPref = z.infer<typeof DecodedSysPrefSchema>;
|
||||||
|
|
||||||
@IsPrefValue()
|
export const DecodedUsrPrefSchema = DecodedSysPrefSchema.merge(z.object({
|
||||||
value: PrefValueType;
|
user: IsEntityID(),
|
||||||
|
}))
|
||||||
|
export type DecodedUsrPref = z.infer<typeof DecodedUsrPrefSchema>;
|
||||||
|
|
||||||
@IsEnum(PrefValueTypes)
|
|
||||||
type: PrefValueTypeStrings;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class DecodedUsrPref extends DecodedSysPref {
|
|
||||||
@IsEntityID()
|
|
||||||
user: string;
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,19 +1,11 @@
|
||||||
import { IsHash, IsOptional, IsString } from 'class-validator';
|
import { z } from 'zod';
|
||||||
|
import { SHA256 } from '../util/common-regex';
|
||||||
import { IsEntityID } from '../validators/entity-id.validator';
|
import { IsEntityID } from '../validators/entity-id.validator';
|
||||||
import { IsNotDefined } from '../validators/not-defined.validator';
|
|
||||||
|
|
||||||
export class EImage {
|
export const EImageSchema = z.object({
|
||||||
@IsOptional()
|
id: IsEntityID().optional(),
|
||||||
@IsEntityID()
|
hash: z.string().regex(SHA256),
|
||||||
id?: string;
|
data: z.undefined(),
|
||||||
|
mime: z.string(),
|
||||||
@IsHash('sha256')
|
});
|
||||||
hash: string;
|
export type EImage = z.infer<typeof EImageSchema>;
|
||||||
|
|
||||||
// Because typescript does not support exact types, we have to do this stupidness
|
|
||||||
@IsNotDefined()
|
|
||||||
data: undefined;
|
|
||||||
|
|
||||||
@IsString()
|
|
||||||
mime: string;
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,24 +1,17 @@
|
||||||
import { IsOptional } from 'class-validator';
|
import { z } from 'zod';
|
||||||
import { IsEntityID } 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';
|
||||||
|
|
||||||
export class SimpleRole {
|
export const SimpleRoleSchema = z.object({
|
||||||
@IsRoleName()
|
name: IsRoleName(),
|
||||||
name: string;
|
permissions: IsStringList(),
|
||||||
|
});
|
||||||
|
export type SimpleRole = z.infer<typeof SimpleRoleSchema>;
|
||||||
|
|
||||||
@IsStringList()
|
export const ERoleSchema = z.object({
|
||||||
permissions: string[];
|
id: IsEntityID().optional(),
|
||||||
}
|
name: IsRoleName(),
|
||||||
|
permissions: IsStringList(),
|
||||||
export class ERole {
|
});
|
||||||
@IsOptional()
|
export type ERole = z.infer<typeof ERoleSchema>;
|
||||||
@IsEntityID()
|
|
||||||
id?: string;
|
|
||||||
|
|
||||||
@IsRoleName()
|
|
||||||
name: string;
|
|
||||||
|
|
||||||
@IsStringList()
|
|
||||||
permissions: string[];
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,14 +1,9 @@
|
||||||
import { IsOptional, IsString } from 'class-validator';
|
import { z } from 'zod';
|
||||||
import { IsEntityID } from '../validators/entity-id.validator';
|
import { IsEntityID } from '../validators/entity-id.validator';
|
||||||
|
|
||||||
export class ESysPreference {
|
export const ESysPreferenceSchema = z.object({
|
||||||
@IsOptional()
|
id: IsEntityID().optional(),
|
||||||
@IsEntityID()
|
key: z.string(),
|
||||||
id?: string;
|
value: z.string(),
|
||||||
|
});
|
||||||
@IsString()
|
export type ESysPreference = z.infer<typeof ESysPreferenceSchema>;
|
||||||
key: string;
|
|
||||||
|
|
||||||
@IsString()
|
|
||||||
value: string;
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,32 +1,19 @@
|
||||||
import { IsOptional } from 'class-validator';
|
import { z } from 'zod';
|
||||||
import { IsEntityID } from '../validators/entity-id.validator';
|
import { IsEntityID } 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';
|
||||||
|
|
||||||
export class SimpleUser {
|
export const SimpleUserSchema = z.object({
|
||||||
@IsUsername()
|
username: IsUsername(),
|
||||||
username: string;
|
password: IsPlainTextPwd(),
|
||||||
|
roles: IsStringList(),
|
||||||
|
});
|
||||||
|
export type SimpleUser = z.infer<typeof SimpleUserSchema>;
|
||||||
|
|
||||||
@IsPlainTextPwd()
|
export const EUserSchema = z.object({
|
||||||
password: string;
|
id: IsEntityID().optional(),
|
||||||
|
username: IsUsername(),
|
||||||
@IsStringList()
|
roles: IsStringList(),
|
||||||
roles: string[];
|
hashedPassword: z.undefined(),
|
||||||
}
|
});
|
||||||
|
export type EUser = z.infer<typeof EUserSchema>;
|
||||||
export class EUser {
|
|
||||||
@IsOptional()
|
|
||||||
@IsEntityID()
|
|
||||||
id?: string;
|
|
||||||
|
|
||||||
@IsUsername()
|
|
||||||
username: string;
|
|
||||||
|
|
||||||
@IsStringList()
|
|
||||||
roles: string[];
|
|
||||||
|
|
||||||
// Because typescript does not support exact types, we have to do this stupidness
|
|
||||||
@IsNotDefined()
|
|
||||||
hashedPassword: undefined;
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,18 +1,11 @@
|
||||||
import { IsOptional, IsString } from 'class-validator';
|
import { z } from 'zod';
|
||||||
import { IsEntityID } 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 const EUsrPreferenceSchema = z.object({
|
||||||
@IsOptional()
|
id: IsEntityID().optional(),
|
||||||
@IsEntityID()
|
key: z.string(),
|
||||||
id?: string;
|
value: z.string(),
|
||||||
|
userId: IsPosInt(),
|
||||||
@IsString()
|
})
|
||||||
key: string;
|
export type EUsrPreference = z.infer<typeof EUsrPreferenceSchema>;
|
||||||
|
|
||||||
@IsString()
|
|
||||||
value: string;
|
|
||||||
|
|
||||||
@IsPosInt()
|
|
||||||
userId: number;
|
|
||||||
}
|
|
||||||
|
|
3
shared/src/util/common-regex.ts
Normal file
3
shared/src/util/common-regex.ts
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
export const AlphaNumeric = /^[a-zA-Z0-9]+$/;
|
||||||
|
export const SHA256 = /^[a-f0-9A-F]{64}$/;
|
||||||
|
export const SemVer = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$/;
|
29
shared/src/util/create-zod-dto.ts
Normal file
29
shared/src/util/create-zod-dto.ts
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
import * as z from 'zod';
|
||||||
|
/**
|
||||||
|
* This file was originally taken from:
|
||||||
|
* https://github.com/anatine/zod-plugins/blob/main/libs/zod-nestjs/src/lib/create-zod-dto.ts
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
export type CompatibleZodType = z.ZodType<unknown>;
|
||||||
|
export type CompatibleZodInfer<T extends CompatibleZodType> = T['_output'];
|
||||||
|
|
||||||
|
export type ZodDtoStatic<T extends CompatibleZodType = CompatibleZodType> = {
|
||||||
|
new (): CompatibleZodInfer<T>;
|
||||||
|
zodSchema: T;
|
||||||
|
create(input: unknown): CompatibleZodInfer<T>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const createZodDto = <T extends CompatibleZodType>(
|
||||||
|
zodSchema: T,
|
||||||
|
): ZodDtoStatic<T> => {
|
||||||
|
class SchemaHolderClass {
|
||||||
|
public static zodSchema = zodSchema;
|
||||||
|
|
||||||
|
public static create(input: unknown): CompatibleZodInfer<T> {
|
||||||
|
return this.zodSchema.parse(input);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return SchemaHolderClass;
|
||||||
|
};
|
|
@ -1,15 +0,0 @@
|
||||||
import { validate, ValidatorOptions } from 'class-validator';
|
|
||||||
|
|
||||||
// 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
|
|
||||||
|
|
||||||
export const ValidateOptions: ValidatorOptions = {
|
|
||||||
forbidNonWhitelisted: true,
|
|
||||||
forbidUnknownValues: true,
|
|
||||||
stopAtFirstError: true,
|
|
||||||
whitelist: true,
|
|
||||||
strictGroups: true,
|
|
||||||
};
|
|
||||||
|
|
||||||
export const strictValidate = (object: object) =>
|
|
||||||
validate(object, ValidateOptions);
|
|
|
@ -1,4 +1,3 @@
|
||||||
import { IsUUID } from 'class-validator';
|
import { z } from 'zod';
|
||||||
import { CombinePDecorators } from '../util/decorator';
|
|
||||||
|
|
||||||
export const IsEntityID = CombinePDecorators(IsUUID('4'));
|
export const IsEntityID = () => z.string().uuid();
|
||||||
|
|
|
@ -1,15 +0,0 @@
|
||||||
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);
|
|
||||||
};
|
|
||||||
};
|
|
|
@ -1,26 +0,0 @@
|
||||||
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,12 +1,11 @@
|
||||||
import { isArray, isEnum, isString } from 'class-validator';
|
|
||||||
|
|
||||||
export function isPermissionsArray(
|
export function isPermissionsArray(
|
||||||
value: any,
|
value: any,
|
||||||
permissionsList: string[],
|
permissionsList: string[],
|
||||||
): value is string[] {
|
): value is string[] {
|
||||||
if (!isArray(value)) return false;
|
if (!Array.isArray(value)) return false;
|
||||||
if (!value.every((item: unknown) => isString(item))) return false;
|
if (!value.every((item: unknown) => typeof item === 'string')) return false;
|
||||||
if (!value.every((item: string) => isEnum(item, permissionsList)))
|
if (!value.every((item: string) => permissionsList.includes(item)))
|
||||||
return false;
|
return false;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
import { IsInt, Min } from 'class-validator';
|
import { z } from 'zod';
|
||||||
import { CombinePDecorators } from '../util/decorator';
|
|
||||||
|
|
||||||
export const IsPosInt = CombinePDecorators(IsInt(), Min(0));
|
export const IsPosInt = () => z.number().int().nonnegative();
|
||||||
|
|
|
@ -1,21 +1,3 @@
|
||||||
import { registerDecorator, ValidationArguments, ValidationOptions } from 'class-validator';
|
import { z } from 'zod';
|
||||||
import { PrefValueTypes } from '../dto/preferences.dto';
|
|
||||||
|
|
||||||
export function isPrefValue(value: any, args: ValidationArguments) {
|
export const IsPrefValue = () => z.string().or(z.number().or(z.boolean()));
|
||||||
const type = typeof value;
|
|
||||||
return PrefValueTypes.includes(type);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function IsPrefValue(validationOptions?: ValidationOptions) {
|
|
||||||
return function (object: Object, propertyName: string) {
|
|
||||||
registerDecorator({
|
|
||||||
name: 'isPrefValue',
|
|
||||||
target: object.constructor,
|
|
||||||
propertyName: propertyName,
|
|
||||||
options: validationOptions ?? {},
|
|
||||||
validator: {
|
|
||||||
validate: isPrefValue,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,8 +1,4 @@
|
||||||
import { IsAlphanumeric, IsString, Length } from 'class-validator';
|
import { z } from 'zod';
|
||||||
import { CombinePDecorators } from '../util/decorator';
|
import { AlphaNumeric } from '../util/common-regex';
|
||||||
|
|
||||||
export const IsRoleName = CombinePDecorators(
|
export const IsRoleName = () => z.string().min(4).max(32).regex(AlphaNumeric);
|
||||||
IsString(),
|
|
||||||
Length(4, 32),
|
|
||||||
IsAlphanumeric(),
|
|
||||||
);
|
|
||||||
|
|
|
@ -1,9 +1,3 @@
|
||||||
import {
|
import { z } from 'zod';
|
||||||
IsArray, IsString
|
|
||||||
} from 'class-validator';
|
|
||||||
import { CombinePDecorators } from '../util/decorator';
|
|
||||||
|
|
||||||
export const IsStringList = CombinePDecorators(
|
export const IsStringList = () => z.array(z.string());
|
||||||
IsArray(),
|
|
||||||
IsString({ each: true }),
|
|
||||||
);
|
|
||||||
|
|
|
@ -1,16 +1,9 @@
|
||||||
import { IsAlphanumeric, IsString, Length } from 'class-validator';
|
import { z } from 'zod';
|
||||||
import { CombinePDecorators } from '../util/decorator';
|
import { AlphaNumeric } from '../util/common-regex';
|
||||||
|
|
||||||
// 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 = () => z.string().min(4).max(32).regex(AlphaNumeric);
|
||||||
IsString(),
|
|
||||||
Length(4, 32),
|
|
||||||
IsAlphanumeric(),
|
|
||||||
);
|
|
||||||
|
|
||||||
export const IsPlainTextPwd = CombinePDecorators(
|
export const IsPlainTextPwd = () => z.string().min(4).max(1024);
|
||||||
IsString(),
|
|
||||||
Length(4, 1024),
|
|
||||||
);
|
|
||||||
|
|
260
yarn.lock
260
yarn.lock
|
@ -95,10 +95,10 @@
|
||||||
"@angular-devkit/architect" "0.1400.0-next.7"
|
"@angular-devkit/architect" "0.1400.0-next.7"
|
||||||
rxjs "6.6.7"
|
rxjs "6.6.7"
|
||||||
|
|
||||||
"@angular-devkit/core@13.2.5":
|
"@angular-devkit/core@13.3.0":
|
||||||
version "13.2.5"
|
version "13.3.0"
|
||||||
resolved "https://registry.yarnpkg.com/@angular-devkit/core/-/core-13.2.5.tgz#376f3d205a75cd381191ddabfe3c368e0e9a7d82"
|
resolved "https://registry.yarnpkg.com/@angular-devkit/core/-/core-13.3.0.tgz#ad15f2a6b72599a3e22194ebf2decc35e91e592c"
|
||||||
integrity sha512-WuWp/1R0FtCHPBcJLF13lTLHETtDGFUX0ULfGPRaYB5OVCSQcovVp5UbZTTy/Ss3ub3EOEmJlU8kMJfBrWuq+A==
|
integrity sha512-8YrreVbWlJVZnk5zs4vfkRItrPEtWhUcxWOBfYT/Kwu4FwJVAnNuhJAxxXOAQ2Ckd7cv30Idh/RFVLbTZ5Gs9w==
|
||||||
dependencies:
|
dependencies:
|
||||||
ajv "8.9.0"
|
ajv "8.9.0"
|
||||||
ajv-formats "2.1.1"
|
ajv-formats "2.1.1"
|
||||||
|
@ -107,10 +107,10 @@
|
||||||
rxjs "6.6.7"
|
rxjs "6.6.7"
|
||||||
source-map "0.7.3"
|
source-map "0.7.3"
|
||||||
|
|
||||||
"@angular-devkit/core@13.3.0":
|
"@angular-devkit/core@13.3.1":
|
||||||
version "13.3.0"
|
version "13.3.1"
|
||||||
resolved "https://registry.yarnpkg.com/@angular-devkit/core/-/core-13.3.0.tgz#ad15f2a6b72599a3e22194ebf2decc35e91e592c"
|
resolved "https://registry.yarnpkg.com/@angular-devkit/core/-/core-13.3.1.tgz#e79237d9cffedcc32c59702131e700eb52de76e2"
|
||||||
integrity sha512-8YrreVbWlJVZnk5zs4vfkRItrPEtWhUcxWOBfYT/Kwu4FwJVAnNuhJAxxXOAQ2Ckd7cv30Idh/RFVLbTZ5Gs9w==
|
integrity sha512-eXAcQaP1mn6rnQb+5bv5NsamY6b34UYM7G+S154Hnma6CTTSGBtcmoNAJs8cekuFqWlw7YgpB/e15jR5OLPkDA==
|
||||||
dependencies:
|
dependencies:
|
||||||
ajv "8.9.0"
|
ajv "8.9.0"
|
||||||
ajv-formats "2.1.1"
|
ajv-formats "2.1.1"
|
||||||
|
@ -143,17 +143,6 @@
|
||||||
minimist "1.2.5"
|
minimist "1.2.5"
|
||||||
symbol-observable "4.0.0"
|
symbol-observable "4.0.0"
|
||||||
|
|
||||||
"@angular-devkit/schematics@13.2.5":
|
|
||||||
version "13.2.5"
|
|
||||||
resolved "https://registry.yarnpkg.com/@angular-devkit/schematics/-/schematics-13.2.5.tgz#d1425ab7ce38c835e776329bea61925b333f53b8"
|
|
||||||
integrity sha512-kAye6VYiF9JQAoeO+BYhy8eT2QOmhB+WLziRjXoFCBxh5+yXTygTVfs9fD5jmIpHmeu4hd2ErSh69yT5xWcD9g==
|
|
||||||
dependencies:
|
|
||||||
"@angular-devkit/core" "13.2.5"
|
|
||||||
jsonc-parser "3.0.0"
|
|
||||||
magic-string "0.25.7"
|
|
||||||
ora "5.4.1"
|
|
||||||
rxjs "6.6.7"
|
|
||||||
|
|
||||||
"@angular-devkit/schematics@13.3.0":
|
"@angular-devkit/schematics@13.3.0":
|
||||||
version "13.3.0"
|
version "13.3.0"
|
||||||
resolved "https://registry.yarnpkg.com/@angular-devkit/schematics/-/schematics-13.3.0.tgz#c79dba627d1ec4cef6d38bc282aeb7447446c94c"
|
resolved "https://registry.yarnpkg.com/@angular-devkit/schematics/-/schematics-13.3.0.tgz#c79dba627d1ec4cef6d38bc282aeb7447446c94c"
|
||||||
|
@ -165,6 +154,17 @@
|
||||||
ora "5.4.1"
|
ora "5.4.1"
|
||||||
rxjs "6.6.7"
|
rxjs "6.6.7"
|
||||||
|
|
||||||
|
"@angular-devkit/schematics@13.3.1":
|
||||||
|
version "13.3.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/@angular-devkit/schematics/-/schematics-13.3.1.tgz#808af593cd59c0de76361b72346a506cd1624a5a"
|
||||||
|
integrity sha512-DxXMjlq/sALcHuONZRMTBX5k30XPfN4b6Ue4k7Xl8JKZqyHhEzfXaZzgD9u2cwb7wybKEeF/BZ5eJd8JG525og==
|
||||||
|
dependencies:
|
||||||
|
"@angular-devkit/core" "13.3.1"
|
||||||
|
jsonc-parser "3.0.0"
|
||||||
|
magic-string "0.25.7"
|
||||||
|
ora "5.4.1"
|
||||||
|
rxjs "6.6.7"
|
||||||
|
|
||||||
"@angular-devkit/schematics@14.0.0-next.7":
|
"@angular-devkit/schematics@14.0.0-next.7":
|
||||||
version "14.0.0-next.7"
|
version "14.0.0-next.7"
|
||||||
resolved "https://registry.yarnpkg.com/@angular-devkit/schematics/-/schematics-14.0.0-next.7.tgz#fce33ffbd4dcfb048f0969b0d49dc884ce6ecfc1"
|
resolved "https://registry.yarnpkg.com/@angular-devkit/schematics/-/schematics-14.0.0-next.7.tgz#fce33ffbd4dcfb048f0969b0d49dc884ce6ecfc1"
|
||||||
|
@ -1211,9 +1211,9 @@
|
||||||
"@cspotcode/source-map-consumer" "0.8.0"
|
"@cspotcode/source-map-consumer" "0.8.0"
|
||||||
|
|
||||||
"@csstools/postcss-color-function@^1.0.3":
|
"@csstools/postcss-color-function@^1.0.3":
|
||||||
version "1.0.3"
|
version "1.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/@csstools/postcss-color-function/-/postcss-color-function-1.0.3.tgz#251c961a852c99e9aabdbbdbefd50e9a96e8a9ff"
|
resolved "https://registry.yarnpkg.com/@csstools/postcss-color-function/-/postcss-color-function-1.1.0.tgz#229966327747f58fbe586de35daa139db3ce1e5d"
|
||||||
integrity sha512-J26I69pT2B3MYiLY/uzCGKVJyMYVg9TCpXkWsRlt+Yfq+nELUEm72QXIMYXs4xA9cJA4Oqs2EylrfokKl3mJEQ==
|
integrity sha512-5D5ND/mZWcQoSfYnSPsXtuiFxhzmhxt6pcjrFLJyldj+p0ZN2vvRpYNX+lahFTtMhAYOa2WmkdGINr0yP0CvGA==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@csstools/postcss-progressive-custom-properties" "^1.1.0"
|
"@csstools/postcss-progressive-custom-properties" "^1.1.0"
|
||||||
postcss-value-parser "^4.2.0"
|
postcss-value-parser "^4.2.0"
|
||||||
|
@ -1241,11 +1241,11 @@
|
||||||
postcss-value-parser "^4.2.0"
|
postcss-value-parser "^4.2.0"
|
||||||
|
|
||||||
"@csstools/postcss-is-pseudo-class@^2.0.1":
|
"@csstools/postcss-is-pseudo-class@^2.0.1":
|
||||||
version "2.0.1"
|
version "2.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/@csstools/postcss-is-pseudo-class/-/postcss-is-pseudo-class-2.0.1.tgz#472fff2cf434bdf832f7145b2a5491587e790c9e"
|
resolved "https://registry.yarnpkg.com/@csstools/postcss-is-pseudo-class/-/postcss-is-pseudo-class-2.0.2.tgz#a834ca11a43d6ed9bc9e3ff53c80d490a4b1aaad"
|
||||||
integrity sha512-Og5RrTzwFhrKoA79c3MLkfrIBYmwuf/X83s+JQtz/Dkk/MpsaKtqHV1OOzYkogQ+tj3oYp5Mq39XotBXNqVc3Q==
|
integrity sha512-L9h1yxXMj7KpgNzlMrw3isvHJYkikZgZE4ASwssTnGEH8tm50L6QsM9QQT5wR4/eO5mU0rN5axH7UzNxEYg5CA==
|
||||||
dependencies:
|
dependencies:
|
||||||
postcss-selector-parser "^6.0.9"
|
postcss-selector-parser "^6.0.10"
|
||||||
|
|
||||||
"@csstools/postcss-normalize-display-values@^1.0.0":
|
"@csstools/postcss-normalize-display-values@^1.0.0":
|
||||||
version "1.0.0"
|
version "1.0.0"
|
||||||
|
@ -1255,9 +1255,9 @@
|
||||||
postcss-value-parser "^4.2.0"
|
postcss-value-parser "^4.2.0"
|
||||||
|
|
||||||
"@csstools/postcss-oklab-function@^1.0.2":
|
"@csstools/postcss-oklab-function@^1.0.2":
|
||||||
version "1.0.2"
|
version "1.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/@csstools/postcss-oklab-function/-/postcss-oklab-function-1.0.2.tgz#87cd646e9450347a5721e405b4f7cc35157b7866"
|
resolved "https://registry.yarnpkg.com/@csstools/postcss-oklab-function/-/postcss-oklab-function-1.1.0.tgz#e9a269487a292e0930760948e923e1d46b638ee6"
|
||||||
integrity sha512-QwhWesEkMlp4narAwUi6pgc6kcooh8cC7zfxa9LSQNYXqzcdNUtNBzbGc5nuyAVreb7uf5Ox4qH1vYT3GA1wOg==
|
integrity sha512-e/Q5HopQzmnQgqimG9v3w2IG4VRABsBq3itOcn4bnm+j4enTgQZ0nWsaH/m9GV2otWGQ0nwccYL5vmLKyvP1ww==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@csstools/postcss-progressive-custom-properties" "^1.1.0"
|
"@csstools/postcss-progressive-custom-properties" "^1.1.0"
|
||||||
postcss-value-parser "^4.2.0"
|
postcss-value-parser "^4.2.0"
|
||||||
|
@ -1474,12 +1474,12 @@
|
||||||
tslib "2.3.1"
|
tslib "2.3.1"
|
||||||
|
|
||||||
"@nestjs/schematics@^8.0.3", "@nestjs/schematics@^8.0.8":
|
"@nestjs/schematics@^8.0.3", "@nestjs/schematics@^8.0.8":
|
||||||
version "8.0.8"
|
version "8.0.9"
|
||||||
resolved "https://registry.yarnpkg.com/@nestjs/schematics/-/schematics-8.0.8.tgz#b1612b535c1007d1503b7e624795708055460432"
|
resolved "https://registry.yarnpkg.com/@nestjs/schematics/-/schematics-8.0.9.tgz#796e06c14ba43930abb12cde0e0144b16925a6a3"
|
||||||
integrity sha512-xIIb5YnMQN/OJQ68+MCapy2bXvTxSWgINoqQbyZWkLL/yTIuROvZCdtV850NPGyr7f7l93VBP0ZPitbFIexy3Q==
|
integrity sha512-2YNRFWyQCxKaK7XTKBRKecERKQj2cg9FLxsHwAf9beryXxVmciTmHV0gHCW7Pfatj38R50QFGPN71wuyyZIC5g==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@angular-devkit/core" "13.2.5"
|
"@angular-devkit/core" "13.3.1"
|
||||||
"@angular-devkit/schematics" "13.2.5"
|
"@angular-devkit/schematics" "13.3.1"
|
||||||
fs-extra "10.0.1"
|
fs-extra "10.0.1"
|
||||||
jsonc-parser "3.0.0"
|
jsonc-parser "3.0.0"
|
||||||
pluralize "8.0.0"
|
pluralize "8.0.0"
|
||||||
|
@ -1756,9 +1756,9 @@
|
||||||
"@types/node" "*"
|
"@types/node" "*"
|
||||||
|
|
||||||
"@types/jasmine@~4.0.1":
|
"@types/jasmine@~4.0.1":
|
||||||
version "4.0.1"
|
version "4.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/@types/jasmine/-/jasmine-4.0.1.tgz#be74714a651bc76e11fbc66c55dcfacae872361f"
|
resolved "https://registry.yarnpkg.com/@types/jasmine/-/jasmine-4.0.2.tgz#9a03589f6aa00ef42a90a8d37f09c893efced14c"
|
||||||
integrity sha512-6KtN9P42PfhUSxybWY0iG6wpMJEoMMXyd9qi06EiOf5p6fOwAj9t/BwBEkx0Ys47oxAbMKv9sKqTV9igHYeUsQ==
|
integrity sha512-mSPIWhDyQ4nzYdR6Ixy15VhVKMVw93mSUlQxxpVb4S9Hj90lBvg+7kkBw23uYcv8CESPPXit+u3cARYcPeC8Jg==
|
||||||
|
|
||||||
"@types/json-schema@*", "@types/json-schema@^7.0.5", "@types/json-schema@^7.0.8", "@types/json-schema@^7.0.9":
|
"@types/json-schema@*", "@types/json-schema@^7.0.5", "@types/json-schema@^7.0.8", "@types/json-schema@^7.0.9":
|
||||||
version "7.0.11"
|
version "7.0.11"
|
||||||
|
@ -2510,7 +2510,7 @@ bl@^4.1.0:
|
||||||
inherits "^2.0.4"
|
inherits "^2.0.4"
|
||||||
readable-stream "^3.4.0"
|
readable-stream "^3.4.0"
|
||||||
|
|
||||||
body-parser@1.19.2, body-parser@^1.19.0:
|
body-parser@1.19.2:
|
||||||
version "1.19.2"
|
version "1.19.2"
|
||||||
resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.19.2.tgz#4714ccd9c157d44797b8b5607d72c0b89952f26e"
|
resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.19.2.tgz#4714ccd9c157d44797b8b5607d72c0b89952f26e"
|
||||||
integrity sha512-SAAwOxgoCKMGs9uUAUFHygfLAyaniaoun6I8mFY9pRAJL9+Kec34aU+oIjDhTycub1jozEfEwx1W1IuOYxVSFw==
|
integrity sha512-SAAwOxgoCKMGs9uUAUFHygfLAyaniaoun6I8mFY9pRAJL9+Kec34aU+oIjDhTycub1jozEfEwx1W1IuOYxVSFw==
|
||||||
|
@ -2526,6 +2526,24 @@ body-parser@1.19.2, body-parser@^1.19.0:
|
||||||
raw-body "2.4.3"
|
raw-body "2.4.3"
|
||||||
type-is "~1.6.18"
|
type-is "~1.6.18"
|
||||||
|
|
||||||
|
body-parser@^1.19.0:
|
||||||
|
version "1.20.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.0.tgz#3de69bd89011c11573d7bfee6a64f11b6bd27cc5"
|
||||||
|
integrity sha512-DfJ+q6EPcGKZD1QWUjSpqp+Q7bDQTsQIF4zfUAtZ6qk+H/3/QRhg9CEp39ss+/T2vw0+HaidC0ecJj/DRLIaKg==
|
||||||
|
dependencies:
|
||||||
|
bytes "3.1.2"
|
||||||
|
content-type "~1.0.4"
|
||||||
|
debug "2.6.9"
|
||||||
|
depd "2.0.0"
|
||||||
|
destroy "1.2.0"
|
||||||
|
http-errors "2.0.0"
|
||||||
|
iconv-lite "0.4.24"
|
||||||
|
on-finished "2.4.1"
|
||||||
|
qs "6.10.3"
|
||||||
|
raw-body "2.5.1"
|
||||||
|
type-is "~1.6.18"
|
||||||
|
unpipe "1.0.0"
|
||||||
|
|
||||||
bonjour@^3.5.0:
|
bonjour@^3.5.0:
|
||||||
version "3.5.0"
|
version "3.5.0"
|
||||||
resolved "https://registry.yarnpkg.com/bonjour/-/bonjour-3.5.0.tgz#8e890a183d8ee9a2393b3844c691a42bcf7bc9f5"
|
resolved "https://registry.yarnpkg.com/bonjour/-/bonjour-3.5.0.tgz#8e890a183d8ee9a2393b3844c691a42bcf7bc9f5"
|
||||||
|
@ -2677,9 +2695,9 @@ camelcase@^5.3.1:
|
||||||
integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==
|
integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==
|
||||||
|
|
||||||
caniuse-lite@^1.0.30001317:
|
caniuse-lite@^1.0.30001317:
|
||||||
version "1.0.30001323"
|
version "1.0.30001325"
|
||||||
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001323.tgz#a451ff80dec7033016843f532efda18f02eec011"
|
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001325.tgz#2b4ad19b77aa36f61f2eaf72e636d7481d55e606"
|
||||||
integrity sha512-e4BF2RlCVELKx8+RmklSEIVub1TWrmdhvA5kEUueummz1XyySW0DVk+3x9HyhU9MuWTa2BhqLgEuEmUwASAdCA==
|
integrity sha512-sB1bZHjseSjDtijV1Hb7PB2Zd58Kyx+n/9EotvZ4Qcz2K3d0lWB8dB4nb8wN/TsOGFq3UuAm0zQZNQ4SoR7TrQ==
|
||||||
|
|
||||||
chalk@3.0.0:
|
chalk@3.0.0:
|
||||||
version "3.0.0"
|
version "3.0.0"
|
||||||
|
@ -2736,19 +2754,6 @@ chrome-trace-event@^1.0.2:
|
||||||
resolved "https://registry.yarnpkg.com/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz#1015eced4741e15d06664a957dbbf50d041e26ac"
|
resolved "https://registry.yarnpkg.com/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz#1015eced4741e15d06664a957dbbf50d041e26ac"
|
||||||
integrity sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==
|
integrity sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==
|
||||||
|
|
||||||
class-transformer@^0.5.1:
|
|
||||||
version "0.5.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/class-transformer/-/class-transformer-0.5.1.tgz#24147d5dffd2a6cea930a3250a677addf96ab336"
|
|
||||||
integrity sha512-SQa1Ws6hUbfC98vKGxZH3KFY0Y1lm5Zm0SY8XX9zbK7FJCyVEac3ATW0RIpwzW+oOfmHE5PMPufDG9hCfoEOMw==
|
|
||||||
|
|
||||||
class-validator@^0.13.2:
|
|
||||||
version "0.13.2"
|
|
||||||
resolved "https://registry.yarnpkg.com/class-validator/-/class-validator-0.13.2.tgz#64b031e9f3f81a1e1dcd04a5d604734608b24143"
|
|
||||||
integrity sha512-yBUcQy07FPlGzUjoLuUfIOXzgynnQPPruyK1Ge2B74k9ROwnle1E+NxLWnUv5OLU8hA/qL5leAE9XnXq3byaBw==
|
|
||||||
dependencies:
|
|
||||||
libphonenumber-js "^1.9.43"
|
|
||||||
validator "^13.7.0"
|
|
||||||
|
|
||||||
clean-stack@^2.0.0:
|
clean-stack@^2.0.0:
|
||||||
version "2.2.0"
|
version "2.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/clean-stack/-/clean-stack-2.2.0.tgz#ee8472dbb129e727b31e8a10a427dee9dfe4008b"
|
resolved "https://registry.yarnpkg.com/clean-stack/-/clean-stack-2.2.0.tgz#ee8472dbb129e727b31e8a10a427dee9dfe4008b"
|
||||||
|
@ -3206,6 +3211,11 @@ delegates@^1.0.0:
|
||||||
resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a"
|
resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a"
|
||||||
integrity sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=
|
integrity sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=
|
||||||
|
|
||||||
|
depd@2.0.0:
|
||||||
|
version "2.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df"
|
||||||
|
integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==
|
||||||
|
|
||||||
depd@^1.1.2, depd@~1.1.2:
|
depd@^1.1.2, depd@~1.1.2:
|
||||||
version "1.1.2"
|
version "1.1.2"
|
||||||
resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9"
|
resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9"
|
||||||
|
@ -3216,6 +3226,11 @@ dependency-graph@^0.11.0:
|
||||||
resolved "https://registry.yarnpkg.com/dependency-graph/-/dependency-graph-0.11.0.tgz#ac0ce7ed68a54da22165a85e97a01d53f5eb2e27"
|
resolved "https://registry.yarnpkg.com/dependency-graph/-/dependency-graph-0.11.0.tgz#ac0ce7ed68a54da22165a85e97a01d53f5eb2e27"
|
||||||
integrity sha512-JeMq7fEshyepOWDfcfHK06N3MhyPhz++vtqWhMT5O9A3K42rdsEDpfdVqjaqaAhsw6a+ZqeDvQVtD0hFHQWrzg==
|
integrity sha512-JeMq7fEshyepOWDfcfHK06N3MhyPhz++vtqWhMT5O9A3K42rdsEDpfdVqjaqaAhsw6a+ZqeDvQVtD0hFHQWrzg==
|
||||||
|
|
||||||
|
destroy@1.2.0:
|
||||||
|
version "1.2.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.2.0.tgz#4803735509ad8be552934c67df614f94e66fa015"
|
||||||
|
integrity sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==
|
||||||
|
|
||||||
destroy@~1.0.4:
|
destroy@~1.0.4:
|
||||||
version "1.0.4"
|
version "1.0.4"
|
||||||
resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.0.4.tgz#978857442c44749e4206613e37946205826abd80"
|
resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.0.4.tgz#978857442c44749e4206613e37946205826abd80"
|
||||||
|
@ -4424,6 +4439,17 @@ http-errors@1.8.1:
|
||||||
statuses ">= 1.5.0 < 2"
|
statuses ">= 1.5.0 < 2"
|
||||||
toidentifier "1.0.1"
|
toidentifier "1.0.1"
|
||||||
|
|
||||||
|
http-errors@2.0.0:
|
||||||
|
version "2.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-2.0.0.tgz#b7774a1486ef73cf7667ac9ae0858c012c57b9d3"
|
||||||
|
integrity sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==
|
||||||
|
dependencies:
|
||||||
|
depd "2.0.0"
|
||||||
|
inherits "2.0.4"
|
||||||
|
setprototypeof "1.2.0"
|
||||||
|
statuses "2.0.1"
|
||||||
|
toidentifier "1.0.1"
|
||||||
|
|
||||||
http-errors@~1.6.2:
|
http-errors@~1.6.2:
|
||||||
version "1.6.3"
|
version "1.6.3"
|
||||||
resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.6.3.tgz#8b55680bb4be283a0b5bf4ea2e38580be1d9320d"
|
resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.6.3.tgz#8b55680bb4be283a0b5bf4ea2e38580be1d9320d"
|
||||||
|
@ -5107,11 +5133,6 @@ levn@^0.4.1:
|
||||||
prelude-ls "^1.2.1"
|
prelude-ls "^1.2.1"
|
||||||
type-check "~0.4.0"
|
type-check "~0.4.0"
|
||||||
|
|
||||||
libphonenumber-js@^1.9.43:
|
|
||||||
version "1.9.50"
|
|
||||||
resolved "https://registry.yarnpkg.com/libphonenumber-js/-/libphonenumber-js-1.9.50.tgz#f5028a2c4cc47a69d69a0de3629afad97a613712"
|
|
||||||
integrity sha512-cCzQPChw2XbordcO2LKiw5Htx5leHVfFk/EXkxNHqJfFo7Fndcb1kF5wPJpc316vCJhhikedYnVysMh3Sc7Ocw==
|
|
||||||
|
|
||||||
license-webpack-plugin@4.0.2:
|
license-webpack-plugin@4.0.2:
|
||||||
version "4.0.2"
|
version "4.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/license-webpack-plugin/-/license-webpack-plugin-4.0.2.tgz#1e18442ed20b754b82f1adeff42249b81d11aec6"
|
resolved "https://registry.yarnpkg.com/license-webpack-plugin/-/license-webpack-plugin-4.0.2.tgz#1e18442ed20b754b82f1adeff42249b81d11aec6"
|
||||||
|
@ -5621,9 +5642,9 @@ node-forge@^1:
|
||||||
integrity sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==
|
integrity sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==
|
||||||
|
|
||||||
node-gyp-build@^4.2.2:
|
node-gyp-build@^4.2.2:
|
||||||
version "4.3.0"
|
version "4.4.0"
|
||||||
resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-4.3.0.tgz#9f256b03e5826150be39c764bf51e993946d71a3"
|
resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-4.4.0.tgz#42e99687ce87ddeaf3a10b99dc06abc11021f3f4"
|
||||||
integrity sha512-iWjXZvmboq0ja1pUGULQBexmxq8CV4xBhX7VDOTbL7ZR4FOowwY/VOtRxBN/yKxmdGoIp4j5ysNT4u3S2pDQ3Q==
|
integrity sha512-amJnQCcgtRVw9SvoebO3BKGESClrfXGCUTX9hSn1OuGQTQBOZmVd0Z0OlecpuRksKvbsUqALE8jls/ErClAPuQ==
|
||||||
|
|
||||||
node-gyp@^9.0.0:
|
node-gyp@^9.0.0:
|
||||||
version "9.0.0"
|
version "9.0.0"
|
||||||
|
@ -5778,6 +5799,11 @@ object-hash@3.0.0:
|
||||||
resolved "https://registry.yarnpkg.com/object-hash/-/object-hash-3.0.0.tgz#73f97f753e7baffc0e2cc9d6e079079744ac82e9"
|
resolved "https://registry.yarnpkg.com/object-hash/-/object-hash-3.0.0.tgz#73f97f753e7baffc0e2cc9d6e079079744ac82e9"
|
||||||
integrity sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==
|
integrity sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==
|
||||||
|
|
||||||
|
object-inspect@^1.9.0:
|
||||||
|
version "1.12.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.12.0.tgz#6e2c120e868fd1fd18cb4f18c31741d0d6e776f0"
|
||||||
|
integrity sha512-Ho2z80bVIvJloH+YzRmpZVQe87+qASmBUKZDWgx9cu+KDrX2ZDH/3tMy+gXbZETVGs2M8YdxObOh7XAtim9Y0g==
|
||||||
|
|
||||||
object-is@^1.0.1:
|
object-is@^1.0.1:
|
||||||
version "1.1.5"
|
version "1.1.5"
|
||||||
resolved "https://registry.yarnpkg.com/object-is/-/object-is-1.1.5.tgz#b9deeaa5fc7f1846a0faecdceec138e5778f53ac"
|
resolved "https://registry.yarnpkg.com/object-is/-/object-is-1.1.5.tgz#b9deeaa5fc7f1846a0faecdceec138e5778f53ac"
|
||||||
|
@ -5806,6 +5832,13 @@ obuf@^1.0.0, obuf@^1.1.2:
|
||||||
resolved "https://registry.yarnpkg.com/obuf/-/obuf-1.1.2.tgz#09bea3343d41859ebd446292d11c9d4db619084e"
|
resolved "https://registry.yarnpkg.com/obuf/-/obuf-1.1.2.tgz#09bea3343d41859ebd446292d11c9d4db619084e"
|
||||||
integrity sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==
|
integrity sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==
|
||||||
|
|
||||||
|
on-finished@2.4.1:
|
||||||
|
version "2.4.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.4.1.tgz#58c8c44116e54845ad57f14ab10b03533184ac3f"
|
||||||
|
integrity sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==
|
||||||
|
dependencies:
|
||||||
|
ee-first "1.1.1"
|
||||||
|
|
||||||
on-finished@~2.3.0:
|
on-finished@~2.3.0:
|
||||||
version "2.3.0"
|
version "2.3.0"
|
||||||
resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947"
|
resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947"
|
||||||
|
@ -6351,9 +6384,9 @@ postcss-initial@^4.0.1:
|
||||||
integrity sha512-0ueD7rPqX8Pn1xJIjay0AZeIuDoF+V+VvMt/uOnn+4ezUKhZM/NokDeP6DwMNyIoYByuN/94IQnt5FEkaN59xQ==
|
integrity sha512-0ueD7rPqX8Pn1xJIjay0AZeIuDoF+V+VvMt/uOnn+4ezUKhZM/NokDeP6DwMNyIoYByuN/94IQnt5FEkaN59xQ==
|
||||||
|
|
||||||
postcss-lab-function@^4.1.2:
|
postcss-lab-function@^4.1.2:
|
||||||
version "4.1.2"
|
version "4.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/postcss-lab-function/-/postcss-lab-function-4.1.2.tgz#b75afe43ba9c1f16bfe9bb12c8109cabd55b5fc2"
|
resolved "https://registry.yarnpkg.com/postcss-lab-function/-/postcss-lab-function-4.2.0.tgz#e054e662c6480202f5760887ec1ae0d153357123"
|
||||||
integrity sha512-isudf5ldhg4fk16M8viAwAbg6Gv14lVO35N3Z/49NhbwPQ2xbiEoHgrRgpgQojosF4vF7jY653ktB6dDrUOR8Q==
|
integrity sha512-Zb1EO9DGYfa3CP8LhINHCcTTCTLI+R3t7AX2mKsDzdgVQ/GkCpHOTgOr6HBHslP7XDdVbqgHW5vvRPMdVANQ8w==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@csstools/postcss-progressive-custom-properties" "^1.1.0"
|
"@csstools/postcss-progressive-custom-properties" "^1.1.0"
|
||||||
postcss-value-parser "^4.2.0"
|
postcss-value-parser "^4.2.0"
|
||||||
|
@ -6406,11 +6439,11 @@ postcss-modules-values@^4.0.0:
|
||||||
icss-utils "^5.0.0"
|
icss-utils "^5.0.0"
|
||||||
|
|
||||||
postcss-nesting@^10.1.3:
|
postcss-nesting@^10.1.3:
|
||||||
version "10.1.3"
|
version "10.1.4"
|
||||||
resolved "https://registry.yarnpkg.com/postcss-nesting/-/postcss-nesting-10.1.3.tgz#f0b1cd7ae675c697ab6a5a5ca1feea4784a2ef77"
|
resolved "https://registry.yarnpkg.com/postcss-nesting/-/postcss-nesting-10.1.4.tgz#80de9d1c2717bc44df918dd7f118929300192a7a"
|
||||||
integrity sha512-wUC+/YCik4wH3StsbC5fBG1s2Z3ZV74vjGqBFYtmYKlVxoio5TYGM06AiaKkQPPlkXWn72HKfS7Cw5PYxnoXSw==
|
integrity sha512-2ixdQ59ik/Gt1+oPHiI1kHdwEI8lLKEmui9B1nl6163ANLC+GewQn7fXMxJF2JSb4i2MKL96GU8fIiQztK4TTA==
|
||||||
dependencies:
|
dependencies:
|
||||||
postcss-selector-parser "^6.0.9"
|
postcss-selector-parser "^6.0.10"
|
||||||
|
|
||||||
postcss-opacity-percentage@^1.1.2:
|
postcss-opacity-percentage@^1.1.2:
|
||||||
version "1.1.2"
|
version "1.1.2"
|
||||||
|
@ -6484,11 +6517,11 @@ postcss-preset-env@7.4.3:
|
||||||
postcss-value-parser "^4.2.0"
|
postcss-value-parser "^4.2.0"
|
||||||
|
|
||||||
postcss-pseudo-class-any-link@^7.1.1:
|
postcss-pseudo-class-any-link@^7.1.1:
|
||||||
version "7.1.1"
|
version "7.1.2"
|
||||||
resolved "https://registry.yarnpkg.com/postcss-pseudo-class-any-link/-/postcss-pseudo-class-any-link-7.1.1.tgz#534eb1dadd9945eb07830dbcc06fb4d5d865b8e0"
|
resolved "https://registry.yarnpkg.com/postcss-pseudo-class-any-link/-/postcss-pseudo-class-any-link-7.1.2.tgz#81ec491aa43f97f9015e998b7a14263b4630bdf0"
|
||||||
integrity sha512-JRoLFvPEX/1YTPxRxp1JO4WxBVXJYrSY7NHeak5LImwJ+VobFMwYDQHvfTXEpcn+7fYIeGkC29zYFhFWIZD8fg==
|
integrity sha512-76XzEQv3g+Vgnz3tmqh3pqQyRojkcJ+pjaePsyhcyf164p9aZsu3t+NWxkZYbcHLK1ju5Qmalti2jPI5IWCe5w==
|
||||||
dependencies:
|
dependencies:
|
||||||
postcss-selector-parser "^6.0.9"
|
postcss-selector-parser "^6.0.10"
|
||||||
|
|
||||||
postcss-replace-overflow-wrap@^4.0.0:
|
postcss-replace-overflow-wrap@^4.0.0:
|
||||||
version "4.0.0"
|
version "4.0.0"
|
||||||
|
@ -6502,7 +6535,7 @@ postcss-selector-not@^5.0.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
balanced-match "^1.0.0"
|
balanced-match "^1.0.0"
|
||||||
|
|
||||||
postcss-selector-parser@^6.0.2, postcss-selector-parser@^6.0.4, postcss-selector-parser@^6.0.9:
|
postcss-selector-parser@^6.0.10, postcss-selector-parser@^6.0.2, postcss-selector-parser@^6.0.4, postcss-selector-parser@^6.0.9:
|
||||||
version "6.0.10"
|
version "6.0.10"
|
||||||
resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz#79b61e2c0d1bfc2602d549e11d0876256f8df88d"
|
resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz#79b61e2c0d1bfc2602d549e11d0876256f8df88d"
|
||||||
integrity sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==
|
integrity sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==
|
||||||
|
@ -6559,9 +6592,9 @@ prettier-linter-helpers@^1.0.0:
|
||||||
fast-diff "^1.1.2"
|
fast-diff "^1.1.2"
|
||||||
|
|
||||||
prettier@^2.6.1:
|
prettier@^2.6.1:
|
||||||
version "2.6.1"
|
version "2.6.2"
|
||||||
resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.6.1.tgz#d472797e0d7461605c1609808e27b80c0f9cfe17"
|
resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.6.2.tgz#e26d71a18a74c3d0f0597f55f01fb6c06c206032"
|
||||||
integrity sha512-8UVbTBYGwN37Bs9LERmxCPjdvPxlEowx2urIL6urHzdb3SDq4B/Z6xLFCblrSnE4iKWcS6ziJ3aOYrc1kz/E2A==
|
integrity sha512-PkUpF+qoXTqhOeWL9fu7As8LXsIUZ1WYaJiY/a7McAQzxjk82OF0tibkFXVCDImZtWxbvojFjerkiLb0/q8mew==
|
||||||
|
|
||||||
pretty-bytes@^5.3.0:
|
pretty-bytes@^5.3.0:
|
||||||
version "5.6.0"
|
version "5.6.0"
|
||||||
|
@ -6634,6 +6667,13 @@ qjobs@^1.2.0:
|
||||||
resolved "https://registry.yarnpkg.com/qjobs/-/qjobs-1.2.0.tgz#c45e9c61800bd087ef88d7e256423bdd49e5d071"
|
resolved "https://registry.yarnpkg.com/qjobs/-/qjobs-1.2.0.tgz#c45e9c61800bd087ef88d7e256423bdd49e5d071"
|
||||||
integrity sha512-8YOJEHtxpySA3fFDyCRxA+UUV+fA+rTWnuWvylOK/NCjhY+b4ocCtmu8TtsWb+mYeU+GCHf/S66KZF/AsteKHg==
|
integrity sha512-8YOJEHtxpySA3fFDyCRxA+UUV+fA+rTWnuWvylOK/NCjhY+b4ocCtmu8TtsWb+mYeU+GCHf/S66KZF/AsteKHg==
|
||||||
|
|
||||||
|
qs@6.10.3:
|
||||||
|
version "6.10.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/qs/-/qs-6.10.3.tgz#d6cde1b2ffca87b5aa57889816c5f81535e22e8e"
|
||||||
|
integrity sha512-wr7M2E0OFRfIfJZjKGieI8lBKb7fRCH4Fv5KNPEs7gJ8jadvotdsS08PzOKR7opXhZ/Xkjtt3WF9g38drmyRqQ==
|
||||||
|
dependencies:
|
||||||
|
side-channel "^1.0.4"
|
||||||
|
|
||||||
qs@6.9.7:
|
qs@6.9.7:
|
||||||
version "6.9.7"
|
version "6.9.7"
|
||||||
resolved "https://registry.yarnpkg.com/qs/-/qs-6.9.7.tgz#4610846871485e1e048f44ae3b94033f0e675afe"
|
resolved "https://registry.yarnpkg.com/qs/-/qs-6.9.7.tgz#4610846871485e1e048f44ae3b94033f0e675afe"
|
||||||
|
@ -6671,6 +6711,16 @@ raw-body@2.4.3:
|
||||||
iconv-lite "0.4.24"
|
iconv-lite "0.4.24"
|
||||||
unpipe "1.0.0"
|
unpipe "1.0.0"
|
||||||
|
|
||||||
|
raw-body@2.5.1:
|
||||||
|
version "2.5.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.5.1.tgz#fe1b1628b181b700215e5fd42389f98b71392857"
|
||||||
|
integrity sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==
|
||||||
|
dependencies:
|
||||||
|
bytes "3.1.2"
|
||||||
|
http-errors "2.0.0"
|
||||||
|
iconv-lite "0.4.24"
|
||||||
|
unpipe "1.0.0"
|
||||||
|
|
||||||
read-cache@^1.0.0:
|
read-cache@^1.0.0:
|
||||||
version "1.0.0"
|
version "1.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/read-cache/-/read-cache-1.0.0.tgz#e664ef31161166c9751cdbe8dbcf86b5fb58f774"
|
resolved "https://registry.yarnpkg.com/read-cache/-/read-cache-1.0.0.tgz#e664ef31161166c9751cdbe8dbcf86b5fb58f774"
|
||||||
|
@ -7142,6 +7192,15 @@ shelljs@0.8.5:
|
||||||
interpret "^1.0.0"
|
interpret "^1.0.0"
|
||||||
rechoir "^0.6.2"
|
rechoir "^0.6.2"
|
||||||
|
|
||||||
|
side-channel@^1.0.4:
|
||||||
|
version "1.0.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.4.tgz#efce5c8fdc104ee751b25c58d4290011fa5ea2cf"
|
||||||
|
integrity sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==
|
||||||
|
dependencies:
|
||||||
|
call-bind "^1.0.0"
|
||||||
|
get-intrinsic "^1.0.2"
|
||||||
|
object-inspect "^1.9.0"
|
||||||
|
|
||||||
signal-exit@^3.0.0, signal-exit@^3.0.2, signal-exit@^3.0.3, signal-exit@^3.0.7:
|
signal-exit@^3.0.0, signal-exit@^3.0.2, signal-exit@^3.0.3, signal-exit@^3.0.7:
|
||||||
version "3.0.7"
|
version "3.0.7"
|
||||||
resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9"
|
resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9"
|
||||||
|
@ -7345,6 +7404,11 @@ ssri@^8.0.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
minipass "^3.1.1"
|
minipass "^3.1.1"
|
||||||
|
|
||||||
|
statuses@2.0.1:
|
||||||
|
version "2.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/statuses/-/statuses-2.0.1.tgz#55cb000ccf1d48728bd23c685a063998cf1a1b63"
|
||||||
|
integrity sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==
|
||||||
|
|
||||||
"statuses@>= 1.4.0 < 2", "statuses@>= 1.5.0 < 2", statuses@~1.5.0:
|
"statuses@>= 1.4.0 < 2", "statuses@>= 1.5.0 < 2", statuses@~1.5.0:
|
||||||
version "1.5.0"
|
version "1.5.0"
|
||||||
resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c"
|
resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c"
|
||||||
|
@ -7876,11 +7940,6 @@ validate-npm-package-name@^4.0.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
builtins "^5.0.0"
|
builtins "^5.0.0"
|
||||||
|
|
||||||
validator@^13.7.0:
|
|
||||||
version "13.7.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/validator/-/validator-13.7.0.tgz#4f9658ba13ba8f3d82ee881d3516489ea85c0857"
|
|
||||||
integrity sha512-nYXQLCBkpJ8X6ltALua9dRrZDHVYxjJ1wgskNt1lH9fzGjs3tgojGSCBjmEPwkWS1y29+DrizMTW19Pr9uB2nw==
|
|
||||||
|
|
||||||
vary@^1, vary@^1.1.2, vary@~1.1.2:
|
vary@^1, vary@^1.1.2, vary@~1.1.2:
|
||||||
version "1.1.2"
|
version "1.1.2"
|
||||||
resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc"
|
resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc"
|
||||||
|
@ -7990,7 +8049,7 @@ webpack-subresource-integrity@5.1.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
typed-assert "^1.0.8"
|
typed-assert "^1.0.8"
|
||||||
|
|
||||||
webpack@5.70.0, webpack@^5.70.0:
|
webpack@5.70.0:
|
||||||
version "5.70.0"
|
version "5.70.0"
|
||||||
resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.70.0.tgz#3461e6287a72b5e6e2f4872700bc8de0d7500e6d"
|
resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.70.0.tgz#3461e6287a72b5e6e2f4872700bc8de0d7500e6d"
|
||||||
integrity sha512-ZMWWy8CeuTTjCxbeaQI21xSswseF2oNOwc70QSKNePvmxE7XW36i7vpBMYZFAUHPwQiEbNGCEYIOOlyRbdGmxw==
|
integrity sha512-ZMWWy8CeuTTjCxbeaQI21xSswseF2oNOwc70QSKNePvmxE7XW36i7vpBMYZFAUHPwQiEbNGCEYIOOlyRbdGmxw==
|
||||||
|
@ -8020,6 +8079,36 @@ webpack@5.70.0, webpack@^5.70.0:
|
||||||
watchpack "^2.3.1"
|
watchpack "^2.3.1"
|
||||||
webpack-sources "^3.2.3"
|
webpack-sources "^3.2.3"
|
||||||
|
|
||||||
|
webpack@^5.70.0:
|
||||||
|
version "5.71.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.71.0.tgz#b01fcf379570b8c5ee06ca06c829ca168c951884"
|
||||||
|
integrity sha512-g4dFT7CFG8LY0iU5G8nBL6VlkT21Z7dcYDpJAEJV5Q1WLb9UwnFbrem1k7K52ILqEmomN7pnzWFxxE6SlDY56A==
|
||||||
|
dependencies:
|
||||||
|
"@types/eslint-scope" "^3.7.3"
|
||||||
|
"@types/estree" "^0.0.51"
|
||||||
|
"@webassemblyjs/ast" "1.11.1"
|
||||||
|
"@webassemblyjs/wasm-edit" "1.11.1"
|
||||||
|
"@webassemblyjs/wasm-parser" "1.11.1"
|
||||||
|
acorn "^8.4.1"
|
||||||
|
acorn-import-assertions "^1.7.6"
|
||||||
|
browserslist "^4.14.5"
|
||||||
|
chrome-trace-event "^1.0.2"
|
||||||
|
enhanced-resolve "^5.9.2"
|
||||||
|
es-module-lexer "^0.9.0"
|
||||||
|
eslint-scope "5.1.1"
|
||||||
|
events "^3.2.0"
|
||||||
|
glob-to-regexp "^0.4.1"
|
||||||
|
graceful-fs "^4.2.9"
|
||||||
|
json-parse-better-errors "^1.0.2"
|
||||||
|
loader-runner "^4.2.0"
|
||||||
|
mime-types "^2.1.27"
|
||||||
|
neo-async "^2.6.2"
|
||||||
|
schema-utils "^3.1.0"
|
||||||
|
tapable "^2.1.1"
|
||||||
|
terser-webpack-plugin "^5.1.3"
|
||||||
|
watchpack "^2.3.1"
|
||||||
|
webpack-sources "^3.2.3"
|
||||||
|
|
||||||
websocket-driver@>=0.5.1, websocket-driver@^0.7.4:
|
websocket-driver@>=0.5.1, websocket-driver@^0.7.4:
|
||||||
version "0.7.4"
|
version "0.7.4"
|
||||||
resolved "https://registry.yarnpkg.com/websocket-driver/-/websocket-driver-0.7.4.tgz#89ad5295bbf64b480abcba31e4953aca706f5760"
|
resolved "https://registry.yarnpkg.com/websocket-driver/-/websocket-driver-0.7.4.tgz#89ad5295bbf64b480abcba31e4953aca706f5760"
|
||||||
|
@ -8183,6 +8272,11 @@ yocto-queue@^0.1.0:
|
||||||
resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b"
|
resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b"
|
||||||
integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==
|
integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==
|
||||||
|
|
||||||
|
zod@^3.14.3:
|
||||||
|
version "3.14.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/zod/-/zod-3.14.3.tgz#60e86341c05883c281fe96a0e79acea48a09f123"
|
||||||
|
integrity sha512-OzwRCSXB1+/8F6w6HkYHdbuWysYWnAF4fkRgKDcSFc54CE+Sv0rHXKfeNUReGCrHukm1LNpi6AYeXotznhYJbQ==
|
||||||
|
|
||||||
zone.js@~0.11.5:
|
zone.js@~0.11.5:
|
||||||
version "0.11.5"
|
version "0.11.5"
|
||||||
resolved "https://registry.yarnpkg.com/zone.js/-/zone.js-0.11.5.tgz#ab0b449e91fadb5ebb2db189ffe1b7b6048dc8b1"
|
resolved "https://registry.yarnpkg.com/zone.js/-/zone.js-0.11.5.tgz#ab0b449e91fadb5ebb2db189ffe1b7b6048dc8b1"
|
||||||
|
|
Loading…
Reference in a new issue