/** * Middleware для интеграции системы безопасности в существующие резолверы * * Автоматически применяет фильтрацию данных и аудит к резолверам поставок * без необходимости переписывания всего кода */ import { GraphQLError } from 'graphql' import { OrganizationType } from '@prisma/client' import { FEATURE_FLAGS } from '../../config/features' import { SecurityLogger } from '../../lib/security-logger' import { SupplyDataFilter } from './supply-data-filter' import { ParticipantIsolation } from './participant-isolation' import { CommercialDataAudit } from './commercial-data-audit' import { createSecurityContext } from './index' import type { SecurityContext, ResourceType, CommercialAccessType } from './types' /** * Конфигурация безопасности для резолвера */ interface SecurityConfig { resourceType: ResourceType auditAction: CommercialAccessType requiredRole?: OrganizationType[] enableFiltering: boolean enableAudit: boolean enablePartnershipCheck: boolean } /** * Маппинг резолверов на конфигурации безопасности */ const RESOLVER_SECURITY_CONFIG: Record = { // Query резолверы 'Query.supplyOrders': { resourceType: 'SUPPLY_ORDER', auditAction: 'VIEW_PRICE', enableFiltering: true, enableAudit: true, enablePartnershipCheck: false, }, 'Query.mySupplyOrders': { resourceType: 'SUPPLY_ORDER', auditAction: 'VIEW_PRICE', enableFiltering: true, enableAudit: true, enablePartnershipCheck: false, }, 'Query.pendingSuppliesCount': { resourceType: 'SUPPLY_ORDER', auditAction: 'VIEW_PRICE', enableFiltering: false, enableAudit: true, enablePartnershipCheck: false, }, // Mutation резолверы 'Mutation.createSupplyOrder': { resourceType: 'SUPPLY_ORDER', auditAction: 'VIEW_RECIPE', requiredRole: ['SELLER', 'FULFILLMENT'], enableFiltering: false, enableAudit: true, enablePartnershipCheck: true, }, 'Mutation.updateSupplyOrderStatus': { resourceType: 'SUPPLY_ORDER', auditAction: 'VIEW_PRICE', enableFiltering: true, enableAudit: true, enablePartnershipCheck: false, }, 'Mutation.supplierApproveOrder': { resourceType: 'SUPPLY_ORDER', auditAction: 'VIEW_PRICE', requiredRole: ['WHOLESALE'], enableFiltering: true, enableAudit: true, enablePartnershipCheck: true, }, 'Mutation.supplierRejectOrder': { resourceType: 'SUPPLY_ORDER', auditAction: 'VIEW_PRICE', requiredRole: ['WHOLESALE'], enableFiltering: true, enableAudit: true, enablePartnershipCheck: true, }, 'Mutation.assignLogisticsToSupply': { resourceType: 'SUPPLY_ORDER', auditAction: 'VIEW_CONTACTS', requiredRole: ['SELLER', 'FULFILLMENT'], enableFiltering: true, enableAudit: true, enablePartnershipCheck: true, }, } /** * Middleware функция для применения безопасности к резолверу */ export function applySecurityMiddleware( resolverName: string, originalResolver: Function, ): Function { const config = RESOLVER_SECURITY_CONFIG[resolverName] // Если конфигурация не найдена - возвращаем оригинальный резолвер if (!config) { return originalResolver } return async function securedResolver(parent: unknown, args: unknown, context: unknown, info: unknown) { // Проверяем включена ли система безопасности if (!FEATURE_FLAGS.SUPPLY_DATA_SECURITY.enabled) { return originalResolver(parent, args, context, info) } const securityContext = createSecurityContext(context as Record) try { // 1. Проверка аутентификации if (!securityContext.user.id) { throw new GraphQLError('Authentication required', { extensions: { code: 'UNAUTHENTICATED' }, }) } // 2. Проверка роли если требуется if (config.requiredRole && !config.requiredRole.includes(securityContext.user.organizationType)) { // Логируем попытку несанкционированного доступа if (config.enableAudit && FEATURE_FLAGS.SUPPLY_DATA_SECURITY.auditEnabled) { await CommercialDataAudit.logUnauthorizedAccess((context as any).prisma, { userId: securityContext.user.id, organizationType: securityContext.user.organizationType, resourceType: config.resourceType, resourceId: 'unknown', reason: `Insufficient role: ${securityContext.user.organizationType}, required: ${config.requiredRole.join(', ')}`, ipAddress: securityContext.ipAddress, userAgent: securityContext.userAgent, }) } throw new GraphQLError('Insufficient permissions', { extensions: { code: 'FORBIDDEN' }, }) } // 3. Проверка партнерских отношений если требуется if (config.enablePartnershipCheck && (args as any)?.input?.partnerId) { try { await ParticipantIsolation.validatePartnerAccess( (context as any).prisma, securityContext.user.organizationId, (args as any).input.partnerId, securityContext, ) } catch (error) { SecurityLogger.logSecurityError(error as Error, { operation: 'partnershipCheck', resolverName, userId: securityContext.user.id, organizationType: securityContext.user.organizationType, }) throw error } } // 4. Логирование доступа if (config.enableAudit && FEATURE_FLAGS.SUPPLY_DATA_SECURITY.auditEnabled) { await CommercialDataAudit.logAccess((context as any).prisma, { userId: securityContext.user.id, organizationType: securityContext.user.organizationType, action: config.auditAction, resourceType: config.resourceType, metadata: { resolverName, args }, ipAddress: securityContext.ipAddress, userAgent: securityContext.userAgent, }) } // 5. Выполнение оригинального резолвера let result = await originalResolver(parent, args, context, info) // 6. Фильтрация результата если включена if (config.enableFiltering && result && config.resourceType === 'SUPPLY_ORDER') { result = await filterSupplyOrderResult(result, securityContext) } return result } catch (error) { SecurityLogger.logSecurityError(error as Error, { operation: 'securityMiddleware', resolverName, resourceType: config.resourceType, userId: securityContext.user.id, organizationType: securityContext.user.organizationType, }) throw error } } } /** * Фильтрует результат с заказами поставок */ async function filterSupplyOrderResult( result: unknown, context: SecurityContext, ): Promise { // Если это массив заказов if (Array.isArray(result)) { return Promise.all( result.map(async (order) => { if (order && typeof order === 'object' && 'id' in order) { const filtered = SupplyDataFilter.filterSupplyOrder(order as any, context) return filtered.data } return order }), ) } // Если это одиночный заказ if (result && typeof result === 'object' && 'id' in result) { const filtered = SupplyDataFilter.filterSupplyOrder(result as any, context) return filtered.data } // Если это ответ с заказом внутри if (result && typeof result === 'object' && 'order' in result) { const resultObj = result as any if (resultObj.order && typeof resultObj.order === 'object' && 'id' in resultObj.order) { const filtered = SupplyDataFilter.filterSupplyOrder(resultObj.order, context) return { ...resultObj, order: filtered.data, } } } return result } /** * Автоматически применяет middleware ко всем резолверам из конфигурации */ export function wrapResolversWithSecurity(resolvers: Record): Record { const wrappedResolvers = { ...resolvers } // Обрабатываем Query резолверы if (wrappedResolvers.Query) { for (const [queryName, resolver] of Object.entries(wrappedResolvers.Query)) { const resolverName = `Query.${queryName}` if (RESOLVER_SECURITY_CONFIG[resolverName] && typeof resolver === 'function') { wrappedResolvers.Query[queryName] = applySecurityMiddleware(resolverName, resolver) SecurityLogger.logFilteringPerformance({ operation: 'wrapResolver', duration: 0, recordsFiltered: 0, fieldsRemoved: 0, cacheHit: false, }) } } } // Обрабатываем Mutation резолверы if (wrappedResolvers.Mutation) { for (const [mutationName, resolver] of Object.entries(wrappedResolvers.Mutation)) { const resolverName = `Mutation.${mutationName}` if (RESOLVER_SECURITY_CONFIG[resolverName] && typeof resolver === 'function') { wrappedResolvers.Mutation[mutationName] = applySecurityMiddleware(resolverName, resolver) } } } return wrappedResolvers } /** * Добавляет новую конфигурацию безопасности для резолвера */ export function addSecurityConfig(resolverName: string, config: SecurityConfig): void { RESOLVER_SECURITY_CONFIG[resolverName] = config } /** * Получает конфигурацию безопасности для резолвера */ export function getSecurityConfig(resolverName: string): SecurityConfig | undefined { return RESOLVER_SECURITY_CONFIG[resolverName] } /** * Выводит список всех защищенных резолверов */ export function listSecuredResolvers(): string[] { return Object.keys(RESOLVER_SECURITY_CONFIG) }