// src/lib/tenantAccess.ts import type { Access, PayloadRequest } from 'payload' /** * Ermittelt die Tenant-ID aus dem Request-Host. * Gleicht die Domain mit der tenants-Collection ab. */ export async function getTenantIdFromHost(req: PayloadRequest): Promise { try { // Host-Header extrahieren (unterstützt verschiedene Formate) const headers = req.headers as Headers | Record const host = typeof headers.get === 'function' ? headers.get('host') : (headers as Record)['host'] if (!host || typeof host !== 'string') { return null } // Domain normalisieren: Port und www entfernen const domain = host.split(':')[0].replace(/^www\./, '').toLowerCase().trim() if (!domain) { return null } // Tenant aus Datenbank suchen (domains ist ein Array mit domain-Subfeld) const result = await req.payload.find({ collection: 'tenants', where: { 'domains.domain': { equals: domain }, }, limit: 1, depth: 0, }) if (result.docs.length > 0 && result.docs[0]?.id) { return Number(result.docs[0].id) } return null } catch (error) { console.error('[TenantAccess] Error resolving tenant from host:', error) return null } } /** * Extracts tenant ID from the where[tenant][equals] query parameter. * Used as fallback when the Host header doesn't match any tenant domain * (e.g. when the CMS is accessed via cms.c2sgmbh.de by frontend API clients). */ function getTenantIdFromQuery(req: PayloadRequest): number | null { try { const url = req.url ? new URL(req.url, 'http://localhost') : null const param = url?.searchParams.get('where[tenant][equals]') if (param) { const id = Number(param) if (!isNaN(id) && id > 0) return id } return null } catch { return null } } /** * Access-Control für öffentlich lesbare, aber tenant-isolierte Collections. * * - Authentifizierte Admin-User: Voller Lesezugriff * - Anonyme Requests: Nur Daten des eigenen Tenants * * Tenant resolution order: * 1. Host header (matches tenant domain config) * 2. where[tenant][equals] query parameter (for API clients like contracts) * * The returned access filter ensures tenant isolation regardless of which * method resolved the tenant ID. */ export const tenantScopedPublicRead: Access = async ({ req }) => { const hasUser = !!req.user const hostTenantId = await getTenantIdFromHost(req) const queryTenantId = getTenantIdFromQuery(req) const tenantId = hostTenantId ?? queryTenantId return hasUser ? true : tenantId ? { tenant: { equals: tenantId } } : false } /** * Access-Control: Nur authentifizierte User */ export const authenticatedOnly: Access = ({ req }) => { return !!req.user }