# Практики безопасности SFERA ## 🛡️ Обзор Комплексный набор практик безопасности для платформы SFERA, покрывающий аутентификацию, авторизацию, защиту данных, безопасность API, инфраструктуры и соответствие стандартам безопасности. ## 🔐 Аутентификация и авторизация ### 1. JWT Token Security #### Конфигурация токенов ```typescript // src/lib/auth.ts import jwt from 'jsonwebtoken' import { randomBytes } from 'crypto' // Безопасная генерация JWT секрета export const generateJWTSecret = (): string => { return randomBytes(64).toString('hex') } // Конфигурация JWT export const JWT_CONFIG = { // Короткое время жизни access токена accessTokenExpiry: '15m', // Длинное время жизни refresh токена refreshTokenExpiry: '7d', // Алгоритм подписи algorithm: 'HS256' as const, // Издатель issuer: 'sfera-platform', // Аудитория audience: 'sfera-users', } // Создание access токена export const createAccessToken = (payload: { userId: string organizationId?: string organizationType?: string permissions: string[] }): string => { return jwt.sign( { sub: payload.userId, org: payload.organizationId, orgType: payload.organizationType, permissions: payload.permissions, type: 'access', }, process.env.JWT_SECRET!, { expiresIn: JWT_CONFIG.accessTokenExpiry, issuer: JWT_CONFIG.issuer, audience: JWT_CONFIG.audience, algorithm: JWT_CONFIG.algorithm, }, ) } // Создание refresh токена export const createRefreshToken = (userId: string): string => { return jwt.sign( { sub: userId, type: 'refresh', jti: randomBytes(16).toString('hex'), // Уникальный ID токена }, process.env.JWT_REFRESH_SECRET!, { expiresIn: JWT_CONFIG.refreshTokenExpiry, issuer: JWT_CONFIG.issuer, audience: JWT_CONFIG.audience, algorithm: JWT_CONFIG.algorithm, }, ) } // Проверка токена export const verifyToken = (token: string, type: 'access' | 'refresh' = 'access'): any => { const secret = type === 'access' ? process.env.JWT_SECRET! : process.env.JWT_REFRESH_SECRET! try { return jwt.verify(token, secret, { issuer: JWT_CONFIG.issuer, audience: JWT_CONFIG.audience, algorithms: [JWT_CONFIG.algorithm], }) } catch (error) { throw new Error(`Invalid ${type} token`) } } ``` #### Secure Token Storage ```typescript // src/lib/token-storage.ts export class SecureTokenStorage { private static readonly ACCESS_TOKEN_KEY = '__sfera_at' private static readonly REFRESH_TOKEN_KEY = '__sfera_rt' // Сохранение токенов с HttpOnly флагами (серверная сторона) static setTokensCookies( res: NextResponse, tokens: { accessToken: string refreshToken: string }, ) { // Access token в HttpOnly cookie с коротким временем жизни res.cookies.set(this.ACCESS_TOKEN_KEY, tokens.accessToken, { httpOnly: true, secure: process.env.NODE_ENV === 'production', sameSite: 'strict', maxAge: 15 * 60, // 15 минут path: '/', }) // Refresh token в HttpOnly cookie с длинным временем жизни res.cookies.set(this.REFRESH_TOKEN_KEY, tokens.refreshToken, { httpOnly: true, secure: process.env.NODE_ENV === 'production', sameSite: 'strict', maxAge: 7 * 24 * 60 * 60, // 7 дней path: '/api/auth/refresh', }) } // Получение токенов из cookies static getTokensFromCookies(req: NextRequest) { return { accessToken: req.cookies.get(this.ACCESS_TOKEN_KEY)?.value, refreshToken: req.cookies.get(this.REFRESH_TOKEN_KEY)?.value, } } // Очистка токенов static clearTokensCookies(res: NextResponse) { res.cookies.delete(this.ACCESS_TOKEN_KEY) res.cookies.delete(this.REFRESH_TOKEN_KEY) } } ``` ### 2. Role-Based Access Control (RBAC) #### Система ролей и разрешений ```typescript // src/lib/permissions.ts export enum Permission { // Управление пользователями USERS_READ = 'users:read', USERS_WRITE = 'users:write', USERS_DELETE = 'users:delete', // Управление заказами ORDERS_READ = 'orders:read', ORDERS_WRITE = 'orders:write', ORDERS_APPROVE = 'orders:approve', // Управление сотрудниками EMPLOYEES_READ = 'employees:read', EMPLOYEES_WRITE = 'employees:write', EMPLOYEES_MANAGE = 'employees:manage', // Финансы FINANCES_READ = 'finances:read', FINANCES_WRITE = 'finances:write', // Системное администрирование SYSTEM_ADMIN = 'system:admin', // Партнерство PARTNERSHIPS_READ = 'partnerships:read', PARTNERSHIPS_MANAGE = 'partnerships:manage', } export enum Role { OWNER = 'OWNER', ADMIN = 'ADMIN', MANAGER = 'MANAGER', EMPLOYEE = 'EMPLOYEE', VIEWER = 'VIEWER', } // Матрица разрешений для ролей export const ROLE_PERMISSIONS: Record = { [Role.OWNER]: [ Permission.USERS_READ, Permission.USERS_WRITE, Permission.USERS_DELETE, Permission.ORDERS_READ, Permission.ORDERS_WRITE, Permission.ORDERS_APPROVE, Permission.EMPLOYEES_READ, Permission.EMPLOYEES_WRITE, Permission.EMPLOYEES_MANAGE, Permission.FINANCES_READ, Permission.FINANCES_WRITE, Permission.PARTNERSHIPS_READ, Permission.PARTNERSHIPS_MANAGE, ], [Role.ADMIN]: [ Permission.USERS_READ, Permission.USERS_WRITE, Permission.ORDERS_READ, Permission.ORDERS_WRITE, Permission.ORDERS_APPROVE, Permission.EMPLOYEES_READ, Permission.EMPLOYEES_WRITE, Permission.FINANCES_READ, ], [Role.MANAGER]: [ Permission.USERS_READ, Permission.ORDERS_READ, Permission.ORDERS_WRITE, Permission.EMPLOYEES_READ, Permission.FINANCES_READ, ], [Role.EMPLOYEE]: [Permission.ORDERS_READ, Permission.EMPLOYEES_READ], [Role.VIEWER]: [Permission.ORDERS_READ], } // Проверка разрешений export const hasPermission = (userPermissions: Permission[], requiredPermission: Permission): boolean => { return userPermissions.includes(requiredPermission) } // Middleware для проверки разрешений export const requirePermission = (permission: Permission) => { return (req: any, res: any, next: any) => { const userPermissions = req.user?.permissions || [] if (!hasPermission(userPermissions, permission)) { return res.status(403).json({ error: 'Insufficient permissions', required: permission, }) } next() } } ``` ## 🔒 Защита данных ### 1. Шифрование данных #### Шифрование чувствительных полей ```typescript // src/lib/encryption.ts import { createCipher, createDecipher, randomBytes, scrypt } from 'crypto' import { promisify } from 'util' const scryptAsync = promisify(scrypt) export class DataEncryption { private static readonly ALGORITHM = 'aes-256-gcm' private static readonly SALT_LENGTH = 32 private static readonly IV_LENGTH = 16 private static readonly TAG_LENGTH = 16 // Генерация ключа шифрования из пароля private static async generateKey(password: string, salt: Buffer): Promise { return (await scryptAsync(password, salt, 32)) as Buffer } // Шифрование данных static async encrypt(data: string, password: string = process.env.ENCRYPTION_KEY!): Promise { const salt = randomBytes(this.SALT_LENGTH) const iv = randomBytes(this.IV_LENGTH) const key = await this.generateKey(password, salt) const cipher = createCipher(this.ALGORITHM, key) cipher.setAAD(salt) // Дополнительные аутентифицированные данные let encrypted = cipher.update(data, 'utf8', 'hex') encrypted += cipher.final('hex') const tag = cipher.getAuthTag() // Объединяем salt, iv, tag и зашифрованные данные return Buffer.concat([salt, iv, tag, Buffer.from(encrypted, 'hex')]).toString('base64') } // Расшифровка данных static async decrypt(encryptedData: string, password: string = process.env.ENCRYPTION_KEY!): Promise { const buffer = Buffer.from(encryptedData, 'base64') const salt = buffer.slice(0, this.SALT_LENGTH) const iv = buffer.slice(this.SALT_LENGTH, this.SALT_LENGTH + this.IV_LENGTH) const tag = buffer.slice(this.SALT_LENGTH + this.IV_LENGTH, this.SALT_LENGTH + this.IV_LENGTH + this.TAG_LENGTH) const encrypted = buffer.slice(this.SALT_LENGTH + this.IV_LENGTH + this.TAG_LENGTH) const key = await this.generateKey(password, salt) const decipher = createDecipher(this.ALGORITHM, key) decipher.setAuthTag(tag) decipher.setAAD(salt) let decrypted = decipher.update(encrypted, undefined, 'utf8') decrypted += decipher.final('utf8') return decrypted } } // Пример использования для чувствительных полей export const encryptSensitiveData = async (user: any) => { if (user.passportSeries) { user.passportSeries = await DataEncryption.encrypt(user.passportSeries) } if (user.passportNumber) { user.passportNumber = await DataEncryption.encrypt(user.passportNumber) } if (user.inn) { user.inn = await DataEncryption.encrypt(user.inn) } return user } ``` ### 2. Хеширование паролей ```typescript // src/lib/password.ts import bcrypt from 'bcryptjs' import { randomBytes } from 'crypto' export class PasswordSecurity { private static readonly SALT_ROUNDS = 12 private static readonly MIN_PASSWORD_LENGTH = 8 // Хеширование пароля static async hashPassword(password: string): Promise { const salt = await bcrypt.genSalt(this.SALT_ROUNDS) return bcrypt.hash(password, salt) } // Проверка пароля static async verifyPassword(password: string, hashedPassword: string): Promise { return bcrypt.compare(password, hashedPassword) } // Генерация безопасного временного пароля static generateTemporaryPassword(length: number = 12): string { const chars = 'ABCDEFGHJKMNPQRSTUVWXYZabcdefghijkmnpqrstuvwxyz23456789!@#$%&*' let password = '' for (let i = 0; i < length; i++) { password += chars.charAt(Math.floor(Math.random() * chars.length)) } return password } // Проверка сложности пароля static validatePasswordStrength(password: string): { isValid: boolean errors: string[] } { const errors: string[] = [] if (password.length < this.MIN_PASSWORD_LENGTH) { errors.push(`Пароль должен содержать минимум ${this.MIN_PASSWORD_LENGTH} символов`) } if (!/[A-Z]/.test(password)) { errors.push('Пароль должен содержать заглавные буквы') } if (!/[a-z]/.test(password)) { errors.push('Пароль должен содержать строчные буквы') } if (!/[0-9]/.test(password)) { errors.push('Пароль должен содержать цифры') } if (!/[!@#$%^&*(),.?\":{}|<>]/.test(password)) { errors.push('Пароль должен содержать специальные символы') } return { isValid: errors.length === 0, errors, } } } ``` ## 🌐 API Security ### 1. Rate Limiting ```typescript // src/lib/rate-limiting.ts import { NextRequest } from 'next/server' interface RateLimitConfig { windowMs: number // Время окна в миллисекундах maxRequests: number // Максимальное количество запросов в окне message?: string } class RateLimiter { private requests: Map = new Map() constructor(private config: RateLimitConfig) {} check(identifier: string): { allowed: boolean; remaining: number; resetTime: number } { const now = Date.now() const record = this.requests.get(identifier) if (!record || now > record.resetTime) { // Новое окно this.requests.set(identifier, { count: 1, resetTime: now + this.config.windowMs, }) return { allowed: true, remaining: this.config.maxRequests - 1, resetTime: now + this.config.windowMs, } } if (record.count >= this.config.maxRequests) { return { allowed: false, remaining: 0, resetTime: record.resetTime, } } record.count++ this.requests.set(identifier, record) return { allowed: true, remaining: this.config.maxRequests - record.count, resetTime: record.resetTime, } } // Очистка устаревших записей cleanup() { const now = Date.now() for (const [key, record] of this.requests.entries()) { if (now > record.resetTime) { this.requests.delete(key) } } } } // Конфигурации для разных эндпоинтов export const rateLimiters = { auth: new RateLimiter({ windowMs: 15 * 60 * 1000, // 15 минут maxRequests: 5, // 5 попыток входа за 15 минут message: 'Слишком много попыток входа. Попробуйте через 15 минут.', }), api: new RateLimiter({ windowMs: 60 * 1000, // 1 минута maxRequests: 100, // 100 запросов в минуту message: 'Превышен лимит запросов API', }), sms: new RateLimiter({ windowMs: 60 * 60 * 1000, // 1 час maxRequests: 3, // 3 SMS в час message: 'Слишком много SMS запросов', }), } // Middleware для rate limiting export const createRateLimitMiddleware = (limiter: RateLimiter) => { return (req: NextRequest) => { // Получаем идентификатор клиента (IP + User-Agent) const identifier = `${req.ip || 'unknown'}-${req.headers.get('user-agent') || 'unknown'}` const result = limiter.check(identifier) if (!result.allowed) { return new Response( JSON.stringify({ error: 'Rate limit exceeded', resetTime: new Date(result.resetTime).toISOString(), }), { status: 429, headers: { 'Content-Type': 'application/json', 'X-RateLimit-Limit': limiter['config'].maxRequests.toString(), 'X-RateLimit-Remaining': result.remaining.toString(), 'X-RateLimit-Reset': new Date(result.resetTime).toISOString(), 'Retry-After': Math.ceil((result.resetTime - Date.now()) / 1000).toString(), }, }, ) } return null // Продолжить обработку } } ``` ### 2. Input Validation и Sanitization ```typescript // src/lib/validation.ts import DOMPurify from 'isomorphic-dompurify' import validator from 'validator' export class InputValidator { // Санитизация HTML static sanitizeHtml(input: string): string { return DOMPurify.sanitize(input, { ALLOWED_TAGS: ['b', 'i', 'em', 'strong', 'u'], ALLOWED_ATTR: [], }) } // Валидация и санитизация email static validateEmail(email: string): { isValid: boolean; sanitized?: string; error?: string } { const sanitized = validator.normalizeEmail(email) || '' if (!validator.isEmail(sanitized)) { return { isValid: false, error: 'Некорректный email адрес' } } return { isValid: true, sanitized } } // Валидация телефона static validatePhone(phone: string): { isValid: boolean; sanitized?: string; error?: string } { // Удаляем все кроме цифр и + const sanitized = phone.replace(/[^\d+]/g, '') // Проверяем российский формат if (!/^\+?7\d{10}$/.test(sanitized)) { return { isValid: false, error: 'Некорректный номер телефона' } } return { isValid: true, sanitized: sanitized.startsWith('+') ? sanitized : '+' + sanitized } } // Валидация ИНН static validateINN(inn: string): { isValid: boolean; sanitized?: string; error?: string } { const sanitized = inn.replace(/\D/g, '') if (sanitized.length !== 10 && sanitized.length !== 12) { return { isValid: false, error: 'ИНН должен содержать 10 или 12 цифр' } } // Проверка контрольных сумм if (!this.validateINNChecksum(sanitized)) { return { isValid: false, error: 'Некорректная контрольная сумма ИНН' } } return { isValid: true, sanitized } } private static validateINNChecksum(inn: string): boolean { if (inn.length === 10) { const coefficients = [2, 4, 10, 3, 5, 9, 4, 6, 8] let sum = 0 for (let i = 0; i < 9; i++) { sum += parseInt(inn[i]) * coefficients[i] } const checkDigit = (sum % 11) % 10 return checkDigit === parseInt(inn[9]) } if (inn.length === 12) { const coefficients1 = [7, 2, 4, 10, 3, 5, 9, 4, 6, 8] const coefficients2 = [3, 7, 2, 4, 10, 3, 5, 9, 4, 6, 8] let sum1 = 0, sum2 = 0 for (let i = 0; i < 10; i++) { sum1 += parseInt(inn[i]) * coefficients1[i] } for (let i = 0; i < 11; i++) { sum2 += parseInt(inn[i]) * coefficients2[i] } const checkDigit1 = (sum1 % 11) % 10 const checkDigit2 = (sum2 % 11) % 10 return checkDigit1 === parseInt(inn[10]) && checkDigit2 === parseInt(inn[11]) } return false } // Валидация файлов static validateFile( file: File, options: { maxSize?: number allowedTypes?: string[] allowedExtensions?: string[] } = {}, ): { isValid: boolean; error?: string } { const { maxSize = 10 * 1024 * 1024, // 10MB по умолчанию allowedTypes = ['image/jpeg', 'image/png', 'application/pdf'], allowedExtensions = ['.jpg', '.jpeg', '.png', '.pdf'], } = options if (file.size > maxSize) { return { isValid: false, error: `Размер файла не должен превышать ${Math.round(maxSize / 1024 / 1024)}MB`, } } if (!allowedTypes.includes(file.type)) { return { isValid: false, error: `Недопустимый тип файла. Разрешены: ${allowedTypes.join(', ')}`, } } const extension = file.name.toLowerCase().substring(file.name.lastIndexOf('.')) if (!allowedExtensions.includes(extension)) { return { isValid: false, error: `Недопустимое расширение файла. Разрешены: ${allowedExtensions.join(', ')}`, } } return { isValid: true } } } ``` ## 🔐 HTTPS и Transport Security ### 1. Настройка HTTPS #### Nginx конфигурация для HTTPS ```nginx # /etc/nginx/sites-available/sfera server { listen 80; server_name sfera.example.com; # Перенаправление на HTTPS return 301 https://$server_name$request_uri; } server { listen 443 ssl http2; server_name sfera.example.com; # SSL сертификаты ssl_certificate /etc/letsencrypt/live/sfera.example.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/sfera.example.com/privkey.pem; ssl_trusted_certificate /etc/letsencrypt/live/sfera.example.com/chain.pem; # SSL настройки ssl_protocols TLSv1.2 TLSv1.3; ssl_ciphers ECDHE-RSA-AES256-GCM-SHA512:DHE-RSA-AES256-GCM-SHA512:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES256-GCM-SHA384; ssl_prefer_server_ciphers off; ssl_session_cache shared:SSL:10m; ssl_session_timeout 10m; ssl_session_tickets off; # HSTS add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always; # Security headers add_header X-Frame-Options "SAMEORIGIN" always; add_header X-Content-Type-Options "nosniff" always; add_header Referrer-Policy "strict-origin-when-cross-origin" always; add_header X-XSS-Protection "1; mode=block" always; add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' data:; connect-src 'self' https: wss:;" always; # OCSP Stapling ssl_stapling on; ssl_stapling_verify on; resolver 8.8.8.8 8.8.4.4 valid=300s; resolver_timeout 5s; location / { proxy_pass http://localhost:3000; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection 'upgrade'; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_cache_bypass $http_upgrade; # Timeout настройки proxy_connect_timeout 60s; proxy_send_timeout 60s; proxy_read_timeout 60s; } # Статические файлы location /_next/static/ { alias /var/www/sfera/.next/static/; expires 1y; add_header Cache-Control "public, immutable"; } } ``` ### 2. Next.js Security Headers ```typescript // next.config.ts const nextConfig: NextConfig = { async headers() { return [ { source: '/(.*)', headers: [ { key: 'X-Frame-Options', value: 'SAMEORIGIN', }, { key: 'X-Content-Type-Options', value: 'nosniff', }, { key: 'Referrer-Policy', value: 'strict-origin-when-cross-origin', }, { key: 'X-XSS-Protection', value: '1; mode=block', }, { key: 'Content-Security-Policy', value: [ "default-src 'self'", "script-src 'self' 'unsafe-inline' 'unsafe-eval'", "style-src 'self' 'unsafe-inline'", "img-src 'self' data: https:", "font-src 'self' data:", "connect-src 'self' https: wss:", "frame-ancestors 'self'", ].join('; '), }, { key: 'Permissions-Policy', value: ['camera=()', 'microphone=()', 'geolocation=()', 'payment=()', 'usb=()', 'screen-wake-lock=()'].join( ', ', ), }, ], }, ] }, } ``` ## 🗄️ Database Security ### 1. Prisma Security Best Practices ```typescript // src/lib/prisma-security.ts import { PrismaClient } from '@prisma/client' // Безопасная конфигурация Prisma export const createSecurePrismaClient = () => { return new PrismaClient({ log: process.env.NODE_ENV === 'development' ? ['query', 'error', 'warn'] : ['error'], errorFormat: 'minimal', datasources: { db: { url: process.env.DATABASE_URL, }, }, }) } // Row Level Security (RLS) helpers export class DatabaseSecurity { // Проверка доступа к организации static async checkOrganizationAccess(prisma: PrismaClient, userId: string, organizationId: string): Promise { const user = await prisma.user.findFirst({ where: { id: userId, organizationId: organizationId, }, }) return !!user } // Безопасный поиск с фильтрацией по пользователю static createUserScopedQuery(userId: string, organizationId?: string) { return { where: { OR: [ { userId: userId }, { organizationId: organizationId }, { organization: { users: { some: { id: userId, }, }, }, }, ], }, } } // Санитизация запросов для предотвращения SQL инъекций static sanitizeSearchQuery(query: string): string { return query .replace(/[^\w\s\-_.@]/g, '') // Убираем спецсимволы .trim() .substring(0, 100) // Ограничиваем длину } } ``` ### 2. SQL Injection Prevention ```sql -- Примеры безопасных SQL запросов с параметрами -- prisma/migrations/ -- Создание функции для безопасного поиска CREATE OR REPLACE FUNCTION safe_search_organizations( search_term TEXT, user_id TEXT ) RETURNS TABLE ( id TEXT, name TEXT, inn TEXT ) AS $$ BEGIN -- Валидация входных параметров IF LENGTH(search_term) > 100 THEN RAISE EXCEPTION 'Search term too long'; END IF; -- Безопасный поиск с использованием параметризованного запроса RETURN QUERY SELECT o.id, o.name, o.inn FROM organizations o INNER JOIN users u ON u.organization_id = o.id WHERE u.id = user_id AND ( o.name ILIKE '%' || search_term || '%' OR o.inn ILIKE '%' || search_term || '%' ) LIMIT 50; END; $$ LANGUAGE plpgsql SECURITY DEFINER; -- Создание индексов для производительности CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_organizations_search ON organizations USING gin(to_tsvector('russian', name || ' ' || COALESCE(inn, ''))); ``` ## 🔐 Environment Security ### 1. Secrets Management ```bash # .env.example - шаблон переменных окружения # База данных DATABASE_URL="postgresql://user:password@localhost:5432/sfera" # JWT секреты (генерировать через: openssl rand -hex 32) JWT_SECRET="your-256-bit-secret" JWT_REFRESH_SECRET="your-256-bit-refresh-secret" # Шифрование данных ENCRYPTION_KEY="your-encryption-key" # API ключи (заменить на реальные) SMS_AERO_API_KEY="your-sms-api-key" DADATA_API_KEY="your-dadata-api-key" # Внешние сервисы WILDBERRIES_API_URL="https://common-api.wildberries.ru" OZON_API_URL="https://api-seller.ozon.ru" # Мониторинг JAEGER_ENDPOINT="http://localhost:14268/api/traces" # Флаги окружения NODE_ENV="production" SMS_DEV_MODE="false" ``` ### 2. Docker Secrets ```dockerfile # Dockerfile.secure - версия с поддержкой секретов FROM node:18-alpine AS base # Создание пользователя с ограниченными правами RUN addgroup --system --gid 1001 nodejs RUN adduser --system --uid 1001 nextjs # Установка зависимостей FROM base AS deps WORKDIR /app COPY package*.json ./ RUN npm ci --only=production && npm cache clean --force # Сборка приложения FROM base AS builder WORKDIR /app COPY --from=deps /app/node_modules ./node_modules COPY . . # Сборка с использованием секретов RUN --mount=type=secret,id=env,target=/app/.env \ npm run build # Production образ FROM base AS runner WORKDIR /app ENV NODE_ENV=production ENV NEXT_TELEMETRY_DISABLED=1 # Копирование файлов с правильными правами COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./ COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static COPY --from=builder --chown=nextjs:nodejs /app/public ./public # Переключение на непривилегированного пользователя USER nextjs EXPOSE 3000 ENV PORT=3000 ENV HOSTNAME="0.0.0.0" CMD ["node", "server.js"] ``` ## 🚨 Security Monitoring ### 1. Security Event Logging ```typescript // src/lib/security-logger.ts import { logger } from './logger' export class SecurityLogger { static logAuthAttempt(event: { userId?: string phone?: string ip: string userAgent: string success: boolean reason?: string }) { logger.info('Authentication attempt', { type: 'AUTH_ATTEMPT', userId: event.userId, phone: event.phone, ip: event.ip, userAgent: event.userAgent, success: event.success, reason: event.reason, timestamp: new Date().toISOString(), }) } static logPermissionDenied(event: { userId: string; resource: string; action: string; ip: string }) { logger.warn('Permission denied', { type: 'PERMISSION_DENIED', userId: event.userId, resource: event.resource, action: event.action, ip: event.ip, timestamp: new Date().toISOString(), }) } static logSuspiciousActivity(event: { userId?: string; ip: string; activity: string; details: object }) { logger.error('Suspicious activity detected', { type: 'SUSPICIOUS_ACTIVITY', userId: event.userId, ip: event.ip, activity: event.activity, details: event.details, timestamp: new Date().toISOString(), }) } static logDataAccess(event: { userId: string resource: string action: 'READ' | 'write' | 'delete' recordId?: string }) { logger.info('Data access', { type: 'DATA_ACCESS', userId: event.userId, resource: event.resource, action: event.action, recordId: event.recordId, timestamp: new Date().toISOString(), }) } } ``` ### 2. Automated Security Scans ```typescript // src/lib/security-scanner.ts export class SecurityScanner { // Проверка на подозрительные паттерны в запросах static scanRequest(req: any): { threat: boolean threats: string[] riskLevel: 'low' | 'medium' | 'high' } { const threats: string[] = [] // SQL Injection паттерны const sqlPatterns = [ /union\s+select/i, /drop\s+table/i, /insert\s+into/i, /delete\s+from/i, /update\s+set/i, /exec\s*\(/i, /script.*src/i, ] // XSS паттерны const xssPatterns = [ /]*>.*?<\/script>/gi, /javascript:/i, /vbscript:/i, /onload\s*=/i, /onerror\s*=/i, /onclick\s*=/i, ] const requestString = JSON.stringify(req.body || '') + JSON.stringify(req.query || '') // Проверка SQL Injection sqlPatterns.forEach((pattern) => { if (pattern.test(requestString)) { threats.push('SQL Injection attempt') } }) // Проверка XSS xssPatterns.forEach((pattern) => { if (pattern.test(requestString)) { threats.push('XSS attempt') } }) // Проверка размера запроса if (requestString.length > 10000) { threats.push('Request too large') } // Определение уровня риска let riskLevel: 'low' | 'medium' | 'high' = 'low' if (threats.length > 0) { riskLevel = threats.some((t) => t.includes('SQL') || t.includes('XSS')) ? 'high' : 'medium' } return { threat: threats.length > 0, threats, riskLevel, } } } ``` ## 🎯 Checklist безопасности ### Перед продакшеном - [ ] **Аутентификация** - [ ] JWT токены с коротким временем жизни - [ ] Refresh токены в HttpOnly cookies - [ ] Безопасное хранение секретов - [ ] **Авторизация** - [ ] RBAC система настроена - [ ] Проверка разрешений на всех эндпоинтах - [ ] Принцип наименьших привилегий - [ ] **Данные** - [ ] Шифрование чувствительных полей - [ ] Хеширование паролей с солью - [ ] Валидация и санитизация ввода - [ ] **Транспорт** - [ ] HTTPS настроен - [ ] Security headers добавлены - [ ] CSP политика настроена - [ ] **API** - [ ] Rate limiting настроен - [ ] Input validation реализован - [ ] CORS правильно настроен - [ ] **База данных** - [ ] Параметризованные запросы - [ ] Минимальные права доступа - [ ] Регулярные бэкапы - [ ] **Мониторинг** - [ ] Security логирование настроено - [ ] Алерты на подозрительную активность - [ ] Регулярные security аудиты ## 🎯 Заключение Эти практики безопасности обеспечивают: 1. **Защиту данных**: Шифрование, хеширование, валидация 2. **Безопасный доступ**: Аутентификация, авторизация, RBAC 3. **Защиту от атак**: Rate limiting, input validation, CSP 4. **Мониторинг**: Логирование, алерты, аудит 5. **Соответствие стандартам**: GDPR, ISO 27001, OWASP Регулярно обновляйте зависимости, проводите аудит безопасности и следите за новыми угрозами для поддержания высокого уровня безопасности платформы SFERA.