const bcrypt = require('bcrypt');
class UserService {
// Configuration recommandée
static SALT_ROUNDS = 12;
static async hashPassword(password) {
try {
const salt = await bcrypt.genSalt(this.SALT_ROUNDS);
return await bcrypt.hash(password, salt);
} catch (error) {
throw new Error('Password hashing failed');
}
}
static async verifyPassword(password, hash) {
try {
return await bcrypt.compare(password, hash);
} catch (error) {
throw new Error('Password verification failed');
}
}
}
// Utilisation dans un contrôleur
class AuthController {
async register(req, res) {
try {
const { email, password } = req.body;
const hashedPassword = await UserService.hashPassword(password);
const user = await User.create({
email,
password: hashedPassword
});
res.status(201).json({
message: 'User created successfully'
});
} catch (error) {
res.status(500).json({
message: 'Registration failed'
});
}
}
async login(req, res) {
try {
const { email, password } = req.body;
const user = await User.findOne({ email });
if (!user) {
return res.status(401).json({
message: 'Invalid credentials'
});
}
const isValid = await UserService.verifyPassword(
password,
user.password
);
if (!isValid) {
return res.status(401).json({
message: 'Invalid credentials'
});
}
// Génération du token JWT...
} catch (error) {
res.status(500).json({
message: 'Login failed'
});
}
}
}
from argon2 import PasswordHasher
from argon2.exceptions import VerifyMismatchError
class PasswordService:
def __init__(self):
# Configuration recommandée
self.ph = PasswordHasher(
time_cost=3, # Nombre d'itérations
memory_cost=65536, # Utilisation mémoire (64 MB)
parallelism=4, # Degré de parallélisme
hash_len=32, # Longueur du hash
salt_len=16 # Longueur du sel
)
def hash_password(self, password: str) -> str:
try:
return self.ph.hash(password)
except Exception as e:
raise ValueError("Password hashing failed") from e
def verify_password(self, hash: str, password: str) -> bool:
try:
self.ph.verify(hash, password)
return True
except VerifyMismatchError:
return False
except Exception as e:
raise ValueError("Password verification failed") from e
# Utilisation dans FastAPI
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
app = FastAPI()
password_service = PasswordService()
class UserCreate(BaseModel):
email: str
password: str
@app.post("/register")
async def register(user: UserCreate):
try:
hashed_password = password_service.hash_password(
user.password
)
# Création de l'utilisateur dans la base
db_user = await User.create(
email=user.email,
password=hashed_password
)
return {"message": "User created successfully"}
except Exception as e:
raise HTTPException(
status_code=500,
detail="Registration failed"
)
@app.post("/login")
async def login(user: UserCreate):
try:
db_user = await User.get(email=user.email)
if not db_user:
raise HTTPException(
status_code=401,
detail="Invalid credentials"
)
is_valid = password_service.verify_password(
db_user.password,
user.password
)
if not is_valid:
raise HTTPException(
status_code=401,
detail="Invalid credentials"
)
# Génération du token JWT...
except Exception as e:
raise HTTPException(
status_code=500,
detail="Login failed"
)
// Validation du mot de passe
function validatePassword(password) {
const MIN_LENGTH = 12;
const REGEX = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{12,}$/;
return password.length >= MIN_LENGTH && REGEX.test(password);
}
// Limitation des tentatives
const rateLimit = require('express-rate-limit');
const loginLimiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 5 // 5 tentatives
});
class PasswordMigrationService {
static async migrateHash(user, plainPassword) {
// Vérifier l'ancien hash (ex: MD5)
if (user.password === md5(plainPassword)) {
// Générer un nouveau hash sécurisé
const newHash = await bcrypt.hash(
plainPassword,
12
);
// Mettre à jour l'utilisateur
await User.update({
id: user.id,
password: newHash,
password_version: 2
});
return true;
}
return false;
}
}
// Utilisation pendant la connexion
async function login(email, password) {
const user = await User.findOne({ email });
if (user.password_version === 1) {
// Ancienne version : tenter la migration
const success = await PasswordMigrationService
.migrateHash(user, password);
if (!success) {
throw new Error('Invalid credentials');
}
} else {
// Nouvelle version : vérification normale
const valid = await bcrypt.compare(
password,
user.password
);
if (!valid) {
throw new Error('Invalid credentials');
}
}
}
// Tests unitaires
describe('PasswordService', () => {
const service = new PasswordService();
test('should hash password correctly', async () => {
const password = 'MySecurePass123!';
const hash = await service.hashPassword(password);
expect(hash).not.toBe(password);
expect(hash).toMatch(/^\$2[aby]\$/);
});
test('should verify password correctly', async () => {
const password = 'MySecurePass123!';
const hash = await service.hashPassword(password);
const isValid = await service.verifyPassword(
hash,
password
);
expect(isValid).toBe(true);
const isInvalid = await service.verifyPassword(
hash,
'WrongPassword123!'
);
expect(isInvalid).toBe(false);
});
test('should use sufficient rounds', async () => {
const start = Date.now();
await service.hashPassword('test');
const duration = Date.now() - start;
// Devrait prendre au moins 250ms
expect(duration).toBeGreaterThan(250);
});
});