json logging + fix lint

This commit is contained in:
unurled 2025-02-07 14:54:10 +01:00
parent 24f3fb9b8e
commit 13ec6e388d
Signed by: unurled
GPG key ID: EFC5F5E709B47DDD
18 changed files with 551 additions and 465 deletions

View file

@ -1,18 +1,18 @@
// @ts-ignore
await Bun.build({
entrypoints: ["./src/app.ts"],
format: "esm",
outdir: "./dist",
target: "node",
external: [
"@nestjs/websockets/socket-module",
"@nestjs/microservices",
"class-transformer/storage",
"@fastify/view",
"@nestjs/platform-express",
],
minify: {
whitespace: true,
syntax: true,
},
entrypoints: ['./src/app.ts'],
format: 'esm',
outdir: './dist',
target: 'node',
external: [
'@nestjs/websockets/socket-module',
'@nestjs/microservices',
'class-transformer/storage',
'@fastify/view',
'@nestjs/platform-express',
],
minify: {
whitespace: true,
syntax: true,
},
});

View file

@ -1,16 +1,16 @@
import {CacheInterceptor} from "@nestjs/cache-manager";
import {VersionEntity} from "./common/models/entities/version.entity";
import {Controller, Get, UseInterceptors} from "@nestjs/common";
import {ApiTags} from "@nestjs/swagger";
import { CacheInterceptor } from '@nestjs/cache-manager';
import { VersionEntity } from './common/models/entities/version.entity';
import { Controller, Get, UseInterceptors } from '@nestjs/common';
import { ApiTags } from '@nestjs/swagger';
@Controller()
@ApiTags("Misc")
@ApiTags('Misc')
@UseInterceptors(CacheInterceptor)
export class AppController{
@Get("version")
async getVersion(): Promise<VersionEntity>{
return {
version: process.env.npm_package_version,
};
}
export class AppController {
@Get('version')
async getVersion(): Promise<VersionEntity> {
return {
version: process.env.npm_package_version,
};
}
}

View file

@ -1,83 +1,91 @@
import {FastifyAdapter, NestFastifyApplication} from "@nestjs/platform-fastify";
import {CustomValidationPipe} from "./common/pipes/custom-validation.pipe";
import {LoggerMiddleware} from "./common/middlewares/logger.middleware";
import {SwaggerTheme, SwaggerThemeNameEnum} from "swagger-themes";
import {DocumentBuilder, SwaggerModule} from "@nestjs/swagger";
import {FastifyListenOptions} from "fastify/types/instance";
import fastifyMultipart from "@fastify/multipart";
import fastifyStatic from "@fastify/static";
import fastifyHelmet from "@fastify/helmet";
import {NestFactory} from "@nestjs/core";
import {AppModule} from "./app.module";
import {Logger} from "@nestjs/common";
import * as process from "process";
import {join} from "node:path";
import {
FastifyAdapter,
NestFastifyApplication,
} from '@nestjs/platform-fastify';
import { CustomValidationPipe } from './common/pipes/custom-validation.pipe';
import { LoggerMiddleware } from './common/middlewares/logger.middleware';
import { SwaggerTheme, SwaggerThemeNameEnum } from 'swagger-themes';
import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';
import { FastifyListenOptions } from 'fastify/types/instance';
import fastifyMultipart from '@fastify/multipart';
import fastifyHelmet from '@fastify/helmet';
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
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;
async function bootstrap(){
const app = await NestFactory.create<NestFastifyApplication>(
AppModule,
new FastifyAdapter({exposeHeadRoutes: true}),
);
await loadServer(app);
async function bootstrap() {
const app = await NestFactory.create<NestFastifyApplication>(
AppModule,
new FastifyAdapter({ exposeHeadRoutes: true }),
{
logger: new ConsoleLogger({
json: true,
}),
},
);
await loadServer(app);
await app.listen({
port: port,
host: "0.0.0.0",
} as FastifyListenOptions);
app.enableShutdownHooks();
logger.log(`Listening on http://0.0.0.0:${port}`);
await app.listen({
port: port,
host: '0.0.0.0',
} as FastifyListenOptions);
app.enableShutdownHooks();
logger.log(`Listening on http://0.0.0.0:${port}`);
}
async function loadServer(server: NestFastifyApplication){
// Config
server.setGlobalPrefix(process.env.PREFIX);
server.enableCors({
origin: "*",
});
async function loadServer(server: NestFastifyApplication) {
// Config
server.setGlobalPrefix(process.env.PREFIX);
server.enableCors({
origin: '*',
});
// Middlewares
server.use(new LoggerMiddleware().use);
await server.register(fastifyMultipart as any);
await server.register(fastifyStatic as any, {
root: join(process.cwd(), "public_answers"),
prefix: "/public_answers/",
});
await server.register(fastifyHelmet as any, {
contentSecurityPolicy: false,
crossOriginEmbedderPolicy: false,
crossOriginOpenerPolicy: false,
crossOriginResourcePolicy: false,
} as any);
// Middlewares
server.use(new LoggerMiddleware().use);
await server.register(fastifyMultipart as any);
await server.register(
fastifyHelmet as any,
{
contentSecurityPolicy: false,
crossOriginEmbedderPolicy: false,
crossOriginOpenerPolicy: false,
crossOriginResourcePolicy: false,
} as any,
);
// Swagger
const config = new DocumentBuilder()
.setTitle(process.env.APP_NAME)
.setDescription(`Documentation for ${process.env.APP_NAME}`)
.setVersion(process.env.npm_package_version)
.addBearerAuth()
.build();
// Swagger
const config = new DocumentBuilder()
.setTitle(process.env.APP_NAME)
.setDescription(`Documentation for ${process.env.APP_NAME}`)
.setVersion(process.env.npm_package_version)
.addBearerAuth()
.build();
const document = SwaggerModule.createDocument(server, config);
const theme = new SwaggerTheme();
const customCss = theme.getBuffer(SwaggerThemeNameEnum.DARK);
SwaggerModule.setup("api", server, document, {
swaggerOptions: {
filter: true,
displayRequestDuration: true,
persistAuthorization: true,
docExpansion: "none",
tagsSorter: "alpha",
operationsSorter: "method",
},
customCss,
});
const document = SwaggerModule.createDocument(server, config);
const theme = new SwaggerTheme();
const customCss = theme.getBuffer(SwaggerThemeNameEnum.DARK);
SwaggerModule.setup('api', server, document, {
swaggerOptions: {
filter: true,
displayRequestDuration: true,
persistAuthorization: true,
docExpansion: 'none',
tagsSorter: 'alpha',
operationsSorter: 'method',
},
customCss,
});
server.useGlobalPipes(new CustomValidationPipe());
server.useGlobalPipes(new CustomValidationPipe());
}
bootstrap();

View file

@ -1,52 +1,52 @@
import {Injectable, Logger, NestMiddleware} from "@nestjs/common";
import {FastifyReply, FastifyRequest} from "fastify";
import { Injectable, Logger, NestMiddleware } from '@nestjs/common';
import { FastifyReply, FastifyRequest } from 'fastify';
@Injectable()
export class LoggerMiddleware implements NestMiddleware{
static logger: Logger = new Logger(LoggerMiddleware.name);
export class LoggerMiddleware implements NestMiddleware {
static logger: Logger = new Logger(LoggerMiddleware.name);
use(req: FastifyRequest["raw"], res: FastifyReply["raw"], next: () => void){
const startTime = Date.now();
(res as any).on("finish", () => {
const path = req.url;
try{
const protocol = LoggerMiddleware.getProtocol(req);
const method = req.method;
if(method === "OPTIONS")
return;
const statusCode = res.statusCode;
const duration = Date.now() - startTime;
const resSize: any = res.getHeader("Content-Length") || "0";
const intResSize = parseInt(resSize);
LoggerMiddleware.logger.log(`${protocol} ${method} ${path} ${statusCode} ${duration}ms ${intResSize}`);
LoggerMiddleware.logRequestTime(path, method, duration);
}catch(e){
LoggerMiddleware.logger.warn(`Can't log route ${path} : ${e}`);
}
});
next();
}
use(req: FastifyRequest['raw'], res: FastifyReply['raw'], next: () => void) {
const startTime = Date.now();
(res as any).on('finish', () => {
const path = req.url;
try {
const protocol = LoggerMiddleware.getProtocol(req);
const method = req.method;
if (method === 'OPTIONS') return;
const statusCode = res.statusCode;
const duration = Date.now() - startTime;
const resSize: any = res.getHeader('Content-Length') || '0';
const intResSize = parseInt(resSize);
LoggerMiddleware.logger.log(
`${protocol} ${method} ${path} ${statusCode} ${duration}ms ${intResSize}`,
);
LoggerMiddleware.logRequestTime(path, method, duration);
} catch (e) {
LoggerMiddleware.logger.warn(`Can't log route ${path} : ${e}`);
}
});
next();
}
static getProtocol(req: FastifyRequest["raw"]): string{
const localPort: number = req.connection.localPort;
if(!localPort)
return "H2";
return localPort.toString() === process.env.HTTPS_PORT ? "HTTPS" : "HTTP";
}
static getProtocol(req: FastifyRequest['raw']): string {
const localPort: number = req.connection.localPort;
if (!localPort) return 'H2';
return localPort.toString() === process.env.HTTPS_PORT ? 'HTTPS' : 'HTTP';
}
static logRequestTime(path: string, method: string, duration: number): void{
const thresholds: Record<string, number> = {
GET: 750,
POST: 1500,
PUT: 1500,
PATCH: 500,
DELETE: 500,
};
const threshold = thresholds[method];
if(threshold && duration > threshold){
LoggerMiddleware.logger.warn(
`${method} (${path}) request exceeded ${threshold}ms (${duration}ms)`,
);
}
static logRequestTime(path: string, method: string, duration: number): void {
const thresholds: Record<string, number> = {
GET: 750,
POST: 1500,
PUT: 1500,
PATCH: 500,
DELETE: 500,
};
const threshold = thresholds[method];
if (threshold && duration > threshold) {
LoggerMiddleware.logger.warn(
`${method} (${path}) request exceeded ${threshold}ms (${duration}ms)`,
);
}
}
}

View file

@ -1,13 +1,13 @@
import {IsNumber, IsOptional, Min} from "class-validator";
import { IsNumber, IsOptional, Min } from 'class-validator';
export class PaginationDto{
@IsNumber()
@Min(1)
@IsOptional()
take?: number;
export class PaginationDto {
@IsNumber()
@Min(1)
@IsOptional()
take?: number;
@IsNumber()
@Min(0)
@IsOptional()
skip?: number;
@IsNumber()
@Min(0)
@IsOptional()
skip?: number;
}

View file

@ -1,6 +1,6 @@
import {ApiProperty} from "@nestjs/swagger";
import { ApiProperty } from '@nestjs/swagger';
export class VersionEntity{
@ApiProperty()
version: string;
export class VersionEntity {
@ApiProperty()
version: string;
}

View file

@ -1,6 +1,6 @@
export class PaginationResponse<T>{
data: T;
total: number;
take: number;
skip: number;
export class PaginationResponse<T> {
data: T;
total: number;
take: number;
skip: number;
}

View file

@ -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 {
constructor() {}
@Delete("logout/all")
@Delete('logout/all')
logoutAll() {
throw new NotImplementedException();
}

View file

@ -1,5 +1,5 @@
import {Injectable} from "@nestjs/common";
import {AuthGuard} from "@nestjs/passport";
import { Injectable } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
@Injectable()
export class JwtAuthGuard extends AuthGuard("jwt"){}
export class JwtAuthGuard extends AuthGuard('jwt') {}

View file

@ -1,11 +1,11 @@
import {IsNotEmpty, IsString} from "class-validator";
import { IsNotEmpty, IsString } from 'class-validator';
export class LocalLoginDto{
@IsString()
@IsNotEmpty()
email: string;
export class LocalLoginDto {
@IsString()
@IsNotEmpty()
email: string;
@IsString()
@IsNotEmpty()
password: string;
@IsString()
@IsNotEmpty()
password: string;
}

View file

@ -1,5 +1,5 @@
export class RegisterDto{
email: string;
username: string;
password: string;
export class RegisterDto {
email: string;
username: string;
password: string;
}

View file

@ -1,20 +1,20 @@
import {Exclude} from "class-transformer";
import { Exclude } from 'class-transformer';
export class UserEntity{
id: string;
email: string;
username: string;
createdAt: Date;
updatedAt: Date;
verified: boolean;
export class UserEntity {
id: string;
email: string;
username: string;
createdAt: Date;
updatedAt: Date;
verified: boolean;
@Exclude()
password: string;
@Exclude()
password: string;
@Exclude()
tokenId: string;
@Exclude()
tokenId: string;
constructor(partial: Partial<UserEntity>){
Object.assign(this, partial);
}
constructor(partial: Partial<UserEntity>) {
Object.assign(this, partial);
}
}

View file

@ -1,173 +1,248 @@
import {Injectable} from "@nestjs/common";
import * as crypto from "crypto";
import { Injectable } from '@nestjs/common';
import * as crypto from 'crypto';
@Injectable()
export class CipherService{
// Hash functions
getSum(content: Bun.BlobOrStringOrBuffer): string{
if(!content) content = "";
return new Bun.CryptoHasher("sha256").update(content).digest().toString("hex");
}
export class CipherService {
// Hash functions
getSum(content: Bun.BlobOrStringOrBuffer): string {
if (!content) content = '';
return new Bun.CryptoHasher('sha256')
.update(content)
.digest()
.toString('hex');
}
hashPassword(content: Bun.StringOrBuffer, cost = 10): string{
return Bun.password.hashSync(content, {
algorithm: "argon2id",
timeCost: cost,
});
}
hashPassword(content: Bun.StringOrBuffer, cost = 10): string {
return Bun.password.hashSync(content, {
algorithm: 'argon2id',
timeCost: cost,
});
}
comparePassword(password: Bun.StringOrBuffer, hash: string): boolean{
return Bun.password.verifySync(password, hash);
}
comparePassword(password: Bun.StringOrBuffer, hash: string): boolean {
return Bun.password.verifySync(password, hash);
}
// Symmetric functions
private prepareEncryptionKey(encryptionKey: string | Buffer): Buffer{
let keyBuffer: Buffer;
if(typeof encryptionKey === "string")
keyBuffer = Buffer.from(encryptionKey);
else
keyBuffer = encryptionKey;
const key = Buffer.alloc(64);
keyBuffer.copy(key);
return key;
}
// Symmetric functions
private prepareEncryptionKey(encryptionKey: string | Buffer): Buffer {
let keyBuffer: Buffer;
if (typeof encryptionKey === 'string')
keyBuffer = Buffer.from(encryptionKey);
else keyBuffer = encryptionKey;
const key = Buffer.alloc(64);
keyBuffer.copy(key);
return key;
}
cipherSymmetric(content: string, encryptionKey: string | Buffer): string{
if(!content) content = "";
return this.cipherBufferSymmetric(Buffer.from(content, "utf-8"), encryptionKey).toString("hex");
}
cipherSymmetric(content: string, encryptionKey: string | Buffer): string {
if (!content) content = '';
return this.cipherBufferSymmetric(
Buffer.from(content, 'utf-8'),
encryptionKey,
).toString('hex');
}
cipherBufferSymmetric(content: Buffer, encryptionKey: string | Buffer): Buffer{
if(!content) content = Buffer.alloc(0);
const iv = crypto.randomBytes(12);
const key = this.prepareEncryptionKey(encryptionKey);
const cipher = crypto.createCipheriv("aes-256-gcm", key.subarray(0, 32), iv);
const encrypted = Buffer.concat([cipher.update(content), cipher.final()]);
const tag = cipher.getAuthTag();
return Buffer.concat([iv, encrypted, tag]);
}
cipherBufferSymmetric(
content: Buffer,
encryptionKey: string | Buffer,
): Buffer {
if (!content) content = Buffer.alloc(0);
const iv = crypto.randomBytes(12);
const key = this.prepareEncryptionKey(encryptionKey);
const cipher = crypto.createCipheriv(
'aes-256-gcm',
key.subarray(0, 32),
iv,
);
const encrypted = Buffer.concat([cipher.update(content), cipher.final()]);
const tag = cipher.getAuthTag();
return Buffer.concat([iv, encrypted, tag]);
}
decipherSymmetric(encryptedContent: string, encryptionKey: string | Buffer): string{
return this.decipherBufferSymmetric(Buffer.from(encryptedContent, "hex"), encryptionKey).toString("utf-8");
}
decipherSymmetric(
encryptedContent: string,
encryptionKey: string | Buffer,
): string {
return this.decipherBufferSymmetric(
Buffer.from(encryptedContent, 'hex'),
encryptionKey,
).toString('utf-8');
}
decipherBufferSymmetric(encryptedContent: Buffer, encryptionKey: string | Buffer): Buffer{
const iv = encryptedContent.subarray(0, 12);
const encrypted = encryptedContent.subarray(12, encryptedContent.length - 16);
const tag = encryptedContent.subarray(encryptedContent.length - 16);
const key = this.prepareEncryptionKey(encryptionKey);
const decipher = crypto.createDecipheriv("aes-256-gcm", key.subarray(0, 32), iv);
decipher.setAuthTag(tag);
try{
return Buffer.concat([decipher.update(encrypted), decipher.final()]);
}catch(_){
throw new Error("Decryption failed");
}
decipherBufferSymmetric(
encryptedContent: Buffer,
encryptionKey: string | Buffer,
): Buffer {
const iv = encryptedContent.subarray(0, 12);
const encrypted = encryptedContent.subarray(
12,
encryptedContent.length - 16,
);
const tag = encryptedContent.subarray(encryptedContent.length - 16);
const key = this.prepareEncryptionKey(encryptionKey);
const decipher = crypto.createDecipheriv(
'aes-256-gcm',
key.subarray(0, 32),
iv,
);
decipher.setAuthTag(tag);
try {
return Buffer.concat([decipher.update(encrypted), decipher.final()]);
} catch (_) {
throw new Error('Decryption failed');
}
}
cipherHardSymmetric(content: string, encryptionKey: string | Buffer, timeCost = 200000){
if(!content) content = "";
const salt = crypto.randomBytes(32);
const key = crypto.pbkdf2Sync(encryptionKey, salt, timeCost, 64, "sha512");
const iv = crypto.randomBytes(16);
const cipher = crypto.createCipheriv("aes-256-cbc", key.subarray(0, 32), iv);
let encrypted = cipher.update(content, "utf-8", "hex");
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}`;
cipherHardSymmetric(
content: string,
encryptionKey: string | Buffer,
timeCost = 200000,
) {
if (!content) content = '';
const salt = crypto.randomBytes(32);
const key = crypto.pbkdf2Sync(encryptionKey, salt, timeCost, 64, 'sha512');
const iv = crypto.randomBytes(16);
const cipher = crypto.createCipheriv(
'aes-256-cbc',
key.subarray(0, 32),
iv,
);
let encrypted = cipher.update(content, 'utf-8', 'hex');
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,
) {
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}`);
const calculatedDigest = hmac.digest('hex');
if (calculatedDigest !== digest) throw new Error('Integrity check failed');
const decipher = crypto.createDecipheriv(
'aes-256-cbc',
key.subarray(0, 32),
iv,
);
let decrypted = decipher.update(encryptedString, 'hex', 'utf-8');
decrypted += decipher.final('utf-8');
return decrypted;
}
// Asymmetric functions
generateKeyPair(
modulusLength = 4096,
privateEncryptionKey = null,
): crypto.KeyPairSyncResult<string, string> {
if (!privateEncryptionKey)
console.warn(
'No private encryption key provided, the private key will not be encrypted',
);
let options = undefined;
if (privateEncryptionKey) {
options = {
cipher: 'aes-256-cbc',
passphrase: privateEncryptionKey,
};
}
return crypto.generateKeyPairSync('rsa', {
modulusLength: modulusLength,
publicKeyEncoding: {
type: 'spki',
format: 'pem',
},
privateKeyEncoding: {
type: 'pkcs8',
format: 'pem',
...options,
},
});
}
decipherHardSymmetric(encryptedContent: string, encryptionKey: string | Buffer, timeCost = 200000){
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}`);
const calculatedDigest = hmac.digest("hex");
if(calculatedDigest !== digest)
throw new Error("Integrity check failed");
const decipher = crypto.createDecipheriv("aes-256-cbc", key.subarray(0, 32), iv);
let decrypted = decipher.update(encryptedString, "hex", "utf-8");
decrypted += decipher.final("utf-8");
return decrypted;
}
encryptAsymmetric(content: string, publicKey: string | Buffer): string {
if (!content) content = '';
const buffer = Buffer.from(content, 'utf-8');
const encrypted = crypto.publicEncrypt(
{
key: publicKey,
padding: crypto.constants.RSA_PKCS1_OAEP_PADDING,
},
buffer,
);
return encrypted.toString('base64');
}
// Asymmetric functions
generateKeyPair(modulusLength = 4096, privateEncryptionKey = null): crypto.KeyPairSyncResult<string, string>{
if(!privateEncryptionKey)
console.warn("No private encryption key provided, the private key will not be encrypted");
let options = undefined;
if(privateEncryptionKey){
options = {
cipher: "aes-256-cbc",
passphrase: privateEncryptionKey,
};
}
return crypto.generateKeyPairSync("rsa", {
modulusLength: modulusLength,
publicKeyEncoding: {
type: "spki",
format: "pem",
},
privateKeyEncoding: {
type: "pkcs8",
format: "pem",
...options,
},
});
}
encryptAsymmetric(content: string, publicKey: string | Buffer): string{
if(!content) content = "";
const buffer = Buffer.from(content, "utf-8");
const encrypted = crypto.publicEncrypt({
key: publicKey,
decryptAsymmetric(
encryptedContent: string,
privateKey: string | Buffer,
privateEncryptionKey = undefined,
): string {
const buffer = Buffer.from(encryptedContent, 'base64');
if (!privateEncryptionKey)
return crypto
.privateDecrypt(
{
key: privateKey,
padding: crypto.constants.RSA_PKCS1_OAEP_PADDING,
}, buffer);
return encrypted.toString("base64");
}
},
buffer,
)
.toString('utf-8');
else
return crypto
.privateDecrypt(
{
key: privateKey,
padding: crypto.constants.RSA_PKCS1_OAEP_PADDING,
passphrase: privateEncryptionKey,
},
buffer,
)
.toString('utf-8');
}
decryptAsymmetric(encryptedContent: string, privateKey: string | Buffer, privateEncryptionKey = undefined): string{
const buffer = Buffer.from(encryptedContent, "base64");
if(!privateEncryptionKey)
return crypto.privateDecrypt({
key: privateKey,
padding: crypto.constants.RSA_PKCS1_OAEP_PADDING,
}, buffer).toString("utf-8");
else
return crypto.privateDecrypt({
key: privateKey,
padding: crypto.constants.RSA_PKCS1_OAEP_PADDING,
passphrase: privateEncryptionKey,
}, buffer).toString("utf-8");
}
// Secret functions
/**
* Generate a random string
* @param bytes Number of bytes to generate
*/
generateRandomBytes(bytes = 32): string {
return crypto.randomBytes(bytes).toString('hex');
}
// Secret functions
/**
* Generate a random string
* @param bytes Number of bytes to generate
*/
generateRandomBytes(bytes = 32): string{
return crypto.randomBytes(bytes).toString("hex");
}
/**
* Generate a random number
* @param numbersNumber Number of numbers to generate
*/
generateRandomNumbers(numbersNumber = 6): string {
return Array.from({ length: numbersNumber }, () =>
Math.floor(Math.random() * 10),
).join('');
}
/**
* Generate a random number
* @param numbersNumber Number of numbers to generate
*/
generateRandomNumbers(numbersNumber = 6): string{
return Array.from({length: numbersNumber}, () => Math.floor(Math.random() * 10)).join("");
}
maskSensitiveInfo(
str: string,
visibleCount: number = 4,
maskChar: string = '*',
) {
return (
str.slice(0, visibleCount) +
maskChar.repeat(Math.max(0, str.length - visibleCount))
);
}
maskSensitiveInfo(str: string, visibleCount: number = 4, maskChar: string = "*"){
return str.slice(0, visibleCount) + maskChar.repeat(Math.max(0, str.length - visibleCount));
}
maskEmail(email: string){
const [username, domain] = email.split("@");
return `${this.maskSensitiveInfo(username)}@${domain}`;
}
maskEmail(email: string) {
const [username, domain] = email.split('@');
return `${this.maskSensitiveInfo(username)}@${domain}`;
}
}

View file

@ -1,32 +1,34 @@
import {Injectable, Logger, OnModuleInit} from "@nestjs/common";
import {PrismaClient} from "@prisma/client";
import { Injectable, Logger, OnModuleInit } from '@nestjs/common';
import { PrismaClient } from '@prisma/client';
@Injectable()
export class PrismaService extends PrismaClient implements OnModuleInit{
static readonly logger = new Logger(PrismaService.name);
export class PrismaService extends PrismaClient implements OnModuleInit {
static readonly logger = new Logger(PrismaService.name);
constructor(){
super();
return this.$extends({
query: {
async $allOperations({operation, model, args, query}): Promise<any>{
const startTime: number = Date.now();
const result: any = await query(args);
const duration: number = Date.now() - startTime;
const requestCount: number = args.length || 1;
const resultCount: number = !result ? 0 : result.length || 1;
PrismaService.logger.log(`${model.toUpperCase()} ${operation.toLowerCase()} ${duration}ms ${requestCount} ${resultCount}`);
return result;
},
},
}) as PrismaService;
}
constructor() {
super();
return this.$extends({
query: {
async $allOperations({ operation, model, args, query }): Promise<any> {
const startTime: number = Date.now();
const result: any = await query(args);
const duration: number = Date.now() - startTime;
const requestCount: number = args.length || 1;
const resultCount: number = !result ? 0 : result.length || 1;
PrismaService.logger.log(
`${model.toUpperCase()} ${operation.toLowerCase()} ${duration}ms ${requestCount} ${resultCount}`,
);
return result;
},
},
}) as PrismaService;
}
async onModuleInit(){
await this.$connect();
}
async onModuleInit() {
await this.$connect();
}
async onModuleDestroy(){
await this.$disconnect();
}
async onModuleDestroy() {
await this.$disconnect();
}
}

View file

@ -1,26 +1,31 @@
import {BadRequestException, Logger, ValidationError, ValidationPipe} from "@nestjs/common";
import {
BadRequestException,
Logger,
ValidationError,
ValidationPipe,
} from '@nestjs/common';
export class CustomValidationPipe extends ValidationPipe{
private readonly logger = new Logger(CustomValidationPipe.name);
export class CustomValidationPipe extends ValidationPipe {
private readonly logger = new Logger(CustomValidationPipe.name);
constructor(){
super({
transform: true,
transformOptions: {enableImplicitConversion: true},
});
}
constructor() {
super({
transform: true,
transformOptions: { enableImplicitConversion: true },
});
}
createExceptionFactory(){
return (validationErrors: ValidationError[] = []) => {
if(this.isDetailedOutputDisabled){
return new BadRequestException();
}
const messages = validationErrors.map(error => ({
property: error.property,
constraints: error.constraints,
}));
// this.logger.error(messages);
return new BadRequestException(messages);
};
}
createExceptionFactory() {
return (validationErrors: ValidationError[] = []) => {
if (this.isDetailedOutputDisabled) {
return new BadRequestException();
}
const messages = validationErrors.map((error) => ({
property: error.property,
constraints: error.constraints,
}));
// this.logger.error(messages);
return new BadRequestException(messages);
};
}
}

View file

@ -1,16 +1,16 @@
import {Controller, Get, Req, UseGuards} from "@nestjs/common";
import {JwtAuthGuard} from "../../common/modules/auth/guards/jwt-auth.guard";
import {UserEntity} from "../../common/modules/auth/models/entities/user.entity";
import {ApiBearerAuth} from "@nestjs/swagger";
import { Controller, Get, Req, UseGuards } from '@nestjs/common';
import { JwtAuthGuard } from '../../common/modules/auth/guards/jwt-auth.guard';
import { UserEntity } from '../../common/modules/auth/models/entities/user.entity';
import { ApiBearerAuth } from '@nestjs/swagger';
@Controller("users")
export class UsersController{
constructor(){}
@Controller('users')
export class UsersController {
constructor() {}
@Get("me")
@UseGuards(JwtAuthGuard)
@ApiBearerAuth()
getMyself(@Req() req: any): UserEntity{
return req.user;
}
@Get('me')
@UseGuards(JwtAuthGuard)
@ApiBearerAuth()
getMyself(@Req() req: any): UserEntity {
return req.user;
}
}

View file

@ -1,10 +1,10 @@
import {Module} from "@nestjs/common";
import {UsersService} from "./users.service";
import {UsersController} from "./users.controller";
import { Module } from '@nestjs/common';
import { UsersService } from './users.service';
import { UsersController } from './users.controller';
@Module({
providers: [UsersService],
exports: [UsersService],
controllers: [UsersController],
providers: [UsersService],
exports: [UsersService],
controllers: [UsersController],
})
export class UsersModule{}
export class UsersModule {}

View file

@ -1,56 +1,52 @@
import {Injectable, NotFoundException} from "@nestjs/common";
import {UserEntity} from "../../common/modules/auth/models/entities/user.entity";
import {PrismaService} from "../../common/modules/helper/prisma.service";
import { Injectable, NotFoundException } from '@nestjs/common';
import { UserEntity } from '../../common/modules/auth/models/entities/user.entity';
import { PrismaService } from '../../common/modules/helper/prisma.service';
@Injectable()
export class UsersService{
constructor(
private readonly prismaService: PrismaService,
){}
export class UsersService {
constructor(private readonly prismaService: PrismaService) {}
async getUserById(id: string){
const user = await this.prismaService.users.findUnique({
where: {
id,
},
include: {
email_verifications: true,
},
});
if(!user)
throw new NotFoundException("User not found");
return new UserEntity({
id: user.id,
username: user.username,
email: user.email,
verified: !user.email_verifications,
password: user.password,
tokenId: user.token_id,
createdAt: user.created_at,
updatedAt: user.updated_at,
});
}
async getUserById(id: string) {
const user = await this.prismaService.users.findUnique({
where: {
id,
},
include: {
email_verifications: true,
},
});
if (!user) throw new NotFoundException('User not found');
return new UserEntity({
id: user.id,
username: user.username,
email: user.email,
verified: !user.email_verifications,
password: user.password,
tokenId: user.token_id,
createdAt: user.created_at,
updatedAt: user.updated_at,
});
}
async getUserByEmail(email: string): Promise<UserEntity>{
const user = await this.prismaService.users.findUnique({
where: {
email,
},
include: {
email_verifications: true,
},
});
if(!user)
throw new NotFoundException("User not found");
return new UserEntity({
id: user.id,
username: user.username,
email: user.email,
verified: !user.email_verifications,
password: user.password,
tokenId: user.token_id,
createdAt: user.created_at,
updatedAt: user.updated_at,
});
}
async getUserByEmail(email: string): Promise<UserEntity> {
const user = await this.prismaService.users.findUnique({
where: {
email,
},
include: {
email_verifications: true,
},
});
if (!user) throw new NotFoundException('User not found');
return new UserEntity({
id: user.id,
username: user.username,
email: user.email,
verified: !user.email_verifications,
password: user.password,
tokenId: user.token_id,
createdAt: user.created_at,
updatedAt: user.updated_at,
});
}
}