Checkpoint: Connexion locale avec identifiant libre (non-email), compte adminItinova créé, page Login mise à jour avec champ Identifiant

This commit is contained in:
Manus
2026-03-22 13:23:16 -04:00
parent cd11059a89
commit d8d2ef14da
4 changed files with 77 additions and 14 deletions

View File

@@ -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() {
<form onSubmit={handleLocalLogin} className="space-y-4">
<div className="space-y-2">
<Label htmlFor="email">Email</Label>
<Label htmlFor="username">Identifiant</Label>
<Input
id="email"
type="email"
placeholder="votre@email.com"
value={email}
onChange={(e) => setEmail(e.target.value)}
id="username"
type="text"
placeholder="Votre identifiant"
value={username}
onChange={(e) => setUsername(e.target.value)}
required
autoFocus
autoComplete="username"
/>
</div>
@@ -73,6 +74,7 @@ export default function Login() {
value={password}
onChange={(e) => setPassword(e.target.value)}
required
autoComplete="current-password"
/>
</div>

52
scripts/create-admin.mjs Normal file
View File

@@ -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);

View File

@@ -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"]),
}))

View File

@@ -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