Checkpoint: Migration complète vers l'authentification locale avec email/mot de passe

 Modifications apportées :
- Remplacement de l'authentification OAuth Manus par un système local
- Ajout des champs email, password, emailVerified dans la table users
- Implémentation du hashage des mots de passe avec bcrypt
- Création des procédures auth.login et auth.register
- Mise à jour du système de context pour utiliser JWT
- Création des pages de connexion et d'inscription
- Mise à jour de la page AdminUsers pour créer des utilisateurs avec mot de passe
- Ajout de la protection des routes d'administration (vérification du rôle admin)

 Tests effectués :
- Connexion avec email/mot de passe ✓
- Création d'utilisateurs via l'interface d'administration ✓
- Protection des routes (seuls les admins peuvent accéder au dashboard) ✓
- Redirection automatique après connexion ✓

Note : Les fichiers OAuth Manus originaux ont été conservés pour référence mais ne sont plus utilisés.
This commit is contained in:
Manus Sandbox
2025-11-19 05:35:17 -05:00
parent 934edc63cd
commit cc0bd97248
16 changed files with 212 additions and 20 deletions

View File

@@ -42,7 +42,7 @@ export function getSessionCookieOptions(
return {
httpOnly: true,
path: "/",
sameSite: "none",
sameSite: "lax",
secure: isSecureRequest(req),
};
}

View File

@@ -1,6 +1,7 @@
export const ENV = {
appId: process.env.VITE_APP_ID ?? "",
cookieSecret: process.env.JWT_SECRET ?? "",
jwtSecret: process.env.JWT_SECRET ?? "",
databaseUrl: process.env.DATABASE_URL ?? "",
oAuthServerUrl: process.env.OAUTH_SERVER_URL ?? "",
ownerOpenId: process.env.OWNER_OPEN_ID ?? "",

View File

@@ -2,6 +2,7 @@ import "dotenv/config";
import express from "express";
import { createServer } from "http";
import net from "net";
import cookieParser from "cookie-parser";
import { createExpressMiddleware } from "@trpc/server/adapters/express";
import { registerOAuthRoutes } from "./oauth";
import { appRouter } from "../routers";
@@ -30,6 +31,8 @@ async function findAvailablePort(startPort: number = 3000): Promise<number> {
async function startServer() {
const app = express();
const server = createServer(app);
// Configure cookie parser
app.use(cookieParser());
// Configure body parser with larger size limit for file uploads
app.use(express.json({ limit: "50mb" }));
app.use(express.urlencoded({ limit: "50mb", extended: true }));

View File

@@ -55,7 +55,7 @@ export const appRouter = router({
// Définir le cookie de session
const cookieOptions = getSessionCookieOptions(ctx.req);
ctx.res.cookie(COOKIE_NAME, token, cookieOptions);
ctx.res.cookie(COOKIE_NAME, token, { ...cookieOptions, maxAge: 30 * 24 * 60 * 60 * 1000 }); // 30 jours
return { success: true, userId };
}),
@@ -79,8 +79,16 @@ export const appRouter = router({
throw new TRPCError({ code: 'FORBIDDEN', message: 'Votre compte a été désactivé' });
}
// Vérifier que le mot de passe existe
if (!user.password) {
console.error('[LOGIN] User has no password:', user.email);
throw new TRPCError({ code: 'UNAUTHORIZED', message: 'Email ou mot de passe incorrect' });
}
// Vérifier le mot de passe
console.log('[LOGIN] Verifying password for:', user.email);
const isValid = await verifyPassword(input.password, user.password);
console.log('[LOGIN] Password valid:', isValid);
if (!isValid) {
throw new TRPCError({ code: 'UNAUTHORIZED', message: 'Email ou mot de passe incorrect' });
}
@@ -93,7 +101,7 @@ export const appRouter = router({
// Définir le cookie de session
const cookieOptions = getSessionCookieOptions(ctx.req);
ctx.res.cookie(COOKIE_NAME, token, cookieOptions);
ctx.res.cookie(COOKIE_NAME, token, { ...cookieOptions, maxAge: 30 * 24 * 60 * 60 * 1000 }); // 30 jours
return { success: true, user: { id: user.id, email: user.email, name: user.name, role: user.role } };
}),
@@ -626,13 +634,32 @@ export const appRouter = router({
}),
create: adminProcedure.input(z.object({
openId: z.string().min(1),
email: z.string().email(),
password: z.string().min(6),
name: z.string().optional(),
email: z.string().email().optional(),
role: z.enum(["user", "admin"]).default("user"),
isActive: z.boolean().default(true),
})).mutation(async ({ input }) => {
await db.createUser(input);
const { hashPassword } = await import("./_core/auth");
// Vérifier si l'utilisateur existe déjà
const existingUser = await db.getUserByEmail(input.email);
if (existingUser) {
throw new TRPCError({ code: 'CONFLICT', message: 'Un compte existe déjà avec cet email' });
}
// Hasher le mot de passe
const hashedPassword = await hashPassword(input.password);
// Créer l'utilisateur
await db.createUser({
email: input.email,
password: hashedPassword,
name: input.name,
emailVerified: false,
role: input.role,
isActive: input.isActive,
});
return { success: true };
}),