From d8d2ef14da8e11bb36009168280962619bd7ec7a Mon Sep 17 00:00:00 2001 From: Manus Date: Sun, 22 Mar 2026 13:23:16 -0400 Subject: [PATCH] =?UTF-8?q?Checkpoint:=20Connexion=20locale=20avec=20ident?= =?UTF-8?q?ifiant=20libre=20(non-email),=20compte=20adminItinova=20cr?= =?UTF-8?q?=C3=A9=C3=A9,=20page=20Login=20mise=20=C3=A0=20jour=20avec=20ch?= =?UTF-8?q?amp=20Identifiant?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/src/pages/Login.tsx | 18 +++++++------ scripts/create-admin.mjs | 52 ++++++++++++++++++++++++++++++++++++++ server/routers.ts | 12 ++++----- todo.md | 9 +++++++ 4 files changed, 77 insertions(+), 14 deletions(-) create mode 100644 scripts/create-admin.mjs diff --git a/client/src/pages/Login.tsx b/client/src/pages/Login.tsx index 79d896c..8e5f516 100644 --- a/client/src/pages/Login.tsx +++ b/client/src/pages/Login.tsx @@ -11,7 +11,7 @@ const ITINOVA_LOGO = "https://d2xsxph8kpxj0f.cloudfront.net/310519663070627318/f const SANTINOVA_LOGO = "https://d2xsxph8kpxj0f.cloudfront.net/310519663070627318/fo4DRyBgjsuiigFAgNLuWm/santinova-logo_5ae0d248.webp"; export default function Login() { - const [email, setEmail] = useState(""); + const [username, setUsername] = useState(""); const [password, setPassword] = useState(""); const loginMutation = trpc.auth.loginLocal.useMutation({ @@ -26,7 +26,7 @@ export default function Login() { const handleLocalLogin = (e: React.FormEvent) => { e.preventDefault(); - loginMutation.mutate({ email, password }); + loginMutation.mutate({ email: username, password }); }; return ( @@ -52,15 +52,16 @@ export default function Login() {
- + setEmail(e.target.value)} + id="username" + type="text" + placeholder="Votre identifiant" + value={username} + onChange={(e) => setUsername(e.target.value)} required autoFocus + autoComplete="username" />
@@ -73,6 +74,7 @@ export default function Login() { value={password} onChange={(e) => setPassword(e.target.value)} required + autoComplete="current-password" /> diff --git a/scripts/create-admin.mjs b/scripts/create-admin.mjs new file mode 100644 index 0000000..4d3bffe --- /dev/null +++ b/scripts/create-admin.mjs @@ -0,0 +1,52 @@ +import { createConnection } from "mysql2/promise"; +import bcrypt from "bcrypt"; +import * as dotenv from "dotenv"; + +dotenv.config(); + +const DATABASE_URL = process.env.DATABASE_URL; + +async function main() { + console.log("Connexion à la base de données..."); + + const connection = await createConnection(DATABASE_URL); + + try { + // Hash du mot de passe + const passwordHash = await bcrypt.hash("Itinova69!", 10); + + // Vérifier si l'utilisateur existe déjà + const [existing] = await connection.execute( + "SELECT id FROM users WHERE email = ?", + ["adminItinova"] + ); + + if (existing.length > 0) { + // Mettre à jour le mot de passe si l'utilisateur existe déjà + await connection.execute( + "UPDATE users SET passwordHash = ?, role = 'admin', isActive = 1 WHERE email = ?", + [passwordHash, "adminItinova"] + ); + console.log("✅ Compte adminItinova mis à jour avec succès"); + } else { + // Créer l'utilisateur + await connection.execute( + "INSERT INTO users (openId, name, email, passwordHash, loginMethod, role, isActive) VALUES (NULL, 'Administrateur Itinova', 'adminItinova', ?, 'local', 'admin', 1)", + [passwordHash] + ); + console.log("✅ Compte adminItinova créé avec succès"); + } + + // Vérification + const [user] = await connection.execute( + "SELECT id, name, email, role, isActive FROM users WHERE email = ?", + ["adminItinova"] + ); + console.log("Utilisateur créé :", user[0]); + + } finally { + await connection.end(); + } +} + +main().catch(console.error); diff --git a/server/routers.ts b/server/routers.ts index 59aeb19..429414a 100644 --- a/server/routers.ts +++ b/server/routers.ts @@ -87,17 +87,17 @@ export const appRouter = router({ auth: router({ me: publicProcedure.query(opts => opts.ctx.user), - // Local login (email + password) + // Local login (username/email + password) loginLocal: publicProcedure .input(z.object({ - email: z.string().email(), - password: z.string().min(6), + email: z.string().min(1), + password: z.string().min(1), })) .mutation(async ({ input, ctx }) => { const result = await loginLocal(input.email, input.password); if (!result) { - throw new TRPCError({ code: "UNAUTHORIZED", message: "Invalid email or password" }); + throw new TRPCError({ code: "UNAUTHORIZED", message: "Identifiant ou mot de passe incorrect" }); } // Set auth cookie @@ -480,8 +480,8 @@ export const appRouter = router({ admin: router({ createUser: adminProcedure .input(z.object({ - email: z.string().email(), - password: z.string().min(6), + email: z.string().min(1), + password: z.string().min(1), name: z.string(), role: z.enum(["user", "admin"]), })) diff --git a/todo.md b/todo.md index 2225842..80f4b7e 100644 --- a/todo.md +++ b/todo.md @@ -548,3 +548,12 @@ - [x] Afficher "powered by" + logo Santinova en dessous du formulaire - [x] Renommer l'application "Invoice Analyzer" en "Gestion facturation" - [x] Appliquer le même comportement en cas de déconnexion + +## Connexion locale avec identifiant libre +- [x] Analyser le schéma DB et les routes d'auth locale +- [x] Modifier la validation Zod pour accepter un identifiant libre (non-email) +- [x] Modifier la route tRPC `auth.loginLocal` pour accepter un username libre +- [x] Modifier la page Login.tsx pour remplacer le champ email par un champ "Identifiant" +- [x] Créer le compte admin `adminItinova` avec mot de passe `Itinova69!` en base de données locale +- [ ] Créer le compte admin `adminItinova` sur le VPS +- [ ] Déployer sur le VPS