# Security Patterns - Detailed Reference

This document contains comprehensive code examples and implementation details for the Security skill.

## Table of Contents

1. [Authentication Middleware](#authentication-middleware)
2. [Role-Based Authorization](#role-based-authorization)
3. [Resource Ownership Validation](#resource-ownership-validation)
4. [Encryption Service](#encryption-service)
5. [Password Hashing](#password-hashing)
6. [Token Hashing](#token-hashing)
7. [Input Validation with Zod](#input-validation-with-zod)
8. [File Upload Validation](#file-upload-validation)
9. [SQL Injection Prevention](#sql-injection-prevention)
10. [Rate Limiting](#rate-limiting)
11. [Error Handling Security](#error-handling-security)
12. [Secrets Management](#secrets-management)
13. [Audit Logging](#audit-logging)
14. [Security Headers with Helmet](#security-headers-with-helmet)
15. [CORS Configuration](#cors-configuration)
16. [Security Testing](#security-testing)
17. [Security Anti-Patterns](#security-anti-patterns)
18. [Incident Response](#incident-response)

---

## Authentication Middleware

### JWT Token Validation

```typescript
// src/middlewares/auth.middleware.ts
import { Request, Response, NextFunction } from 'express';
import { jwtService } from '@goodgo/auth-sdk';
import { logger } from '@goodgo/logger';

export const authenticate = () => {
  return async (req: Request, res: Response, next: NextFunction) => {
    try {
      // Extract token from Authorization header or cookie
      let token: string | null = null;

      const authHeader = req.headers.authorization;
      if (authHeader?.startsWith('Bearer ')) {
        token = authHeader.substring(7);
      } else if (req.cookies?.access_token) {
        token = req.cookies.access_token;
      }

      if (!token) {
        return res.status(401).json({
          success: false,
          error: { code: 'AUTH_REQUIRED', message: 'Authentication required' }
        });
      }

      // Verify token
      const payload = await jwtService.verifyAccessToken(token);

      // Attach user to request
      req.user = {
        id: payload.sub,
        userId: payload.sub,
        email: payload.email,
        roles: payload.roles || [],
        permissions: payload.permissions || []
      };

      next();
    } catch (error) {
      logger.warn('Authentication failed', { error: error.message });
      return res.status(401).json({
        success: false,
        error: { code: 'INVALID_TOKEN', message: 'Invalid or expired token' }
      });
    }
  };
};
```

---

## Role-Based Authorization

### RBAC Middleware

```typescript
// src/middlewares/rbac.middleware.ts
export const requireRole = (...allowedRoles: string[]) => {
  return (req: Request, res: Response, next: NextFunction) => {
    if (!req.user) {
      return res.status(401).json({
        success: false,
        error: { code: 'AUTH_REQUIRED', message: 'Authentication required' }
      });
    }

    const userRoles = req.user.roles || [];
    const hasRole = userRoles.some(role => allowedRoles.includes(role));

    if (!hasRole) {
      logger.warn('Access denied - insufficient role', {
        userId: req.user.id,
        userRoles,
        requiredRoles: allowedRoles
      });

      return res.status(403).json({
        success: false,
        error: { code: 'FORBIDDEN', message: 'Insufficient permissions' }
      });
    }

    next();
  };
};
```

### Permission-Based Authorization

```typescript
// Permission-based authorization
export const requirePermission = (resource: string, action: string) => {
  return async (req: Request, res: Response, next: NextFunction) => {
    if (!req.user) {
      return res.status(401).json({
        success: false,
        error: { code: 'AUTH_REQUIRED', message: 'Authentication required' }
      });
    }

    const permission = `${resource}:${action}`;
    const hasPermission = req.user.permissions?.includes(permission);

    if (!hasPermission) {
      logger.warn('Access denied - insufficient permission', {
        userId: req.user.id,
        required: permission
      });

      return res.status(403).json({
        success: false,
        error: { code: 'FORBIDDEN', message: 'Insufficient permissions' }
      });
    }

    next();
  };
};

// Usage in routes
router.post(
  '/api/v1/users',
  authenticate(),
  requirePermission('users', 'create'),
  userController.create
);
```

---

## Resource Ownership Validation

```typescript
// Ensure users can only access their own resources
export const requireOwnership = (resourceIdParam: string = 'id') => {
  return (req: Request, res: Response, next: NextFunction) => {
    const resourceId = req.params[resourceIdParam];
    const userId = req.user?.id;

    if (resourceId !== userId) {
      logger.warn('Access denied - resource ownership mismatch', {
        userId,
        resourceId
      });

      return res.status(403).json({
        success: false,
        error: { code: 'FORBIDDEN', message: 'Access denied' }
      });
    }

    next();
  };
};
```

---

## Encryption Service

### AES-256-GCM Encryption

```typescript
// src/core/security/encryption.service.ts
import crypto from 'crypto';

const ALGORITHM = 'aes-256-gcm';
const IV_LENGTH = 16;
const TAG_LENGTH = 16;

export class EncryptionService {
  private getKey(): Buffer {
    const secret = process.env.ENCRYPTION_KEY;
    if (!secret || secret.length < 32) {
      throw new Error('ENCRYPTION_KEY must be at least 32 characters');
    }
    return crypto.scryptSync(secret, 'salt', 32);
  }

  encrypt(text: string): string {
    const key = this.getKey();
    const iv = crypto.randomBytes(IV_LENGTH);
    const cipher = crypto.createCipheriv(ALGORITHM, key, iv);

    let encrypted = cipher.update(text, 'utf8', 'hex');
    encrypted += cipher.final('hex');
    const tag = cipher.getAuthTag();

    return `${iv.toString('hex')}:${tag.toString('hex')}:${encrypted}`;
  }

  decrypt(encryptedText: string): string {
    const [ivHex, tagHex, encrypted] = encryptedText.split(':');
    const iv = Buffer.from(ivHex, 'hex');
    const tag = Buffer.from(tagHex, 'hex');

    const key = this.getKey();
    const decipher = crypto.createDecipheriv(ALGORITHM, key, iv);
    decipher.setAuthTag(tag);

    let decrypted = decipher.update(encrypted, 'hex', 'utf8');
    decrypted += decipher.final('utf8');
    return decrypted;
  }
}

// Usage: Encrypt PII before storing
const encryption = new EncryptionService();
const encryptedPhone = encryption.encrypt(user.phone);
```

---

## Password Hashing

### Bcrypt Password Service

```typescript
// Always use bcrypt with appropriate cost factor
import bcrypt from 'bcrypt';

const SALT_ROUNDS = 12; // Production: 12, Development: 10

export class PasswordService {
  async hash(password: string): Promise<string> {
    return bcrypt.hash(password, SALT_ROUNDS);
  }

  async verify(password: string, hash: string): Promise<boolean> {
    return bcrypt.compare(password, hash);
  }

  // Never log passwords
  sanitizeForLogging(data: any): any {
    const sanitized = { ...data };
    if (sanitized.password) sanitized.password = '[REDACTED]';
    if (sanitized.passwordHash) sanitized.passwordHash = '[REDACTED]';
    return sanitized;
  }
}
```

---

## Token Hashing

```typescript
// Hash tokens before storing in database
import crypto from 'crypto';

export class TokenService {
  hashToken(token: string): string {
    const salt = process.env.TOKEN_SALT || 'default-salt-change-in-production';
    return crypto
      .createHash('sha256')
      .update(token + salt)
      .digest('hex');
  }

  generateSecureToken(length: number = 32): string {
    return crypto.randomBytes(length).toString('hex');
  }
}
```

---

## Input Validation with Zod

### DTO Schema Validation

```typescript
// Always validate inputs with Zod
import { z } from 'zod';

// DTO with validation
export const CreateUserDto = z.object({
  email: z.string().email('Invalid email format'),
  password: z.string()
    .min(8, 'Password must be at least 8 characters')
    .regex(/[A-Z]/, 'Password must contain uppercase letter')
    .regex(/[a-z]/, 'Password must contain lowercase letter')
    .regex(/[0-9]/, 'Password must contain number')
    .regex(/[^A-Za-z0-9]/, 'Password must contain special character'),
  phone: z.string()
    .regex(/^\+[1-9]\d{1,14}$/, 'Invalid phone format (E.164)')
    .optional(),
  name: z.string().min(1).max(255)
});

// In controller
export class UserController {
  async create(req: Request, res: Response) {
    try {
      const dto = CreateUserDto.parse(req.body);
      const user = await this.service.create(dto);
      res.status(201).json({ success: true, data: user });
    } catch (error) {
      if (error instanceof z.ZodError) {
        return res.status(400).json({
          success: false,
          error: {
            code: 'VALIDATION_ERROR',
            message: 'Invalid input data',
            details: error.errors
          }
        });
      }
      throw error;
    }
  }
}
```

---

## File Upload Validation

```typescript
// Validate file uploads
import fileType from 'file-type';

export class FileValidationService {
  private readonly MAX_FILE_SIZE = 10 * 1024 * 1024; // 10MB
  private readonly ALLOWED_TYPES = ['image/jpeg', 'image/png', 'application/pdf'];

  async validateFile(file: Express.Multer.File): Promise<void> {
    // Size check
    if (file.size > this.MAX_FILE_SIZE) {
      throw new HttpError(400, 'FILE_TOO_LARGE', 'File exceeds maximum size');
    }

    // Type check
    if (!this.ALLOWED_TYPES.includes(file.mimetype)) {
      throw new HttpError(400, 'INVALID_FILE_TYPE', 'File type not allowed');
    }

    // Content validation (prevent MIME type spoofing)
    const type = await fileType.fromBuffer(file.buffer);
    if (!type || !this.ALLOWED_TYPES.includes(type.mime)) {
      throw new HttpError(400, 'INVALID_FILE_CONTENT', 'File content mismatch');
    }

    // TODO: Add virus scanning for production
  }
}
```

---

## SQL Injection Prevention

```typescript
// Always use Prisma parameterized queries (automatic)
// Never use string concatenation for queries

// BAD - Never do this
const query = `SELECT * FROM users WHERE email = '${email}'`;

// GOOD - Use Prisma
const user = await prisma.user.findUnique({
  where: { email }
});

// GOOD - For dynamic queries
const where: any = {};
if (email) where.email = email;
if (status) where.status = status;

const users = await prisma.user.findMany({ where });
```

---

## Rate Limiting

### Redis-Backed Rate Limiter

```typescript
// Implement rate limiting for all endpoints
import rateLimit from 'express-rate-limit';
import RedisStore from 'rate-limit-redis';
import Redis from 'ioredis';

const redis = new Redis(process.env.REDIS_URL);

// Standard rate limit
export const standardLimiter = rateLimit({
  store: new RedisStore({
    client: redis,
    prefix: 'rl:standard:'
  }),
  windowMs: 15 * 60 * 1000, // 15 minutes
  max: 100, // 100 requests per window
  message: 'Too many requests, please try again later',
  standardHeaders: true,
  legacyHeaders: false
});

// Strict rate limit for sensitive operations
export const strictLimiter = rateLimit({
  store: new RedisStore({
    client: redis,
    prefix: 'rl:strict:'
  }),
  windowMs: 60 * 60 * 1000, // 1 hour
  max: 10,
  message: 'Rate limit exceeded for this operation'
});

// Login-specific rate limit
export const loginLimiter = rateLimit({
  store: new RedisStore({
    client: redis,
    prefix: 'rl:login:'
  }),
  windowMs: 15 * 60 * 1000,
  max: 5, // 5 login attempts per 15 minutes
  skipSuccessfulRequests: true,
  message: 'Too many login attempts, please try again later'
});

// Usage
router.post('/api/v1/auth/login', loginLimiter, authController.login);
router.post('/api/v1/users', authenticate(), strictLimiter, userController.create);
```

---

## Error Handling Security

### Secure Error Handler

```typescript
// Sanitize error messages to prevent information disclosure
export class SecureErrorHandler {
  handleError(error: Error, req: Request, res: Response) {
    const isDev = process.env.NODE_ENV === 'development';
    const isProd = process.env.NODE_ENV === 'production';

    // Log full error internally
    logger.error('Request error', {
      error: error.message,
      stack: error.stack,
      path: req.path,
      method: req.method,
      userId: req.user?.id
    });

    // Don't expose user existence
    if (error.message.includes('user not found') ||
        error.message.includes('invalid credentials')) {
      return res.status(401).json({
        success: false,
        error: {
          code: 'INVALID_CREDENTIALS',
          message: 'Invalid email or password'
        }
      });
    }

    // Validation errors - safe to expose
    if (error instanceof z.ZodError) {
      return res.status(400).json({
        success: false,
        error: {
          code: 'VALIDATION_ERROR',
          message: 'Invalid input data',
          details: error.errors
        }
      });
    }

    // Generic errors for production
    if (isProd) {
      return res.status(500).json({
        success: false,
        error: {
          code: 'INTERNAL_ERROR',
          message: 'An error occurred. Please try again later.'
        }
      });
    }

    // Detailed errors only in development
    return res.status(500).json({
      success: false,
      error: {
        code: 'INTERNAL_ERROR',
        message: error.message,
        stack: isDev ? error.stack : undefined
      }
    });
  }
}
```

---

## Secrets Management

### Environment Variable Validation

```typescript
// Never hardcode secrets
// Always use environment variables with validation
import { z } from 'zod';

const secretsSchema = z.object({
  JWT_SECRET: z.string().min(32, 'JWT_SECRET must be at least 32 characters'),
  JWT_REFRESH_SECRET: z.string().min(32),
  DATABASE_URL: z.string().url(),
  REDIS_URL: z.string().url().optional(),
  ENCRYPTION_KEY: z.string().min(32).optional()
});

export const secrets = secretsSchema.parse(process.env);

// For production, use secret management:
// - AWS Secrets Manager
// - HashiCorp Vault
// - Kubernetes Secrets
// - Azure Key Vault

// Rotate secrets regularly (quarterly recommended)
```

---

## Audit Logging

### Audit Service Implementation

```typescript
// Log all security-relevant events
export class AuditService {
  async logSecurityEvent(
    event: string,
    userId: string | null,
    details: Record<string, any>,
    req?: Request
  ) {
    await this.prisma.auditLog.create({
      data: {
        event,
        userId,
        type: 'SECURITY',
        details: this.sanitizeDetails(details),
        ipAddress: req?.ip || details.ipAddress,
        userAgent: req?.get('user-agent'),
        timestamp: new Date()
      }
    });
  }

  // Sanitize PII from logs
  private sanitizeDetails(details: Record<string, any>): Record<string, any> {
    const sensitive = ['password', 'token', 'secret', 'ssn', 'creditCard'];
    const sanitized = { ...details };

    for (const key of sensitive) {
      if (sanitized[key]) {
        sanitized[key] = '[REDACTED]';
      }
    }

    return sanitized;
  }
}

// Usage
await auditService.logSecurityEvent('LOGIN_SUCCESS', user.id, {
  email: user.email,
  ipAddress: req.ip
}, req);

await auditService.logSecurityEvent('PERMISSION_DENIED', user.id, {
  resource: 'users',
  action: 'delete',
  targetId: targetUserId
}, req);
```

---

## Security Headers with Helmet

```typescript
// Add security headers middleware
import helmet from 'helmet';

app.use(helmet({
  contentSecurityPolicy: {
    directives: {
      defaultSrc: ["'self'"],
      styleSrc: ["'self'", "'unsafe-inline'"],
      scriptSrc: ["'self'"],
      imgSrc: ["'self'", "data:", "https:"]
    }
  },
  hsts: {
    maxAge: 31536000,
    includeSubDomains: true,
    preload: true
  }
}));

// Additional headers
app.use((req, res, next) => {
  res.setHeader('X-Content-Type-Options', 'nosniff');
  res.setHeader('X-Frame-Options', 'DENY');
  res.setHeader('X-XSS-Protection', '1; mode=block');
  res.setHeader('Referrer-Policy', 'strict-origin-when-cross-origin');
  next();
});
```

---

## CORS Configuration

```typescript
// Configure CORS securely
import cors from 'cors';

const allowedOrigins = process.env.CORS_ORIGIN?.split(',') || [];

app.use(cors({
  origin: (origin, callback) => {
    if (!origin || allowedOrigins.includes(origin)) {
      callback(null, true);
    } else {
      callback(new Error('Not allowed by CORS'));
    }
  },
  credentials: true,
  methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'],
  allowedHeaders: ['Content-Type', 'Authorization'],
  exposedHeaders: ['X-Request-ID'],
  maxAge: 86400 // 24 hours
}));
```

---

## Security Testing

### Test Patterns

```typescript
// Security test patterns
describe('Security Tests', () => {
  it('should prevent SQL injection', async () => {
    const maliciousInput = "'; DROP TABLE users; --";
    const response = await request(app)
      .get(`/api/v1/users?search=${encodeURIComponent(maliciousInput)}`)
      .set('Authorization', `Bearer ${token}`);

    expect(response.status).not.toBe(500);
    // Should return 400 or empty results, not crash
  });

  it('should prevent XSS attacks', async () => {
    const xssPayload = '<script>alert("XSS")</script>';
    const response = await request(app)
      .post('/api/v1/users')
      .send({ email: xssPayload, password: 'test123' });

    // Response should sanitize or reject
    expect(response.body.data?.email).not.toContain('<script>');
  });

  it('should enforce authentication', async () => {
    const response = await request(app)
      .get('/api/v1/users');

    expect(response.status).toBe(401);
  });

  it('should enforce authorization', async () => {
    const userToken = await createUserToken({ roles: ['user'] });
    const response = await request(app)
      .delete('/api/v1/users/123')
      .set('Authorization', `Bearer ${userToken}`);

    expect(response.status).toBe(403);
  });

  it('should rate limit excessive requests', async () => {
    const requests = Array(20).fill(null).map(() =>
      request(app).get('/api/v1/users')
    );

    const responses = await Promise.all(requests);
    const rateLimited = responses.filter(r => r.status === 429);

    expect(rateLimited.length).toBeGreaterThan(0);
  });
});
```

---

## Security Anti-Patterns

### Examples of What NOT to Do

```typescript
// BAD: Hardcoded secrets
const SECRET = 'my-secret-key';

// GOOD: Environment variables
const SECRET = process.env.JWT_SECRET;

// BAD: Plain text passwords
await prisma.user.create({ data: { password: password } });

// GOOD: Hashed passwords
await prisma.user.create({
  data: { passwordHash: await bcrypt.hash(password, 12) }
});

// BAD: Exposing user existence
if (!user) {
  throw new Error('User not found'); // Reveals user doesn't exist
}

// GOOD: Generic error
if (!user || !await bcrypt.compare(password, user.passwordHash)) {
  throw new Error('Invalid credentials');
}

// BAD: No input validation
const email = req.body.email;

// GOOD: Validate with Zod
const { email } = CreateUserDto.parse(req.body);

// BAD: Stack traces in production
res.status(500).json({ error: error.stack });

// GOOD: Sanitized errors
res.status(500).json({
  error: { code: 'INTERNAL_ERROR', message: 'An error occurred' }
});
```

---

## Incident Response

### Security Incident Detection

```typescript
// Security incident detection and response
export class SecurityIncidentService {
  async detectAnomaly(userId: string, event: string, context: any) {
    // Check for suspicious patterns
    const recentEvents = await this.getRecentEvents(userId, '1h');

    if (recentEvents.length > 10) {
      await this.triggerAlert('SUSPICIOUS_ACTIVITY', {
        userId,
        eventCount: recentEvents.length,
        timeWindow: '1h'
      });
    }

    // Check for privilege escalation attempts
    if (event === 'PERMISSION_DENIED' && context.requiredPermission) {
      await this.logSecurityEvent('PRIVILEGE_ESCALATION_ATTEMPT', userId, context);
    }
  }

  async triggerAlert(type: string, details: any) {
    // Send to monitoring system
    logger.error('Security alert', { type, details });

    // TODO: Integrate with PagerDuty, Slack, etc.
  }
}
```

---

## Security Checklist

Before deploying any service:

- [ ] All endpoints require authentication (except public)
- [ ] Authorization checks implemented (RBAC/ABAC)
- [ ] Input validation with Zod schemas
- [ ] Rate limiting configured
- [ ] Error messages sanitized (no info disclosure)
- [ ] PII encrypted at rest
- [ ] Passwords hashed with bcrypt (cost 12+)
- [ ] Tokens hashed before storing
- [ ] Secrets in environment variables (never hardcoded)
- [ ] HTTPS enforced (TLS 1.2+)
- [ ] CORS configured correctly
- [ ] Security headers set (helmet)
- [ ] Audit logging enabled
- [ ] SQL injection prevented (use Prisma)
- [ ] XSS prevention (input sanitization)
- [ ] File upload validation
- [ ] Security tests passing
- [ ] Dependencies scanned for vulnerabilities
- [ ] Secrets rotation plan in place
