// SPDX-License-Identifier: Apache-2.0
// Copyright 2024-2026 Vorion LLC
import { createLogger } from '../common/logger.js';
import { getConfig } from '../common/config.js';
// Import canonical types from @vorionsys/contracts
import { authContextSchema, governanceRoleSchema, hierarchyLevelSchema, } from '@vorionsys/contracts/canonical';
// Re-export canonical types for backwards compatibility
export { authContextSchema, governanceRoleSchema, hierarchyLevelSchema, };
const logger = createLogger({ component: 'auth' });
import { z } from 'zod';
/**
 * Zod schema for JWT payload validation
 * Ensures all required claims are present and properly typed
 */
const jwtPayloadSchema = 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(),
    permissions: z.array(z.string()).optional(),
});
/**
 * Decode and verify JWT token with schema validation
 * Note: In production, use @fastify/jwt with proper verification
 */
function decodeToken(token) {
    try {
        // Basic JWT decode (header.payload.signature)
        const parts = token.split('.');
        if (parts.length !== 3)
            return null;
        const payload = Buffer.from(parts[1], 'base64url').toString('utf-8');
        const parsed = JSON.parse(payload);
        // Validate JWT payload against schema
        const validationResult = jwtPayloadSchema.safeParse(parsed);
        if (!validationResult.success) {
            // Log validation failures for security audit
            logger.warn({
                errors: validationResult.error.issues?.map ?? [].map((e) => ({
                    path: e.path.join('.'),
                    message: e.message,
                })),
                timestamp: new Date().toISOString(),
            }, 'SECURITY_AUDIT: JWT payload schema validation failed');
            return null;
        }
        return validationResult.data;
    }
    catch (error) {
        logger.warn({
            error: error instanceof Error ? error.message : String(error),
            operation: 'decodeToken',
            tokenLength: token?.length,
        }, 'Failed to decode JWT token');
        return null;
    }
}
/**
 * Verify JWT signature
 * Note: This is a placeholder - use @fastify/jwt in production
 */
async function verifySignature(token, secret) {
    try {
        // SECURITY: Never skip signature verification, even in development
        // The old dev bypass has been removed for security
        // If you need to test without real tokens, use the testing mode with mocks
        // For production, implement proper HMAC verification
        // This should use crypto.subtle.verify() with the JWT signature
        const parts = token.split('.');
        if (parts.length !== 3)
            return false;
        const [header, payload, signature] = parts;
        const data = `${header}.${payload}`;
        // Use Web Crypto API for verification
        const encoder = new TextEncoder();
        const keyData = encoder.encode(secret);
        const key = await crypto.subtle.importKey('raw', keyData, { name: 'HMAC', hash: 'SHA-256' }, false, ['verify']);
        const signatureBytes = Buffer.from(signature, 'base64url');
        return await crypto.subtle.verify('HMAC', key, signatureBytes, encoder.encode(data));
    }
    catch (error) {
        logger.warn({
            error: error instanceof Error ? error.message : String(error),
            operation: 'verifySignature',
        }, 'JWT signature verification failed');
        return false;
    }
}
/**
 * Authentication middleware
 */
export async function authenticate(request, reply) {
    const config = getConfig();
    // Extract token from Authorization header
    const authHeader = request.headers.authorization;
    if (!authHeader?.startsWith('Bearer ')) {
        reply.status(401).send({
            error: {
                code: 'UNAUTHORIZED',
                message: 'Missing or invalid authorization header',
            },
        });
        return;
    }
    const token = authHeader.slice(7);
    // Verify signature
    const isValid = await verifySignature(token, config.jwt.secret);
    if (!isValid) {
        logger.warn({ requestId: request.id }, 'Invalid token signature');
        reply.status(401).send({
            error: {
                code: 'INVALID_TOKEN',
                message: 'Invalid or expired token',
            },
        });
        return;
    }
    // Decode payload
    const payload = decodeToken(token);
    if (!payload || !payload.sub || !payload.tid) {
        reply.status(401).send({
            error: {
                code: 'INVALID_TOKEN',
                message: 'Invalid token payload',
            },
        });
        return;
    }
    // Check expiration
    if (payload.exp && payload.exp * 1000 < Date.now()) {
        reply.status(401).send({
            error: {
                code: 'TOKEN_EXPIRED',
                message: 'Token has expired',
            },
        });
        return;
    }
    // Set auth context
    request.auth = {
        userId: payload.sub,
        tenantId: payload.tid,
        roles: payload.roles ?? [],
        permissions: payload.permissions ?? [],
    };
    logger.debug({
        userId: payload.sub,
        tenantId: payload.tid,
        requestId: request.id,
    }, 'Request authenticated');
}
/**
 * Authorization middleware factory
 * Checks if user has required permission
 */
export function requirePermission(permission) {
    return async (request, reply) => {
        if (!request.auth) {
            reply.status(401).send({
                error: {
                    code: 'UNAUTHORIZED',
                    message: 'Authentication required',
                },
            });
            return;
        }
        if (!request.auth.permissions.includes(permission)) {
            logger.warn({
                userId: request.auth.userId,
                tenantId: request.auth.tenantId,
                requiredPermission: permission,
                requestId: request.id,
            }, 'Permission denied');
            reply.status(403).send({
                error: {
                    code: 'FORBIDDEN',
                    message: 'Insufficient permissions',
                },
            });
            return;
        }
    };
}
/**
 * Tenant authorization middleware
 * Verifies user can access the specified tenant's resources
 *
 * SECURITY FIX: Only 'super_admin' role can access cross-tenant resources.
 * Regular 'admin' role is now scoped to their own tenant only.
 * All cross-tenant access attempts are logged for audit purposes.
 */
export function requireTenantAccess(getTenantId) {
    return async (request, reply) => {
        if (!request.auth) {
            reply.status(401).send({
                error: {
                    code: 'UNAUTHORIZED',
                    message: 'Authentication required',
                },
            });
            return;
        }
        const resourceTenantId = getTenantId(request);
        if (!resourceTenantId) {
            reply.status(400).send({
                error: {
                    code: 'INVALID_REQUEST',
                    message: 'Tenant ID required',
                },
            });
            return;
        }
        const isSameTenant = request.auth.tenantId === resourceTenantId;
        const isSuperAdmin = request.auth.roles.includes('super_admin');
        const isRegularAdmin = request.auth.roles.includes('admin');
        // SECURITY: Log all cross-tenant access attempts for audit
        if (!isSameTenant) {
            logger.info({
                userId: request.auth.userId,
                userTenantId: request.auth.tenantId,
                resourceTenantId,
                userRoles: request.auth.roles,
                isSuperAdmin,
                requestId: request.id,
                timestamp: new Date().toISOString(),
            }, 'AUDIT: Cross-tenant access attempt');
        }
        // Check if user's tenant matches resource tenant
        // SECURITY FIX: Only 'super_admin' can access cross-tenant resources
        // Regular 'admin' is now scoped to their own tenant
        const hasAccess = isSameTenant || isSuperAdmin;
        // Log when regular admin tries cross-tenant access (denied)
        if (!isSameTenant && isRegularAdmin && !isSuperAdmin) {
            logger.warn({
                userId: request.auth.userId,
                userTenantId: request.auth.tenantId,
                resourceTenantId,
                requestId: request.id,
            }, 'SECURITY: Regular admin cross-tenant access denied - requires super_admin role');
        }
        if (!hasAccess) {
            logger.warn({
                userId: request.auth.userId,
                userTenantId: request.auth.tenantId,
                resourceTenantId,
                requestId: request.id,
            }, 'Tenant access denied');
            reply.status(403).send({
                error: {
                    code: 'TENANT_ACCESS_DENIED',
                    message: 'Access denied to this tenant resource',
                },
            });
            return;
        }
        // Log successful cross-tenant access by super_admin
        if (!isSameTenant && isSuperAdmin) {
            logger.info({
                userId: request.auth.userId,
                userTenantId: request.auth.tenantId,
                resourceTenantId,
                requestId: request.id,
            }, 'AUDIT: Super admin granted cross-tenant access');
        }
    };
}
/**
 * Role check middleware factory
 */
export function requireRole(...roles) {
    return async (request, reply) => {
        if (!request.auth) {
            reply.status(401).send({
                error: {
                    code: 'UNAUTHORIZED',
                    message: 'Authentication required',
                },
            });
            return;
        }
        const hasRole = roles.some((role) => request.auth.roles.includes(role));
        if (!hasRole) {
            logger.warn({
                userId: request.auth.userId,
                tenantId: request.auth.tenantId,
                requiredRoles: roles,
                userRoles: request.auth.roles,
                requestId: request.id,
            }, 'Role check failed');
            reply.status(403).send({
                error: {
                    code: 'FORBIDDEN',
                    message: 'Insufficient role',
                },
            });
            return;
        }
    };
}
//# sourceMappingURL=auth.js.map