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
}
};