Aller au contenu principal

🛡️ 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, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#x27;')
.replace(/\//g, '&#x2F;');
}

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.