🛡️ Sécurité
Architecture de sécurité et bonnes pratiques du système Commerce Tracking.
🎯 Vue d'ensemble
Commerce Tracking implémente une architecture de sécurité multi-couches pour protéger les données sensibles et garantir l'intégrité du système.
Principes de sécurité
- Défense en profondeur : Plusieurs couches de sécurité
- Principe du moindre privilège : Accès minimal nécessaire
- Chiffrement des données : Protection des données sensibles
- Audit et monitoring : Traçabilité complète
- Sécurité par défaut : Configuration sécurisée par défaut
🔐 Authentification et autorisation
Authentification JWT
// Configuration JWT sécurisée
{
secret: process.env.JWT_SECRET, // Secret fort et unique
expiresIn: '1h', // Expiration courte
refreshExpiresIn: '7d', // Refresh token plus long
algorithm: 'HS256', // Algorithme sécurisé
issuer: 'commerce-tracking', // Émetteur identifié
audience: 'commerce-clients' // Audience spécifique
}
// Payload JWT sécurisé
interface JwtPayload {
sub: number; // User ID
username: string; // Username
email: string; // Email
role_id: number; // Role ID
country_id: number; // Country ID
iat: number; // Issued at
exp: number; // Expires at
jti: string; // JWT ID unique
}
Système de rôles et permissions
// Hiérarchie des rôles
enum UserRole {
AGENT = 3, // Utilisateur de base
TEAM_MANAGER = 4, // Chef d'équipe
SUPERVISOR = 5 // Superviseur
}
// Permissions par rôle
const ROLE_PERMISSIONS = {
[UserRole.AGENT]: [
'collections:create',
'collections:read:own',
'collections:update:own',
'collections:delete:own'
],
[UserRole.TEAM_MANAGER]: [
'collections:create',
'collections:read:team',
'collections:update:team',
'collections:validate:level_1',
'reports:read:team'
],
[UserRole.SUPERVISOR]: [
'collections:create',
'collections:read:all',
'collections:update:all',
'collections:validate:level_2',
'users:manage',
'reports:read:all',
'system:configure'
]
};
Validation des permissions
@Injectable()
export class PermissionGuard implements CanActivate {
constructor(private authService: AuthService) {}
async canActivate(context: ExecutionContext): Promise<boolean> {
const request = context.switchToHttp().getRequest();
const user = request.user;
const requiredPermission = this.reflector.get<string>('permission', context.getHandler());
if (!requiredPermission) {
return true;
}
const userPermissions = ROLE_PERMISSIONS[user.role_id] || [];
return userPermissions.includes(requiredPermission);
}
}
🔒 Chiffrement des données
Chiffrement des mots de passe
// Utilisation de bcrypt pour le hachage des mots de passe
import * as bcrypt from 'bcrypt';
@Injectable()
export class PasswordService {
private readonly saltRounds = 12;
async hashPassword(password: string): Promise<string> {
return bcrypt.hash(password, this.saltRounds);
}
async comparePassword(password: string, hash: string): Promise<boolean> {
return bcrypt.compare(password, hash);
}
async validatePasswordStrength(password: string): Promise<boolean> {
// Minimum 8 caractères, au moins 1 majuscule, 1 minuscule, 1 chiffre, 1 caractère spécial
const passwordRegex = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}$/;
return passwordRegex.test(password);
}
}
Chiffrement des données sensibles
// Chiffrement AES pour les données sensibles
import * as crypto from 'crypto';
@Injectable()
export class EncryptionService {
private readonly algorithm = 'aes-256-gcm';
private readonly secretKey = process.env.ENCRYPTION_KEY;
encrypt(text: string): { encrypted: string; iv: string; tag: string } {
const iv = crypto.randomBytes(16);
const cipher = crypto.createCipher(this.algorithm, this.secretKey);
cipher.setAAD(Buffer.from('commerce-tracking', 'utf8'));
let encrypted = cipher.update(text, 'utf8', 'hex');
encrypted += cipher.final('hex');
const tag = cipher.getAuthTag();
return {
encrypted,
iv: iv.toString('hex'),
tag: tag.toString('hex')
};
}
decrypt(encryptedData: { encrypted: string; iv: string; tag: string }): string {
const decipher = crypto.createDecipher(this.algorithm, this.secretKey);
decipher.setAAD(Buffer.from('commerce-tracking', 'utf8'));
decipher.setAuthTag(Buffer.from(encryptedData.tag, 'hex'));
let decrypted = decipher.update(encryptedData.encrypted, 'hex', 'utf8');
decrypted += decipher.final('utf8');
return decrypted;
}
}
🌐 Sécurité réseau
Configuration CORS sécurisée
// Configuration CORS restrictive
app.enableCors({
origin: (origin, callback) => {
const allowedOrigins = process.env.ALLOWED_ORIGINS?.split(',') || [];
if (!origin || allowedOrigins.includes(origin)) {
callback(null, true);
} else {
callback(new Error('Non autorisé par CORS'));
}
},
methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH'],
allowedHeaders: [
'Content-Type',
'Authorization',
'X-Requested-With',
'Accept',
'Origin',
'X-Trace-ID'
],
credentials: true,
maxAge: 86400 // 24 heures
});
Headers de sécurité
// Middleware de sécurité
app.use((req, res, next) => {
// Protection XSS
res.setHeader('X-Content-Type-Options', 'nosniff');
res.setHeader('X-Frame-Options', 'DENY');
res.setHeader('X-XSS-Protection', '1; mode=block');
// Politique de sécurité du contenu
res.setHeader('Content-Security-Policy',
"default-src 'self'; " +
"script-src 'self' 'unsafe-inline'; " +
"style-src 'self' 'unsafe-inline'; " +
"img-src 'self' data: https:; " +
"connect-src 'self'"
);
// HSTS (HTTP Strict Transport Security)
res.setHeader('Strict-Transport-Security',
'max-age=31536000; includeSubDomains; preload'
);
// Référent Policy
res.setHeader('Referrer-Policy', 'strict-origin-when-cross-origin');
next();
});
Rate Limiting
// Protection contre les attaques par force brute
import rateLimit from 'express-rate-limit';
const loginLimiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 5, // 5 tentatives par fenêtre
message: {
success: false,
message: 'Trop de tentatives de connexion',
errors: ['Veuillez attendre 15 minutes avant de réessayer']
},
standardHeaders: true,
legacyHeaders: false,
skipSuccessfulRequests: true
});
const apiLimiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100, // 100 requêtes par fenêtre
message: {
success: false,
message: 'Trop de requêtes',
errors: ['Limite de taux dépassée']
}
});
app.use('/auth/login', loginLimiter);
app.use('/api', apiLimiter);
🔍 Audit et monitoring
Logs de sécurité
// Service de logging sécurisé
@Injectable()
export class SecurityLogger {
private readonly logger = new Logger('SecurityLogger');
logAuthenticationAttempt(username: string, success: boolean, ip: string) {
this.logger.warn(`Authentication attempt: ${username}, success: ${success}, IP: ${ip}`);
}
logPermissionDenied(userId: number, resource: string, action: string, ip: string) {
this.logger.warn(`Permission denied: User ${userId} attempted ${action} on ${resource} from IP ${ip}`);
}
logSuspiciousActivity(userId: number, activity: string, details: any, ip: string) {
this.logger.error(`Suspicious activity: User ${userId}, activity: ${activity}, details: ${JSON.stringify(details)}, IP: ${ip}`);
}
logDataAccess(userId: number, resource: string, action: string, resourceId: string) {
this.logger.log(`Data access: User ${userId} performed ${action} on ${resource}:${resourceId}`);
}
}
Audit trail complet
// Entité d'audit
@Entity('audit_logs')
export class AuditLog {
@PrimaryGeneratedColumn()
id: number;
@Column()
user_id: number;
@Column()
user_name: string;
@Column()
action: string;
@Column()
resource_type: string;
@Column({ nullable: true })
resource_id: number;
@Column({ nullable: true })
resource_name: string;
@Column('json', { nullable: true })
details: any;
@Column()
ip_address: string;
@Column('text', { nullable: true })
user_agent: string;
@CreateDateColumn()
created_at: Date;
}
// Intercepteur d'audit
@Injectable()
export class AuditInterceptor implements NestInterceptor {
constructor(private auditService: AuditService) {}
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
const request = context.switchToHttp().getRequest();
const user = request.user;
return next.handle().pipe(
tap((response) => {
if (user) {
this.auditService.log({
user_id: user.id,
user_name: user.username,
action: request.method,
resource_type: this.getResourceType(request.url),
resource_id: this.getResourceId(request),
ip_address: request.ip,
user_agent: request.headers['user-agent'],
details: {
url: request.url,
method: request.method,
response_status: response?.status || 200
}
});
}
})
);
}
}
🛡️ Validation et sanitisation
Validation des entrées
// DTOs avec validation stricte
export class CreateCollectionDto {
@IsNotEmpty()
@IsDateString()
collection_date: string;
@IsNotEmpty()
@IsInt()
@Min(1)
collector_id: number;
@IsNotEmpty()
@IsObject()
@ValidateNested()
@Type(() => LocationDto)
location: LocationDto;
@IsNotEmpty()
@IsObject()
@ValidateNested()
@Type(() => TradeDataDto)
trade_data: TradeDataDto;
@IsOptional()
@IsObject()
metadata?: any;
}
// Sanitisation des entrées
@Injectable()
export class SanitizationService {
sanitizeInput(input: any): any {
if (typeof input === 'string') {
// Échapper les caractères HTML
return input
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''')
.replace(/\//g, '/');
}
if (Array.isArray(input)) {
return input.map(item => this.sanitizeInput(item));
}
if (typeof input === 'object' && input !== null) {
const sanitized: any = {};
for (const [key, value] of Object.entries(input)) {
sanitized[key] = this.sanitizeInput(value);
}
return sanitized;
}
return input;
}
}
🔐 Sécurité de la base de données
Connexions sécurisées
// Configuration de connexion sécurisée
{
type: 'mysql',
host: process.env.DB_HOST,
port: parseInt(process.env.DB_PORT),
username: process.env.DB_USERNAME,
password: process.env.DB_PASSWORD,
database: process.env.DB_DATABASE,
ssl: {
rejectUnauthorized: true,
ca: process.env.DB_SSL_CA,
cert: process.env.DB_SSL_CERT,
key: process.env.DB_SSL_KEY
},
extra: {
ssl: {
require: true,
rejectUnauthorized: true
}
}
}
Requêtes sécurisées
// Utilisation de paramètres préparés
@Injectable()
export class CollectionService {
constructor(
@InjectRepository(Collection)
private collectionRepository: Repository<Collection>
) {}
async findByCollector(collectorId: number, userId: number, userRole: number): Promise<Collection[]> {
let query = this.collectionRepository.createQueryBuilder('collection')
.leftJoinAndSelect('collection.collector', 'collector')
.where('collection.collector_id = :collectorId', { collectorId });
// Filtrage basé sur les permissions
if (userRole === UserRole.AGENT) {
query = query.andWhere('collection.collector_id = :userId', { userId });
}
return query.getMany();
}
}
🔒 Sécurité des fichiers
Upload sécurisé
// Configuration multer sécurisée
const storage = multer.diskStorage({
destination: (req, file, cb) => {
const uploadPath = path.join(__dirname, '../uploads');
if (!fs.existsSync(uploadPath)) {
fs.mkdirSync(uploadPath, { recursive: true });
}
cb(null, uploadPath);
},
filename: (req, file, cb) => {
const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1E9);
cb(null, file.fieldname + '-' + uniqueSuffix);
}
});
const fileFilter = (req: any, file: Express.Multer.File, cb: any) => {
// Vérifier le type MIME
const allowedMimes = ['image/jpeg', 'image/png', 'application/pdf'];
if (allowedMimes.includes(file.mimetype)) {
cb(null, true);
} else {
cb(new Error('Type de fichier non autorisé'), false);
}
};
const upload = multer({
storage,
fileFilter,
limits: {
fileSize: 10 * 1024 * 1024, // 10MB max
files: 5 // 5 fichiers max
}
});
🚨 Détection d'intrusion
Monitoring des activités suspectes
@Injectable()
export class IntrusionDetectionService {
private readonly suspiciousActivities = new Map<string, number>();
private readonly blockedIPs = new Set<string>();
detectSuspiciousActivity(ip: string, userId: number, action: string): boolean {
const key = `${ip}:${userId}:${action}`;
const count = this.suspiciousActivities.get(key) || 0;
// Seuils de détection
const thresholds = {
'failed_login': 5,
'permission_denied': 10,
'data_access': 50
};
if (count >= (thresholds[action] || 10)) {
this.blockIP(ip);
this.logSuspiciousActivity(ip, userId, action, count);
return true;
}
this.suspiciousActivities.set(key, count + 1);
return false;
}
private blockIP(ip: string) {
this.blockedIPs.add(ip);
// Bloquer l'IP pendant 1 heure
setTimeout(() => {
this.blockedIPs.delete(ip);
}, 60 * 60 * 1000);
}
isBlocked(ip: string): boolean {
return this.blockedIPs.has(ip);
}
}
📋 Checklist de sécurité
Déploiement sécurisé
- Variables d'environnement sécurisées
- Secrets chiffrés et rotés régulièrement
- HTTPS activé en production
- Headers de sécurité configurés
- Rate limiting activé
- CORS configuré correctement
- Logs de sécurité activés
- Monitoring des activités suspectes
- Sauvegardes chiffrées
- Tests de pénétration effectués
Maintenance sécurisée
- Mise à jour des dépendances
- Rotation des secrets
- Révision des logs de sécurité
- Audit des permissions
- Tests de sécurité réguliers
- Formation de l'équipe
- Plan de réponse aux incidents
- Documentation de sécurité à jour
Cette architecture de sécurité multi-couches garantit la protection des données et l'intégrité du système Commerce Tracking contre les menaces modernes.