Files
crowdlending-app/backend/src/routes/auth.js
T

134 lines
4.8 KiB
JavaScript

import { Router } from 'express';
import bcrypt from 'bcryptjs';
import { z } from 'zod';
import db from '../db/index.js';
import { signToken, requireAuth } from '../middleware/auth.js';
import { HttpError } from '../middleware/errorHandler.js';
const router = Router();
const RegisterSchema = z.object({
email: z.string().email(),
password: z.string().min(8),
displayName: z.string().min(1).optional(),
});
const LoginSchema = z.object({
email: z.string().email(),
password: z.string().min(1),
});
router.post('/register', (req, res, next) => {
try {
const body = RegisterSchema.parse(req.body);
const exists = db.prepare('SELECT id FROM users WHERE email = ?').get(body.email);
if (exists) throw new HttpError(409, 'Email already registered');
// Le premier utilisateur inscrit devient automatiquement administrateur
const isFirst = db.prepare('SELECT COUNT(*) AS n FROM users').get().n === 0;
const role = isFirst ? 'admin' : 'user';
const hash = bcrypt.hashSync(body.password, 10);
const result = db
.prepare('INSERT INTO users (email, password_hash, display_name, role) VALUES (?, ?, ?, ?)')
.run(body.email, hash, body.displayName || null, role);
const userId = result.lastInsertRowid;
// Auto-create le premier profil famille (= l'utilisateur lui-même)
const fullName = body.displayName || 'Mon profil';
const prenom = fullName.includes(' ') ? fullName.split(' ')[0] : null;
const invResult = db.prepare(
`INSERT INTO investisseurs (user_id, nom, prenom, type, type_fiscal, is_principal) VALUES (?, ?, ?, 'famille', 'PP', 1)`
).run(userId, fullName, prenom);
// Auto-créer un compte courant pour le profil principal
db.prepare(
'INSERT INTO comptes (user_id, nom, type, investisseur_id) VALUES (?,?,?,?)'
).run(userId, `Compte courant — ${fullName}`, 'compte_courant', invResult.lastInsertRowid);
const token = signToken({ sub: userId, email: body.email });
res.status(201).json({
token,
user: { id: userId, email: body.email, displayName: body.displayName || null, role },
});
} catch (e) { next(e); }
});
router.post('/login', (req, res, next) => {
try {
const body = LoginSchema.parse(req.body);
const user = db
.prepare('SELECT id, email, password_hash, display_name, role FROM users WHERE email = ?')
.get(body.email);
if (!user) throw new HttpError(401, 'Invalid credentials');
const ok = bcrypt.compareSync(body.password, user.password_hash);
if (!ok) throw new HttpError(401, 'Invalid credentials');
const token = signToken({ sub: user.id, email: user.email });
res.json({
token,
user: { id: user.id, email: user.email, displayName: user.display_name, role: user.role },
});
} catch (e) { next(e); }
});
router.get('/me', requireAuth, (req, res) => {
const user = db
.prepare('SELECT id, email, display_name, role FROM users WHERE id = ?')
.get(req.user.id);
res.json({ user });
});
const UpdateMeSchema = z.object({
displayName: z.string().min(1).max(80).optional(),
email: z.string().email().optional(),
currentPassword: z.string().optional(),
newPassword: z.string().min(8).optional(),
});
router.put('/me', requireAuth, (req, res, next) => {
try {
const body = UpdateMeSchema.parse(req.body);
const current = db
.prepare('SELECT id, email, password_hash, display_name FROM users WHERE id = ?')
.get(req.user.id);
let newHash = undefined;
if (body.newPassword) {
if (!body.currentPassword)
throw new HttpError(400, 'Mot de passe actuel requis');
const ok = bcrypt.compareSync(body.currentPassword, current.password_hash);
if (!ok) throw new HttpError(401, 'Mot de passe actuel incorrect');
newHash = bcrypt.hashSync(body.newPassword, 10);
}
if (body.email && body.email !== current.email) {
const taken = db.prepare('SELECT id FROM users WHERE email = ? AND id != ?').get(body.email, req.user.id);
if (taken) throw new HttpError(409, 'Email déjà utilisé par un autre compte');
}
const newEmail = body.email ?? current.email;
const newDisplayName = body.displayName !== undefined ? body.displayName : current.display_name;
const newPasswordHash = newHash ?? current.password_hash;
db.prepare(
"UPDATE users SET email=?, display_name=?, password_hash=?, updated_at=datetime('now') WHERE id=?"
).run(newEmail, newDisplayName, newPasswordHash, req.user.id);
const token = newEmail !== current.email
? signToken({ sub: req.user.id, email: newEmail })
: undefined;
res.json({
user: { id: req.user.id, email: newEmail, display_name: newDisplayName },
...(token ? { token } : {}),
});
} catch (e) { next(e); }
});
export default router;