UNPKG

prisma-zod-generator

Version:

Prisma 2+ generator to emit Zod schemas from your Prisma schema

399 lines 16 kB
"use strict"; /** * Business Logic Security Utilities * * Comprehensive protection against business logic vulnerabilities * including authorization bypasses, privilege escalation, and data leakage */ Object.defineProperty(exports, "__esModule", { value: true }); exports.businessSecurity = exports.BusinessSecurity = exports.BusinessSecurityError = void 0; exports.secureBusinessLogic = secureBusinessLogic; class BusinessSecurityError extends Error { constructor(message, errorType, context) { super(message); this.errorType = errorType; this.context = context; this.name = 'BusinessSecurityError'; } } exports.BusinessSecurityError = BusinessSecurityError; /** * Business Logic Security Manager */ class BusinessSecurity { constructor() { this.authorizationRules = new Map(); this.sensitiveFields = new Set(); this.auditLog = []; this.initializeDefaultRules(); this.initializeSensitiveFields(); } /** * Validate feature access with comprehensive checks */ validateFeatureAccess(feature, license, context) { var _a; // Step 1: License validation if (!license) { return { allowed: false, reason: 'No valid license found' }; } // Step 2: Feature-to-plan mapping (corrected logic) const featurePlans = this.getFeaturePlans(); const requiredPlans = featurePlans.get(feature); if (!requiredPlans) { return { allowed: false, reason: `Unknown feature: ${feature}` }; } if (!requiredPlans.includes(license.plan)) { const minPlan = this.getMinimumPlan(requiredPlans); const requiredName = this.describePlan(minPlan); const currentName = this.describePlan(license.plan); return { allowed: false, reason: `Feature '${feature}' requires ${requiredName} plan or higher, current: ${currentName}`, }; } // Step 3: Additional security checks if (license.status && license.status !== 'active') { return { allowed: false, reason: `License status: ${license.status}` }; } const expiry = (_a = license.valid_until) !== null && _a !== void 0 ? _a : new Date(license.validUntil); if (new Date() > expiry) { return { allowed: false, reason: 'License has expired' }; } // Step 4: Context validation if (!this.validateSecurityContext(context)) { return { allowed: false, reason: 'Invalid security context' }; } // Log successful access this.auditLog.push({ timestamp: new Date(), userId: context.userId, action: 'feature_access', resource: feature, allowed: true, }); return { allowed: true }; } /** * Strict tenant isolation validation */ validateTenantIsolation(data, context, tenantField = 'tenantId') { // Step 1: Context validation if (!context.tenantId) { return { isolated: false, reason: 'No tenant context available' }; } // Step 2: Data validation if (!data || typeof data !== 'object') { return { isolated: false, reason: 'Invalid data object' }; } const dataTenant = data[tenantField]; // Step 3: Tenant field presence if (dataTenant === undefined || dataTenant === null) { return { isolated: false, reason: `Missing ${tenantField} in data` }; } // Step 4: Strict tenant matching if (String(dataTenant) !== String(context.tenantId)) { this.auditLog.push({ timestamp: new Date(), userId: context.userId, action: 'tenant_violation', resource: 'data_access', allowed: false, reason: `Tenant mismatch: expected ${context.tenantId}, got ${dataTenant}`, }); return { isolated: false, reason: `Cross-tenant access denied: ${context.tenantId} -> ${dataTenant}`, }; } return { isolated: true }; } /** * Comprehensive authorization check */ authorize(context, resource, action, data) { var _a, _b; const ruleKey = `${resource}:${action}`; const rule = this.authorizationRules.get(ruleKey); if (!rule) { // Default deny for undefined rules return { authorized: false, reason: `No authorization rule for ${ruleKey}` }; } // Step 1: Role-based check if ((_a = rule.requiredRoles) === null || _a === void 0 ? void 0 : _a.length) { const hasRole = rule.requiredRoles.some((role) => context.roles.includes(role)); if (!hasRole) { return { authorized: false, reason: `Required roles: ${rule.requiredRoles.join(', ')}, user has: ${context.roles.join(', ')}`, }; } } // Step 2: Permission-based check if ((_b = rule.requiredPermissions) === null || _b === void 0 ? void 0 : _b.length) { const hasPermission = rule.requiredPermissions.some((perm) => context.permissions.includes(perm)); if (!hasPermission) { return { authorized: false, reason: `Required permissions: ${rule.requiredPermissions.join(', ')}`, }; } } // Step 3: Tenant isolation check if (rule.tenantIsolation && data) { const isolation = this.validateTenantIsolation(data, context); if (!isolation.isolated) { return { authorized: false, reason: isolation.reason }; } } // Step 4: Custom validation if (rule.customValidator) { const customResult = rule.customValidator(context, data); if (!customResult) { return { authorized: false, reason: 'Custom validation failed' }; } } // Log successful authorization this.auditLog.push({ timestamp: new Date(), userId: context.userId, action, resource, allowed: true, }); return { authorized: true }; } /** * Sanitize data to prevent information leakage */ sanitizeData(data, context) { if (!data || typeof data !== 'object') { return data; } const sanitized = Array.isArray(data) ? [] : {}; for (const [key, value] of Object.entries(data)) { // Remove sensitive fields based on context if (this.isSensitiveField(key, context)) { continue; // Skip sensitive field } // Recursively sanitize nested objects if (value && typeof value === 'object') { sanitized[key] = this.sanitizeData(value, context); } else { sanitized[key] = value; } } return sanitized; } /** * Validate dashboard permissions securely */ validateDashboardAccess(dashboard, context) { if (!(dashboard === null || dashboard === void 0 ? void 0 : dashboard.permissions)) { return { allowed: false, reason: 'Dashboard has no permissions defined' }; } // Admin override if (context.isAdmin) { return { allowed: true }; } // Check if user has any of the required permissions const hasPermission = dashboard.permissions.some((perm) => context.permissions.includes(perm) || context.roles.includes(perm)); if (!hasPermission) { return { allowed: false, reason: `Required permissions: ${dashboard.permissions.join(', ')}`, }; } return { allowed: true }; } /** * Secure dashboard creation with permission validation */ validateDashboardCreation(dashboardConfig, context) { // Step 1: Basic validation if (!(dashboardConfig === null || dashboardConfig === void 0 ? void 0 : dashboardConfig.permissions)) { return { allowed: false, reason: 'Dashboard must specify permissions' }; } // Step 2: Permission escalation check const requestedPerms = dashboardConfig.permissions; const unauthorizedPerms = requestedPerms.filter((perm) => !context.permissions.includes(perm) && !context.roles.includes(perm)); if (!context.isAdmin && unauthorizedPerms.length > 0) { return { allowed: false, reason: `Cannot assign unauthorized permissions: ${unauthorizedPerms.join(', ')}`, }; } // Step 3: Sanitize configuration const sanitizedConfig = { ...dashboardConfig, created_by: context.userId, // Force correct creator permissions: context.isAdmin ? dashboardConfig.permissions : dashboardConfig.permissions.filter((perm) => context.permissions.includes(perm) || context.roles.includes(perm)), }; return { allowed: true, sanitizedConfig }; } /** * Get audit log for security monitoring */ getAuditLog(filter) { let filtered = this.auditLog; if (filter === null || filter === void 0 ? void 0 : filter.userId) { filtered = filtered.filter((entry) => entry.userId === filter.userId); } if (filter === null || filter === void 0 ? void 0 : filter.action) { filtered = filtered.filter((entry) => entry.action === filter.action); } if (filter === null || filter === void 0 ? void 0 : filter.resource) { filtered = filtered.filter((entry) => entry.resource === filter.resource); } if (filter === null || filter === void 0 ? void 0 : filter.startDate) { filtered = filtered.filter((entry) => entry.timestamp >= filter.startDate); } if (filter === null || filter === void 0 ? void 0 : filter.endDate) { filtered = filtered.filter((entry) => entry.timestamp <= filter.endDate); } if ((filter === null || filter === void 0 ? void 0 : filter.allowedOnly) !== undefined) { filtered = filtered.filter((entry) => entry.allowed === filter.allowedOnly); } return filtered.slice(-1000); // Return last 1000 entries } // Private helper methods initializeDefaultRules() { // Dashboard rules this.authorizationRules.set('dashboard:create', { resource: 'dashboard', action: 'create', requiredPermissions: ['dashboard_create'], customValidator: (context, data) => this.validateDashboardCreation(data, context).allowed, }); this.authorizationRules.set('dashboard:read', { resource: 'dashboard', action: 'read', customValidator: (context, data) => this.validateDashboardAccess(data, context).allowed, }); this.authorizationRules.set('dashboard:update', { resource: 'dashboard', action: 'update', requiredPermissions: ['dashboard_update'], customValidator: (context, data) => data.created_by === context.userId || context.isAdmin, }); // Multi-tenant rules this.authorizationRules.set('data:read', { resource: 'data', action: 'read', tenantIsolation: true, }); this.authorizationRules.set('data:write', { resource: 'data', action: 'write', tenantIsolation: true, }); // License rules this.authorizationRules.set('license:validate', { resource: 'license', action: 'validate', requiredRoles: ['authenticated'], }); } initializeSensitiveFields() { this.sensitiveFields.add('password'); this.sensitiveFields.add('secret'); this.sensitiveFields.add('token'); this.sensitiveFields.add('api_key'); this.sensitiveFields.add('license_key'); this.sensitiveFields.add('webhook_secret'); this.sensitiveFields.add('private_key'); this.sensitiveFields.add('encryption_key'); this.sensitiveFields.add('session_id'); this.sensitiveFields.add('ssn'); this.sensitiveFields.add('credit_card'); this.sensitiveFields.add('bank_account'); } getFeaturePlans() { return new Map([ ['policies', ['professional', 'business', 'enterprise']], ['server-actions', ['starter', 'professional', 'business', 'enterprise']], ['sdk-publisher', ['professional', 'business', 'enterprise']], ['drift-guard', ['professional', 'business', 'enterprise']], ['contract-testing-pack', ['business', 'enterprise']], ['postgres-rls-pack', ['professional', 'business', 'enterprise']], ['form-ux', ['starter', 'professional', 'business', 'enterprise']], ['api-docs-pack', ['business', 'enterprise']], ['multi-tenant-kit', ['enterprise']], ['performance-pack', ['professional', 'business', 'enterprise']], ['data-factories', ['business', 'enterprise']], ]); } getMinimumPlan(plans) { const hierarchy = ['starter', 'professional', 'business', 'enterprise']; for (const plan of hierarchy) { if (plans.includes(plan)) { return plan; } } return 'enterprise'; } describePlan(plan) { switch (plan) { case 'starter': return 'Starter'; case 'professional': return 'Professional'; case 'business': return 'Business'; case 'enterprise': return 'Enterprise'; default: return plan; } } validateSecurityContext(context) { return !!(context.userId && context.sessionId && Array.isArray(context.roles) && Array.isArray(context.permissions)); } isSensitiveField(fieldName, context) { const field = fieldName.toLowerCase(); // Always sensitive fields if (this.sensitiveFields.has(field)) { return true; } // Context-sensitive fields if (!context.isAdmin) { const adminOnlyFields = ['created_by', 'admin_notes', 'internal_id']; if (adminOnlyFields.some((adminField) => field.includes(adminField))) { return true; } } return false; } } exports.BusinessSecurity = BusinessSecurity; /** * Global business security instance */ exports.businessSecurity = new BusinessSecurity(); /** * Decorator for securing business logic methods */ function secureBusinessLogic(resource, action, options) { return function (target, propertyKey, descriptor) { const originalMethod = descriptor.value; descriptor.value = async function (context, ...args) { const auth = exports.businessSecurity.authorize(context, resource, action, (options === null || options === void 0 ? void 0 : options.tenantIsolation) ? args[0] : undefined); if (!auth.authorized) { throw new BusinessSecurityError(`Unauthorized access: ${auth.reason}`, 'authorization', { resource, action, userId: context.userId, }); } return originalMethod.apply(this, [context, ...args]); }; return descriptor; }; } //# sourceMappingURL=businessSecurity.js.map