From bb640cd8f9e46b4a40ba71d2c96e3680198e2ddf Mon Sep 17 00:00:00 2001 From: Mathis HERRIOT <197931332+0x485254@users.noreply.github.com> Date: Tue, 20 Jan 2026 09:31:30 +0100 Subject: [PATCH 01/13] ci(workflows): remove Next.js build caching from deployment workflow --- .gitea/workflows/deploy.yml | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/.gitea/workflows/deploy.yml b/.gitea/workflows/deploy.yml index c20ffb8..719c1d4 100644 --- a/.gitea/workflows/deploy.yml +++ b/.gitea/workflows/deploy.yml @@ -40,17 +40,6 @@ jobs: restore-keys: | ${{ runner.os }}-pnpm-store- - - name: Cache Next.js build - if: matrix.component != 'backend' - uses: actions/cache@v4 - with: - path: ${{ matrix.component }}/.next/cache - # Clé basée sur le lockfile et les fichiers source du composant - key: ${{ runner.os }}-nextjs-${{ matrix.component }}-${{ hashFiles('**/pnpm-lock.yaml') }}-${{ hashFiles(concat(matrix.component, '/**/*.[jt]s'), concat(matrix.component, '/**/*.[jt]sx')) }} - restore-keys: | - ${{ runner.os }}-nextjs-${{ matrix.component }}-${{ hashFiles('**/pnpm-lock.yaml') }}- - ${{ runner.os }}-nextjs-${{ matrix.component }}- - - name: Install dependencies run: pnpm install --frozen-lockfile --prefer-offline From f247a01ac7028557dfd828c6b20239c5d57bd2c3 Mon Sep 17 00:00:00 2001 From: Mathis HERRIOT <197931332+0x485254@users.noreply.github.com> Date: Tue, 20 Jan 2026 09:43:52 +0100 Subject: [PATCH 02/13] feat(middleware): add HTTP logging middleware to application configuration --- backend/src/app.module.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/backend/src/app.module.ts b/backend/src/app.module.ts index 5b46aab..62b7614 100644 --- a/backend/src/app.module.ts +++ b/backend/src/app.module.ts @@ -12,6 +12,7 @@ import { AuthModule } from "./auth/auth.module"; import { CategoriesModule } from "./categories/categories.module"; import { CommonModule } from "./common/common.module"; import { CrawlerDetectionMiddleware } from "./common/middlewares/crawler-detection.middleware"; +import { HTTPLoggerMiddleware } from "./common/middlewares/http-logger.middleware"; import { validateEnv } from "./config/env.schema"; import { ContentsModule } from "./contents/contents.module"; import { CryptoModule } from "./crypto/crypto.module"; @@ -76,6 +77,6 @@ import { UsersModule } from "./users/users.module"; }) export class AppModule implements NestModule { configure(consumer: MiddlewareConsumer) { - consumer.apply(CrawlerDetectionMiddleware).forRoutes("*"); + consumer.apply(HTTPLoggerMiddleware, CrawlerDetectionMiddleware).forRoutes("*"); } } From e285a4e634789f129860e11063f91b3edc90cee5 Mon Sep 17 00:00:00 2001 From: Mathis HERRIOT <197931332+0x485254@users.noreply.github.com> Date: Tue, 20 Jan 2026 09:44:12 +0100 Subject: [PATCH 03/13] feat(auth): add detailed logging for login and 2FA operations Introduce warnings for failed login attempts and invalid 2FA tokens. Add logs for successful logins and 2FA requirements to improve authentication traceability. --- backend/src/auth/auth.service.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/backend/src/auth/auth.service.ts b/backend/src/auth/auth.service.ts index 2133afb..e5adba1 100644 --- a/backend/src/auth/auth.service.ts +++ b/backend/src/auth/auth.service.ts @@ -110,6 +110,7 @@ export class AuthService { const user = await this.usersService.findByEmailHash(emailHash); if (!user) { + this.logger.warn(`Login failed: user not found for email hash`); throw new UnauthorizedException("Invalid credentials"); } @@ -119,10 +120,12 @@ export class AuthService { ); if (!isPasswordValid) { + this.logger.warn(`Login failed: invalid password for user ${user.uuid}`); throw new UnauthorizedException("Invalid credentials"); } if (user.isTwoFactorEnabled) { + this.logger.log(`2FA required for user ${user.uuid}`); return { message: "2FA required", requires2FA: true, @@ -141,6 +144,7 @@ export class AuthService { ip, ); + this.logger.log(`User ${user.uuid} logged in successfully`); return { message: "User logged in successfully", access_token: accessToken, @@ -165,6 +169,7 @@ export class AuthService { const isValid = authenticator.verify({ token, secret }); if (!isValid) { + this.logger.warn(`2FA verification failed for user ${userId}: invalid token`); throw new UnauthorizedException("Invalid 2FA token"); } @@ -179,6 +184,7 @@ export class AuthService { ip, ); + this.logger.log(`User ${userId} logged in successfully via 2FA`); return { message: "User logged in successfully (2FA)", access_token: accessToken, From 9a70dd02bb7f8cbadcb9b29c5008a172d875f46b Mon Sep 17 00:00:00 2001 From: Mathis HERRIOT <197931332+0x485254@users.noreply.github.com> Date: Tue, 20 Jan 2026 09:44:45 +0100 Subject: [PATCH 04/13] feat(s3): add detailed logging for upload and delete operations --- backend/src/s3/s3.service.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/backend/src/s3/s3.service.ts b/backend/src/s3/s3.service.ts index 21d0fe0..0efa784 100644 --- a/backend/src/s3/s3.service.ts +++ b/backend/src/s3/s3.service.ts @@ -54,6 +54,7 @@ export class S3Service implements OnModuleInit, IStorageService { ...metaData, "Content-Type": mimeType, }); + this.logger.log(`File uploaded successfully: ${fileName} to ${bucketName}`); return fileName; } catch (error) { this.logger.error(`Error uploading file to ${bucketName}: ${error.message}`); @@ -113,6 +114,7 @@ export class S3Service implements OnModuleInit, IStorageService { async deleteFile(fileName: string, bucketName: string = this.bucketName) { try { await this.minioClient.removeObject(bucketName, fileName); + this.logger.log(`File deleted successfully: ${fileName} from ${bucketName}`); } catch (error) { this.logger.error( `Error deleting file from ${bucketName}: ${error.message}`, From 01b66d6f2fa9a7fe870e48822a9b47b2f4f313ff Mon Sep 17 00:00:00 2001 From: Mathis HERRIOT <197931332+0x485254@users.noreply.github.com> Date: Tue, 20 Jan 2026 09:44:57 +0100 Subject: [PATCH 05/13] feat(logging): enhance exception filter with user context in logs Integrate user context (`userId`) into exception filter logging for improved traceability. Adjust log messages to include `[User: ]` when user data is available. --- .../src/common/filters/http-exception.filter.ts | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/backend/src/common/filters/http-exception.filter.ts b/backend/src/common/filters/http-exception.filter.ts index 6ef6e4f..a93e28c 100644 --- a/backend/src/common/filters/http-exception.filter.ts +++ b/backend/src/common/filters/http-exception.filter.ts @@ -9,6 +9,14 @@ import { import * as Sentry from "@sentry/nestjs"; import { Request, Response } from "express"; +interface RequestWithUser extends Request { + user?: { + sub?: string; + username?: string; + id?: string; + }; +} + @Catch() export class AllExceptionsFilter implements ExceptionFilter { private readonly logger = new Logger("ExceptionFilter"); @@ -16,7 +24,7 @@ export class AllExceptionsFilter implements ExceptionFilter { catch(exception: unknown, host: ArgumentsHost) { const ctx = host.switchToHttp(); const response = ctx.getResponse(); - const request = ctx.getRequest(); + const request = ctx.getRequest(); const status = exception instanceof HttpException @@ -28,6 +36,9 @@ export class AllExceptionsFilter implements ExceptionFilter { ? exception.getResponse() : "Internal server error"; + const userId = request.user?.sub || request.user?.id; + const userPart = userId ? `[User: ${userId}] ` : ""; + const errorResponse = { statusCode: status, timestamp: new Date().toISOString(), @@ -42,12 +53,12 @@ export class AllExceptionsFilter implements ExceptionFilter { if (status === HttpStatus.INTERNAL_SERVER_ERROR) { Sentry.captureException(exception); this.logger.error( - `${request.method} ${request.url} - Error: ${exception instanceof Error ? exception.message : "Unknown error"}`, + `${userPart}${request.method} ${request.url} - Error: ${exception instanceof Error ? exception.message : "Unknown error"}`, exception instanceof Error ? exception.stack : "", ); } else { this.logger.warn( - `${request.method} ${request.url} - Status: ${status} - Message: ${JSON.stringify(message)}`, + `${userPart}${request.method} ${request.url} - Status: ${status} - Message: ${JSON.stringify(message)}`, ); } From edc1ab2438c1f25bfb2f64044a438e9e028b8d82 Mon Sep 17 00:00:00 2001 From: Mathis HERRIOT <197931332+0x485254@users.noreply.github.com> Date: Tue, 20 Jan 2026 09:45:06 +0100 Subject: [PATCH 06/13] feat(logging): introduce HTTP logging middleware Add middleware to log HTTP request and response details, including method, URL, status, duration, user agent, and hashed IP address. Logs categorized by severity based on response status code. --- .../middlewares/http-logger.middleware.ts | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 backend/src/common/middlewares/http-logger.middleware.ts diff --git a/backend/src/common/middlewares/http-logger.middleware.ts b/backend/src/common/middlewares/http-logger.middleware.ts new file mode 100644 index 0000000..bebb492 --- /dev/null +++ b/backend/src/common/middlewares/http-logger.middleware.ts @@ -0,0 +1,35 @@ +import { Injectable, Logger, NestMiddleware } from "@nestjs/common"; +import { NextFunction, Request, Response } from "express"; +import { createHash } from "node:crypto"; + +@Injectable() +export class HTTPLoggerMiddleware implements NestMiddleware { + private readonly logger = new Logger("HTTP"); + + use(request: Request, response: Response, next: NextFunction): void { + const { method, originalUrl, ip } = request; + const userAgent = request.get("user-agent") || ""; + const startTime = Date.now(); + + response.on("finish", () => { + const { statusCode } = response; + const contentLength = response.get("content-length"); + const duration = Date.now() - startTime; + + const hashedIp = createHash("sha256").update(ip).digest("hex"); + const message = `${method} ${originalUrl} ${statusCode} ${contentLength || 0} - ${userAgent} ${hashedIp} +${duration}ms`; + + if (statusCode >= 500) { + return this.logger.error(message); + } + + if (statusCode >= 400) { + return this.logger.warn(message); + } + + return this.logger.log(message); + }); + + next(); + } +} From f080919563ffd4cb3dd9938dc3e2d559852e0e4a Mon Sep 17 00:00:00 2001 From: Mathis HERRIOT <197931332+0x485254@users.noreply.github.com> Date: Tue, 20 Jan 2026 09:56:44 +0100 Subject: [PATCH 07/13] fix(logging): resolve type issue in hashed IP logging Ensure `ip` parameter is explicitly cast to string before creating a SHA-256 hash to prevent runtime errors. --- backend/src/common/middlewares/http-logger.middleware.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/src/common/middlewares/http-logger.middleware.ts b/backend/src/common/middlewares/http-logger.middleware.ts index bebb492..42dda4b 100644 --- a/backend/src/common/middlewares/http-logger.middleware.ts +++ b/backend/src/common/middlewares/http-logger.middleware.ts @@ -1,6 +1,6 @@ +import { createHash } from "node:crypto"; import { Injectable, Logger, NestMiddleware } from "@nestjs/common"; import { NextFunction, Request, Response } from "express"; -import { createHash } from "node:crypto"; @Injectable() export class HTTPLoggerMiddleware implements NestMiddleware { @@ -16,7 +16,7 @@ export class HTTPLoggerMiddleware implements NestMiddleware { const contentLength = response.get("content-length"); const duration = Date.now() - startTime; - const hashedIp = createHash("sha256").update(ip).digest("hex"); + const hashedIp = createHash("sha256").update(ip as string).digest("hex"); const message = `${method} ${originalUrl} ${statusCode} ${contentLength || 0} - ${userAgent} ${hashedIp} +${duration}ms`; if (statusCode >= 500) { From 3bbbbc307f1b64ff6f5e148b350ca5697a404330 Mon Sep 17 00:00:00 2001 From: Mathis HERRIOT <197931332+0x485254@users.noreply.github.com> Date: Tue, 20 Jan 2026 09:57:11 +0100 Subject: [PATCH 08/13] test(media): fix type casting in MediaController unit tests Update type casting for `Response` object in MediaController tests to use `unknown as Response` for stricter type safety. Remove unused `s3Service` variable for cleanup. --- backend/src/media/media.controller.spec.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/backend/src/media/media.controller.spec.ts b/backend/src/media/media.controller.spec.ts index 608bbcb..359a512 100644 --- a/backend/src/media/media.controller.spec.ts +++ b/backend/src/media/media.controller.spec.ts @@ -1,12 +1,12 @@ import { Readable } from "node:stream"; import { NotFoundException } from "@nestjs/common"; import { Test, TestingModule } from "@nestjs/testing"; +import type { Response } from "express"; import { S3Service } from "../s3/s3.service"; import { MediaController } from "./media.controller"; describe("MediaController", () => { let controller: MediaController; - let s3Service: S3Service; const mockS3Service = { getFileInfo: jest.fn(), @@ -20,7 +20,6 @@ describe("MediaController", () => { }).compile(); controller = module.get(MediaController); - s3Service = module.get(S3Service); }); it("should be defined", () => { @@ -31,7 +30,7 @@ describe("MediaController", () => { it("should stream the file and set headers with path containing slashes", async () => { const res = { setHeader: jest.fn(), - } as any; + } as unknown as Response; const stream = new Readable(); stream.pipe = jest.fn(); const key = "contents/user-id/test.webp"; @@ -52,7 +51,7 @@ describe("MediaController", () => { it("should throw NotFoundException if file is not found", async () => { mockS3Service.getFileInfo.mockRejectedValue(new Error("Not found")); - const res = {} as any; + const res = {} as unknown as Response; await expect(controller.getFile("invalid", res)).rejects.toThrow( NotFoundException, From 8b2728dc5aee0f36c7990d16b229ccb8fc6aafb4 Mon Sep 17 00:00:00 2001 From: Mathis HERRIOT <197931332+0x485254@users.noreply.github.com> Date: Tue, 20 Jan 2026 09:57:27 +0100 Subject: [PATCH 09/13] test(s3): update mock implementation types for stricter type safety Refactor mock implementations in S3 service tests to replace `any` with `unknown` for improved type safety and consistency. --- backend/src/s3/s3.service.spec.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/src/s3/s3.service.spec.ts b/backend/src/s3/s3.service.spec.ts index 5e9cdcc..be3281f 100644 --- a/backend/src/s3/s3.service.spec.ts +++ b/backend/src/s3/s3.service.spec.ts @@ -197,7 +197,7 @@ describe("S3Service", () => { it("should use DOMAIN_NAME and PORT for localhost", () => { (configService.get as jest.Mock).mockImplementation( - (key: string, def: any) => { + (key: string, def: unknown) => { if (key === "API_URL") return null; if (key === "DOMAIN_NAME") return "localhost"; if (key === "PORT") return 3000; @@ -210,7 +210,7 @@ describe("S3Service", () => { it("should use api.DOMAIN_NAME for production", () => { (configService.get as jest.Mock).mockImplementation( - (key: string, def: any) => { + (key: string, def: unknown) => { if (key === "API_URL") return null; if (key === "DOMAIN_NAME") return "memegoat.fr"; return def; From 28caf92f9ac665153359a2353f0a1fa98d3f013c Mon Sep 17 00:00:00 2001 From: Mathis HERRIOT <197931332+0x485254@users.noreply.github.com> Date: Tue, 20 Jan 2026 09:57:38 +0100 Subject: [PATCH 10/13] fix(media): update S3 file info type casting for stricter type safety Replace `any` with `BucketItemStat` for `getFileInfo` response in MediaController to ensure accurate type definition. --- backend/src/media/media.controller.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/backend/src/media/media.controller.ts b/backend/src/media/media.controller.ts index f38edec..9a538ba 100644 --- a/backend/src/media/media.controller.ts +++ b/backend/src/media/media.controller.ts @@ -1,5 +1,6 @@ import { Controller, Get, NotFoundException, Param, Res } from "@nestjs/common"; import type { Response } from "express"; +import type { BucketItemStat } from "minio"; import { S3Service } from "../s3/s3.service"; @Controller("media") @@ -9,7 +10,7 @@ export class MediaController { @Get("*key") async getFile(@Param("key") key: string, @Res() res: Response) { try { - const stats = (await this.s3Service.getFileInfo(key)) as any; + const stats = (await this.s3Service.getFileInfo(key)) as BucketItemStat; const stream = await this.s3Service.getFile(key); const contentType = From 9a1cdb05a44c2166e8eec563137dcc908a25255d Mon Sep 17 00:00:00 2001 From: Mathis HERRIOT <197931332+0x485254@users.noreply.github.com> Date: Tue, 20 Jan 2026 09:57:59 +0100 Subject: [PATCH 11/13] fix(auth): adjust 2FA verification log formatting for consistency --- backend/src/auth/auth.service.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/backend/src/auth/auth.service.ts b/backend/src/auth/auth.service.ts index e5adba1..f564272 100644 --- a/backend/src/auth/auth.service.ts +++ b/backend/src/auth/auth.service.ts @@ -169,7 +169,9 @@ export class AuthService { const isValid = authenticator.verify({ token, secret }); if (!isValid) { - this.logger.warn(`2FA verification failed for user ${userId}: invalid token`); + this.logger.warn( + `2FA verification failed for user ${userId}: invalid token`, + ); throw new UnauthorizedException("Invalid 2FA token"); } From 863a4bf528d063dad8ab4fa7fd57ca5368c8fdea Mon Sep 17 00:00:00 2001 From: Mathis HERRIOT <197931332+0x485254@users.noreply.github.com> Date: Tue, 20 Jan 2026 09:58:10 +0100 Subject: [PATCH 12/13] style(app): reformat middleware configuration for improved readability --- backend/src/app.module.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/backend/src/app.module.ts b/backend/src/app.module.ts index 62b7614..859bbec 100644 --- a/backend/src/app.module.ts +++ b/backend/src/app.module.ts @@ -77,6 +77,8 @@ import { UsersModule } from "./users/users.module"; }) export class AppModule implements NestModule { configure(consumer: MiddlewareConsumer) { - consumer.apply(HTTPLoggerMiddleware, CrawlerDetectionMiddleware).forRoutes("*"); + consumer + .apply(HTTPLoggerMiddleware, CrawlerDetectionMiddleware) + .forRoutes("*"); } } From 2df45af305dac484510e27c8d4a5e46c8c6b7ebd Mon Sep 17 00:00:00 2001 From: Mathis HERRIOT <197931332+0x485254@users.noreply.github.com> Date: Tue, 20 Jan 2026 10:01:40 +0100 Subject: [PATCH 13/13] style(logging): reformat hashed IP computation for improved readability --- backend/src/common/middlewares/http-logger.middleware.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/backend/src/common/middlewares/http-logger.middleware.ts b/backend/src/common/middlewares/http-logger.middleware.ts index 42dda4b..f42538a 100644 --- a/backend/src/common/middlewares/http-logger.middleware.ts +++ b/backend/src/common/middlewares/http-logger.middleware.ts @@ -16,7 +16,9 @@ export class HTTPLoggerMiddleware implements NestMiddleware { const contentLength = response.get("content-length"); const duration = Date.now() - startTime; - const hashedIp = createHash("sha256").update(ip as string).digest("hex"); + const hashedIp = createHash("sha256") + .update(ip as string) + .digest("hex"); const message = `${method} ${originalUrl} ${statusCode} ${contentLength || 0} - ${userAgent} ${hashedIp} +${duration}ms`; if (statusCode >= 500) {