feat: integrate NextAuth for authentication, refactor login and registration processes, and enhance middleware for session management

This commit is contained in:
Julien Froidefond
2025-10-16 15:50:37 +02:00
parent 9ecdd72804
commit 7426bfb33c
33 changed files with 417 additions and 729 deletions

View File

@@ -1,7 +1,6 @@
import { cookies } from "next/headers";
import connectDB from "@/lib/mongodb";
import { UserModel } from "@/lib/models/user.model";
import bcrypt from "bcrypt";
import bcrypt from "bcryptjs";
import { ERROR_CODES } from "../../constants/errorCodes";
import { AppError } from "../../utils/errors";
@@ -15,7 +14,7 @@ export interface UserData {
export class AuthServerService {
private static readonly SALT_ROUNDS = 10;
static async createUser(email: string, password: string): Promise<UserData> {
static async registerUser(email: string, password: string): Promise<UserData> {
await connectDB();
//check if password is strong
@@ -67,37 +66,6 @@ export class AuthServerService {
return true;
}
static async setUserCookie(userData: UserData, remember: boolean = false): Promise<void> {
// Encode user data in base64
const encodedUserData = Buffer.from(JSON.stringify(userData)).toString("base64");
// Set cookie with user data
const cookieStore = await cookies();
cookieStore.set("stripUser", encodedUserData, {
httpOnly: true,
secure: process.env.NODE_ENV === "production",
sameSite: "lax",
path: "/",
maxAge: remember ? 30 * 24 * 60 * 60 : 24 * 60 * 60, // 30 days if remember, 24 hours otherwise
});
}
static async getCurrentUser(): Promise<UserData | null> {
const cookieStore = await cookies();
const userCookie = cookieStore.get("stripUser");
if (!userCookie) {
return null;
}
try {
return JSON.parse(atob(userCookie.value));
} catch (error) {
console.error("Error while getting user from cookie:", error);
return null;
}
}
static async loginUser(email: string, password: string): Promise<UserData> {
await connectDB();

View File

@@ -1,105 +0,0 @@
"use client";
import type { AppErrorType } from "@/types/global";
import { ERROR_CODES } from "@/constants/errorCodes";
class AuthService {
private static instance: AuthService;
// Constructeur privé pour le pattern Singleton
private constructor() {
// Pas d'initialisation nécessaire
}
public static getInstance(): AuthService {
if (!AuthService.instance) {
AuthService.instance = new AuthService();
}
return AuthService.instance;
}
/**
* Authentifie un utilisateur
*/
async login(email: string, password: string, remember: boolean = false): Promise<void> {
try {
const response = await fetch("/api/auth/login", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ email, password, remember }),
});
if (!response.ok) {
const data = await response.json();
throw data.error;
}
} catch (error) {
if ((error as AppErrorType).code) {
throw error;
}
throw {
code: ERROR_CODES.AUTH.INVALID_CREDENTIALS,
name: "Invalid credentials",
message: "The email or password is incorrect",
} as AppErrorType;
}
}
/**
* Crée un nouvel utilisateur
*/
async register(email: string, password: string): Promise<void> {
try {
const response = await fetch("/api/auth/register", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ email, password }),
});
if (!response.ok) {
const data = await response.json();
throw data.error;
}
} catch (error) {
if ((error as AppErrorType).code) {
throw error;
}
throw {
code: ERROR_CODES.AUTH.INVALID_USER_DATA,
name: "Invalid user data",
message: "The email or password is incorrect",
} as AppErrorType;
}
}
/**
* Déconnecte l'utilisateur
*/
async logout(): Promise<void> {
try {
const response = await fetch("/api/auth/logout", {
method: "POST",
});
if (!response.ok) {
const data = await response.json();
throw data.error;
}
} catch (error) {
if ((error as AppErrorType).code) {
throw error;
}
throw {
code: ERROR_CODES.AUTH.LOGOUT_ERROR,
name: "Logout error",
message: "The logout failed",
} as AppErrorType;
}
}
}
export const authService = AuthService.getInstance();

View File

@@ -2,14 +2,14 @@ import connectDB from "@/lib/mongodb";
import { KomgaConfig as KomgaConfigModel } from "@/lib/models/config.model";
import { TTLConfig as TTLConfigModel } from "@/lib/models/ttl-config.model";
import { DebugService } from "./debug.service";
import { AuthServerService } from "./auth-server.service";
import { getCurrentUser } from "../auth-utils";
import { ERROR_CODES } from "../../constants/errorCodes";
import { AppError } from "../../utils/errors";
import type { User, KomgaConfigData, TTLConfigData, KomgaConfig, TTLConfig } from "@/types/komga";
export class ConfigDBService {
private static async getCurrentUser(): Promise<User> {
const user: User | null = await AuthServerService.getCurrentUser();
const user: User | null = await getCurrentUser();
if (!user) {
throw new AppError(ERROR_CODES.AUTH.UNAUTHENTICATED);
}

View File

@@ -1,8 +1,8 @@
import fs from "fs/promises";
import path from "path";
import type { CacheType } from "./base-api.service";
import { AuthServerService } from "./auth-server.service";
import { PreferencesService } from "./preferences.service";
import { getCurrentUser } from "../auth-utils";
import { ERROR_CODES } from "../../constants/errorCodes";
import { AppError } from "../../utils/errors";
@@ -28,7 +28,7 @@ export class DebugService {
private static writeQueues = new Map<string, Promise<void>>();
private static async getCurrentUserId(): Promise<string> {
const user = await AuthServerService.getCurrentUser();
const user = await getCurrentUser();
if (!user) {
throw new AppError(ERROR_CODES.AUTH.UNAUTHENTICATED);
}
@@ -49,7 +49,7 @@ export class DebugService {
}
private static async isDebugEnabled(): Promise<boolean> {
const user = await AuthServerService.getCurrentUser();
const user = await getCurrentUser();
if (!user) {
return false;
}

View File

@@ -1,7 +1,7 @@
import connectDB from "@/lib/mongodb";
import { FavoriteModel } from "@/lib/models/favorite.model";
import { DebugService } from "./debug.service";
import { AuthServerService } from "./auth-server.service";
import { getCurrentUser } from "../auth-utils";
import { ERROR_CODES } from "../../constants/errorCodes";
import { AppError } from "../../utils/errors";
import type { User } from "@/types/komga";
@@ -17,7 +17,7 @@ export class FavoriteService {
}
private static async getCurrentUser(): Promise<User> {
const user = await AuthServerService.getCurrentUser();
const user = await getCurrentUser();
if (!user) {
throw new AppError(ERROR_CODES.AUTH.UNAUTHENTICATED);
}

View File

@@ -1,5 +1,5 @@
import { PreferencesModel } from "@/lib/models/preferences.model";
import { AuthServerService } from "./auth-server.service";
import { getCurrentUser } from "../auth-utils";
import { ERROR_CODES } from "../../constants/errorCodes";
import { AppError } from "../../utils/errors";
import type { UserPreferences } from "@/types/preferences";
@@ -9,7 +9,7 @@ import connectDB from "@/lib/mongodb";
export class PreferencesService {
static async getCurrentUser(): Promise<User> {
const user = await AuthServerService.getCurrentUser();
const user = await getCurrentUser();
if (!user) {
throw new AppError(ERROR_CODES.AUTH.UNAUTHENTICATED);
}

View File

@@ -33,6 +33,7 @@ class RequestMonitor {
} else if (count >= this.thresholds.high) {
console.warn(`[REQUEST-MONITOR] ⚠️ HIGH concurrency: ${count} active requests`);
} else if (count >= this.thresholds.warning) {
// eslint-disable-next-line no-console
console.log(`[REQUEST-MONITOR] ⚡ Warning concurrency: ${count} active requests`);
}
}

View File

@@ -2,7 +2,7 @@ import fs from "fs";
import path from "path";
import { PreferencesService } from "./preferences.service";
import { DebugService } from "./debug.service";
import { AuthServerService } from "./auth-server.service";
import { getCurrentUser } from "../auth-utils";
export type CacheMode = "file" | "memory";
@@ -45,7 +45,7 @@ class ServerCacheService {
private async initializeCacheMode(): Promise<void> {
try {
const user = await AuthServerService.getCurrentUser();
const user = await getCurrentUser();
if (!user) {
this.setCacheMode("memory");
return;
@@ -293,7 +293,7 @@ class ServerCacheService {
* Supprime une entrée du cache
*/
async delete(key: string): Promise<void> {
const user = await AuthServerService.getCurrentUser();
const user = await getCurrentUser();
if (!user) {
throw new Error("Utilisateur non authentifié");
}
@@ -313,7 +313,7 @@ class ServerCacheService {
* Supprime toutes les entrées du cache qui commencent par un préfixe
*/
async deleteAll(prefix: string): Promise<void> {
const user = await AuthServerService.getCurrentUser();
const user = await getCurrentUser();
if (!user) {
throw new Error("Utilisateur non authentifié");
}
@@ -390,7 +390,7 @@ class ServerCacheService {
type: keyof typeof ServerCacheService.DEFAULT_TTL = "DEFAULT"
): Promise<T> {
const startTime = performance.now();
const user = await AuthServerService.getCurrentUser();
const user = await getCurrentUser();
if (!user) {
throw new Error("Utilisateur non authentifié");
}