Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Security: code-cfernandes/Varion

Security

docs/SECURITY.md

Práticas de Segurança - Varion

🔒 Visão Geral

Este documento aborda todas as práticas de segurança implementadas no projeto Varion, incluindo autenticação, autorização, proteção de dados, segurança de API e conformidade com padrões de segurança.

🛡️ Princípios de Segurança

1. Defesa em Profundidade

  • Múltiplas camadas de segurança
  • Validação em frontend e backend
  • Monitoramento contínuo

2. Princípio do Menor Privilégio

  • Usuários têm apenas permissões necessárias
  • Tokens com escopo limitado
  • Acesso baseado em roles

3. Fail-Safe Defaults

  • Negação por padrão
  • Configurações seguras por default
  • Logs detalhados de segurança

🔐 Autenticação e Autorização

JWT Implementation

// backend/src/utils/auth.ts
import jwt from 'jsonwebtoken';
import bcrypt from 'bcryptjs';
import { User } from '../entities/User';
export interface JWTPayload {
 userId: string;
 email: string;
 role: string;
 iat: number;
 exp: number;
}
export class AuthService {
 private readonly JWT_SECRET = process.env.JWT_SECRET!;
 private readonly JWT_EXPIRES_IN = process.env.JWT_EXPIRES_IN || '7d';
 private readonly REFRESH_SECRET = process.env.REFRESH_SECRET!;
 async generateTokens(user: User) {
 const payload = {
 userId: user.id,
 email: user.email,
 role: user.role,
 };
 const accessToken = jwt.sign(payload, this.JWT_SECRET, {
 expiresIn: '15m',
 algorithm: 'HS256',
 });
 const refreshToken = jwt.sign(payload, this.REFRESH_SECRET, {
 expiresIn: '7d',
 algorithm: 'HS256',
 });
 return { accessToken, refreshToken };
 }
 async verifyToken(token: string): Promise<JWTPayload> {
 try {
 return jwt.verify(token, this.JWT_SECRET) as JWTPayload;
 } catch (error) {
 throw new Error('Token inválido');
 }
 }
 async hashPassword(password: string): Promise<string> {
 const saltRounds = 12;
 return bcrypt.hash(password, saltRounds);
 }
 async comparePassword(password: string, hash: string): Promise<boolean> {
 return bcrypt.compare(password, hash);
 }
}

Middleware de Autenticação

// backend/src/middlewares/auth.middleware.ts
import { Request, Response, NextFunction } from 'express';
import { AuthService } from '../utils/auth';
export interface AuthenticatedRequest extends Request {
 user?: {
 userId: string;
 email: string;
 role: string;
 };
}
export const authMiddleware = async (
 req: AuthenticatedRequest,
 res: Response,
 next: NextFunction
) => {
 try {
 const authHeader = req.headers.authorization;
 if (!authHeader) {
 return res.status(401).json({
 success: false,
 error: 'Token de acesso requerido',
 });
 }
 const token = authHeader.split(' ')[1]; // Bearer <token>
 if (!token) {
 return res.status(401).json({
 success: false,
 error: 'Formato de token inválido',
 });
 }
 const authService = new AuthService();
 const payload = await authService.verifyToken(token);
 req.user = {
 userId: payload.userId,
 email: payload.email,
 role: payload.role,
 };
 next();
 } catch (error) {
 return res.status(401).json({
 success: false,
 error: 'Token inválido ou expirado',
 });
 }
};

Autorização Baseada em Roles

// backend/src/middlewares/role.middleware.ts
import { Response, NextFunction } from 'express';
import { AuthenticatedRequest } from './auth.middleware';
export enum UserRole {
 ADMIN = 'admin',
 USER = 'user',
 VIEWER = 'viewer',
}
export const roleMiddleware = (allowedRoles: UserRole[]) => {
 return (req: AuthenticatedRequest, res: Response, next: NextFunction) => {
 const userRole = req.user?.role as UserRole;
 if (!userRole || !allowedRoles.includes(userRole)) {
 return res.status(403).json({
 success: false,
 error: 'Acesso negado: permissões insuficientes',
 });
 }
 next();
 };
};
// Uso em rotas
// router.delete('/projects/:id', authMiddleware, roleMiddleware([UserRole.ADMIN]), deleteProject);

🔍 Validação e Sanitização

Schema Validation com Zod

// backend/src/schemas/user.schema.ts
import { z } from 'zod';
export const createUserSchema = z.object({
 email: z
 .string()
 .email('Email inválido')
 .max(255, 'Email muito longo'),
 password: z
 .string()
 .min(8, 'Senha deve ter pelo menos 8 caracteres')
 .max(128, 'Senha muito longa')
 .regex(
 /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]/,
 'Senha deve conter pelo menos: 1 minúscula, 1 maiúscula, 1 número e 1 caractere especial'
 ),
 name: z
 .string()
 .min(2, 'Nome deve ter pelo menos 2 caracteres')
 .max(100, 'Nome muito longo')
 .regex(/^[a-zA-ZÀ-ÿ\s]+$/, 'Nome deve conter apenas letras e espaços'),
});
export const loginSchema = z.object({
 email: z.string().email('Email inválido'),
 password: z.string().min(1, 'Senha é obrigatória'),
});
export type CreateUserInput = z.infer<typeof createUserSchema>;
export type LoginInput = z.infer<typeof loginSchema>;

Middleware de Validação

// backend/src/middlewares/validation.middleware.ts
import { Request, Response, NextFunction } from 'express';
import { ZodSchema } from 'zod';
export const validateBody = (schema: ZodSchema) => {
 return (req: Request, res: Response, next: NextFunction) => {
 try {
 schema.parse(req.body);
 next();
 } catch (error) {
 if (error instanceof z.ZodError) {
 return res.status(400).json({
 success: false,
 error: 'Dados inválidos',
 details: error.errors.map(err => ({
 field: err.path.join('.'),
 message: err.message,
 })),
 });
 }
 next(error);
 }
 };
};

Sanitização de Dados

// backend/src/utils/sanitizer.ts
import DOMPurify from 'dompurify';
import { JSDOM } from 'jsdom';
const window = new JSDOM('').window;
const purify = DOMPurify(window);
export class Sanitizer {
 static sanitizeHtml(dirty: string): string {
 return purify.sanitize(dirty, {
 ALLOWED_TAGS: ['b', 'i', 'em', 'strong', 'p', 'br'],
 ALLOWED_ATTR: [],
 });
 }
 static sanitizeInput(input: string): string {
 return input
 .trim()
 .replace(/[<>\"']/g, '') // Remove caracteres perigosos
 .substring(0, 1000); // Limite de tamanho
 }
 static sanitizeObject(obj: Record<string, any>): Record<string, any> {
 const sanitized: Record<string, any> = {};
 for (const [key, value] of Object.entries(obj)) {
 if (typeof value === 'string') {
 sanitized[key] = this.sanitizeInput(value);
 } else if (typeof value === 'object' && value !== null) {
 sanitized[key] = this.sanitizeObject(value);
 } else {
 sanitized[key] = value;
 }
 }
 return sanitized;
 }
}

🚫 Proteção Contra Ataques

Rate Limiting

// backend/src/middlewares/rate-limit.middleware.ts
import rateLimit from 'express-rate-limit';
import { Request } from 'express';
// Rate limiter geral
export const generalLimiter = rateLimit({
 windowMs: 15 * 60 * 1000 // 15 minutos
 max: 100, // Máximo 100 requests por IP
 message: {
 success: false,
 error: 'Muitas requisições. Tente novamente em 15 minutos.',
 },
 standardHeaders: true,
 legacyHeaders: false,
});
// Rate limiter para login
export const loginLimiter = rateLimit({
 windowMs: 15 * 60 * 1000, // 15 minutos
 max: 5, // Máximo 5 tentativas de login
 skipSuccessfulRequests: true,
 keyGenerator: (req: Request) => {
 return req.ip + ':' + req.body.email;
 },
 message: {
 success: false,
 error: 'Muitas tentativas de login. Tente novamente em 15 minutos.',
 },
});
// Rate limiter para criação de recursos
export const createLimiter = rateLimit({
 windowMs: 60 * 1000, // 1 minuto
 max: 10, // Máximo 10 criações por minuto
 message: {
 success: false,
 error: 'Limite de criação excedido. Aguarde 1 minuto.',
 },
});

Proteção CSRF

// backend/src/middlewares/csrf.middleware.ts
import csrf from 'csurf';
export const csrfProtection = csrf({
 cookie: {
 httpOnly: true,
 secure: process.env.NODE_ENV === 'production',
 sameSite: 'strict',
 },
});

Proteção XSS

// backend/src/middlewares/xss.middleware.ts
import helmet from 'helmet';
export const xssProtection = helmet({
 contentSecurityPolicy: {
 directives: {
 defaultSrc: ["'self'"],
 styleSrc: ["'self'", "'unsafe-inline'"],
 scriptSrc: ["'self'"],
 imgSrc: ["'self'", "data:", "https:"],
 connectSrc: ["'self'"],
 fontSrc: ["'self'"],
 objectSrc: ["'none'"],
 mediaSrc: ["'self'"],
 frameSrc: ["'none'"],
 },
 },
 crossOriginEmbedderPolicy: false,
});

SQL Injection Prevention

// backend/src/utils/query-builder.ts
import { Repository, SelectQueryBuilder } from 'typeorm';
export class SafeQueryBuilder<T> {
 constructor(private repository: Repository<T>) {}
 createSafeQuery(): SelectQueryBuilder<T> {
 return this.repository.createQueryBuilder();
 }
 // Método seguro para adicionar condições WHERE
 addWhereCondition(
 query: SelectQueryBuilder<T>,
 field: string,
 value: any,
 operator: '=' | 'LIKE' | 'IN' | '>' | '<' = '='
 ): SelectQueryBuilder<T> {
 const paramName = `param_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
 switch (operator) {
 case 'LIKE':
 return query.andWhere(`${field} LIKE :${paramName}`, {
 [paramName]: `%${value}%`,
 });
 case 'IN':
 return query.andWhere(`${field} IN (:...${paramName})`, {
 [paramName]: Array.isArray(value) ? value : [value],
 });
 default:
 return query.andWhere(`${field} ${operator} :${paramName}`, {
 [paramName]: value,
 });
 }
 }
}
// Uso
const queryBuilder = new SafeQueryBuilder(projectRepository);
const query = queryBuilder
 .createSafeQuery()
 .select(['project.id', 'project.name'])
 .addWhereCondition('project.name', searchTerm, 'LIKE')
 .addWhereCondition('project.status', 'active', '=');

🔒 Segurança de Dados

Criptografia de Dados Sensíveis

// backend/src/utils/encryption.ts
import crypto from 'crypto';
export class EncryptionService {
 private readonly algorithm = 'aes-256-gcm';
 private readonly key = Buffer.from(process.env.ENCRYPTION_KEY!, 'hex');
 encrypt(text: string): string {
 const iv = crypto.randomBytes(16);
 const cipher = crypto.createCipher(this.algorithm, this.key);
 cipher.setAAD(Buffer.from('varion-app', 'utf8'));
 let encrypted = cipher.update(text, 'utf8', 'hex');
 encrypted += cipher.final('hex');
 const authTag = cipher.getAuthTag();
 return iv.toString('hex') + ':' + authTag.toString('hex') + ':' + encrypted;
 }
 decrypt(encryptedData: string): string {
 const [ivHex, authTagHex, encrypted] = encryptedData.split(':');
 const iv = Buffer.from(ivHex, 'hex');
 const authTag = Buffer.from(authTagHex, 'hex');
 const decipher = crypto.createDecipher(this.algorithm, this.key);
 decipher.setAAD(Buffer.from('varion-app', 'utf8'));
 decipher.setAuthTag(authTag);
 let decrypted = decipher.update(encrypted, 'hex', 'utf8');
 decrypted += decipher.final('utf8');
 return decrypted;
 }
 hashSensitiveData(data: string): string {
 return crypto
 .createHmac('sha256', this.key)
 .update(data)
 .digest('hex');
 }
}

Proteção de Dados Pessoais (LGPD/GDPR)

// backend/src/entities/User.ts
import { Entity, Column, BeforeInsert, BeforeUpdate } from 'typeorm';
import { EncryptionService } from '../utils/encryption';
@Entity('users')
export class User {
 @Column({ type: 'varchar', length: 255 })
 email: string;
 @Column({ type: 'varchar', length: 255 })
 private encryptedPhone?: string;
 @Column({ type: 'varchar', length: 255 })
 private encryptedDocument?: string;
 @Column({ type: 'timestamp', nullable: true })
 consentDate?: Date;
 @Column({ type: 'boolean', default: false })
 marketingConsent: boolean;
 @Column({ type: 'timestamp', nullable: true })
 dataRetentionDate?: Date;
 private encryptionService = new EncryptionService();
 // Getters e Setters para dados sensíveis
 get phone(): string | undefined {
 return this.encryptedPhone
 ? this.encryptionService.decrypt(this.encryptedPhone)
 : undefined;
 }
 set phone(value: string | undefined) {
 this.encryptedPhone = value
 ? this.encryptionService.encrypt(value)
 : undefined;
 }
 get document(): string | undefined {
 return this.encryptedDocument
 ? this.encryptionService.decrypt(this.encryptedDocument)
 : undefined;
 }
 set document(value: string | undefined) {
 this.encryptedDocument = value
 ? this.encryptionService.encrypt(value)
 : undefined;
 }
 @BeforeInsert()
 @BeforeUpdate()
 updateDataRetention() {
 // Definir data de retenção (ex: 2 anos após último acesso)
 this.dataRetentionDate = new Date();
 this.dataRetentionDate.setFullYear(this.dataRetentionDate.getFullYear() + 2);
 }
 // Método para anonimizar dados
 anonymize() {
 this.email = `anonymous_${this.id}@deleted.local`;
 this.encryptedPhone = undefined;
 this.encryptedDocument = undefined;
 this.marketingConsent = false;
 }
}

🌐 Segurança de API

Headers de Segurança

// backend/src/config/security.ts
import helmet from 'helmet';
import cors from 'cors';
export const securityMiddleware = [
 helmet({
 contentSecurityPolicy: {
 directives: {
 defaultSrc: ["'self'"],
 styleSrc: ["'self'", "'unsafe-inline'", "https://fonts.googleapis.com"],
 fontSrc: ["'self'", "https://fonts.gstatic.com"],
 scriptSrc: ["'self'"],
 imgSrc: ["'self'", "data:", "https:"],
 connectSrc: ["'self'", process.env.FRONTEND_URL || "http://localhost:3000"],
 },
 },
 hsts: {
 maxAge: 31536000,
 includeSubDomains: true,
 preload: true,
 },
 }),
 cors({
 origin: process.env.CORS_ORIGIN?.split(',') || ['http://localhost:3000'],
 credentials: true,
 methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH'],
 allowedHeaders: ['Content-Type', 'Authorization', 'X-Requested-With'],
 }),
];

API Versioning Seguro

// backend/src/routes/v1/index.ts
import express from 'express';
import { authMiddleware } from '../../middlewares/auth.middleware';
import { generalLimiter } from '../../middlewares/rate-limit.middleware';
const router = express.Router();
// Aplicar middlewares de segurança para toda a API v1
router.use(generalLimiter);
router.use('/auth', require('./auth.routes'));
router.use('/projects', authMiddleware, require('./projects.routes'));
router.use('/states', authMiddleware, require('./states.routes'));
export default router;

🚨 Logging e Monitoramento

Security Logging

// backend/src/utils/security-logger.ts
import winston from 'winston';
export class SecurityLogger {
 private logger = winston.createLogger({
 level: 'info',
 format: winston.format.combine(
 winston.format.timestamp(),
 winston.format.json()
 ),
 transports: [
 new winston.transports.File({
 filename: 'logs/security.log',
 level: 'warn'
 }),
 new winston.transports.File({
 filename: 'logs/audit.log'
 }),
 ],
 });
 logFailedLogin(email: string, ip: string, userAgent: string) {
 this.logger.warn('Failed login attempt', {
 event: 'FAILED_LOGIN',
 email,
 ip,
 userAgent,
 timestamp: new Date().toISOString(),
 });
 }
 logSuccessfulLogin(userId: string, email: string, ip: string) {
 this.logger.info('Successful login', {
 event: 'SUCCESSFUL_LOGIN',
 userId,
 email,
 ip,
 timestamp: new Date().toISOString(),
 });
 }
 logSuspiciousActivity(userId: string, activity: string, details: any) {
 this.logger.warn('Suspicious activity detected', {
 event: 'SUSPICIOUS_ACTIVITY',
 userId,
 activity,
 details,
 timestamp: new Date().toISOString(),
 });
 }
 logDataAccess(userId: string, resource: string, action: string) {
 this.logger.info('Data access', {
 event: 'DATA_ACCESS',
 userId,
 resource,
 action,
 timestamp: new Date().toISOString(),
 });
 }
}

Detecção de Anomalias

// backend/src/utils/anomaly-detector.ts
import { SecurityLogger } from './security-logger';
export class AnomalyDetector {
 private securityLogger = new SecurityLogger();
 private userActivity = new Map<string, any>();
 detectAnomalies(userId: string, activity: any) {
 const userHistory = this.userActivity.get(userId) || {
 locations: [],
 timePatterns: [],
 actionCounts: {},
 };
 // Detectar login de localização incomum
 if (activity.type === 'login') {
 const isUnusualLocation = this.checkUnusualLocation(
 userHistory.locations,
 activity.location
 );
 if (isUnusualLocation) {
 this.securityLogger.logSuspiciousActivity(
 userId,
 'Unusual location login',
 { location: activity.location, previousLocations: userHistory.locations }
 );
 }
 }
 // Detectar atividade excessiva
 if (activity.type === 'api_call') {
 const actionCount = userHistory.actionCounts[activity.endpoint] || 0;
 if (actionCount > 100) { // Threshold configurável
 this.securityLogger.logSuspiciousActivity(
 userId,
 'Excessive API calls',
 { endpoint: activity.endpoint, count: actionCount }
 );
 }
 }
 this.updateUserActivity(userId, activity);
 }
 private checkUnusualLocation(previousLocations: string[], currentLocation: string): boolean {
 // Implementar lógica de detecção de localização incomum
 return !previousLocations.includes(currentLocation);
 }
 private updateUserActivity(userId: string, activity: any) {
 // Atualizar histórico de atividade do usuário
 const userHistory = this.userActivity.get(userId) || {
 locations: [],
 timePatterns: [],
 actionCounts: {},
 };
 if (activity.type === 'login') {
 userHistory.locations.push(activity.location);
 }
 if (activity.type === 'api_call') {
 userHistory.actionCounts[activity.endpoint] =
 (userHistory.actionCounts[activity.endpoint] || 0) + 1;
 }
 this.userActivity.set(userId, userHistory);
 }
}

🔐 Frontend Security

Secure HTTP Client

// frontend/src/utils/api-client.ts
import axios, { AxiosRequestConfig } from 'axios';
const apiClient = axios.create({
 baseURL: process.env.NEXT_PUBLIC_API_URL,
 timeout: 10000,
 withCredentials: true,
});
// Request interceptor para adicionar token
apiClient.interceptors.request.use(
 (config) => {
 const token = localStorage.getItem('accessToken');
 if (token) {
 config.headers.Authorization = `Bearer ${token}`;
 }
 // Adicionar CSRF token se disponível
 const csrfToken = document.querySelector('meta[name="csrf-token"]')?.getAttribute('content');
 if (csrfToken) {
 config.headers['X-CSRF-Token'] = csrfToken;
 }
 return config;
 },
 (error) => Promise.reject(error)
);
// Response interceptor para lidar com erros de autenticação
apiClient.interceptors.response.use(
 (response) => response,
 (error) => {
 if (error.response?.status === 401) {
 // Token expirado, redirecionar para login
 localStorage.removeItem('accessToken');
 window.location.href = '/login';
 }
 return Promise.reject(error);
 }
);
export default apiClient;

Content Security Policy

// frontend/next.config.ts
const nextConfig = {
 async headers() {
 return [
 {
 source: '/(.*)',
 headers: [
 {
 key: 'X-Frame-Options',
 value: 'DENY',
 },
 {
 key: 'X-Content-Type-Options',
 value: 'nosniff',
 },
 {
 key: 'Referrer-Policy',
 value: 'strict-origin-when-cross-origin',
 },
 {
 key: 'Permissions-Policy',
 value: 'camera=(), microphone=(), geolocation=()',
 },
 ],
 },
 ];
 },
};
export default nextConfig;

🔧 Configurações de Segurança

Variáveis de Ambiente Seguras

# .env.example
# Banco de Dados
DATABASE_URL=postgresql://user:password@localhost:5432/varion
DB_ENCRYPTION_KEY=32-byte-hex-key-for-data-encryption
# Autenticação
JWT_SECRET=very-long-random-string-at-least-256-bits
REFRESH_SECRET=another-very-long-random-string
JWT_EXPIRES_IN=15m
REFRESH_EXPIRES_IN=7d
# Criptografia
ENCRYPTION_KEY=32-byte-hex-key-for-general-encryption
SALT_ROUNDS=12
# Rate Limiting
RATE_LIMIT_WINDOW=900000
RATE_LIMIT_MAX=100
LOGIN_RATE_LIMIT_MAX=5
# CORS
CORS_ORIGIN=https://yourdomain.com,https://www.yourdomain.com
FRONTEND_URL=https://yourdomain.com
# Logs
LOG_LEVEL=info
SECURITY_LOG_FILE=logs/security.log
AUDIT_LOG_FILE=logs/audit.log
# Feature Flags
ENABLE_RATE_LIMITING=true
ENABLE_CSRF_PROTECTION=true
ENABLE_ANOMALY_DETECTION=true

Docker Security

# backend/Dockerfile
FROM node:18-alpine
# Criar usuário não-root
RUN addgroup -g 1001 -S nodejs
RUN adduser -S nodejs -u 1001
# Definir diretório de trabalho
WORKDIR /app
# Copiar arquivos de dependências
COPY package*.json ./
COPY pnpm-lock.yaml ./
# Instalar dependências
RUN npm install -g pnpm
RUN pnpm install --frozen-lockfile --only=production
# Copiar código da aplicação
COPY --chown=nodejs:nodejs . .
# Remover arquivos desnecessários
RUN rm -rf .git .env.example README.md
# Mudar para usuário não-root
USER nodejs
# Expor porta
EXPOSE 3001
# Comando de inicialização
CMD ["node", "dist/server.js"]

📋 Checklist de Segurança

Backend Security

  • Autenticação JWT implementada
  • Rate limiting configurado
  • Validação de entrada com Zod
  • Sanitização de dados
  • Headers de segurança (Helmet)
  • CORS configurado adequadamente
  • Logs de segurança implementados
  • Criptografia de dados sensíveis
  • SQL injection prevention
  • XSS protection
  • CSRF protection

Frontend Security

  • HTTPS enforced
  • CSP headers configurados
  • Sanitização de dados de entrada
  • Secure token storage
  • Logout automático em inatividade
  • Validação client-side
  • Error handling seguro

Infrastructure Security

  • Firewall configurado
  • SSL/TLS certificates
  • Backup encryption
  • Database security
  • Container security
  • Network segmentation
  • Monitoring e alertas

Compliance

  • LGPD/GDPR compliance
  • Data retention policies
  • Consent management
  • Audit logging
  • Vulnerability scanning
  • Security testing
  • Incident response plan

🚨 Resposta a Incidentes

Plano de Resposta

  1. Detecção: Monitoramento contínuo e alertas
  2. Contenção: Isolamento imediato da ameaça
  3. Erradicação: Remoção da causa raiz
  4. Recuperação: Restauração dos serviços
  5. Lições Aprendidas: Melhoria dos processos

Contatos de Emergência

Ferramentas de Monitoramento

  • SIEM: Splunk/ELK Stack
  • Vulnerability Scanner: OWASP ZAP
  • Dependency Check: Snyk
  • Container Security: Trivy

There aren't any published security advisories

AltStyle によって変換されたページ (->オリジナル) /