Checkpoint: Connexion locale avec identifiant libre (non-email), compte adminItinova créé, page Login mise à jour avec champ Identifiant
This commit is contained in:
@@ -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
52
scripts/create-admin.mjs
Normal 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);
|
||||
@@ -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"]),
|
||||
}))
|
||||
|
||||
9
todo.md
9
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
|
||||
|
||||
Reference in New Issue
Block a user