Security Model - gabrielmaialva33/innkeeper GitHub Wiki

🔐 Security Model

Comprehensive security architecture and implementation guide for Innkeeper


📋 Overview

Innkeeper implements a robust, multi-layered security model designed to protect sensitive hotel and guest data while maintaining compliance with industry standards. The security architecture follows defense-in-depth principles, incorporating multiple security layers from network to application level.

Key Security Principles:

  • Zero Trust Architecture - Never trust, always verify
  • Defense in Depth - Multiple security layers
  • Principle of Least Privilege - Minimal access rights
  • Data Protection by Design - Security built into every component
  • Compliance Ready - GDPR, PCI DSS, and industry standards

🏗️ Security Architecture Overview

graph TB
    subgraph "External Layer"
        INTERNET[Internet]
        CDN[CDN/WAF]
        LB[Load Balancer]
    end
    
    subgraph "Network Security"
        FIREWALL[Firewall]
        VPN[VPN Gateway]
        IDS[Intrusion Detection]
    end
    
    subgraph "Application Security"
        AUTH[Authentication]
        AUTHZ[Authorization]
        RBAC[Role-Based Access Control]
        TENANT[Tenant Isolation]
    end
    
    subgraph "Data Security"
        ENCRYPT[Encryption at Rest]
        TLS[TLS in Transit]
        BACKUP[Secure Backups]
        AUDIT[Audit Logging]
    end
    
    subgraph "Infrastructure Security"
        CONTAINER[Container Security]
        SECRETS[Secret Management]
        MONITOR[Security Monitoring]
        INCIDENT[Incident Response]
    end
    
    INTERNET --> CDN
    CDN --> LB
    LB --> FIREWALL
    FIREWALL --> AUTH
    AUTH --> AUTHZ
    AUTHZ --> RBAC
    RBAC --> TENANT
    TENANT --> ENCRYPT
    ENCRYPT --> AUDIT
Loading

🔑 Authentication System

Multi-Factor Authentication

Innkeeper supports multiple authentication methods:

// Authentication configuration
export default defineConfig({
  default: 'web',

  guards: {
    web: {
      driver: 'session',
      provider: 'users',
    },
    api: {
      driver: 'access_tokens',
      provider: 'users',
    },
  },

  providers: {
    users: {
      driver: 'lucid',
      identifierKey: 'id',
      uids: ['email', 'username'],
      model: () => import('#models/user'),
    },
  },
})

JWT Token Management

// User model with token support
export default class User extends BaseModel {
  static accessTokens = DbAccessTokensProvider.forModel(User)
  static refreshTokens = DbAccessTokensProvider.forModel(User, {
    type: 'refresh_token',
    expiresIn: '3d',
  })

  // Token abilities for API access
  async createApiToken(abilities: string[] = ['*']) {
    return await User.accessTokens.create(this, abilities, {
      expiresIn: '30d'
    })
  }
}

Password Security

// Secure password handling
export default class User extends BaseModel {
  @beforeSave()
  static async hashUserPassword(user: User) {
    if (user.$dirty.password && !hash.isValidHash(user.password)) {
      user.password = await hash.make(user.password)
    }
  }

  // Password validation rules
  static passwordRules = {
    minLength: 8,
    requireUppercase: true,
    requireLowercase: true,
    requireNumbers: true,
    requireSpecialChars: true,
    preventCommonPasswords: true,
  }
}

🛡️ Authorization & RBAC System

Role-Based Access Control

Innkeeper implements a flexible RBAC system with granular permissions:

erDiagram
    users ||--o{ user_roles : has
    roles ||--o{ user_roles : assigned_to
    roles ||--o{ role_permissions : has
    permissions ||--o{ role_permissions : granted_to
    users ||--o{ user_permissions : has_direct
    permissions ||--o{ user_permissions : granted_directly
    
    users {
        int id PK
        string email
        string full_name
        int organization_id FK
    }
    
    roles {
        int id PK
        string name
        string slug
        string description
    }
    
    permissions {
        int id PK
        string name
        string resource
        string action
        string context
    }
Loading

Permission Structure

// Permission model with granular control
export default class Permission extends BaseModel {
  @column()
  declare resource: string  // e.g., 'hotel', 'reservation', 'guest'

  @column()
  declare action: string    // e.g., 'create', 'read', 'update', 'delete'

  @column()
  declare context: string   // e.g., 'own', 'organization', 'any'

  @beforeCreate()
  static async generateName(permission: Permission) {
    if (!permission.name) {
      const context = permission.context || 'any'
      permission.name = `${permission.resource}.${permission.action}.${context}`
    }
  }
}

Authorization Middleware

// Permission-based authorization
export default class PermissionMiddleware {
  async handle(
    {auth, response}: HttpContext,
    next: NextFn,
    options: { permission: string }
  ) {
    const user = auth.user!

    // Check if user has required permission
    const hasPermission = await this.checkPermission(user, options.permission)

    if (!hasPermission) {
      return response.forbidden({
        error: 'Insufficient permissions',
        required: options.permission
      })
    }

    return next()
  }

  private async checkPermission(user: User, permissionName: string): Promise<boolean> {
    // Check direct user permissions
    const directPermission = await user
      .related('permissions')
      .query()
      .where('name', permissionName)
      .where('granted', true)
      .first()

    if (directPermission) return true

    // Check role-based permissions
    const rolePermissions = await user
      .related('roles')
      .query()
      .preload('permissions', (query) => {
        query.where('name', permissionName)
      })

    return rolePermissions.some(role => role.permissions.length > 0)
  }
}

🏢 Multi-Tenant Security

Tenant Isolation

// Tenant context middleware for data isolation
export default class TenantContextMiddleware {
  async handle({auth, response}: HttpContext, next: NextFn) {
    try {
      const organizationId = auth.user?.organization_id

      if (!organizationId) {
        return await next()
      }

      // Set PostgreSQL session variable for RLS
      await db.rawQuery('SELECT set_current_organization(?)', [organizationId])

      return await next()

    } catch (error) {
      return response.status(500).json({
        error: 'Failed to set tenant context'
      })
    }
  }
}

Row-Level Security (RLS)

-- Enable RLS on tenant-specific tables
ALTER TABLE hotels
  ENABLE ROW LEVEL SECURITY;
ALTER TABLE reservations
  ENABLE ROW LEVEL SECURITY;
ALTER TABLE guests
  ENABLE ROW LEVEL SECURITY;

-- Create tenant isolation policies
CREATE POLICY tenant_isolation_policy ON hotels
  FOR ALL
  TO application_role
  USING (organization_id = current_setting('app.current_organization')::INTEGER);

CREATE POLICY tenant_isolation_policy ON reservations
  FOR ALL
  TO application_role
  USING (organization_id = current_setting('app.current_organization')::INTEGER);

Tenant Security Validation

// Validate tenant access in controllers
export default class HotelsController {
  async show({params, auth, response}: HttpContext) {
    const hotel = await Hotel.find(params.id)

    if (!hotel) {
      return response.notFound({message: 'Hotel not found'})
    }

    // Validate tenant ownership
    if (hotel.organization_id !== auth.user!.organization_id) {
      return response.forbidden({message: 'Access denied'})
    }

    return hotel
  }
}

🔒 Data Protection & Encryption

Encryption at Rest

// Database encryption configuration
export default defineConfig({
  connections: {
    pg: {
      client: 'pg',
      connection: {
        host: Env.get('DB_HOST'),
        port: Env.get('DB_PORT'),
        user: Env.get('DB_USER'),
        password: Env.get('DB_PASSWORD'),
        database: Env.get('DB_DATABASE'),
        ssl: {
          rejectUnauthorized: false,
          ca: Env.get('DB_SSL_CA'),
          key: Env.get('DB_SSL_KEY'),
          cert: Env.get('DB_SSL_CERT'),
        },
      },
    },
  },
})

Sensitive Data Handling

// PII data encryption
export class DataProtection {
  private static encryptionKey = Env.get('ENCRYPTION_KEY')

  static async encryptPII(data: string): Promise<string> {
    const cipher = crypto.createCipher('aes-256-cbc', this.encryptionKey)
    let encrypted = cipher.update(data, 'utf8', 'hex')
    encrypted += cipher.final('hex')
    return encrypted
  }

  static async decryptPII(encryptedData: string): Promise<string> {
    const decipher = crypto.createDecipher('aes-256-cbc', this.encryptionKey)
    let decrypted = decipher.update(encryptedData, 'hex', 'utf8')
    decrypted += decipher.final('utf8')
    return decrypted
  }
}

// Guest model with PII protection
export default class Guest extends BaseModel {
  @column({
    serialize: (value) => DataProtection.encryptPII(value),
    consume: (value) => DataProtection.decryptPII(value),
  })
  declare document_number: string

  @column({
    serialize: (value) => DataProtection.encryptPII(value),
    consume: (value) => DataProtection.decryptPII(value),
  })
  declare phone: string
}

Data Masking

// Data masking for logs and exports
export class DataMasking {
  static maskEmail(email: string): string {
    const [username, domain] = email.split('@')
    const maskedUsername = username.slice(0, 2) + '*'.repeat(username.length - 2)
    return `${maskedUsername}@${domain}`
  }

  static maskPhone(phone: string): string {
    return phone.replace(/(\d{3})\d{4}(\d{4})/, '$1****$2')
  }

  static maskCreditCard(cardNumber: string): string {
    return cardNumber.replace(/\d(?=\d{4})/g, '*')
  }
}

🌐 API Security

Rate Limiting

// API rate limiting configuration
export default defineConfig({
  default: 'redis',

  stores: {
    redis: {
      connectionName: 'main',
    },
  },

  // Rate limiting rules by endpoint type
  rules: {
    api: {
      requests: 1000,
      duration: '1h',
    },
    auth: {
      requests: 5,
      duration: '15m',
    },
    sensitive: {
      requests: 10,
      duration: '1h',
    },
  },
})

Input Validation & Sanitization

// Comprehensive input validation
export class SecurityValidator {
  static sanitizeInput = vine.compile(
    vine.object({
      // Prevent XSS attacks
      name: vine.string().trim().escape(),
      email: vine.string().email().normalizeEmail(),
      phone: vine.string().regex(/^\+?[\d\s\-\(\)]+$/),

      // SQL injection prevention (handled by ORM)
      search: vine.string().trim().maxLength(100),

      // File upload validation
      file: vine.file({
        size: '10mb',
        extnames: ['jpg', 'jpeg', 'png', 'pdf'],
      }),
    })
  )
}

CORS Security

// Secure CORS configuration
export default defineConfig({
  enabled: true,
  origin: (origin, callback) => {
    const allowedOrigins = [
      'https://yourdomain.com',
      'https://app.yourdomain.com',
    ]

    if (!origin || allowedOrigins.includes(origin)) {
      callback(null, true)
    } else {
      callback(new Error('Not allowed by CORS'))
    }
  },
  methods: ['GET', 'POST', 'PUT', 'DELETE'],
  allowedHeaders: ['Content-Type', 'Authorization'],
  credentials: true,
  maxAge: 86400, // 24 hours
})

📊 Audit Logging & Compliance

Comprehensive Audit Trail

// Audit logging service
export class AuditService {
  async logUserAction(data: AuditLogData): Promise<AuditLog> {
    return await AuditLog.create({
      user_id: data.userId,
      session_id: data.sessionId,
      ip_address: data.ipAddress,
      user_agent: data.userAgent,
      resource: data.resource,
      action: data.action,
      context: data.context,
      resource_id: data.resourceId,
      method: data.method,
      url: data.url,
      request_data: data.requestData,
      result: data.result,
      response_code: data.responseCode,
      metadata: data.metadata,
    })
  }

  async getSecurityAlerts(): Promise<SecurityAlert[]> {
    // Detect suspicious activities
    const suspiciousLogins = await this.detectSuspiciousLogins()
    const failedAttempts = await this.detectFailedAttempts()
    const dataExfiltration = await this.detectDataExfiltration()

    return [...suspiciousLogins, ...failedAttempts, ...dataExfiltration]
  }
}

Audit Log Structure

// Comprehensive audit log model
export default class AuditLog extends BaseModel {
  @column()
  declare user_id: number

  @column()
  declare session_id: string

  @column()
  declare ip_address: string

  @column()
  declare user_agent: string

  @column()
  declare resource: string

  @column()
  declare action: string

  @column()
  declare context: string

  @column()
  declare resource_id: number | null

  @column()
  declare method: string

  @column()
  declare url: string

  @column({
    prepare: (value) => JSON.stringify(value),
    consume: (value) => JSON.parse(value),
  })
  declare request_data: Record<string, any>

  @column()
  declare result: string

  @column()
  declare response_code: number

  @column({
    prepare: (value) => JSON.stringify(value),
    consume: (value) => JSON.parse(value),
  })
  declare metadata: Record<string, any>
}

🚨 Security Monitoring & Incident Response

Real-time Security Monitoring

// Security monitoring service
export class SecurityMonitor {
  async detectAnomalies(): Promise<SecurityAnomaly[]> {
    const anomalies: SecurityAnomaly[] = []

    // Detect unusual login patterns
    const unusualLogins = await this.detectUnusualLogins()
    anomalies.push(...unusualLogins)

    // Detect privilege escalation attempts
    const privilegeEscalation = await this.detectPrivilegeEscalation()
    anomalies.push(...privilegeEscalation)

    // Detect data access anomalies
    const dataAnomalies = await this.detectDataAccessAnomalies()
    anomalies.push(...dataAnomalies)

    return anomalies
  }

  private async detectUnusualLogins(): Promise<SecurityAnomaly[]> {
    // Check for logins from new locations
    // Check for logins at unusual times
    // Check for multiple failed attempts
    return []
  }
}

Incident Response Workflow

graph TB
    DETECT[Security Event Detected]
    ANALYZE[Analyze Threat Level]
    CLASSIFY{Classify Incident}
    
    LOW[Low Priority]
    MEDIUM[Medium Priority]
    HIGH[High Priority]
    CRITICAL[Critical Priority]
    
    LOG[Log Incident]
    NOTIFY[Notify Security Team]
    ISOLATE[Isolate Affected Systems]
    INVESTIGATE[Investigate & Contain]
    REMEDIATE[Remediate & Recover]
    REVIEW[Post-Incident Review]
    
    DETECT --> ANALYZE
    ANALYZE --> CLASSIFY
    
    CLASSIFY --> LOW
    CLASSIFY --> MEDIUM
    CLASSIFY --> HIGH
    CLASSIFY --> CRITICAL
    
    LOW --> LOG
    MEDIUM --> NOTIFY
    HIGH --> ISOLATE
    CRITICAL --> ISOLATE
    
    LOG --> REVIEW
    NOTIFY --> INVESTIGATE
    ISOLATE --> INVESTIGATE
    INVESTIGATE --> REMEDIATE
    REMEDIATE --> REVIEW
Loading

🔍 Threat Modeling & Risk Assessment

STRIDE Threat Analysis

Threat Description Mitigation
Spoofing Identity impersonation Multi-factor authentication, JWT tokens
Tampering Data modification Input validation, checksums, audit logs
Repudiation Denial of actions Comprehensive audit logging, digital signatures
Information Disclosure Unauthorized data access Encryption, access controls, data masking
Denial of Service Service unavailability Rate limiting, load balancing, monitoring
Elevation of Privilege Unauthorized access escalation RBAC, principle of least privilege

Risk Assessment Matrix

// Risk assessment framework
export class RiskAssessment {
  static assessRisk(threat: Threat): RiskLevel {
    const impact = this.calculateImpact(threat)
    const likelihood = this.calculateLikelihood(threat)

    return this.determineRiskLevel(impact, likelihood)
  }

  private static calculateImpact(threat: Threat): Impact {
    // Consider data sensitivity, business impact, compliance requirements
    return threat.dataClassification === 'PII' ? 'HIGH' : 'MEDIUM'
  }

  private static calculateLikelihood(threat: Threat): Likelihood {
    // Consider threat actor capability, attack surface, existing controls
    return threat.hasExistingControls ? 'LOW' : 'MEDIUM'
  }
}

🛠️ Security Best Practices

Secure Development Guidelines

1. Input Validation

// Always validate and sanitize inputs
export class SecureController {
  async create({request, response}: HttpContext) {
    // Validate input
    const payload = await request.validateUsing(createValidator)

    // Sanitize data
    const sanitizedData = this.sanitizeInput(payload)

    // Process with sanitized data
    const result = await Service.create(sanitizedData)

    return response.created(result)
  }
}

2. Error Handling

// Secure error handling - don't expose sensitive information
export class ErrorHandler {
  async handle(error: any, ctx: HttpContext) {
    // Log full error details securely
    logger.error('Application error', {
      error: error.message,
      stack: error.stack,
      user: ctx.auth.user?.id,
      ip: ctx.request.ip(),
      url: ctx.request.url(),
    })

    // Return sanitized error to client
    if (error.status === 422) {
      return ctx.response.status(422).json({
        error: 'Validation failed',
        details: error.messages // Only validation messages
      })
    }

    // Generic error for security
    return ctx.response.status(500).json({
      error: 'Internal server error'
    })
  }
}

3. Secure Configuration

// Environment-based security configuration
export class SecurityConfig {
  static getConfig() {
    return {
      // Strong session configuration
      session: {
        cookieName: 'innkeeper_session',
        secure: Env.get('NODE_ENV') === 'production',
        httpOnly: true,
        sameSite: 'strict',
        maxAge: '2h',
      },

      // CSRF protection
      csrf: {
        enabled: true,
        exceptRoutes: ['/api/webhooks/*'],
      },

      // Content Security Policy
      csp: {
        directives: {
          defaultSrc: ["'self'"],
          scriptSrc: ["'self'", "'unsafe-inline'"],
          styleSrc: ["'self'", "'unsafe-inline'"],
          imgSrc: ["'self'", "data:", "https:"],
        },
      },
    }
  }
}

📋 Security Checklist

Pre-Deployment Security Checklist

  • Authentication & Authorization

    • Multi-factor authentication enabled
    • Strong password policies enforced
    • JWT tokens properly configured
    • RBAC system implemented and tested
    • Session management secure
  • Data Protection

    • Encryption at rest enabled
    • TLS/SSL properly configured
    • PII data encrypted
    • Data masking implemented
    • Secure backup procedures
  • API Security

    • Rate limiting configured
    • Input validation comprehensive
    • CORS properly configured
    • API versioning implemented
    • Error handling secure
  • Infrastructure Security

    • Firewall rules configured
    • VPN access secured
    • Container security implemented
    • Secret management in place
    • Security monitoring active
  • Compliance & Auditing

    • Audit logging comprehensive
    • Compliance requirements met
    • Data retention policies defined
    • Incident response plan ready
    • Security training completed

🔄 Security Updates & Maintenance

Regular Security Tasks

// Automated security maintenance
export class SecurityMaintenance {
  async performSecurityTasks(): Promise<void> {
    // Rotate encryption keys
    await this.rotateEncryptionKeys()

    // Clean up old audit logs
    await this.cleanupAuditLogs()

    // Update security policies
    await this.updateSecurityPolicies()

    // Scan for vulnerabilities
    await this.performVulnerabilityScan()

    // Generate security reports
    await this.generateSecurityReports()
  }

  private async rotateEncryptionKeys(): Promise<void> {
    // Implement key rotation logic
  }

  private async cleanupAuditLogs(): Promise<void> {
    // Remove logs older than retention period
    const retentionDays = 365
    await AuditLog.query()
      .where('created_at', '<', DateTime.now().minus({days: retentionDays}))
      .delete()
  }
}

📚 Compliance Standards

GDPR Compliance

// GDPR compliance utilities
export class GDPRCompliance {
  async handleDataSubjectRequest(request: DataSubjectRequest): Promise<void> {
    switch (request.type) {
      case 'access':
        await this.provideDataAccess(request.userId)
        break
      case 'rectification':
        await this.rectifyData(request.userId, request.corrections)
        break
      case 'erasure':
        await this.eraseData(request.userId)
        break
      case 'portability':
        await this.exportData(request.userId)
        break
    }
  }

  private async eraseData(userId: number): Promise<void> {
    // Anonymize or delete personal data
    await Guest.query()
      .where('user_id', userId)
      .update({
        first_name: 'DELETED',
        last_name: 'DELETED',
        email: '[email protected]',
        phone: null,
        document_number: null,
      })
  }
}

PCI DSS Compliance

// PCI DSS compliance for payment data
export class PCICompliance {
  static validateCardData(cardData: CardData): boolean {
    // Never store full PAN
    if (cardData.number && cardData.number.length > 6) {
      throw new Error('Full card number storage not allowed')
    }

    // Validate CVV is not stored
    if (cardData.cvv) {
      throw new Error('CVV storage not allowed')
    }

    return true
  }

  static maskCardNumber(cardNumber: string): string {
    // Show only first 6 and last 4 digits
    return cardNumber.replace(/(\d{6})\d+(\d{4})/, '$1******$2')
  }
}

📚 Related Documentation


🆘 Need Help?


← Previous: Multi-Tenant Architecture | Wiki Home | Next: API Documentation →

⚠️ **GitHub.com Fallback** ⚠️