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";
|
const SANTINOVA_LOGO = "https://d2xsxph8kpxj0f.cloudfront.net/310519663070627318/fo4DRyBgjsuiigFAgNLuWm/santinova-logo_5ae0d248.webp";
|
||||||
|
|
||||||
export default function Login() {
|
export default function Login() {
|
||||||
const [email, setEmail] = useState("");
|
const [username, setUsername] = useState("");
|
||||||
const [password, setPassword] = useState("");
|
const [password, setPassword] = useState("");
|
||||||
|
|
||||||
const loginMutation = trpc.auth.loginLocal.useMutation({
|
const loginMutation = trpc.auth.loginLocal.useMutation({
|
||||||
@@ -26,7 +26,7 @@ export default function Login() {
|
|||||||
|
|
||||||
const handleLocalLogin = (e: React.FormEvent) => {
|
const handleLocalLogin = (e: React.FormEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
loginMutation.mutate({ email, password });
|
loginMutation.mutate({ email: username, password });
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -52,15 +52,16 @@ export default function Login() {
|
|||||||
|
|
||||||
<form onSubmit={handleLocalLogin} className="space-y-4">
|
<form onSubmit={handleLocalLogin} className="space-y-4">
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label htmlFor="email">Email</Label>
|
<Label htmlFor="username">Identifiant</Label>
|
||||||
<Input
|
<Input
|
||||||
id="email"
|
id="username"
|
||||||
type="email"
|
type="text"
|
||||||
placeholder="votre@email.com"
|
placeholder="Votre identifiant"
|
||||||
value={email}
|
value={username}
|
||||||
onChange={(e) => setEmail(e.target.value)}
|
onChange={(e) => setUsername(e.target.value)}
|
||||||
required
|
required
|
||||||
autoFocus
|
autoFocus
|
||||||
|
autoComplete="username"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -73,6 +74,7 @@ export default function Login() {
|
|||||||
value={password}
|
value={password}
|
||||||
onChange={(e) => setPassword(e.target.value)}
|
onChange={(e) => setPassword(e.target.value)}
|
||||||
required
|
required
|
||||||
|
autoComplete="current-password"
|
||||||
/>
|
/>
|
||||||
</div>
|
</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({
|
auth: router({
|
||||||
me: publicProcedure.query(opts => opts.ctx.user),
|
me: publicProcedure.query(opts => opts.ctx.user),
|
||||||
|
|
||||||
// Local login (email + password)
|
// Local login (username/email + password)
|
||||||
loginLocal: publicProcedure
|
loginLocal: publicProcedure
|
||||||
.input(z.object({
|
.input(z.object({
|
||||||
email: z.string().email(),
|
email: z.string().min(1),
|
||||||
password: z.string().min(6),
|
password: z.string().min(1),
|
||||||
}))
|
}))
|
||||||
.mutation(async ({ input, ctx }) => {
|
.mutation(async ({ input, ctx }) => {
|
||||||
const result = await loginLocal(input.email, input.password);
|
const result = await loginLocal(input.email, input.password);
|
||||||
|
|
||||||
if (!result) {
|
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
|
// Set auth cookie
|
||||||
@@ -480,8 +480,8 @@ export const appRouter = router({
|
|||||||
admin: router({
|
admin: router({
|
||||||
createUser: adminProcedure
|
createUser: adminProcedure
|
||||||
.input(z.object({
|
.input(z.object({
|
||||||
email: z.string().email(),
|
email: z.string().min(1),
|
||||||
password: z.string().min(6),
|
password: z.string().min(1),
|
||||||
name: z.string(),
|
name: z.string(),
|
||||||
role: z.enum(["user", "admin"]),
|
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] Afficher "powered by" + logo Santinova en dessous du formulaire
|
||||||
- [x] Renommer l'application "Invoice Analyzer" en "Gestion facturation"
|
- [x] Renommer l'application "Invoice Analyzer" en "Gestion facturation"
|
||||||
- [x] Appliquer le même comportement en cas de déconnexion
|
- [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