import { NextResponse } from 'next/server' import type { EntityManager } from '@mikro-orm/postgresql' import type { CommandRuntimeContext } from '@open-mercato/shared/lib/commands' import { CrudHttpError } from '@open-mercato/shared/lib/crud/errors' import { getCommandInterceptorHttpRejection } from '@open-mercato/shared/lib/commands/errors' import { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server' import { createRequestContainer } from '@open-mercato/shared/lib/di/container' import { Organization, Tenant } from '@open-mercato/core/modules/directory/data/entities' import type { SudoChallengeConfig } from '../../data/entities' import type { SudoAuthScope, SudoChallengeService, SudoChallengeServiceError, } from '../../services/SudoChallengeService' import { isSudoRequiredError } from '../../lib/sudo-middleware' import { localizeSecurityApiBody, securityApiError } from '../i18n' import { createLogger } from '@open-mercato/shared/lib/logger' const logger = createLogger('security').child({ component: 'sudo' }) type RequestContainer = Awaited> type Auth = NonNullable>> export type SudoRequestContext = { auth: Auth container: RequestContainer commandContext: CommandRuntimeContext sudoChallengeService: SudoChallengeService scope: SudoAuthScope } export function buildSudoAuthScope(auth: Auth): SudoAuthScope { return { tenantId: auth.tenantId ?? null, organizationId: auth.orgId ?? null, isSuperAdmin: auth.isSuperAdmin === true, } } export function toSudoConfigResponse(config: SudoChallengeConfig) { return { id: config.id, tenantId: config.tenantId ?? null, tenantName: null as string | null, organizationId: config.organizationId ?? null, organizationName: null as string | null, label: config.label ?? null, targetIdentifier: config.targetIdentifier, isEnabled: config.isEnabled, isDeveloperDefault: config.isDeveloperDefault, ttlSeconds: config.ttlSeconds, challengeMethod: config.challengeMethod, configuredBy: config.configuredBy ?? null, createdAt: config.createdAt.toISOString(), updatedAt: config.updatedAt.toISOString(), } } export async function attachSudoConfigScopeNames( container: RequestContainer, configs: SudoChallengeConfig[], ): Promise>> { if (configs.length === 0) return [] const em = container.resolve('em') const tenantIds = Array.from( new Set( configs .map((c) => c.tenantId ?? null) .filter((id): id is string => typeof id === 'string' && id.length > 0), ), ) const organizationIds = Array.from( new Set( configs .map((c) => c.organizationId ?? null) .filter((id): id is string => typeof id === 'string' && id.length > 0), ), ) const [tenants, organizations] = await Promise.all([ tenantIds.length ? em.find(Tenant, { id: { $in: tenantIds }, deletedAt: null }) : Promise.resolve([]), organizationIds.length ? em.find(Organization, { id: { $in: organizationIds }, deletedAt: null }) : Promise.resolve([]), ]) const tenantMap = tenants.reduce>((acc, tenant) => { const id = tenant?.id ? String(tenant.id) : null if (!id) return acc acc[id] = typeof tenant.name === 'string' && tenant.name.length > 0 ? tenant.name : id return acc }, {}) const organizationMap = organizations.reduce>((acc, org) => { const id = org?.id ? String(org.id) : null if (!id) return acc acc[id] = typeof org.name === 'string' && org.name.length > 0 ? org.name : id return acc }, {}) return configs.map((config) => { const response = toSudoConfigResponse(config) return { ...response, tenantName: response.tenantId ? tenantMap[response.tenantId] ?? response.tenantId : null, organizationName: response.organizationId ? organizationMap[response.organizationId] ?? response.organizationId : null, } }) } export async function resolveSudoContext(req: Request): Promise { const auth = await getAuthFromRequest(req) if (!auth?.sub) { return securityApiError(401, 'Unauthorized') } const container = await createRequestContainer() return { auth, container, commandContext: { container, auth, organizationScope: null, selectedOrganizationId: auth.orgId ?? null, organizationIds: auth.orgId ? [auth.orgId] : null, request: req, }, sudoChallengeService: container.resolve('sudoChallengeService'), scope: buildSudoAuthScope(auth), } } export async function mapSudoError(error: unknown): Promise { if (error instanceof CrudHttpError) { return NextResponse.json(await localizeSecurityApiBody(error.body), { status: error.status }) } const interceptorRejection = getCommandInterceptorHttpRejection(error) if (interceptorRejection) { return NextResponse.json(interceptorRejection.body, { status: interceptorRejection.status }) } if (isSudoRequiredError(error)) { return NextResponse.json(await localizeSecurityApiBody(error.body), { status: error.statusCode }) } if (isSudoChallengeServiceError(error)) { return securityApiError(error.statusCode, error.message) } logger.error('Sudo route failure', { err: error }) return securityApiError(500, 'Failed to process sudo request.') } function isSudoChallengeServiceError(error: unknown): error is SudoChallengeServiceError { return error instanceof Error && error.name === 'SudoChallengeServiceError' && typeof (error as Partial).statusCode === 'number' }