Skip to main content

Security Architecture

This document outlines the comprehensive security architecture of the Stayzr Hotel Management System, covering authentication, authorization, data protection, and compliance measures.

🔒 Security Overview

The Stayzr platform implements a defense-in-depth security strategy with multiple layers of protection:

  • Multi-tenant data isolation with strict organizational boundaries
  • Zero-trust architecture with continuous verification
  • End-to-end encryption for data in transit and at rest
  • Role-based access control (RBAC) with granular permissions
  • Comprehensive audit logging for compliance and forensics
  • Automated threat detection and response

🛡️ Authentication Architecture

Multi-Factor Authentication System

interface AuthenticationFlow {
primaryAuth: {
method: 'email_password' | 'sso' | 'api_key';
requirements: {
passwordPolicy: {
minLength: 12;
requireUppercase: true;
requireLowercase: true;
requireNumbers: true;
requireSymbols: true;
preventReuse: 12; // last 12 passwords
maxAge: 90; // days
};
accountLockout: {
maxAttempts: 5;
lockoutDuration: 900; // 15 minutes
progressiveLockout: true;
};
};
};

secondaryAuth: {
required: boolean;
methods: ('totp' | 'sms' | 'email' | 'backup_codes')[];
gracePeriod: 30; // days for new device
};

sessionManagement: {
jwtExpiry: 900; // 15 minutes
refreshTokenExpiry: 604800; // 7 days
maxSessions: 5;
sessionTimeout: 3600; // 1 hour inactivity
};
}

JWT Token Architecture

interface JWTPayload {
// Standard claims
iss: string; // issuer: 'stayzr-platform'
sub: string; // subject: user ID
aud: string; // audience: 'stayzr-api'
exp: number; // expiration timestamp
iat: number; // issued at timestamp
jti: string; // JWT ID for revocation

// Custom claims
organizationId: number;
tenantId: string;
roles: string[];
permissions: string[];

// Security context
sessionId: string;
deviceFingerprint: string;
ipAddress: string;
lastActivity: number;

// Super admin context
isSuperAdmin?: boolean;
currentOrgId?: number; // for org switching
originalOrgId?: number;
}

// Token validation middleware
class JWTValidator {
async validateToken(token: string): Promise<ValidationResult> {
try {
// 1. Verify signature and basic structure
const decoded = jwt.verify(token, JWT_SECRET) as JWTPayload;

// 2. Check revocation status
const isRevoked = await this.checkRevocation(decoded.jti);
if (isRevoked) {
throw new Error('Token revoked');
}

// 3. Validate session
const session = await this.validateSession(decoded.sessionId);
if (!session.isValid) {
throw new Error('Invalid session');
}

// 4. Check device fingerprint
if (decoded.deviceFingerprint !== this.getCurrentFingerprint()) {
await this.logSecurityEvent('device_mismatch', decoded);
throw new Error('Device mismatch');
}

// 5. Validate organization access
await this.validateOrganizationAccess(decoded);

return { valid: true, payload: decoded };
} catch (error) {
return { valid: false, error: error.message };
}
}
}

🔐 Authorization Framework

Role-Based Access Control (RBAC)

interface Permission {
resource: string; // 'guest', 'reservation', 'staff', etc.
action: string; // 'create', 'read', 'update', 'delete'
scope: 'own' | 'department' | 'property' | 'organization' | 'all';
conditions?: PermissionCondition[];
}

interface Role {
id: string;
name: string;
description: string;
permissions: Permission[];
isSystemRole: boolean;
organizationId?: number;
}

// Permission checking system
class PermissionChecker {
async checkPermission(
userId: number,
resource: string,
action: string,
context: SecurityContext
): Promise<boolean> {
// 1. Get user roles and permissions
const userPermissions = await this.getUserPermissions(userId);

// 2. Check direct permissions
const hasDirectPermission = userPermissions.some(permission =>
this.matchesPermission(permission, resource, action)
);

if (!hasDirectPermission) {
await this.logAccessDenied(userId, resource, action, context);
return false;
}

// 3. Check scope and conditions
const permission = userPermissions.find(p =>
this.matchesPermission(p, resource, action)
);

if (permission) {
return await this.evaluateScope(permission, context);
}

return false;
}

private async evaluateScope(
permission: Permission,
context: SecurityContext
): Promise<boolean> {
switch (permission.scope) {
case 'own':
return context.resourceOwnerId === context.userId;

case 'department':
return await this.checkDepartmentAccess(
context.userId,
context.resourceId
);

case 'property':
return await this.checkPropertyAccess(
context.userId,
context.propertyId
);

case 'organization':
return context.organizationId === context.userOrganizationId;

case 'all':
return context.isSuperAdmin;

default:
return false;
}
}
}

Dynamic Permission Evaluation

interface PermissionCondition {
type: 'time_based' | 'ip_based' | 'device_based' | 'business_rule';
operator: 'equals' | 'not_equals' | 'in' | 'not_in' | 'greater_than' | 'less_than';
value: any;
field: string;
}

class DynamicPermissionEvaluator {
async evaluateConditions(
conditions: PermissionCondition[],
context: SecurityContext
): Promise<boolean> {
for (const condition of conditions) {
const result = await this.evaluateCondition(condition, context);
if (!result) {
return false;
}
}
return true;
}

private async evaluateCondition(
condition: PermissionCondition,
context: SecurityContext
): Promise<boolean> {
switch (condition.type) {
case 'time_based':
return this.evaluateTimeCondition(condition, context);

case 'ip_based':
return this.evaluateIPCondition(condition, context);

case 'device_based':
return this.evaluateDeviceCondition(condition, context);

case 'business_rule':
return this.evaluateBusinessRule(condition, context);

default:
return false;
}
}

private evaluateTimeCondition(
condition: PermissionCondition,
context: SecurityContext
): boolean {
const currentTime = new Date().getTime();
const allowedHours = condition.value as number[];
const currentHour = new Date().getHours();

return allowedHours.includes(currentHour);
}
}

🔒 Data Protection

Encryption Strategy

interface EncryptionConfig {
algorithms: {
symmetric: 'AES-256-GCM';
asymmetric: 'RSA-4096';
hashing: 'bcrypt';
signing: 'RS256';
};

keyManagement: {
provider: 'aws-kms' | 'azure-keyvault' | 'local';
rotationPeriod: 365; // days
multiRegion: boolean;
};

encryptionScopes: {
atRest: {
database: boolean;
files: boolean;
backups: boolean;
logs: boolean;
};
inTransit: {
api: boolean;
database: boolean;
internal: boolean;
external: boolean;
};
};
}

// Field-level encryption for sensitive data
class FieldEncryption {
private readonly sensitiveFields = [
'creditCardNumber',
'socialSecurityNumber',
'passportNumber',
'driversLicense',
'bankAccountNumber',
'personalNotes'
];

async encryptSensitiveFields<T>(data: T): Promise<T> {
const encrypted = { ...data };

for (const field of this.sensitiveFields) {
if (encrypted[field] && typeof encrypted[field] === 'string') {
encrypted[field] = await this.encrypt(encrypted[field] as string);
}
}

return encrypted;
}

async encrypt(plaintext: string): Promise<string> {
const key = await this.getEncryptionKey();
const iv = crypto.randomBytes(16);
const cipher = crypto.createCipher('aes-256-gcm', key);
cipher.setAAD(Buffer.from('stayzr-platform'));

let encrypted = cipher.update(plaintext, 'utf8', 'hex');
encrypted += cipher.final('hex');

const authTag = cipher.getAuthTag();
return `${iv.toString('hex')}:${authTag.toString('hex')}:${encrypted}`;
}

async decrypt(encryptedData: string): Promise<string> {
const [ivHex, authTagHex, encrypted] = encryptedData.split(':');
const key = await this.getEncryptionKey();
const iv = Buffer.from(ivHex, 'hex');
const authTag = Buffer.from(authTagHex, 'hex');

const decipher = crypto.createDecipher('aes-256-gcm', key);
decipher.setAAD(Buffer.from('stayzr-platform'));
decipher.setAuthTag(authTag);

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

return decrypted;
}
}

Data Loss Prevention (DLP)

interface DLPRule {
id: string;
name: string;
pattern: RegExp;
severity: 'low' | 'medium' | 'high' | 'critical';
action: 'log' | 'block' | 'encrypt' | 'redact';
description: string;
}

class DataLossPreventionService {
private readonly dlpRules: DLPRule[] = [
{
id: 'credit_card',
name: 'Credit Card Number',
pattern: /\b(?:\d{4}[-\s]?){3}\d{4}\b/g,
severity: 'critical',
action: 'encrypt',
description: 'Detects credit card numbers'
},
{
id: 'ssn',
name: 'Social Security Number',
pattern: /\b\d{3}-?\d{2}-?\d{4}\b/g,
severity: 'critical',
action: 'encrypt',
description: 'Detects SSN patterns'
},
{
id: 'email',
name: 'Email Address',
pattern: /\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b/g,
severity: 'medium',
action: 'log',
description: 'Detects email addresses'
}
];

async scanContent(content: string, context: SecurityContext): Promise<DLPResult> {
const violations: DLPViolation[] = [];
let sanitizedContent = content;

for (const rule of this.dlpRules) {
const matches = content.match(rule.pattern);
if (matches) {
for (const match of matches) {
violations.push({
ruleId: rule.id,
ruleName: rule.name,
severity: rule.severity,
matchedText: match,
position: content.indexOf(match)
});

// Apply action
switch (rule.action) {
case 'block':
throw new SecurityError(`Content blocked by DLP rule: ${rule.name}`);

case 'encrypt':
const encrypted = await this.encryptMatch(match);
sanitizedContent = sanitizedContent.replace(match, encrypted);
break;

case 'redact':
sanitizedContent = sanitizedContent.replace(match, '*'.repeat(match.length));
break;

case 'log':
await this.logViolation(rule, match, context);
break;
}
}
}
}

return {
violations,
sanitizedContent,
hasViolations: violations.length > 0
};
}
}

🚨 Threat Detection and Response

Intrusion Detection System

interface ThreatIndicator {
type: 'brute_force' | 'anomalous_access' | 'data_exfiltration' | 'privilege_escalation';
severity: 'low' | 'medium' | 'high' | 'critical';
confidence: number; // 0-100
source: string;
indicators: Record<string, any>;
}

class ThreatDetectionEngine {
private readonly detectionRules = new Map<string, DetectionRule>();

constructor() {
this.initializeRules();
}

async analyzeSecurityEvent(event: SecurityEvent): Promise<ThreatAssessment> {
const threats: ThreatIndicator[] = [];

// Analyze with all applicable rules
for (const [ruleId, rule] of this.detectionRules) {
if (rule.applies(event)) {
const threat = await rule.evaluate(event);
if (threat) {
threats.push(threat);
}
}
}

// Correlate with historical data
const correlatedThreats = await this.correlateThreats(threats, event);

// Calculate overall risk score
const riskScore = this.calculateRiskScore(correlatedThreats);

return {
riskScore,
threats: correlatedThreats,
recommendedActions: await this.getRecommendedActions(correlatedThreats)
};
}

private initializeRules(): void {
// Brute force detection
this.detectionRules.set('brute_force', {
applies: (event) => event.type === 'login_failure',
evaluate: async (event) => {
const recentFailures = await this.getRecentLoginFailures(
event.userId,
event.ipAddress,
15 // minutes
);

if (recentFailures >= 5) {
return {
type: 'brute_force',
severity: 'high',
confidence: 90,
source: event.ipAddress,
indicators: {
failureCount: recentFailures,
timeWindow: 15,
targetUser: event.userId
}
};
}
return null;
}
});

// Anomalous access detection
this.detectionRules.set('anomalous_access', {
applies: (event) => event.type === 'login_success',
evaluate: async (event) => {
const userBaseline = await this.getUserBaseline(event.userId);
const anomalyScore = this.calculateAnomalyScore(event, userBaseline);

if (anomalyScore > 0.8) {
return {
type: 'anomalous_access',
severity: 'medium',
confidence: Math.round(anomalyScore * 100),
source: event.ipAddress,
indicators: {
anomalyScore,
deviations: this.getDeviations(event, userBaseline)
}
};
}
return null;
}
});
}
}

Automated Response System

interface ResponseAction {
type: 'block_ip' | 'disable_account' | 'require_mfa' | 'notify_admin' | 'escalate';
parameters: Record<string, any>;
automaticExecution: boolean;
approvalRequired: boolean;
}

class AutomatedResponseSystem {
async executeResponse(
threat: ThreatIndicator,
context: SecurityContext
): Promise<ResponseResult> {
const actions = this.determineResponseActions(threat);
const results: ResponseActionResult[] = [];

for (const action of actions) {
try {
if (action.automaticExecution) {
const result = await this.executeAction(action, context);
results.push(result);
} else if (action.approvalRequired) {
await this.requestApproval(action, threat, context);
}
} catch (error) {
results.push({
action: action.type,
success: false,
error: error.message
});
}
}

return {
threat,
actionsExecuted: results,
escalationRequired: this.requiresEscalation(threat, results)
};
}

private determineResponseActions(threat: ThreatIndicator): ResponseAction[] {
const actions: ResponseAction[] = [];

switch (threat.type) {
case 'brute_force':
if (threat.severity === 'high') {
actions.push({
type: 'block_ip',
parameters: {
ipAddress: threat.source,
duration: 3600 // 1 hour
},
automaticExecution: true,
approvalRequired: false
});
}
break;

case 'anomalous_access':
actions.push({
type: 'require_mfa',
parameters: { userId: threat.indicators.userId },
automaticExecution: true,
approvalRequired: false
});
break;

case 'privilege_escalation':
actions.push({
type: 'disable_account',
parameters: { userId: threat.indicators.userId },
automaticExecution: false,
approvalRequired: true
});
break;
}

// Always notify for critical threats
if (threat.severity === 'critical') {
actions.push({
type: 'notify_admin',
parameters: { escalationLevel: 'immediate' },
automaticExecution: true,
approvalRequired: false
});
}

return actions;
}
}

📊 Security Monitoring and Analytics

Security Information and Event Management (SIEM)

interface SecurityMetric {
name: string;
value: number;
timestamp: Date;
tags: Record<string, string>;
organizationId?: number;
}

class SecurityAnalytics {
async collectSecurityMetrics(): Promise<SecurityMetric[]> {
const metrics: SecurityMetric[] = [];
const now = new Date();

// Authentication metrics
metrics.push({
name: 'failed_logins_24h',
value: await this.getFailedLoginCount(24),
timestamp: now,
tags: { category: 'authentication' }
});

metrics.push({
name: 'successful_logins_24h',
value: await this.getSuccessfulLoginCount(24),
timestamp: now,
tags: { category: 'authentication' }
});

// Access control metrics
metrics.push({
name: 'permission_denied_24h',
value: await this.getPermissionDeniedCount(24),
timestamp: now,
tags: { category: 'authorization' }
});

// Data access metrics
metrics.push({
name: 'sensitive_data_access_24h',
value: await this.getSensitiveDataAccessCount(24),
timestamp: now,
tags: { category: 'data_access' }
});

// API security metrics
metrics.push({
name: 'api_rate_limit_violations_24h',
value: await this.getRateLimitViolations(24),
timestamp: now,
tags: { category: 'api_security' }
});

return metrics;
}

async generateSecurityReport(
organizationId: number,
timeRange: TimeRange
): Promise<SecurityReport> {
const report: SecurityReport = {
organizationId,
generatedAt: new Date(),
timeRange,
summary: {
totalSecurityEvents: 0,
threatsDetected: 0,
criticalIncidents: 0,
dataBreaches: 0,
complianceViolations: 0
},
details: {
authenticationMetrics: await this.getAuthenticationMetrics(organizationId, timeRange),
accessControlMetrics: await this.getAccessControlMetrics(organizationId, timeRange),
dataProtectionMetrics: await this.getDataProtectionMetrics(organizationId, timeRange),
incidentSummary: await this.getIncidentSummary(organizationId, timeRange),
complianceStatus: await this.getComplianceStatus(organizationId, timeRange)
},
recommendations: await this.generateSecurityRecommendations(organizationId)
};

return report;
}
}

🔍 Compliance and Audit

Regulatory Compliance Framework

interface ComplianceFramework {
name: 'GDPR' | 'CCPA' | 'PCI_DSS' | 'SOX' | 'HIPAA';
requirements: ComplianceRequirement[];
assessmentFrequency: 'monthly' | 'quarterly' | 'annually';
lastAssessment: Date;
status: 'compliant' | 'non_compliant' | 'partial' | 'pending';
}

interface ComplianceRequirement {
id: string;
title: string;
description: string;
controls: ComplianceControl[];
evidence: ComplianceEvidence[];
status: 'met' | 'not_met' | 'partial' | 'not_applicable';
lastReviewed: Date;
}

class ComplianceManager {
private readonly frameworks: Map<string, ComplianceFramework> = new Map();

constructor() {
this.initializeFrameworks();
}

async assessCompliance(
organizationId: number,
framework: string
): Promise<ComplianceAssessment> {
const complianceFramework = this.frameworks.get(framework);
if (!complianceFramework) {
throw new Error(`Unknown compliance framework: ${framework}`);
}

const assessment: ComplianceAssessment = {
organizationId,
framework,
assessmentDate: new Date(),
overallStatus: 'pending',
requirementResults: [],
recommendations: [],
evidenceGaps: []
};

// Assess each requirement
for (const requirement of complianceFramework.requirements) {
const result = await this.assessRequirement(requirement, organizationId);
assessment.requirementResults.push(result);

if (result.status === 'not_met') {
assessment.evidenceGaps.push({
requirementId: requirement.id,
gap: result.gap,
remediation: result.recommendedAction
});
}
}

// Calculate overall status
assessment.overallStatus = this.calculateOverallStatus(assessment.requirementResults);

// Generate recommendations
assessment.recommendations = await this.generateComplianceRecommendations(assessment);

return assessment;
}
}

Audit Trail System

interface AuditEvent {
id: string;
timestamp: Date;
organizationId: number;
userId?: number;
sessionId: string;

// Event classification
eventType: AuditEventType;
category: 'authentication' | 'authorization' | 'data_access' | 'configuration' | 'admin';
severity: 'info' | 'warning' | 'error' | 'critical';

// Event details
resource: string;
action: string;
outcome: 'success' | 'failure' | 'partial';

// Context information
sourceIP: string;
userAgent: string;
deviceFingerprint: string;
geolocation?: GeoLocation;

// Data changes
beforeState?: Record<string, any>;
afterState?: Record<string, any>;
changedFields?: string[];

// Additional metadata
tags: Record<string, string>;
customFields: Record<string, any>;
}

class AuditLogger {
async logEvent(event: Partial<AuditEvent>): Promise<void> {
const auditEvent: AuditEvent = {
id: this.generateEventId(),
timestamp: new Date(),
organizationId: event.organizationId!,
userId: event.userId,
sessionId: event.sessionId!,
eventType: event.eventType!,
category: event.category!,
severity: event.severity || 'info',
resource: event.resource!,
action: event.action!,
outcome: event.outcome!,
sourceIP: event.sourceIP!,
userAgent: event.userAgent!,
deviceFingerprint: event.deviceFingerprint!,
geolocation: event.geolocation,
beforeState: event.beforeState,
afterState: event.afterState,
changedFields: event.changedFields,
tags: event.tags || {},
customFields: event.customFields || {}
};

// Store in primary audit log
await this.storeAuditEvent(auditEvent);

// Forward to SIEM if configured
if (this.siemEnabled) {
await this.forwardToSIEM(auditEvent);
}

// Check for compliance requirements
await this.checkComplianceRequirements(auditEvent);

// Trigger alerts if necessary
await this.evaluateAlertRules(auditEvent);
}

async generateAuditReport(
organizationId: number,
criteria: AuditReportCriteria
): Promise<AuditReport> {
const events = await this.queryAuditEvents(organizationId, criteria);

return {
organizationId,
reportPeriod: criteria.dateRange,
generatedAt: new Date(),
totalEvents: events.length,
eventsByCategory: this.groupEventsByCategory(events),
eventsBySeverity: this.groupEventsBySeverity(events),
topUsers: this.getTopUsers(events),
topResources: this.getTopResources(events),
securityIncidents: this.identifySecurityIncidents(events),
complianceViolations: this.identifyComplianceViolations(events),
recommendations: this.generateAuditRecommendations(events)
};
}
}

🔧 Security Configuration

Security Headers and Policies

interface SecurityConfiguration {
headers: {
strictTransportSecurity: {
enabled: boolean;
maxAge: number;
includeSubDomains: boolean;
preload: boolean;
};

contentSecurityPolicy: {
enabled: boolean;
directives: Record<string, string[]>;
reportUri?: string;
};

xFrameOptions: 'DENY' | 'SAMEORIGIN' | 'ALLOW-FROM';
xContentTypeOptions: boolean;
xXSSProtection: boolean;
referrerPolicy: string;
};

cors: {
origins: string[];
methods: string[];
allowedHeaders: string[];
credentials: boolean;
maxAge: number;
};

rateLimiting: {
enabled: boolean;
windowMs: number;
maxRequests: number;
skipSuccessfulRequests: boolean;
skipFailedRequests: boolean;
};
}

const defaultSecurityConfig: SecurityConfiguration = {
headers: {
strictTransportSecurity: {
enabled: true,
maxAge: 31536000, // 1 year
includeSubDomains: true,
preload: true
},

contentSecurityPolicy: {
enabled: true,
directives: {
'default-src': ["'self'"],
'script-src': ["'self'", "'unsafe-inline'"],
'style-src': ["'self'", "'unsafe-inline'"],
'img-src': ["'self'", 'data:', 'https:'],
'font-src': ["'self'"],
'connect-src': ["'self'"],
'frame-ancestors': ["'none'"]
},
reportUri: '/api/security/csp-report'
},

xFrameOptions: 'DENY',
xContentTypeOptions: true,
xXSSProtection: true,
referrerPolicy: 'strict-origin-when-cross-origin'
},

cors: {
origins: process.env.ALLOWED_ORIGINS?.split(',') || ['http://localhost:3000'],
methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'],
allowedHeaders: ['Content-Type', 'Authorization', 'X-Requested-With'],
credentials: true,
maxAge: 86400 // 24 hours
},

rateLimiting: {
enabled: true,
windowMs: 15 * 60 * 1000, // 15 minutes
maxRequests: 100,
skipSuccessfulRequests: false,
skipFailedRequests: false
}
};