diff --git a/.manus/db/db-query-1763737221512.json b/.manus/db/db-query-1763737221512.json new file mode 100644 index 0000000..a01382f --- /dev/null +++ b/.manus/db/db-query-1763737221512.json @@ -0,0 +1,9 @@ +{ + "query": "ALTER TABLE users ADD COLUMN username VARCHAR(100) AFTER email", + "command": "mysql --batch --raw --column-names --default-character-set=utf8mb4 --host gateway02.us-east-1.prod.aws.tidbcloud.com --port 4000 --user 4CrrYuB5tme73Qo.root --database 7PAT67UmWoxv8vwp8Bbcv6 --execute ALTER TABLE users ADD COLUMN username VARCHAR(100) AFTER email", + "rows": [], + "messages": [], + "stdout": "", + "stderr": "", + "execution_time_ms": 507 +} \ No newline at end of file diff --git a/.manus/db/db-query-1763737229123.json b/.manus/db/db-query-1763737229123.json new file mode 100644 index 0000000..6f096aa --- /dev/null +++ b/.manus/db/db-query-1763737229123.json @@ -0,0 +1,9 @@ +{ + "query": "ALTER TABLE users ADD UNIQUE KEY unique_username (username)", + "command": "mysql --batch --raw --column-names --default-character-set=utf8mb4 --host gateway02.us-east-1.prod.aws.tidbcloud.com --port 4000 --user 4CrrYuB5tme73Qo.root --database 7PAT67UmWoxv8vwp8Bbcv6 --execute ALTER TABLE users ADD UNIQUE KEY unique_username (username)", + "rows": [], + "messages": [], + "stdout": "", + "stderr": "", + "execution_time_ms": 575 +} \ No newline at end of file diff --git a/.manus/db/db-query-error-1763737207540.json b/.manus/db/db-query-error-1763737207540.json new file mode 100644 index 0000000..efdb146 --- /dev/null +++ b/.manus/db/db-query-error-1763737207540.json @@ -0,0 +1,8 @@ +{ + "query": "ALTER TABLE users ADD COLUMN username VARCHAR(100) UNIQUE AFTER email", + "command": "mysql --batch --raw --column-names --default-character-set=utf8mb4 --host gateway02.us-east-1.prod.aws.tidbcloud.com --port 4000 --user 4CrrYuB5tme73Qo.root --database 7PAT67UmWoxv8vwp8Bbcv6 --execute ALTER TABLE users ADD COLUMN username VARCHAR(100) UNIQUE AFTER email", + "returncode": 1, + "logs": [ + "ERROR 8200 (HY000) at line 1: unsupported add column 'username' constraint UNIQUE KEY when altering '7PAT67UmWoxv8vwp8Bbcv6.users'" + ] +} \ No newline at end of file diff --git a/client/src/pages/AdminUsers.tsx b/client/src/pages/AdminUsers.tsx index 835b53f..fb0b1a0 100644 --- a/client/src/pages/AdminUsers.tsx +++ b/client/src/pages/AdminUsers.tsx @@ -44,11 +44,15 @@ export default function AdminUsers() { openId: "", name: "", email: "", + username: "", + password: "", role: "user" as "user" | "admin", }); const [editForm, setEditForm] = useState({ name: "", email: "", + username: "", + password: "", role: "user" as "user" | "admin", }); @@ -63,6 +67,8 @@ export default function AdminUsers() { openId: "", name: "", email: "", + username: "", + password: "", role: "user", }); }, @@ -127,6 +133,8 @@ export default function AdminUsers() { openId: createForm.openId, name: createForm.name || undefined, email: createForm.email || undefined, + username: createForm.username || undefined, + password: createForm.password || undefined, role: createForm.role, isActive: true, }); @@ -137,6 +145,8 @@ export default function AdminUsers() { setEditForm({ name: user.name || "", email: user.email || "", + username: user.username || "", + password: "", // Toujours vide pour la sécurité role: user.role, }); setEditDialogOpen(true); @@ -145,12 +155,23 @@ export default function AdminUsers() { const handleSaveEdit = () => { if (!selectedUser) return; - updateMutation.mutate({ + // Ne pas envoyer username et password s'ils sont vides + const updateData: any = { id: selectedUser.id, name: editForm.name || undefined, email: editForm.email || undefined, role: editForm.role, - }); + }; + + if (editForm.username) { + updateData.username = editForm.username; + } + + if (editForm.password) { + updateData.password = editForm.password; + } + + updateMutation.mutate(updateData); }; const handleToggleStatus = (user: any) => { @@ -281,6 +302,7 @@ export default function AdminUsers() { ID Nom Email + Identifiant Rôle Statut Dernière connexion @@ -291,7 +313,7 @@ export default function AdminUsers() { {filteredUsers.length === 0 ? ( - + Aucun utilisateur trouvé @@ -301,6 +323,7 @@ export default function AdminUsers() { {user.id} {user.name || "N/A"} {user.email || "N/A"} + {user.username || "N/A"} {user.role === "admin" ? "Administrateur" : "Utilisateur"} @@ -403,6 +426,27 @@ export default function AdminUsers() { /> + + Identifiant de connexion + setCreateForm({ ...createForm, username: e.target.value })} + placeholder="Identifiant unique pour la connexion" + /> + + + + Mot de passe + setCreateForm({ ...createForm, password: e.target.value })} + placeholder="Minimum 6 caractères" + /> + + Rôle + + Identifiant de connexion + setEditForm({ ...editForm, username: e.target.value })} + placeholder="Identifiant unique pour la connexion" + /> + Laissez vide pour ne pas modifier + + + + Nouveau mot de passe + setEditForm({ ...editForm, password: e.target.value })} + placeholder="Laissez vide pour ne pas modifier" + /> + Minimum 6 caractères si modifié + + Rôle statement-breakpoint +ALTER TABLE `users` ADD `password` varchar(255);--> statement-breakpoint +ALTER TABLE `users` ADD CONSTRAINT `users_username_unique` UNIQUE(`username`); \ No newline at end of file diff --git a/drizzle/meta/0012_snapshot.json b/drizzle/meta/0012_snapshot.json new file mode 100644 index 0000000..3499879 --- /dev/null +++ b/drizzle/meta/0012_snapshot.json @@ -0,0 +1,854 @@ +{ + "version": "5", + "dialect": "mysql", + "id": "95760afb-5e4d-4e32-afc6-fbbcec28c208", + "prevId": "443cedd2-fcbf-4247-a935-e69461bb7446", + "tables": { + "apprenants": { + "name": "apprenants", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "nom": { + "name": "nom", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "prenom": { + "name": "prenom", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "email": { + "name": "email", + "type": "varchar(320)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "codeEtablissement": { + "name": "codeEtablissement", + "type": "varchar(50)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "fonction": { + "name": "fonction", + "type": "enum('directeur','chef_service','autre')", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(now())" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "onUpdate": true, + "default": "(now())" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": { + "apprenants_id": { + "name": "apprenants_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": { + "apprenants_email_unique": { + "name": "apprenants_email_unique", + "columns": [ + "email" + ] + } + }, + "checkConstraint": {} + }, + "datesFormation": { + "name": "datesFormation", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "sequenceId": { + "name": "sequenceId", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "dateDebut": { + "name": "dateDebut", + "type": "datetime", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "dateFin": { + "name": "dateFin", + "type": "datetime", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "ordre": { + "name": "ordre", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(now())" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": { + "datesFormation_id": { + "name": "datesFormation_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "emailConfig": { + "name": "emailConfig", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "provider": { + "name": "provider", + "type": "varchar(50)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'resend'" + }, + "apiKey": { + "name": "apiKey", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "fromEmail": { + "name": "fromEmail", + "type": "varchar(320)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "fromName": { + "name": "fromName", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'Formation Manager Itinova'" + }, + "mode": { + "name": "mode", + "type": "enum('simulation','production')", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'simulation'" + }, + "domainVerified": { + "name": "domainVerified", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "active": { + "name": "active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(now())" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "onUpdate": true, + "default": "(now())" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": { + "emailConfig_id": { + "name": "emailConfig_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "emailTemplates": { + "name": "emailTemplates", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "type": { + "name": "type", + "type": "varchar(50)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "logoUrl": { + "name": "logoUrl", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "primaryColor": { + "name": "primaryColor", + "type": "varchar(7)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'#2563eb'" + }, + "headerBgColor": { + "name": "headerBgColor", + "type": "varchar(7)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'#2563eb'" + }, + "headerTextColor": { + "name": "headerTextColor", + "type": "varchar(7)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'#ffffff'" + }, + "headerTitle": { + "name": "headerTitle", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'Formation Manager Itinova'" + }, + "footerText": { + "name": "footerText", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "active": { + "name": "active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(now())" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "onUpdate": true, + "default": "(now())" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": { + "emailTemplates_id": { + "name": "emailTemplates_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": { + "emailTemplates_type_unique": { + "name": "emailTemplates_type_unique", + "columns": [ + "type" + ] + } + }, + "checkConstraint": {} + }, + "formateurs": { + "name": "formateurs", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "nom": { + "name": "nom", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(now())" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "onUpdate": true, + "default": "(now())" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": { + "formateurs_id": { + "name": "formateurs_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "formations": { + "name": "formations", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "nom": { + "name": "nom", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "lienUnique": { + "name": "lienUnique", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "actif": { + "name": "actif", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(now())" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "onUpdate": true, + "default": "(now())" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": { + "formations_id": { + "name": "formations_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": { + "formations_lienUnique_unique": { + "name": "formations_lienUnique_unique", + "columns": [ + "lienUnique" + ] + } + }, + "checkConstraint": {} + }, + "inscriptions": { + "name": "inscriptions", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "apprenantId": { + "name": "apprenantId", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "sequenceId": { + "name": "sequenceId", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "statut": { + "name": "statut", + "type": "enum('confirmee','liste_attente','annulee')", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "dateInscription": { + "name": "dateInscription", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(now())" + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(now())" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "onUpdate": true, + "default": "(now())" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": { + "inscriptions_id": { + "name": "inscriptions_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "passwordResetTokens": { + "name": "passwordResetTokens", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "userId": { + "name": "userId", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "token": { + "name": "token", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "expiresAt": { + "name": "expiresAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "used": { + "name": "used", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(now())" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": { + "passwordResetTokens_id": { + "name": "passwordResetTokens_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": { + "passwordResetTokens_token_unique": { + "name": "passwordResetTokens_token_unique", + "columns": [ + "token" + ] + } + }, + "checkConstraint": {} + }, + "sequences": { + "name": "sequences", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "formationId": { + "name": "formationId", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "nom": { + "name": "nom", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "lieu": { + "name": "lieu", + "type": "varchar(500)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "publicCible": { + "name": "publicCible", + "type": "enum('directeur','chef_service','autre')", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "formateurId": { + "name": "formateurId", + "type": "int", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "capaciteMax": { + "name": "capaciteMax", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 12 + }, + "dateBlocage": { + "name": "dateBlocage", + "type": "datetime", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "statut": { + "name": "statut", + "type": "enum('ouverte','bloquee','terminee')", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'ouverte'" + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(now())" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "onUpdate": true, + "default": "(now())" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": { + "sequences_id": { + "name": "sequences_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "users": { + "name": "users", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "openId": { + "name": "openId", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "email": { + "name": "email", + "type": "varchar(320)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "username": { + "name": "username", + "type": "varchar(100)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "password": { + "name": "password", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "loginMethod": { + "name": "loginMethod", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "role": { + "name": "role", + "type": "enum('user','admin')", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'user'" + }, + "isActive": { + "name": "isActive", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(now())" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "onUpdate": true, + "default": "(now())" + }, + "lastSignedIn": { + "name": "lastSignedIn", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(now())" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": { + "users_id": { + "name": "users_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": { + "users_openId_unique": { + "name": "users_openId_unique", + "columns": [ + "openId" + ] + }, + "users_username_unique": { + "name": "users_username_unique", + "columns": [ + "username" + ] + } + }, + "checkConstraint": {} + } + }, + "views": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + }, + "internal": { + "tables": {}, + "indexes": {} + } +} \ No newline at end of file diff --git a/drizzle/meta/_journal.json b/drizzle/meta/_journal.json index befeede..92e97f8 100644 --- a/drizzle/meta/_journal.json +++ b/drizzle/meta/_journal.json @@ -85,6 +85,13 @@ "when": 1763630439521, "tag": "0011_natural_cyclops", "breakpoints": true + }, + { + "idx": 12, + "version": "5", + "when": 1763737191050, + "tag": "0012_dapper_ronan", + "breakpoints": true } ] } \ No newline at end of file diff --git a/drizzle/schema.ts b/drizzle/schema.ts index 875088c..b3b33a8 100644 --- a/drizzle/schema.ts +++ b/drizzle/schema.ts @@ -15,6 +15,10 @@ export const users = mysqlTable("users", { openId: varchar("openId", { length: 64 }).notNull().unique(), name: text("name"), email: varchar("email", { length: 320 }), + /** Identifiant de connexion unique pour l'utilisateur */ + username: varchar("username", { length: 100 }).unique(), + /** Mot de passe hashé (bcrypt) */ + password: varchar("password", { length: 255 }), loginMethod: varchar("loginMethod", { length: 64 }), role: mysqlEnum("role", ["user", "admin"]).default("user").notNull(), /** Statut du compte (actif/inactif) */ diff --git a/package.json b/package.json index e82644a..8ecc102 100644 --- a/package.json +++ b/package.json @@ -46,6 +46,7 @@ "@trpc/react-query": "^11.6.0", "@trpc/server": "^11.6.0", "axios": "^1.12.0", + "bcryptjs": "^3.0.3", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "cmdk": "^1.1.1", @@ -85,6 +86,7 @@ "@builder.io/vite-plugin-jsx-loc": "^0.1.1", "@tailwindcss/typography": "^0.5.15", "@tailwindcss/vite": "^4.1.3", + "@types/bcryptjs": "^3.0.0", "@types/express": "4.17.21", "@types/google.maps": "^3.58.1", "@types/node": "^24.7.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0308415..d4139b0 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -115,6 +115,9 @@ importers: axios: specifier: ^1.12.0 version: 1.12.2 + bcryptjs: + specifier: ^3.0.3 + version: 3.0.3 class-variance-authority: specifier: ^0.7.1 version: 0.7.1 @@ -227,6 +230,9 @@ importers: '@tailwindcss/vite': specifier: ^4.1.3 version: 4.1.14(vite@7.1.9(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.1)(tsx@4.20.6)) + '@types/bcryptjs': + specifier: ^3.0.0 + version: 3.0.0 '@types/express': specifier: 4.17.21 version: 4.17.21 @@ -2157,6 +2163,10 @@ packages: '@types/babel__traverse@7.28.0': resolution: {integrity: sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==} + '@types/bcryptjs@3.0.0': + resolution: {integrity: sha512-WRZOuCuaz8UcZZE4R5HXTco2goQSI2XxjGY3hbM/xDvwmqFWd4ivooImsMx65OKM6CtNKbnZ5YL+YwAwK7c1dg==} + deprecated: This is a stub types definition. bcryptjs provides its own type definitions, so you do not need this installed. + '@types/body-parser@1.19.6': resolution: {integrity: sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==} @@ -2443,6 +2453,10 @@ packages: resolution: {integrity: sha512-vAPMQdnyKCBtkmQA6FMCBvU9qFIppS3nzyXnEM+Lo2IAhG4Mpjv9cCxMudhgV3YdNNJv6TNqXy97dfRVL2LmaQ==} hasBin: true + bcryptjs@3.0.3: + resolution: {integrity: sha512-GlF5wPWnSa/X5LKM1o0wz0suXIINz1iHRLvTS+sLyi7XPbe5ycmYI3DlZqVGZZtDgl4DmasFg7gOB3JYbphV5g==} + hasBin: true + body-parser@1.20.3: resolution: {integrity: sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==} engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} @@ -6641,6 +6655,10 @@ snapshots: dependencies: '@babel/types': 7.28.4 + '@types/bcryptjs@3.0.0': + dependencies: + bcryptjs: 3.0.3 + '@types/body-parser@1.19.6': dependencies: '@types/connect': 3.4.38 @@ -6973,6 +6991,8 @@ snapshots: baseline-browser-mapping@2.8.12: {} + bcryptjs@3.0.3: {} + body-parser@1.20.3: dependencies: bytes: 3.1.2 diff --git a/server/db.ts b/server/db.ts index 5a6b619..11af9f6 100644 --- a/server/db.ts +++ b/server/db.ts @@ -418,6 +418,12 @@ export async function createUser(data: InsertUser) { const db = await getDb(); if (!db) throw new Error("Database not available"); + // Hasher le mot de passe si fourni + if (data.password) { + const bcrypt = await import('bcryptjs'); + data.password = await bcrypt.hash(data.password, 10); + } + const result = await db.insert(users).values(data); return result; } @@ -441,6 +447,12 @@ export async function updateUser(id: number, data: Partial) { const db = await getDb(); if (!db) throw new Error("Database not available"); + // Hasher le mot de passe si fourni + if (data.password) { + const bcrypt = await import('bcryptjs'); + data.password = await bcrypt.hash(data.password, 10); + } + await db.update(users).set(data).where(eq(users.id, id)); } diff --git a/server/routers.ts b/server/routers.ts index 5a58084..b3cb9d4 100644 --- a/server/routers.ts +++ b/server/routers.ts @@ -618,6 +618,8 @@ export const appRouter = router({ openId: z.string().min(1), name: z.string().optional(), email: z.string().email().optional(), + username: z.string().min(3).optional(), + password: z.string().min(6).optional(), role: z.enum(["user", "admin"]).default("user"), isActive: z.boolean().default(true), })).mutation(async ({ input }) => { @@ -629,6 +631,8 @@ export const appRouter = router({ id: z.number(), name: z.string().optional(), email: z.string().email().optional(), + username: z.string().min(3).optional(), + password: z.string().min(6).optional(), role: z.enum(["user", "admin"]).optional(), isActive: z.boolean().optional(), })).mutation(async ({ input }) => { diff --git a/todo.md b/todo.md index 2e326f7..cad8cd6 100644 --- a/todo.md +++ b/todo.md @@ -383,3 +383,12 @@ - [x] Identifier la cause de la création excessive d'utilisateurs - [x] Corriger le problème - [x] Tester la correction + +## Identifiant de connexion et modification du mot de passe + +- [ ] Ajouter le champ username (identifiant de connexion) dans le schéma de la base de données +- [ ] Générer et appliquer la migration de la base de données +- [x] Mettre à jour les procédures tRPC pour gérer username et password +- [ ] Afficher l'identifiant de connexion dans la liste des utilisateurs +- [ ] Permettre la modification de l'identifiant et du mot de passe dans la fenêtre d'édition +- [x] Tester les modifications
Laissez vide pour ne pas modifier
Minimum 6 caractères si modifié