/**
 * Role-Based Access Control (RBAC) Module
 *
 * Comprehensive RBAC implementation for Vorion with:
 * - Hierarchical roles (admin > operator > viewer)
 * - Resource-based permissions (tenant:*, intent:read, proof:verify)
 * - Policy-based access control
 * - TenantContext integration
 * - Trust-engine integration for trust-based access
 * - Audit logging for all access decisions
 *
 * @example
 * ```typescript
 * import { RBACService, createRBACService } from './security/rbac';
 *
 * const rbac = createRBACService();
 *
 * // Check permission
 * const result = await rbac.checkAccess({
 *   subjectId: 'user-123',
 *   subjectType: 'user',
 *   resource: 'intent',
 *   action: 'read',
 *   tenantId: 'tenant-456',
 * });
 *
 * if (!result.permitted) {
 *   throw new ForbiddenError(result.reason);
 * }
 * ```
 *
 * @packageDocumentation
 */
import fp from 'fastify-plugin';
import { createLogger } from '../../common/logger.js';
import { ForbiddenError, UnauthorizedError } from '../../common/errors.js';
import { Counter, Histogram } from 'prom-client';
import { vorionRegistry } from '../../common/metrics-registry.js';
import { getSecurityAuditLogger, } from '../../audit/security-logger.js';
// Re-export role types and utilities
export { BuiltinRoles, ROLE_PRIORITY, ROLE_MINIMUM_TRUST_TIER, getBuiltinRoles, getEffectivePermissions, roleInheritsFrom, compareRoles, getAncestorRoles, validateRoleHierarchy, meetsTrustRequirement, createRoleDefinition, createRoleAssignment, roleDefinitionSchema, roleAssignmentSchema, } from './roles.js';
// Re-export permission types and utilities
export { PermissionActions, ResourceTypes, PermissionCategories, getBuiltinPermissions, parsePermission, buildPermission, permissionMatches, hasPermission, hasAllPermissions, hasAnyPermission, filterMatchingPermissions, isValidPermissionFormat, getPermissionsByCategory, getSensitivePermissions, getCompliancePermissions, createPermissionDefinition, permissionDefinitionSchema, permissionCheckRequestSchema, permissionCheckResultSchema, } from './permissions.js';
// Re-export policy engine types and utilities
export { PolicyTypes, PolicyEffects, CombiningAlgorithms, RBACPolicyEngine, createRBACPolicyEngine, createPolicyEvaluationContext, createAccessPolicy, accessPolicySchema, } from './policy-engine.js';
// Import for internal use
import { BuiltinRoles, } from './roles.js';
import { hasPermission, hasAllPermissions, } from './permissions.js';
import { createRBACPolicyEngine, createPolicyEvaluationContext, } from './policy-engine.js';
const logger = createLogger({ component: 'rbac-service' });
// =============================================================================
// METRICS
// =============================================================================
const rbacChecks = new Counter({
    name: 'vorion_rbac_access_checks_total',
    help: 'Total RBAC access checks',
    labelNames: ['result', 'resource_type', 'action'],
    registers: [vorionRegistry],
});
const rbacCheckDuration = new Histogram({
    name: 'vorion_rbac_access_check_duration_seconds',
    help: 'Duration of RBAC access check',
    labelNames: ['resource_type'],
    buckets: [0.001, 0.005, 0.01, 0.025, 0.05, 0.1],
    registers: [vorionRegistry],
});
/**
 * RBAC Service
 *
 * High-level service for Role-Based Access Control that integrates:
 * - Role management
 * - Permission checking
 * - Policy evaluation
 * - Trust-tier enforcement
 * - Tenant isolation
 * - Audit logging
 */
export class RBACService {
    policyEngine;
    options;
    auditLogger;
    constructor(options = {}) {
        this.options = {
            defaultCombiningAlgorithm: options.defaultCombiningAlgorithm ?? 'deny_overrides',
            defaultEffect: options.defaultEffect ?? 'deny',
            enableAuditLogging: options.enableAuditLogging ?? true,
            enableTracing: options.enableTracing ?? true,
            enableCaching: options.enableCaching ?? true,
            cacheTtlMs: options.cacheTtlMs ?? 60000,
            enforceTrustTier: options.enforceTrustTier ?? true,
            defaultTrustTier: options.defaultTrustTier ?? 0,
            enableTenantIsolation: options.enableTenantIsolation ?? true,
            allowSuperAdminCrossTenant: options.allowSuperAdminCrossTenant ?? true,
        };
        this.policyEngine = createRBACPolicyEngine({
            defaultCombiningAlgorithm: this.options.defaultCombiningAlgorithm,
            defaultEffect: this.options.defaultEffect,
            enableAuditLogging: this.options.enableAuditLogging,
            enableTracing: this.options.enableTracing,
            enableCaching: this.options.enableCaching,
            cacheTtlMs: this.options.cacheTtlMs,
        });
        this.auditLogger = getSecurityAuditLogger();
        logger.info({
            enforceTrustTier: this.options.enforceTrustTier,
            enableTenantIsolation: this.options.enableTenantIsolation,
        }, 'RBAC service initialized');
    }
    // ===========================================================================
    // ACCESS CONTROL
    // ===========================================================================
    /**
     * Check if a subject has access to a resource
     */
    async checkAccess(request) {
        const startTime = performance.now();
        // Get subject's roles and permissions
        const subjectRoles = await this.policyEngine.getSubjectRoles(request.subjectId, request.tenantId);
        const subjectPermissions = await this.policyEngine.getSubjectPermissions(request.subjectId, request.tenantId);
        const trustTier = request.trustTier ?? this.options.defaultTrustTier;
        const roleNames = subjectRoles.map(r => r.id);
        // Build evaluation context
        const context = createPolicyEvaluationContext({
            subjectId: request.subjectId,
            subjectType: request.subjectType,
            trustTier,
            roles: roleNames,
            permissions: Array.from(subjectPermissions),
            resourceType: request.resource,
            resourceAction: request.action,
            resourceId: request.resourceId,
            tenantId: request.tenantId,
            ipAddress: request.ipAddress,
            userAgent: request.userAgent,
            requestId: request.requestId,
            subjectAttributes: request.context,
        });
        // Check trust tier requirement
        if (this.options.enforceTrustTier) {
            const trustCheck = this.checkTrustTierForRoles(subjectRoles, trustTier);
            if (!trustCheck.passed) {
                const durationMs = performance.now() - startTime;
                rbacChecks.inc({ result: 'denied', resource_type: request.resource, action: request.action });
                rbacCheckDuration.observe({ resource_type: request.resource }, durationMs / 1000);
                return {
                    permitted: false,
                    reason: trustCheck.reason,
                    checkedPermissions: [`${request.resource}:${request.action}`],
                    evaluatedRoles: roleNames,
                    missingRequirements: { trustTier: trustCheck.requiredTier },
                    decisionSource: 'role',
                    durationMs,
                    evaluatedAt: new Date().toISOString(),
                };
            }
        }
        // Check tenant isolation
        if (this.options.enableTenantIsolation && request.tenantId) {
            const tenantCheck = await this.checkTenantAccess(request.subjectId, request.tenantId, subjectRoles);
            if (!tenantCheck.passed) {
                const durationMs = performance.now() - startTime;
                rbacChecks.inc({ result: 'denied', resource_type: request.resource, action: request.action });
                rbacCheckDuration.observe({ resource_type: request.resource }, durationMs / 1000);
                return {
                    permitted: false,
                    reason: tenantCheck.reason,
                    checkedPermissions: [`${request.resource}:${request.action}`],
                    evaluatedRoles: roleNames,
                    decisionSource: 'role',
                    durationMs,
                    evaluatedAt: new Date().toISOString(),
                };
            }
        }
        // Evaluate policies
        const policyResult = await this.policyEngine.evaluateWithTracing(context);
        const durationMs = performance.now() - startTime;
        rbacChecks.inc({
            result: policyResult.permitted ? 'granted' : 'denied',
            resource_type: request.resource,
            action: request.action,
        });
        rbacCheckDuration.observe({ resource_type: request.resource }, durationMs / 1000);
        return {
            permitted: policyResult.permitted,
            reason: policyResult.reason,
            checkedPermissions: [`${request.resource}:${request.action}`],
            evaluatedRoles: roleNames,
            missingRequirements: policyResult.missingRequirements,
            decisionSource: policyResult.decidingPolicy ? 'policy' : 'permission',
            durationMs,
            evaluatedAt: policyResult.evaluatedAt,
        };
    }
    /**
     * Check if subject has specific permission
     */
    async hasPermission(subjectId, permission, tenantId) {
        const permissions = await this.policyEngine.getSubjectPermissions(subjectId, tenantId);
        return hasPermission(permissions, permission);
    }
    /**
     * Check if subject has all specified permissions
     */
    async hasAllPermissions(subjectId, permissions, tenantId) {
        const subjectPermissions = await this.policyEngine.getSubjectPermissions(subjectId, tenantId);
        return hasAllPermissions(subjectPermissions, permissions);
    }
    /**
     * Check if subject has a specific role
     */
    async hasRole(subjectId, roleId, tenantId) {
        const roles = await this.policyEngine.getSubjectRoles(subjectId, tenantId);
        return roles.some(r => r.id === roleId);
    }
    /**
     * Require access (throws ForbiddenError if denied)
     */
    async requireAccess(request) {
        const result = await this.checkAccess(request);
        if (!result.permitted) {
            throw new ForbiddenError(result.reason, {
                subjectId: request.subjectId,
                resource: request.resource,
                action: request.action,
                missingRequirements: result.missingRequirements,
            });
        }
    }
    /**
     * Require permission (throws ForbiddenError if denied)
     */
    async requirePermission(subjectId, permission, tenantId) {
        const hasPerm = await this.hasPermission(subjectId, permission, tenantId);
        if (!hasPerm) {
            throw new ForbiddenError(`Missing required permission: ${permission}`, {
                subjectId,
                permission,
                tenantId,
            });
        }
    }
    /**
     * Require role (throws ForbiddenError if denied)
     */
    async requireRole(subjectId, roleId, tenantId) {
        const hasRoleAssigned = await this.hasRole(subjectId, roleId, tenantId);
        if (!hasRoleAssigned) {
            throw new ForbiddenError(`Missing required role: ${roleId}`, {
                subjectId,
                roleId,
                tenantId,
            });
        }
    }
    // ===========================================================================
    // ROLE MANAGEMENT (Delegate to policy engine)
    // ===========================================================================
    /**
     * Add a custom role
     */
    addRole(role) {
        this.policyEngine.addRole(role);
    }
    /**
     * Update a custom role
     */
    updateRole(roleId, updates) {
        this.policyEngine.updateRole(roleId, updates);
    }
    /**
     * Remove a custom role
     */
    removeRole(roleId) {
        return this.policyEngine.removeRole(roleId);
    }
    /**
     * Get a role by ID
     */
    getRole(roleId) {
        return this.policyEngine.getRole(roleId);
    }
    /**
     * Get all roles
     */
    getAllRoles() {
        return this.policyEngine.getAllRoles();
    }
    /**
     * Assign a role to a subject
     */
    assignRole(assignment) {
        this.policyEngine.assignRole(assignment);
    }
    /**
     * Revoke a role from a subject
     */
    revokeRole(subjectId, roleId, tenantId) {
        return this.policyEngine.revokeRole(subjectId, roleId, tenantId);
    }
    /**
     * Get role assignments for a subject
     */
    getRoleAssignments(subjectId) {
        return this.policyEngine.getRoleAssignments(subjectId);
    }
    /**
     * Get subject's effective permissions
     */
    async getSubjectPermissions(subjectId, tenantId) {
        return this.policyEngine.getSubjectPermissions(subjectId, tenantId);
    }
    /**
     * Get subject's roles
     */
    async getSubjectRoles(subjectId, tenantId) {
        return this.policyEngine.getSubjectRoles(subjectId, tenantId);
    }
    // ===========================================================================
    // POLICY MANAGEMENT (Delegate to policy engine)
    // ===========================================================================
    /**
     * Get the underlying policy engine
     */
    getPolicyEngine() {
        return this.policyEngine;
    }
    // ===========================================================================
    // HELPER METHODS
    // ===========================================================================
    /**
     * Check if trust tier meets requirements for assigned roles
     */
    checkTrustTierForRoles(roles, trustTier) {
        for (const role of roles) {
            if (trustTier < role.minimumTrustTier) {
                return {
                    passed: false,
                    reason: `Trust tier T${trustTier} insufficient for role ${role.name} (requires T${role.minimumTrustTier})`,
                    requiredTier: role.minimumTrustTier,
                };
            }
        }
        return { passed: true, reason: 'Trust tier sufficient' };
    }
    /**
     * Check tenant access
     */
    async checkTenantAccess(subjectId, tenantId, roles) {
        // Super admins can access any tenant if configured
        if (this.options.allowSuperAdminCrossTenant) {
            const isSuperAdmin = roles.some(r => r.id === BuiltinRoles.SUPER_ADMIN);
            if (isSuperAdmin) {
                return { passed: true, reason: 'Super admin cross-tenant access' };
            }
        }
        // Check if subject has any role assignment for this tenant
        const assignments = this.policyEngine.getRoleAssignments(subjectId);
        const hasTenantAccess = assignments.some(a => a.active && (!a.tenantId || a.tenantId === tenantId));
        if (!hasTenantAccess) {
            return {
                passed: false,
                reason: `No access to tenant: ${tenantId}`,
            };
        }
        return { passed: true, reason: 'Tenant access verified' };
    }
    /**
     * Get service statistics
     */
    getStats() {
        return {
            ...this.policyEngine.getStats(),
            options: this.options,
        };
    }
    /**
     * Destroy the service
     */
    destroy() {
        this.policyEngine.destroy();
        logger.info('RBAC service destroyed');
    }
}
/**
 * Create a new RBAC service
 */
export function createRBACService(options) {
    return new RBACService(options);
}
/**
 * Create RBAC middleware
 */
export function rbacMiddleware(options = {}) {
    const rbacService = options.rbacService ?? createRBACService();
    const skipPaths = new Set(options.skipPaths ?? []);
    const defaultExtractSubject = (request) => {
        const user = request.user;
        if (!user?.sub && !user?.did)
            return undefined;
        return {
            id: user.did ?? user.sub,
            type: user.type ?? 'user',
            trustTier: user.trustTier,
            tenantId: user.tenantId,
        };
    };
    const extractSubject = options.extractSubject ?? defaultExtractSubject;
    return async (request, reply) => {
        // Skip configured paths
        if (skipPaths.has(request.url) || skipPaths.has(request.routeOptions.url ?? '')) {
            return;
        }
        const subject = extractSubject(request);
        // Build RBAC context
        const rbacContext = {
            rbacService: rbacService,
            subject: subject ? {
                id: subject.id,
                type: subject.type,
                trustTier: subject.trustTier ?? 0,
                roles: [],
                permissions: [],
            } : undefined,
            checkAccess: async (resource, action, resourceId) => {
                if (!subject) {
                    return {
                        permitted: false,
                        reason: 'No authenticated subject',
                        checkedPermissions: [`${resource}:${action}`],
                        evaluatedRoles: [],
                        decisionSource: 'default',
                        durationMs: 0,
                        evaluatedAt: new Date().toISOString(),
                    };
                }
                return rbacService.checkAccess({
                    subjectId: subject.id,
                    subjectType: subject.type,
                    resource,
                    action,
                    resourceId,
                    tenantId: subject.tenantId,
                    trustTier: subject.trustTier,
                    ipAddress: request.ip,
                    userAgent: request.headers['user-agent'],
                    requestId: request.id,
                });
            },
            requireAccess: async (resource, action, resourceId) => {
                if (!subject) {
                    throw new UnauthorizedError('Authentication required');
                }
                await rbacService.requireAccess({
                    subjectId: subject.id,
                    subjectType: subject.type,
                    resource,
                    action,
                    resourceId,
                    tenantId: subject.tenantId,
                    trustTier: subject.trustTier,
                    ipAddress: request.ip,
                    userAgent: request.headers['user-agent'],
                    requestId: request.id,
                });
            },
            hasPermission: async (permission) => {
                if (!subject)
                    return false;
                return rbacService.hasPermission(subject.id, permission, subject.tenantId);
            },
            requirePermission: async (permission) => {
                if (!subject) {
                    throw new UnauthorizedError('Authentication required');
                }
                await rbacService.requirePermission(subject.id, permission, subject.tenantId);
            },
        };
        // Populate roles and permissions if subject is available
        if (subject) {
            const roles = await rbacService.getSubjectRoles(subject.id, subject.tenantId);
            const permissions = await rbacService.getSubjectPermissions(subject.id, subject.tenantId);
            rbacContext.subject = {
                id: subject.id,
                type: subject.type,
                trustTier: subject.trustTier ?? 0,
                roles: roles.map(r => r.id),
                permissions: Array.from(permissions),
            };
        }
        request.securityRbac = rbacContext;
        // Check route-level permission if configured
        if (options.getRoutePermission) {
            const requiredPermission = options.getRoutePermission(request);
            if (requiredPermission) {
                await rbacContext.requirePermission(requiredPermission);
            }
        }
    };
}
/**
 * RBAC Fastify plugin
 */
const rbacPluginCallback = (fastify, options, done) => {
    try {
        const rbacService = options.rbacService ?? createRBACService();
        // Add RBAC middleware
        fastify.addHook('preHandler', rbacMiddleware({
            ...options,
            rbacService,
        }));
        // Decorate fastify instance with RBAC service
        fastify.decorate('rbacService', rbacService);
        logger.info('RBAC plugin registered');
        done();
    }
    catch (error) {
        done(error instanceof Error ? error : new Error(String(error)));
    }
};
/**
 * RBAC Fastify plugin
 */
export const rbacPlugin = fp(rbacPluginCallback, {
    name: 'vorion-rbac',
    fastify: '5.x',
});
/**
 * Create a preHandler that requires specific permissions
 */
export function requirePermissions(...permissions) {
    return async (request, reply) => {
        if (!request.securityRbac?.subject) {
            throw new UnauthorizedError('Authentication required');
        }
        for (const permission of permissions) {
            await request.securityRbac.requirePermission(permission);
        }
    };
}
/**
 * Create a preHandler that requires any of the specified permissions
 */
export function requireAnyPermission(...permissions) {
    return async (request, reply) => {
        if (!request.securityRbac?.subject) {
            throw new UnauthorizedError('Authentication required');
        }
        for (const permission of permissions) {
            const has = await request.securityRbac.hasPermission(permission);
            if (has)
                return;
        }
        throw new ForbiddenError(`Missing one of required permissions: ${permissions.join(', ')}`);
    };
}
/**
 * Create a preHandler that requires specific role
 */
export function requireRole(roleId) {
    return async (request, reply) => {
        if (!request.securityRbac?.subject) {
            throw new UnauthorizedError('Authentication required');
        }
        if (!request.securityRbac.subject.roles.includes(roleId)) {
            throw new ForbiddenError(`Missing required role: ${roleId}`);
        }
    };
}
/**
 * Create a preHandler that requires resource-specific access
 */
export function requireResourceAccess(resource, action, getResourceId) {
    return async (request, reply) => {
        if (!request.securityRbac) {
            throw new UnauthorizedError('RBAC not configured');
        }
        const resourceId = getResourceId?.(request);
        await request.securityRbac.requireAccess(resource, action, resourceId);
    };
}
/**
 * Create a preHandler that requires minimum trust tier
 */
export function requireTrustTier(minimumTier) {
    return async (request, reply) => {
        if (!request.securityRbac?.subject) {
            throw new UnauthorizedError('Authentication required');
        }
        if (request.securityRbac.subject.trustTier < minimumTier) {
            throw new ForbiddenError(`Insufficient trust tier: requires T${minimumTier}, has T${request.securityRbac.subject.trustTier}`);
        }
    };
}
// =============================================================================
// SINGLETON INSTANCE
// =============================================================================
let defaultRBACService = null;
/**
 * Get the default RBAC service singleton
 */
export function getRBACService(options) {
    if (!defaultRBACService) {
        defaultRBACService = createRBACService(options);
        logger.info('Default RBAC service created');
    }
    return defaultRBACService;
}
/**
 * Reset the default RBAC service (for testing)
 */
export function resetRBACService() {
    if (defaultRBACService) {
        defaultRBACService.destroy();
        defaultRBACService = null;
    }
}
//# sourceMappingURL=index.js.map