/**
 * Tenant Context - Secure Tenant ID Handling
 *
 * Provides branded types and secure context creation to prevent
 * tenant ID injection attacks. Tenant IDs can ONLY be extracted
 * from validated JWT tokens, never from request parameters or body.
 *
 * HIGH SECURITY: Prevents cross-tenant data access vulnerability.
 *
 * @packageDocumentation
 */
import { z } from 'zod';
import { createLogger } from './logger.js';
import { UnauthorizedError } from './errors.js';
const logger = createLogger({ component: 'tenant-context' });
/**
 * Zod schema for validating JWT payload for tenant context
 */
export const validatedJwtPayloadSchema = z.object({
    sub: z.string().min(1, 'Subject (sub) claim is required'),
    tid: z.string().min(1, 'Tenant ID (tid) claim is required'),
    iat: z.number().int().positive('Issued at (iat) must be a positive integer'),
    exp: z.number().int().positive('Expiration (exp) must be a positive integer'),
    roles: z.array(z.string()).optional().default([]),
    permissions: z.array(z.string()).optional().default([]),
});
/**
 * Internal function to brand a string as ValidatedTenantId.
 * This should ONLY be called after JWT validation.
 *
 * @internal
 */
function brandTenantId(id) {
    return id;
}
/**
 * Internal function to brand a string as ValidatedUserId.
 * This should ONLY be called after JWT validation.
 *
 * @internal
 */
function brandUserId(id) {
    return id;
}
/**
 * Create a TenantContext from a validated JWT payload.
 *
 * This is the ONLY legitimate way to create a TenantContext.
 * The JWT must already be validated (signature verified, not expired).
 *
 * @param jwt - Validated JWT payload (must have passed signature verification)
 * @param options - Additional context options
 * @returns Immutable TenantContext
 *
 * @example
 * // After JWT verification in middleware:
 * const ctx = createTenantContext(validatedJwt, { traceId: request.id });
 * request.tenantContext = ctx;
 */
export function createTenantContext(jwt, options) {
    // Validate the JWT payload structure
    const validated = validatedJwtPayloadSchema.parse(jwt);
    // Create immutable context with branded IDs
    const context = Object.freeze({
        tenantId: brandTenantId(validated.tid),
        userId: brandUserId(validated.sub),
        roles: Object.freeze([...(validated.roles ?? [])]),
        permissions: Object.freeze([...(validated.permissions ?? [])]),
        traceId: options?.traceId,
        sessionId: options?.sessionId,
        createdAt: Date.now(),
    });
    logger.debug({
        tenantId: validated.tid,
        userId: validated.sub,
        roles: validated.roles,
        traceId: options?.traceId,
    }, 'TenantContext created from validated JWT');
    return context;
}
// =============================================================================
// MIDDLEWARE
// =============================================================================
/**
 * Extract and validate tenant context from JWT token.
 *
 * This middleware:
 * 1. Extracts the Bearer token from Authorization header
 * 2. Verifies the JWT signature
 * 3. Validates required claims (sub, tid)
 * 4. Creates an immutable TenantContext
 * 5. Attaches it to request.tenantContext
 *
 * SECURITY: Tenant ID is extracted ONLY from the JWT, never from
 * request body, query params, or other user-controlled input.
 *
 * @example
 * // Register as route preHandler
 * fastify.post('/api/v1/policies', {
 *   preHandler: [requireTenantContext],
 * }, async (request, reply) => {
 *   const ctx = request.tenantContext!;
 *   const policy = await policyService.create(ctx, input);
 * });
 */
export async function requireTenantContext(request, reply) {
    try {
        // Use Fastify's JWT verification (signature + expiration)
        const jwt = await request.jwtVerify();
        // Validate required claims
        const validation = validatedJwtPayloadSchema.safeParse(jwt);
        if (!validation.success) {
            logger.warn({
                errors: validation.error.errors,
                requestId: request.id,
            }, 'SECURITY: JWT missing required tenant claims');
            reply.status(401).send({
                error: {
                    code: 'INVALID_TOKEN',
                    message: 'Token missing required tenant claims',
                },
            });
            return;
        }
        // Create immutable tenant context
        request.tenantContext = createTenantContext(validation.data, {
            traceId: request.id,
        });
        logger.debug({
            tenantId: request.tenantContext.tenantId,
            userId: request.tenantContext.userId,
            requestId: request.id,
        }, 'Tenant context extracted from JWT');
    }
    catch (error) {
        // Handle JWT verification errors
        logger.warn({
            error: error instanceof Error ? error.message : 'Unknown error',
            requestId: request.id,
        }, 'SECURITY: JWT verification failed');
        reply.status(401).send({
            error: {
                code: 'UNAUTHORIZED',
                message: 'Invalid or expired token',
            },
        });
    }
}
/**
 * Get the tenant context from a request, throwing if not available.
 *
 * Use this in route handlers after `requireTenantContext` middleware.
 *
 * @param request - Fastify request
 * @returns TenantContext
 * @throws UnauthorizedError if context is not set
 *
 * @example
 * fastify.get('/policies', {
 *   preHandler: [requireTenantContext],
 * }, async (request) => {
 *   const ctx = getTenantContext(request);
 *   return policyService.list(ctx);
 * });
 */
export function getTenantContext(request) {
    if (!request.tenantContext) {
        throw new UnauthorizedError('Tenant context not available - ensure requireTenantContext middleware is used');
    }
    return request.tenantContext;
}
// =============================================================================
// UTILITY FUNCTIONS
// =============================================================================
/**
 * Check if user has any of the specified roles
 */
export function hasRole(ctx, ...roles) {
    return roles.some((role) => ctx.roles.includes(role));
}
/**
 * Check if user has all of the specified permissions
 */
export function hasPermission(ctx, ...permissions) {
    return permissions.every((perm) => ctx.permissions.includes(perm));
}
/**
 * Check if user has any of the specified permissions
 */
export function hasAnyPermission(ctx, ...permissions) {
    return permissions.some((perm) => ctx.permissions.includes(perm));
}
/**
 * Check if user is an admin (has admin or super_admin role)
 */
export function isAdmin(ctx) {
    return hasRole(ctx, 'admin', 'super_admin', 'tenant:admin');
}
/**
 * Check if user is a super admin (cross-tenant access)
 */
export function isSuperAdmin(ctx) {
    return hasRole(ctx, 'super_admin');
}
/**
 * Create a tenant context for system operations (internal use only).
 *
 * WARNING: This bypasses JWT validation and should ONLY be used for:
 * - Background jobs
 * - Internal service-to-service calls
 * - Scheduled tasks
 *
 * NEVER use this for handling user requests.
 *
 * @internal
 */
export function createSystemTenantContext(tenantId, options) {
    logger.info({
        tenantId,
        userId: options?.userId ?? 'system',
        traceId: options?.traceId,
        caller: new Error().stack?.split('\n')[2]?.trim(),
    }, 'AUDIT: System tenant context created (bypassing JWT)');
    return Object.freeze({
        tenantId: brandTenantId(tenantId),
        userId: brandUserId(options?.userId ?? 'system'),
        roles: Object.freeze(options?.roles ?? ['system']),
        permissions: Object.freeze(['*']),
        traceId: options?.traceId,
        createdAt: Date.now(),
    });
}
// =============================================================================
// LEGACY COMPATIBILITY
// =============================================================================
/**
 * Extract raw tenant ID from context for legacy code migration.
 *
 * @deprecated Use TenantContext directly in service methods.
 *             This function exists only for incremental migration.
 *
 * @param ctx - TenantContext
 * @returns Raw tenant ID string
 */
export function extractTenantId(ctx) {
    return ctx.tenantId;
}
/**
 * Extract raw user ID from context for legacy code migration.
 *
 * @deprecated Use TenantContext directly in service methods.
 *
 * @param ctx - TenantContext
 * @returns Raw user ID string
 */
export function extractUserId(ctx) {
    return ctx.userId;
}
//# sourceMappingURL=tenant-context.js.map