json logging + fix lint
This commit is contained in:
parent
24f3fb9b8e
commit
13ec6e388d
18 changed files with 551 additions and 465 deletions
18
build.ts
18
build.ts
|
@ -1,15 +1,15 @@
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
await Bun.build({
|
await Bun.build({
|
||||||
entrypoints: ["./src/app.ts"],
|
entrypoints: ['./src/app.ts'],
|
||||||
format: "esm",
|
format: 'esm',
|
||||||
outdir: "./dist",
|
outdir: './dist',
|
||||||
target: "node",
|
target: 'node',
|
||||||
external: [
|
external: [
|
||||||
"@nestjs/websockets/socket-module",
|
'@nestjs/websockets/socket-module',
|
||||||
"@nestjs/microservices",
|
'@nestjs/microservices',
|
||||||
"class-transformer/storage",
|
'class-transformer/storage',
|
||||||
"@fastify/view",
|
'@fastify/view',
|
||||||
"@nestjs/platform-express",
|
'@nestjs/platform-express',
|
||||||
],
|
],
|
||||||
minify: {
|
minify: {
|
||||||
whitespace: true,
|
whitespace: true,
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
import {CacheInterceptor} from "@nestjs/cache-manager";
|
import { CacheInterceptor } from '@nestjs/cache-manager';
|
||||||
import {VersionEntity} from "./common/models/entities/version.entity";
|
import { VersionEntity } from './common/models/entities/version.entity';
|
||||||
import {Controller, Get, UseInterceptors} from "@nestjs/common";
|
import { Controller, Get, UseInterceptors } from '@nestjs/common';
|
||||||
import {ApiTags} from "@nestjs/swagger";
|
import { ApiTags } from '@nestjs/swagger';
|
||||||
|
|
||||||
@Controller()
|
@Controller()
|
||||||
@ApiTags("Misc")
|
@ApiTags('Misc')
|
||||||
@UseInterceptors(CacheInterceptor)
|
@UseInterceptors(CacheInterceptor)
|
||||||
export class AppController {
|
export class AppController {
|
||||||
@Get("version")
|
@Get('version')
|
||||||
async getVersion(): Promise<VersionEntity> {
|
async getVersion(): Promise<VersionEntity> {
|
||||||
return {
|
return {
|
||||||
version: process.env.npm_package_version,
|
version: process.env.npm_package_version,
|
||||||
|
|
64
src/app.ts
64
src/app.ts
|
@ -1,33 +1,42 @@
|
||||||
import {FastifyAdapter, NestFastifyApplication} from "@nestjs/platform-fastify";
|
import {
|
||||||
import {CustomValidationPipe} from "./common/pipes/custom-validation.pipe";
|
FastifyAdapter,
|
||||||
import {LoggerMiddleware} from "./common/middlewares/logger.middleware";
|
NestFastifyApplication,
|
||||||
import {SwaggerTheme, SwaggerThemeNameEnum} from "swagger-themes";
|
} from '@nestjs/platform-fastify';
|
||||||
import {DocumentBuilder, SwaggerModule} from "@nestjs/swagger";
|
import { CustomValidationPipe } from './common/pipes/custom-validation.pipe';
|
||||||
import {FastifyListenOptions} from "fastify/types/instance";
|
import { LoggerMiddleware } from './common/middlewares/logger.middleware';
|
||||||
import fastifyMultipart from "@fastify/multipart";
|
import { SwaggerTheme, SwaggerThemeNameEnum } from 'swagger-themes';
|
||||||
import fastifyStatic from "@fastify/static";
|
import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';
|
||||||
import fastifyHelmet from "@fastify/helmet";
|
import { FastifyListenOptions } from 'fastify/types/instance';
|
||||||
import {NestFactory} from "@nestjs/core";
|
import fastifyMultipart from '@fastify/multipart';
|
||||||
import {AppModule} from "./app.module";
|
import fastifyHelmet from '@fastify/helmet';
|
||||||
import {Logger} from "@nestjs/common";
|
import { NestFactory } from '@nestjs/core';
|
||||||
import * as process from "process";
|
import { AppModule } from './app.module';
|
||||||
import {join} from "node:path";
|
import { ConsoleLogger, Logger } from '@nestjs/common';
|
||||||
|
import * as process from 'process';
|
||||||
|
|
||||||
const logger: Logger = new Logger("App");
|
const logger: Logger = new Logger('App');
|
||||||
|
|
||||||
process.env.APP_NAME = process.env.npm_package_name.split("-").map((word: string): string => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
|
process.env.APP_NAME = process.env.npm_package_name
|
||||||
|
.split('-')
|
||||||
|
.map((word: string): string => word.charAt(0).toUpperCase() + word.slice(1))
|
||||||
|
.join(' ');
|
||||||
const port: number = parseInt(process.env.PORT) || 4000;
|
const port: number = parseInt(process.env.PORT) || 4000;
|
||||||
|
|
||||||
async function bootstrap() {
|
async function bootstrap() {
|
||||||
const app = await NestFactory.create<NestFastifyApplication>(
|
const app = await NestFactory.create<NestFastifyApplication>(
|
||||||
AppModule,
|
AppModule,
|
||||||
new FastifyAdapter({ exposeHeadRoutes: true }),
|
new FastifyAdapter({ exposeHeadRoutes: true }),
|
||||||
|
{
|
||||||
|
logger: new ConsoleLogger({
|
||||||
|
json: true,
|
||||||
|
}),
|
||||||
|
},
|
||||||
);
|
);
|
||||||
await loadServer(app);
|
await loadServer(app);
|
||||||
|
|
||||||
await app.listen({
|
await app.listen({
|
||||||
port: port,
|
port: port,
|
||||||
host: "0.0.0.0",
|
host: '0.0.0.0',
|
||||||
} as FastifyListenOptions);
|
} as FastifyListenOptions);
|
||||||
app.enableShutdownHooks();
|
app.enableShutdownHooks();
|
||||||
logger.log(`Listening on http://0.0.0.0:${port}`);
|
logger.log(`Listening on http://0.0.0.0:${port}`);
|
||||||
|
@ -37,22 +46,21 @@ async function loadServer(server: NestFastifyApplication){
|
||||||
// Config
|
// Config
|
||||||
server.setGlobalPrefix(process.env.PREFIX);
|
server.setGlobalPrefix(process.env.PREFIX);
|
||||||
server.enableCors({
|
server.enableCors({
|
||||||
origin: "*",
|
origin: '*',
|
||||||
});
|
});
|
||||||
|
|
||||||
// Middlewares
|
// Middlewares
|
||||||
server.use(new LoggerMiddleware().use);
|
server.use(new LoggerMiddleware().use);
|
||||||
await server.register(fastifyMultipart as any);
|
await server.register(fastifyMultipart as any);
|
||||||
await server.register(fastifyStatic as any, {
|
await server.register(
|
||||||
root: join(process.cwd(), "public_answers"),
|
fastifyHelmet as any,
|
||||||
prefix: "/public_answers/",
|
{
|
||||||
});
|
|
||||||
await server.register(fastifyHelmet as any, {
|
|
||||||
contentSecurityPolicy: false,
|
contentSecurityPolicy: false,
|
||||||
crossOriginEmbedderPolicy: false,
|
crossOriginEmbedderPolicy: false,
|
||||||
crossOriginOpenerPolicy: false,
|
crossOriginOpenerPolicy: false,
|
||||||
crossOriginResourcePolicy: false,
|
crossOriginResourcePolicy: false,
|
||||||
} as any);
|
} as any,
|
||||||
|
);
|
||||||
|
|
||||||
// Swagger
|
// Swagger
|
||||||
const config = new DocumentBuilder()
|
const config = new DocumentBuilder()
|
||||||
|
@ -65,14 +73,14 @@ async function loadServer(server: NestFastifyApplication){
|
||||||
const document = SwaggerModule.createDocument(server, config);
|
const document = SwaggerModule.createDocument(server, config);
|
||||||
const theme = new SwaggerTheme();
|
const theme = new SwaggerTheme();
|
||||||
const customCss = theme.getBuffer(SwaggerThemeNameEnum.DARK);
|
const customCss = theme.getBuffer(SwaggerThemeNameEnum.DARK);
|
||||||
SwaggerModule.setup("api", server, document, {
|
SwaggerModule.setup('api', server, document, {
|
||||||
swaggerOptions: {
|
swaggerOptions: {
|
||||||
filter: true,
|
filter: true,
|
||||||
displayRequestDuration: true,
|
displayRequestDuration: true,
|
||||||
persistAuthorization: true,
|
persistAuthorization: true,
|
||||||
docExpansion: "none",
|
docExpansion: 'none',
|
||||||
tagsSorter: "alpha",
|
tagsSorter: 'alpha',
|
||||||
operationsSorter: "method",
|
operationsSorter: 'method',
|
||||||
},
|
},
|
||||||
customCss,
|
customCss,
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,24 +1,25 @@
|
||||||
import {Injectable, Logger, NestMiddleware} from "@nestjs/common";
|
import { Injectable, Logger, NestMiddleware } from '@nestjs/common';
|
||||||
import {FastifyReply, FastifyRequest} from "fastify";
|
import { FastifyReply, FastifyRequest } from 'fastify';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class LoggerMiddleware implements NestMiddleware {
|
export class LoggerMiddleware implements NestMiddleware {
|
||||||
static logger: Logger = new Logger(LoggerMiddleware.name);
|
static logger: Logger = new Logger(LoggerMiddleware.name);
|
||||||
|
|
||||||
use(req: FastifyRequest["raw"], res: FastifyReply["raw"], next: () => void){
|
use(req: FastifyRequest['raw'], res: FastifyReply['raw'], next: () => void) {
|
||||||
const startTime = Date.now();
|
const startTime = Date.now();
|
||||||
(res as any).on("finish", () => {
|
(res as any).on('finish', () => {
|
||||||
const path = req.url;
|
const path = req.url;
|
||||||
try {
|
try {
|
||||||
const protocol = LoggerMiddleware.getProtocol(req);
|
const protocol = LoggerMiddleware.getProtocol(req);
|
||||||
const method = req.method;
|
const method = req.method;
|
||||||
if(method === "OPTIONS")
|
if (method === 'OPTIONS') return;
|
||||||
return;
|
|
||||||
const statusCode = res.statusCode;
|
const statusCode = res.statusCode;
|
||||||
const duration = Date.now() - startTime;
|
const duration = Date.now() - startTime;
|
||||||
const resSize: any = res.getHeader("Content-Length") || "0";
|
const resSize: any = res.getHeader('Content-Length') || '0';
|
||||||
const intResSize = parseInt(resSize);
|
const intResSize = parseInt(resSize);
|
||||||
LoggerMiddleware.logger.log(`${protocol} ${method} ${path} ${statusCode} ${duration}ms ${intResSize}`);
|
LoggerMiddleware.logger.log(
|
||||||
|
`${protocol} ${method} ${path} ${statusCode} ${duration}ms ${intResSize}`,
|
||||||
|
);
|
||||||
LoggerMiddleware.logRequestTime(path, method, duration);
|
LoggerMiddleware.logRequestTime(path, method, duration);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
LoggerMiddleware.logger.warn(`Can't log route ${path} : ${e}`);
|
LoggerMiddleware.logger.warn(`Can't log route ${path} : ${e}`);
|
||||||
|
@ -27,11 +28,10 @@ export class LoggerMiddleware implements NestMiddleware{
|
||||||
next();
|
next();
|
||||||
}
|
}
|
||||||
|
|
||||||
static getProtocol(req: FastifyRequest["raw"]): string{
|
static getProtocol(req: FastifyRequest['raw']): string {
|
||||||
const localPort: number = req.connection.localPort;
|
const localPort: number = req.connection.localPort;
|
||||||
if(!localPort)
|
if (!localPort) return 'H2';
|
||||||
return "H2";
|
return localPort.toString() === process.env.HTTPS_PORT ? 'HTTPS' : 'HTTP';
|
||||||
return localPort.toString() === process.env.HTTPS_PORT ? "HTTPS" : "HTTP";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static logRequestTime(path: string, method: string, duration: number): void {
|
static logRequestTime(path: string, method: string, duration: number): void {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import {IsNumber, IsOptional, Min} from "class-validator";
|
import { IsNumber, IsOptional, Min } from 'class-validator';
|
||||||
|
|
||||||
export class PaginationDto {
|
export class PaginationDto {
|
||||||
@IsNumber()
|
@IsNumber()
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import {ApiProperty} from "@nestjs/swagger";
|
import { ApiProperty } from '@nestjs/swagger';
|
||||||
|
|
||||||
export class VersionEntity {
|
export class VersionEntity {
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
import { Controller, Delete, NotImplementedException } from "@nestjs/common";
|
import { Controller, Delete, NotImplementedException } from '@nestjs/common';
|
||||||
|
|
||||||
@Controller("auth")
|
@Controller('auth')
|
||||||
export class AuthController {
|
export class AuthController {
|
||||||
constructor() {}
|
constructor() {}
|
||||||
|
|
||||||
@Delete("logout/all")
|
@Delete('logout/all')
|
||||||
logoutAll() {
|
logoutAll() {
|
||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import {Injectable} from "@nestjs/common";
|
import { Injectable } from '@nestjs/common';
|
||||||
import {AuthGuard} from "@nestjs/passport";
|
import { AuthGuard } from '@nestjs/passport';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class JwtAuthGuard extends AuthGuard("jwt"){}
|
export class JwtAuthGuard extends AuthGuard('jwt') {}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import {IsNotEmpty, IsString} from "class-validator";
|
import { IsNotEmpty, IsString } from 'class-validator';
|
||||||
|
|
||||||
export class LocalLoginDto {
|
export class LocalLoginDto {
|
||||||
@IsString()
|
@IsString()
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import {Exclude} from "class-transformer";
|
import { Exclude } from 'class-transformer';
|
||||||
|
|
||||||
export class UserEntity {
|
export class UserEntity {
|
||||||
id: string;
|
id: string;
|
||||||
|
|
|
@ -1,17 +1,20 @@
|
||||||
import {Injectable} from "@nestjs/common";
|
import { Injectable } from '@nestjs/common';
|
||||||
import * as crypto from "crypto";
|
import * as crypto from 'crypto';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class CipherService {
|
export class CipherService {
|
||||||
// Hash functions
|
// Hash functions
|
||||||
getSum(content: Bun.BlobOrStringOrBuffer): string {
|
getSum(content: Bun.BlobOrStringOrBuffer): string {
|
||||||
if(!content) content = "";
|
if (!content) content = '';
|
||||||
return new Bun.CryptoHasher("sha256").update(content).digest().toString("hex");
|
return new Bun.CryptoHasher('sha256')
|
||||||
|
.update(content)
|
||||||
|
.digest()
|
||||||
|
.toString('hex');
|
||||||
}
|
}
|
||||||
|
|
||||||
hashPassword(content: Bun.StringOrBuffer, cost = 10): string {
|
hashPassword(content: Bun.StringOrBuffer, cost = 10): string {
|
||||||
return Bun.password.hashSync(content, {
|
return Bun.password.hashSync(content, {
|
||||||
algorithm: "argon2id",
|
algorithm: 'argon2id',
|
||||||
timeCost: cost,
|
timeCost: cost,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -23,126 +26,189 @@ export class CipherService{
|
||||||
// Symmetric functions
|
// Symmetric functions
|
||||||
private prepareEncryptionKey(encryptionKey: string | Buffer): Buffer {
|
private prepareEncryptionKey(encryptionKey: string | Buffer): Buffer {
|
||||||
let keyBuffer: Buffer;
|
let keyBuffer: Buffer;
|
||||||
if(typeof encryptionKey === "string")
|
if (typeof encryptionKey === 'string')
|
||||||
keyBuffer = Buffer.from(encryptionKey);
|
keyBuffer = Buffer.from(encryptionKey);
|
||||||
else
|
else keyBuffer = encryptionKey;
|
||||||
keyBuffer = encryptionKey;
|
|
||||||
const key = Buffer.alloc(64);
|
const key = Buffer.alloc(64);
|
||||||
keyBuffer.copy(key);
|
keyBuffer.copy(key);
|
||||||
return key;
|
return key;
|
||||||
}
|
}
|
||||||
|
|
||||||
cipherSymmetric(content: string, encryptionKey: string | Buffer): string {
|
cipherSymmetric(content: string, encryptionKey: string | Buffer): string {
|
||||||
if(!content) content = "";
|
if (!content) content = '';
|
||||||
return this.cipherBufferSymmetric(Buffer.from(content, "utf-8"), encryptionKey).toString("hex");
|
return this.cipherBufferSymmetric(
|
||||||
|
Buffer.from(content, 'utf-8'),
|
||||||
|
encryptionKey,
|
||||||
|
).toString('hex');
|
||||||
}
|
}
|
||||||
|
|
||||||
cipherBufferSymmetric(content: Buffer, encryptionKey: string | Buffer): Buffer{
|
cipherBufferSymmetric(
|
||||||
|
content: Buffer,
|
||||||
|
encryptionKey: string | Buffer,
|
||||||
|
): Buffer {
|
||||||
if (!content) content = Buffer.alloc(0);
|
if (!content) content = Buffer.alloc(0);
|
||||||
const iv = crypto.randomBytes(12);
|
const iv = crypto.randomBytes(12);
|
||||||
const key = this.prepareEncryptionKey(encryptionKey);
|
const key = this.prepareEncryptionKey(encryptionKey);
|
||||||
const cipher = crypto.createCipheriv("aes-256-gcm", key.subarray(0, 32), iv);
|
const cipher = crypto.createCipheriv(
|
||||||
|
'aes-256-gcm',
|
||||||
|
key.subarray(0, 32),
|
||||||
|
iv,
|
||||||
|
);
|
||||||
const encrypted = Buffer.concat([cipher.update(content), cipher.final()]);
|
const encrypted = Buffer.concat([cipher.update(content), cipher.final()]);
|
||||||
const tag = cipher.getAuthTag();
|
const tag = cipher.getAuthTag();
|
||||||
return Buffer.concat([iv, encrypted, tag]);
|
return Buffer.concat([iv, encrypted, tag]);
|
||||||
}
|
}
|
||||||
|
|
||||||
decipherSymmetric(encryptedContent: string, encryptionKey: string | Buffer): string{
|
decipherSymmetric(
|
||||||
return this.decipherBufferSymmetric(Buffer.from(encryptedContent, "hex"), encryptionKey).toString("utf-8");
|
encryptedContent: string,
|
||||||
|
encryptionKey: string | Buffer,
|
||||||
|
): string {
|
||||||
|
return this.decipherBufferSymmetric(
|
||||||
|
Buffer.from(encryptedContent, 'hex'),
|
||||||
|
encryptionKey,
|
||||||
|
).toString('utf-8');
|
||||||
}
|
}
|
||||||
|
|
||||||
decipherBufferSymmetric(encryptedContent: Buffer, encryptionKey: string | Buffer): Buffer{
|
decipherBufferSymmetric(
|
||||||
|
encryptedContent: Buffer,
|
||||||
|
encryptionKey: string | Buffer,
|
||||||
|
): Buffer {
|
||||||
const iv = encryptedContent.subarray(0, 12);
|
const iv = encryptedContent.subarray(0, 12);
|
||||||
const encrypted = encryptedContent.subarray(12, encryptedContent.length - 16);
|
const encrypted = encryptedContent.subarray(
|
||||||
|
12,
|
||||||
|
encryptedContent.length - 16,
|
||||||
|
);
|
||||||
const tag = encryptedContent.subarray(encryptedContent.length - 16);
|
const tag = encryptedContent.subarray(encryptedContent.length - 16);
|
||||||
const key = this.prepareEncryptionKey(encryptionKey);
|
const key = this.prepareEncryptionKey(encryptionKey);
|
||||||
const decipher = crypto.createDecipheriv("aes-256-gcm", key.subarray(0, 32), iv);
|
const decipher = crypto.createDecipheriv(
|
||||||
|
'aes-256-gcm',
|
||||||
|
key.subarray(0, 32),
|
||||||
|
iv,
|
||||||
|
);
|
||||||
decipher.setAuthTag(tag);
|
decipher.setAuthTag(tag);
|
||||||
try {
|
try {
|
||||||
return Buffer.concat([decipher.update(encrypted), decipher.final()]);
|
return Buffer.concat([decipher.update(encrypted), decipher.final()]);
|
||||||
} catch (_) {
|
} catch (_) {
|
||||||
throw new Error("Decryption failed");
|
throw new Error('Decryption failed');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
cipherHardSymmetric(content: string, encryptionKey: string | Buffer, timeCost = 200000){
|
cipherHardSymmetric(
|
||||||
if(!content) content = "";
|
content: string,
|
||||||
|
encryptionKey: string | Buffer,
|
||||||
|
timeCost = 200000,
|
||||||
|
) {
|
||||||
|
if (!content) content = '';
|
||||||
const salt = crypto.randomBytes(32);
|
const salt = crypto.randomBytes(32);
|
||||||
const key = crypto.pbkdf2Sync(encryptionKey, salt, timeCost, 64, "sha512");
|
const key = crypto.pbkdf2Sync(encryptionKey, salt, timeCost, 64, 'sha512');
|
||||||
const iv = crypto.randomBytes(16);
|
const iv = crypto.randomBytes(16);
|
||||||
const cipher = crypto.createCipheriv("aes-256-cbc", key.subarray(0, 32), iv);
|
const cipher = crypto.createCipheriv(
|
||||||
let encrypted = cipher.update(content, "utf-8", "hex");
|
'aes-256-cbc',
|
||||||
encrypted += cipher.final("hex");
|
key.subarray(0, 32),
|
||||||
const hmac = crypto.createHmac("sha256", key.subarray(32));
|
iv,
|
||||||
hmac.update(`${salt.toString("hex")}:${iv.toString("hex")}:${encrypted}`);
|
);
|
||||||
const digest = hmac.digest("hex");
|
let encrypted = cipher.update(content, 'utf-8', 'hex');
|
||||||
return `${salt.toString("hex")}:${iv.toString("hex")}:${encrypted}:${digest}`;
|
encrypted += cipher.final('hex');
|
||||||
|
const hmac = crypto.createHmac('sha256', key.subarray(32));
|
||||||
|
hmac.update(`${salt.toString('hex')}:${iv.toString('hex')}:${encrypted}`);
|
||||||
|
const digest = hmac.digest('hex');
|
||||||
|
return `${salt.toString('hex')}:${iv.toString('hex')}:${encrypted}:${digest}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
decipherHardSymmetric(encryptedContent: string, encryptionKey: string | Buffer, timeCost = 200000){
|
decipherHardSymmetric(
|
||||||
const [saltString, ivString, encryptedString, digest] = encryptedContent.split(":");
|
encryptedContent: string,
|
||||||
const salt = Buffer.from(saltString, "hex");
|
encryptionKey: string | Buffer,
|
||||||
const key = crypto.pbkdf2Sync(encryptionKey, salt, timeCost, 64, "sha512");
|
timeCost = 200000,
|
||||||
const iv = Buffer.from(ivString, "hex");
|
) {
|
||||||
const hmac = crypto.createHmac("sha256", key.subarray(32));
|
const [saltString, ivString, encryptedString, digest] =
|
||||||
|
encryptedContent.split(':');
|
||||||
|
const salt = Buffer.from(saltString, 'hex');
|
||||||
|
const key = crypto.pbkdf2Sync(encryptionKey, salt, timeCost, 64, 'sha512');
|
||||||
|
const iv = Buffer.from(ivString, 'hex');
|
||||||
|
const hmac = crypto.createHmac('sha256', key.subarray(32));
|
||||||
hmac.update(`${saltString}:${ivString}:${encryptedString}`);
|
hmac.update(`${saltString}:${ivString}:${encryptedString}`);
|
||||||
const calculatedDigest = hmac.digest("hex");
|
const calculatedDigest = hmac.digest('hex');
|
||||||
if(calculatedDigest !== digest)
|
if (calculatedDigest !== digest) throw new Error('Integrity check failed');
|
||||||
throw new Error("Integrity check failed");
|
const decipher = crypto.createDecipheriv(
|
||||||
const decipher = crypto.createDecipheriv("aes-256-cbc", key.subarray(0, 32), iv);
|
'aes-256-cbc',
|
||||||
let decrypted = decipher.update(encryptedString, "hex", "utf-8");
|
key.subarray(0, 32),
|
||||||
decrypted += decipher.final("utf-8");
|
iv,
|
||||||
|
);
|
||||||
|
let decrypted = decipher.update(encryptedString, 'hex', 'utf-8');
|
||||||
|
decrypted += decipher.final('utf-8');
|
||||||
return decrypted;
|
return decrypted;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Asymmetric functions
|
// Asymmetric functions
|
||||||
generateKeyPair(modulusLength = 4096, privateEncryptionKey = null): crypto.KeyPairSyncResult<string, string>{
|
generateKeyPair(
|
||||||
|
modulusLength = 4096,
|
||||||
|
privateEncryptionKey = null,
|
||||||
|
): crypto.KeyPairSyncResult<string, string> {
|
||||||
if (!privateEncryptionKey)
|
if (!privateEncryptionKey)
|
||||||
console.warn("No private encryption key provided, the private key will not be encrypted");
|
console.warn(
|
||||||
|
'No private encryption key provided, the private key will not be encrypted',
|
||||||
|
);
|
||||||
let options = undefined;
|
let options = undefined;
|
||||||
if (privateEncryptionKey) {
|
if (privateEncryptionKey) {
|
||||||
options = {
|
options = {
|
||||||
cipher: "aes-256-cbc",
|
cipher: 'aes-256-cbc',
|
||||||
passphrase: privateEncryptionKey,
|
passphrase: privateEncryptionKey,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
return crypto.generateKeyPairSync("rsa", {
|
return crypto.generateKeyPairSync('rsa', {
|
||||||
modulusLength: modulusLength,
|
modulusLength: modulusLength,
|
||||||
publicKeyEncoding: {
|
publicKeyEncoding: {
|
||||||
type: "spki",
|
type: 'spki',
|
||||||
format: "pem",
|
format: 'pem',
|
||||||
},
|
},
|
||||||
privateKeyEncoding: {
|
privateKeyEncoding: {
|
||||||
type: "pkcs8",
|
type: 'pkcs8',
|
||||||
format: "pem",
|
format: 'pem',
|
||||||
...options,
|
...options,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
encryptAsymmetric(content: string, publicKey: string | Buffer): string {
|
encryptAsymmetric(content: string, publicKey: string | Buffer): string {
|
||||||
if(!content) content = "";
|
if (!content) content = '';
|
||||||
const buffer = Buffer.from(content, "utf-8");
|
const buffer = Buffer.from(content, 'utf-8');
|
||||||
const encrypted = crypto.publicEncrypt({
|
const encrypted = crypto.publicEncrypt(
|
||||||
|
{
|
||||||
key: publicKey,
|
key: publicKey,
|
||||||
padding: crypto.constants.RSA_PKCS1_OAEP_PADDING,
|
padding: crypto.constants.RSA_PKCS1_OAEP_PADDING,
|
||||||
}, buffer);
|
},
|
||||||
return encrypted.toString("base64");
|
buffer,
|
||||||
|
);
|
||||||
|
return encrypted.toString('base64');
|
||||||
}
|
}
|
||||||
|
|
||||||
decryptAsymmetric(encryptedContent: string, privateKey: string | Buffer, privateEncryptionKey = undefined): string{
|
decryptAsymmetric(
|
||||||
const buffer = Buffer.from(encryptedContent, "base64");
|
encryptedContent: string,
|
||||||
|
privateKey: string | Buffer,
|
||||||
|
privateEncryptionKey = undefined,
|
||||||
|
): string {
|
||||||
|
const buffer = Buffer.from(encryptedContent, 'base64');
|
||||||
if (!privateEncryptionKey)
|
if (!privateEncryptionKey)
|
||||||
return crypto.privateDecrypt({
|
return crypto
|
||||||
|
.privateDecrypt(
|
||||||
|
{
|
||||||
key: privateKey,
|
key: privateKey,
|
||||||
padding: crypto.constants.RSA_PKCS1_OAEP_PADDING,
|
padding: crypto.constants.RSA_PKCS1_OAEP_PADDING,
|
||||||
}, buffer).toString("utf-8");
|
},
|
||||||
|
buffer,
|
||||||
|
)
|
||||||
|
.toString('utf-8');
|
||||||
else
|
else
|
||||||
return crypto.privateDecrypt({
|
return crypto
|
||||||
|
.privateDecrypt(
|
||||||
|
{
|
||||||
key: privateKey,
|
key: privateKey,
|
||||||
padding: crypto.constants.RSA_PKCS1_OAEP_PADDING,
|
padding: crypto.constants.RSA_PKCS1_OAEP_PADDING,
|
||||||
passphrase: privateEncryptionKey,
|
passphrase: privateEncryptionKey,
|
||||||
}, buffer).toString("utf-8");
|
},
|
||||||
|
buffer,
|
||||||
|
)
|
||||||
|
.toString('utf-8');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Secret functions
|
// Secret functions
|
||||||
|
@ -151,7 +217,7 @@ export class CipherService{
|
||||||
* @param bytes Number of bytes to generate
|
* @param bytes Number of bytes to generate
|
||||||
*/
|
*/
|
||||||
generateRandomBytes(bytes = 32): string {
|
generateRandomBytes(bytes = 32): string {
|
||||||
return crypto.randomBytes(bytes).toString("hex");
|
return crypto.randomBytes(bytes).toString('hex');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -159,15 +225,24 @@ export class CipherService{
|
||||||
* @param numbersNumber Number of numbers to generate
|
* @param numbersNumber Number of numbers to generate
|
||||||
*/
|
*/
|
||||||
generateRandomNumbers(numbersNumber = 6): string {
|
generateRandomNumbers(numbersNumber = 6): string {
|
||||||
return Array.from({length: numbersNumber}, () => Math.floor(Math.random() * 10)).join("");
|
return Array.from({ length: numbersNumber }, () =>
|
||||||
|
Math.floor(Math.random() * 10),
|
||||||
|
).join('');
|
||||||
}
|
}
|
||||||
|
|
||||||
maskSensitiveInfo(str: string, visibleCount: number = 4, maskChar: string = "*"){
|
maskSensitiveInfo(
|
||||||
return str.slice(0, visibleCount) + maskChar.repeat(Math.max(0, str.length - visibleCount));
|
str: string,
|
||||||
|
visibleCount: number = 4,
|
||||||
|
maskChar: string = '*',
|
||||||
|
) {
|
||||||
|
return (
|
||||||
|
str.slice(0, visibleCount) +
|
||||||
|
maskChar.repeat(Math.max(0, str.length - visibleCount))
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
maskEmail(email: string) {
|
maskEmail(email: string) {
|
||||||
const [username, domain] = email.split("@");
|
const [username, domain] = email.split('@');
|
||||||
return `${this.maskSensitiveInfo(username)}@${domain}`;
|
return `${this.maskSensitiveInfo(username)}@${domain}`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import {Injectable, Logger, OnModuleInit} from "@nestjs/common";
|
import { Injectable, Logger, OnModuleInit } from '@nestjs/common';
|
||||||
import {PrismaClient} from "@prisma/client";
|
import { PrismaClient } from '@prisma/client';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class PrismaService extends PrismaClient implements OnModuleInit {
|
export class PrismaService extends PrismaClient implements OnModuleInit {
|
||||||
|
@ -15,7 +15,9 @@ export class PrismaService extends PrismaClient implements OnModuleInit{
|
||||||
const duration: number = Date.now() - startTime;
|
const duration: number = Date.now() - startTime;
|
||||||
const requestCount: number = args.length || 1;
|
const requestCount: number = args.length || 1;
|
||||||
const resultCount: number = !result ? 0 : result.length || 1;
|
const resultCount: number = !result ? 0 : result.length || 1;
|
||||||
PrismaService.logger.log(`${model.toUpperCase()} ${operation.toLowerCase()} ${duration}ms ${requestCount} ${resultCount}`);
|
PrismaService.logger.log(
|
||||||
|
`${model.toUpperCase()} ${operation.toLowerCase()} ${duration}ms ${requestCount} ${resultCount}`,
|
||||||
|
);
|
||||||
return result;
|
return result;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,4 +1,9 @@
|
||||||
import {BadRequestException, Logger, ValidationError, ValidationPipe} from "@nestjs/common";
|
import {
|
||||||
|
BadRequestException,
|
||||||
|
Logger,
|
||||||
|
ValidationError,
|
||||||
|
ValidationPipe,
|
||||||
|
} from '@nestjs/common';
|
||||||
|
|
||||||
export class CustomValidationPipe extends ValidationPipe {
|
export class CustomValidationPipe extends ValidationPipe {
|
||||||
private readonly logger = new Logger(CustomValidationPipe.name);
|
private readonly logger = new Logger(CustomValidationPipe.name);
|
||||||
|
@ -15,7 +20,7 @@ export class CustomValidationPipe extends ValidationPipe{
|
||||||
if (this.isDetailedOutputDisabled) {
|
if (this.isDetailedOutputDisabled) {
|
||||||
return new BadRequestException();
|
return new BadRequestException();
|
||||||
}
|
}
|
||||||
const messages = validationErrors.map(error => ({
|
const messages = validationErrors.map((error) => ({
|
||||||
property: error.property,
|
property: error.property,
|
||||||
constraints: error.constraints,
|
constraints: error.constraints,
|
||||||
}));
|
}));
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
import {Controller, Get, Req, UseGuards} from "@nestjs/common";
|
import { Controller, Get, Req, UseGuards } from '@nestjs/common';
|
||||||
import {JwtAuthGuard} from "../../common/modules/auth/guards/jwt-auth.guard";
|
import { JwtAuthGuard } from '../../common/modules/auth/guards/jwt-auth.guard';
|
||||||
import {UserEntity} from "../../common/modules/auth/models/entities/user.entity";
|
import { UserEntity } from '../../common/modules/auth/models/entities/user.entity';
|
||||||
import {ApiBearerAuth} from "@nestjs/swagger";
|
import { ApiBearerAuth } from '@nestjs/swagger';
|
||||||
|
|
||||||
@Controller("users")
|
@Controller('users')
|
||||||
export class UsersController {
|
export class UsersController {
|
||||||
constructor() {}
|
constructor() {}
|
||||||
|
|
||||||
@Get("me")
|
@Get('me')
|
||||||
@UseGuards(JwtAuthGuard)
|
@UseGuards(JwtAuthGuard)
|
||||||
@ApiBearerAuth()
|
@ApiBearerAuth()
|
||||||
getMyself(@Req() req: any): UserEntity {
|
getMyself(@Req() req: any): UserEntity {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import {Module} from "@nestjs/common";
|
import { Module } from '@nestjs/common';
|
||||||
import {UsersService} from "./users.service";
|
import { UsersService } from './users.service';
|
||||||
import {UsersController} from "./users.controller";
|
import { UsersController } from './users.controller';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
providers: [UsersService],
|
providers: [UsersService],
|
||||||
|
|
|
@ -1,12 +1,10 @@
|
||||||
import {Injectable, NotFoundException} from "@nestjs/common";
|
import { Injectable, NotFoundException } from '@nestjs/common';
|
||||||
import {UserEntity} from "../../common/modules/auth/models/entities/user.entity";
|
import { UserEntity } from '../../common/modules/auth/models/entities/user.entity';
|
||||||
import {PrismaService} from "../../common/modules/helper/prisma.service";
|
import { PrismaService } from '../../common/modules/helper/prisma.service';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class UsersService {
|
export class UsersService {
|
||||||
constructor(
|
constructor(private readonly prismaService: PrismaService) {}
|
||||||
private readonly prismaService: PrismaService,
|
|
||||||
){}
|
|
||||||
|
|
||||||
async getUserById(id: string) {
|
async getUserById(id: string) {
|
||||||
const user = await this.prismaService.users.findUnique({
|
const user = await this.prismaService.users.findUnique({
|
||||||
|
@ -17,8 +15,7 @@ export class UsersService{
|
||||||
email_verifications: true,
|
email_verifications: true,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
if(!user)
|
if (!user) throw new NotFoundException('User not found');
|
||||||
throw new NotFoundException("User not found");
|
|
||||||
return new UserEntity({
|
return new UserEntity({
|
||||||
id: user.id,
|
id: user.id,
|
||||||
username: user.username,
|
username: user.username,
|
||||||
|
@ -40,8 +37,7 @@ export class UsersService{
|
||||||
email_verifications: true,
|
email_verifications: true,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
if(!user)
|
if (!user) throw new NotFoundException('User not found');
|
||||||
throw new NotFoundException("User not found");
|
|
||||||
return new UserEntity({
|
return new UserEntity({
|
||||||
id: user.id,
|
id: user.id,
|
||||||
username: user.username,
|
username: user.username,
|
||||||
|
|
Loading…
Add table
Reference in a new issue