diff --git a/.manus/db/db-query-1763546134103.json b/.manus/db/db-query-1763546134103.json
new file mode 100644
index 0000000..6e1ab9a
--- /dev/null
+++ b/.manus/db/db-query-1763546134103.json
@@ -0,0 +1,9 @@
+{
+ "query": "UPDATE users \nSET password = '$2b$10$N9qo8uLOickgx2ZMRZoMye7FRNv8va91kpMQH6.OfzrcBizxDbvSK',\n role = 'admin',\n emailVerified = TRUE\nWHERE email = 'o.pareige@itinova.org';",
+ "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 UPDATE users \nSET password = '$2b$10$N9qo8uLOickgx2ZMRZoMye7FRNv8va91kpMQH6.OfzrcBizxDbvSK',\n role = 'admin',\n emailVerified = TRUE\nWHERE email = 'o.pareige@itinova.org';",
+ "rows": [],
+ "messages": [],
+ "stdout": "",
+ "stderr": "",
+ "execution_time_ms": 414
+}
\ No newline at end of file
diff --git a/.manus/db/db-query-1763546215617.json b/.manus/db/db-query-1763546215617.json
new file mode 100644
index 0000000..373e4a2
--- /dev/null
+++ b/.manus/db/db-query-1763546215617.json
@@ -0,0 +1,19 @@
+{
+ "query": "SELECT id, email, name, role, password, emailVerified, isActive FROM users WHERE email = 'o.pareige@itinova.org';",
+ "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 SELECT id, email, name, role, password, emailVerified, isActive FROM users WHERE email = 'o.pareige@itinova.org';",
+ "rows": [
+ {
+ "id": "1",
+ "email": "o.pareige@itinova.org",
+ "name": "olivier pareige",
+ "role": "admin",
+ "password": "$2b$10$N9qo8uLOickgx2ZMRZoMye7FRNv8va91kpMQH6.OfzrcBizxDbvSK",
+ "emailVerified": "1",
+ "isActive": "1"
+ }
+ ],
+ "messages": [],
+ "stdout": "id\temail\tname\trole\tpassword\temailVerified\tisActive\n1\to.pareige@itinova.org\tolivier pareige\tadmin\t$2b$10$N9qo8uLOickgx2ZMRZoMye7FRNv8va91kpMQH6.OfzrcBizxDbvSK\t1\t1\n",
+ "stderr": "",
+ "execution_time_ms": 49
+}
\ No newline at end of file
diff --git a/.manus/db/db-query-1763546239968.json b/.manus/db/db-query-1763546239968.json
new file mode 100644
index 0000000..6250a6f
--- /dev/null
+++ b/.manus/db/db-query-1763546239968.json
@@ -0,0 +1,9 @@
+{
+ "query": "DELETE FROM users WHERE email = 'o.pareige@itinova.org';\n\nINSERT INTO users (email, password, name, role, emailVerified, isActive, createdAt, updatedAt, lastSignedIn)\nVALUES (\n 'o.pareige@itinova.org',\n '$2b$10$N9qo8uLOickgx2ZMRZoMye7FRNv8va91kpMQH6.OfzrcBizxDbvSK',\n 'Olivier Pareige',\n 'admin',\n 1,\n 1,\n NOW(),\n NOW(),\n NOW()\n);",
+ "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 DELETE FROM users WHERE email = 'o.pareige@itinova.org';\n\nINSERT INTO users (email, password, name, role, emailVerified, isActive, createdAt, updatedAt, lastSignedIn)\nVALUES (\n 'o.pareige@itinova.org',\n '$2b$10$N9qo8uLOickgx2ZMRZoMye7FRNv8va91kpMQH6.OfzrcBizxDbvSK',\n 'Olivier Pareige',\n 'admin',\n 1,\n 1,\n NOW(),\n NOW(),\n NOW()\n);",
+ "rows": [],
+ "messages": [],
+ "stdout": "",
+ "stderr": "",
+ "execution_time_ms": 71
+}
\ No newline at end of file
diff --git a/.manus/db/db-query-1763546569851.json b/.manus/db/db-query-1763546569851.json
new file mode 100644
index 0000000..b328c16
--- /dev/null
+++ b/.manus/db/db-query-1763546569851.json
@@ -0,0 +1,16 @@
+{
+ "query": "SELECT id, email, password, LENGTH(password) as password_length FROM users WHERE email = 'o.pareige@itinova.org';",
+ "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 SELECT id, email, password, LENGTH(password) as password_length FROM users WHERE email = 'o.pareige@itinova.org';",
+ "rows": [
+ {
+ "id": "2010001",
+ "email": "o.pareige@itinova.org",
+ "password": "$2b$10$N9qo8uLOickgx2ZMRZoMye7FRNv8va91kpMQH6.OfzrcBizxDbvSK",
+ "password_length": "60"
+ }
+ ],
+ "messages": [],
+ "stdout": "id\temail\tpassword\tpassword_length\n2010001\to.pareige@itinova.org\t$2b$10$N9qo8uLOickgx2ZMRZoMye7FRNv8va91kpMQH6.OfzrcBizxDbvSK\t60\n",
+ "stderr": "",
+ "execution_time_ms": 141
+}
\ No newline at end of file
diff --git a/.manus/db/db-query-1763546726252.json b/.manus/db/db-query-1763546726252.json
new file mode 100644
index 0000000..f5aeda1
--- /dev/null
+++ b/.manus/db/db-query-1763546726252.json
@@ -0,0 +1,9 @@
+{
+ "query": "UPDATE users SET password = '$2b$10$wtfrg13y5qw/VZKdQfcs4epTfZT2Hje5eomHggJEfSLp9m9ZGlk2O' WHERE email = 'o.pareige@itinova.org';",
+ "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 UPDATE users SET password = '$2b$10$wtfrg13y5qw/VZKdQfcs4epTfZT2Hje5eomHggJEfSLp9m9ZGlk2O' WHERE email = 'o.pareige@itinova.org';",
+ "rows": [],
+ "messages": [],
+ "stdout": "",
+ "stderr": "",
+ "execution_time_ms": 58
+}
\ No newline at end of file
diff --git a/client/src/components/DashboardLayout.tsx b/client/src/components/DashboardLayout.tsx
index c88a2a6..93c4b67 100644
--- a/client/src/components/DashboardLayout.tsx
+++ b/client/src/components/DashboardLayout.tsx
@@ -97,6 +97,33 @@ export default function DashboardLayout({
);
}
+ // Vérifier que l'utilisateur est administrateur
+ if (user.role !== 'admin') {
+ return (
+
+
+
+
+
Accès refusé
+
+ Vous n'avez pas les permissions nécessaires pour accéder à cette page.
+
+
+
+
+
+
+ );
+ }
+
return (
(null);
const [createForm, setCreateForm] = useState({
- openId: "",
name: "",
email: "",
+ password: "",
role: "user" as "user" | "admin",
});
const [editForm, setEditForm] = useState({
@@ -60,9 +60,9 @@ export default function AdminUsers() {
refetch();
setCreateDialogOpen(false);
setCreateForm({
- openId: "",
name: "",
email: "",
+ password: "",
role: "user",
});
},
@@ -118,15 +118,19 @@ export default function AdminUsers() {
};
const handleSaveCreate = () => {
- if (!createForm.openId) {
- toast.error("L'identifiant OpenID est obligatoire");
+ if (!createForm.email) {
+ toast.error("L'email est obligatoire");
+ return;
+ }
+ if (!createForm.password) {
+ toast.error("Le mot de passe est obligatoire");
return;
}
createMutation.mutate({
- openId: createForm.openId,
+ email: createForm.email,
+ password: createForm.password,
name: createForm.name || undefined,
- email: createForm.email || undefined,
role: createForm.role,
isActive: true,
});
@@ -373,12 +377,13 @@ export default function AdminUsers() {
-
+
setCreateForm({ ...createForm, openId: e.target.value })}
- placeholder="Identifiant unique de l'utilisateur"
+ id="createPassword"
+ type="password"
+ value={createForm.password}
+ onChange={(e) => setCreateForm({ ...createForm, password: e.target.value })}
+ placeholder="Mot de passe de l'utilisateur"
/>
diff --git a/generate-hash.mjs b/generate-hash.mjs
new file mode 100644
index 0000000..5a9d0da
--- /dev/null
+++ b/generate-hash.mjs
@@ -0,0 +1,12 @@
+import bcrypt from 'bcrypt';
+
+const password = 'admin123';
+
+bcrypt.hash(password, 10).then(hash => {
+ console.log('Password:', password);
+ console.log('Hash:', hash);
+ console.log('\nSQL to update:');
+ console.log(`UPDATE users SET password = '${hash}' WHERE email = 'o.pareige@itinova.org';`);
+}).catch(err => {
+ console.error('Error:', err);
+});
diff --git a/package.json b/package.json
index ac8cd79..6747d2b 100644
--- a/package.json
+++ b/package.json
@@ -51,6 +51,7 @@
"clsx": "^2.1.1",
"cmdk": "^1.1.1",
"cookie": "^1.0.2",
+ "cookie-parser": "^1.4.7",
"date-fns": "^4.1.0",
"dotenv": "^17.2.2",
"drizzle-orm": "^0.44.5",
@@ -88,6 +89,7 @@
"@tailwindcss/typography": "^0.5.15",
"@tailwindcss/vite": "^4.1.3",
"@types/bcrypt": "^6.0.0",
+ "@types/cookie-parser": "^1.4.10",
"@types/express": "4.17.21",
"@types/google.maps": "^3.58.1",
"@types/jsonwebtoken": "^9.0.10",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index c4c2bbf..8542bba 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -130,6 +130,9 @@ importers:
cookie:
specifier: ^1.0.2
version: 1.0.2
+ cookie-parser:
+ specifier: ^1.4.7
+ version: 1.4.7
date-fns:
specifier: ^4.1.0
version: 4.1.0
@@ -236,6 +239,9 @@ importers:
'@types/bcrypt':
specifier: ^6.0.0
version: 6.0.0
+ '@types/cookie-parser':
+ specifier: ^1.4.10
+ version: 1.4.10(@types/express@4.17.21)
'@types/express':
specifier: 4.17.21
version: 4.17.21
@@ -2178,6 +2184,11 @@ packages:
'@types/connect@3.4.38':
resolution: {integrity: sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==}
+ '@types/cookie-parser@1.4.10':
+ resolution: {integrity: sha512-B4xqkqfZ8Wek+rCOeRxsjMS9OgvzebEzzLYw7NHYuvzb7IdxOkI0ZHGgeEBX4PUM7QGVvNSK60T3OvWj3YfBRg==}
+ peerDependencies:
+ '@types/express': '*'
+
'@types/d3-array@3.2.2':
resolution: {integrity: sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==}
@@ -2594,6 +2605,10 @@ packages:
convert-source-map@2.0.0:
resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==}
+ cookie-parser@1.4.7:
+ resolution: {integrity: sha512-nGUvgXnotP3BsjiLX2ypbQnWoGUPIIfHQNZkkC668ntrzGWEZVW70HDEB1qnNGMicPje6EttlIgzo51YSwNQGw==}
+ engines: {node: '>= 0.8.0'}
+
cookie-signature@1.0.6:
resolution: {integrity: sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==}
@@ -2601,6 +2616,10 @@ packages:
resolution: {integrity: sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==}
engines: {node: '>= 0.6'}
+ cookie@0.7.2:
+ resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==}
+ engines: {node: '>= 0.6'}
+
cookie@1.0.2:
resolution: {integrity: sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==}
engines: {node: '>=18'}
@@ -6726,6 +6745,10 @@ snapshots:
dependencies:
'@types/node': 24.7.0
+ '@types/cookie-parser@1.4.10(@types/express@4.17.21)':
+ dependencies:
+ '@types/express': 4.17.21
+
'@types/d3-array@3.2.2': {}
'@types/d3-axis@3.0.6':
@@ -7201,10 +7224,17 @@ snapshots:
convert-source-map@2.0.0: {}
+ cookie-parser@1.4.7:
+ dependencies:
+ cookie: 0.7.2
+ cookie-signature: 1.0.6
+
cookie-signature@1.0.6: {}
cookie@0.7.1: {}
+ cookie@0.7.2: {}
+
cookie@1.0.2: {}
copy-anything@3.0.5:
diff --git a/server/_core/cookies.ts b/server/_core/cookies.ts
index 424872c..664b6de 100644
--- a/server/_core/cookies.ts
+++ b/server/_core/cookies.ts
@@ -42,7 +42,7 @@ export function getSessionCookieOptions(
return {
httpOnly: true,
path: "/",
- sameSite: "none",
+ sameSite: "lax",
secure: isSecureRequest(req),
};
}
diff --git a/server/_core/env.ts b/server/_core/env.ts
index 2792b99..305082c 100644
--- a/server/_core/env.ts
+++ b/server/_core/env.ts
@@ -1,6 +1,7 @@
export const ENV = {
appId: process.env.VITE_APP_ID ?? "",
cookieSecret: process.env.JWT_SECRET ?? "",
+ jwtSecret: process.env.JWT_SECRET ?? "",
databaseUrl: process.env.DATABASE_URL ?? "",
oAuthServerUrl: process.env.OAUTH_SERVER_URL ?? "",
ownerOpenId: process.env.OWNER_OPEN_ID ?? "",
diff --git a/server/_core/index.ts b/server/_core/index.ts
index f472331..01c7d86 100644
--- a/server/_core/index.ts
+++ b/server/_core/index.ts
@@ -2,6 +2,7 @@ import "dotenv/config";
import express from "express";
import { createServer } from "http";
import net from "net";
+import cookieParser from "cookie-parser";
import { createExpressMiddleware } from "@trpc/server/adapters/express";
import { registerOAuthRoutes } from "./oauth";
import { appRouter } from "../routers";
@@ -30,6 +31,8 @@ async function findAvailablePort(startPort: number = 3000): Promise
{
async function startServer() {
const app = express();
const server = createServer(app);
+ // Configure cookie parser
+ app.use(cookieParser());
// Configure body parser with larger size limit for file uploads
app.use(express.json({ limit: "50mb" }));
app.use(express.urlencoded({ limit: "50mb", extended: true }));
diff --git a/server/routers.ts b/server/routers.ts
index 774f4a6..6c7302e 100644
--- a/server/routers.ts
+++ b/server/routers.ts
@@ -55,7 +55,7 @@ export const appRouter = router({
// Définir le cookie de session
const cookieOptions = getSessionCookieOptions(ctx.req);
- ctx.res.cookie(COOKIE_NAME, token, cookieOptions);
+ ctx.res.cookie(COOKIE_NAME, token, { ...cookieOptions, maxAge: 30 * 24 * 60 * 60 * 1000 }); // 30 jours
return { success: true, userId };
}),
@@ -79,8 +79,16 @@ export const appRouter = router({
throw new TRPCError({ code: 'FORBIDDEN', message: 'Votre compte a été désactivé' });
}
+ // Vérifier que le mot de passe existe
+ if (!user.password) {
+ console.error('[LOGIN] User has no password:', user.email);
+ throw new TRPCError({ code: 'UNAUTHORIZED', message: 'Email ou mot de passe incorrect' });
+ }
+
// Vérifier le mot de passe
+ console.log('[LOGIN] Verifying password for:', user.email);
const isValid = await verifyPassword(input.password, user.password);
+ console.log('[LOGIN] Password valid:', isValid);
if (!isValid) {
throw new TRPCError({ code: 'UNAUTHORIZED', message: 'Email ou mot de passe incorrect' });
}
@@ -93,7 +101,7 @@ export const appRouter = router({
// Définir le cookie de session
const cookieOptions = getSessionCookieOptions(ctx.req);
- ctx.res.cookie(COOKIE_NAME, token, cookieOptions);
+ ctx.res.cookie(COOKIE_NAME, token, { ...cookieOptions, maxAge: 30 * 24 * 60 * 60 * 1000 }); // 30 jours
return { success: true, user: { id: user.id, email: user.email, name: user.name, role: user.role } };
}),
@@ -626,13 +634,32 @@ export const appRouter = router({
}),
create: adminProcedure.input(z.object({
- openId: z.string().min(1),
+ email: z.string().email(),
+ password: z.string().min(6),
name: z.string().optional(),
- email: z.string().email().optional(),
role: z.enum(["user", "admin"]).default("user"),
isActive: z.boolean().default(true),
})).mutation(async ({ input }) => {
- await db.createUser(input);
+ const { hashPassword } = await import("./_core/auth");
+
+ // Vérifier si l'utilisateur existe déjà
+ const existingUser = await db.getUserByEmail(input.email);
+ if (existingUser) {
+ throw new TRPCError({ code: 'CONFLICT', message: 'Un compte existe déjà avec cet email' });
+ }
+
+ // Hasher le mot de passe
+ const hashedPassword = await hashPassword(input.password);
+
+ // Créer l'utilisateur
+ await db.createUser({
+ email: input.email,
+ password: hashedPassword,
+ name: input.name,
+ emailVerified: false,
+ role: input.role,
+ isActive: input.isActive,
+ });
return { success: true };
}),
diff --git a/test-bcrypt.mjs b/test-bcrypt.mjs
new file mode 100644
index 0000000..297e4da
--- /dev/null
+++ b/test-bcrypt.mjs
@@ -0,0 +1,19 @@
+import bcrypt from 'bcrypt';
+
+const password = 'admin123';
+const hash = '$2b$10$N9qo8uLOickgx2ZMRZoMye7FRNv8va91kpMQH6.OfzrcBizxDbvSK';
+
+console.log('Testing bcrypt...');
+console.log('Password:', password);
+console.log('Hash:', hash);
+
+bcrypt.compare(password, hash).then(result => {
+ console.log('Result:', result);
+ if (result) {
+ console.log('✅ Password is correct!');
+ } else {
+ console.log('❌ Password is incorrect!');
+ }
+}).catch(err => {
+ console.error('Error:', err);
+});
diff --git a/todo.md b/todo.md
index 051b64c..a65d0ef 100644
--- a/todo.md
+++ b/todo.md
@@ -251,9 +251,13 @@
- [x] Modifier le système de context pour utiliser JWT
- [x] Créer la page de connexion
- [x] Créer la page d'inscription
-- [ ] Modifier la page de gestion des utilisateurs (ajouter mot de passe)
-- [ ] Mettre à jour useAuth pour utiliser la nouvelle authentification
-- [ ] Tester la connexion et l'inscription
+- [x] Modifier la page de gestion des utilisateurs (ajouter mot de passe)
+- [x] Mettre à jour useAuth pour utiliser la nouvelle authentification
+- [x] Tester le flux de déconnexion et reconnexion
+- [x] Mettre à jour AdminUsers pour permettre la création d'utilisateurs avec mot de passe
+- [x] Tester l'inscription de nouveaux utilisateurs
+- [x] Vérifier que toutes les routes protégées nécessitent une authentification
+- [x] Nettoyer le code (supprimer les références à openId si nécessaire)
- [ ] Créer un guide de déploiement sans OAuth
## Correction des erreurs de compilation