mirror of
https://github.com/complexcaresolutions/cms.c2sgmbh.git
synced 2026-03-17 19:44:12 +00:00
fix: enforce mandatory tenant parameter on frontend API routes
Custom API routes at /api/posts, /api/search, and /api/search/suggestions used payload.find() with overrideAccess:true (default) and optional tenant filtering. Without a ?tenant= parameter, ALL data from ALL tenants was returned — causing cross-tenant data leaks (e.g. sensualmoment.de Journal showing blogwoman articles). Now all three routes require a tenant parameter (400 error without it). Also accepts where[tenant][equals] format for compatibility with payload-contracts API clients. Removed debug logging from tenantAccess.ts. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
eb31df112b
commit
8cb04fd130
4 changed files with 54 additions and 46 deletions
|
|
@ -46,11 +46,27 @@ export async function GET(request: NextRequest) {
|
||||||
const { searchParams } = new URL(request.url)
|
const { searchParams } = new URL(request.url)
|
||||||
const category = searchParams.get('category')?.trim()
|
const category = searchParams.get('category')?.trim()
|
||||||
const type = searchParams.get('type')?.trim() as 'blog' | 'news' | 'press' | 'announcement' | undefined
|
const type = searchParams.get('type')?.trim() as 'blog' | 'news' | 'press' | 'announcement' | undefined
|
||||||
const tenantParam = searchParams.get('tenant')
|
const tenantParam = searchParams.get('tenant') || searchParams.get('where[tenant][equals]')
|
||||||
const pageParam = searchParams.get('page')
|
const pageParam = searchParams.get('page')
|
||||||
const limitParam = searchParams.get('limit')
|
const limitParam = searchParams.get('limit')
|
||||||
const localeParam = searchParams.get('locale')?.trim()
|
const localeParam = searchParams.get('locale')?.trim()
|
||||||
|
|
||||||
|
// Tenant is required for tenant isolation
|
||||||
|
if (!tenantParam) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: 'Tenant ID is required. Use ?tenant=<id> parameter.' },
|
||||||
|
{ status: 400 },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const tenantId = parseInt(tenantParam, 10)
|
||||||
|
if (isNaN(tenantId) || tenantId < 1) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: 'Invalid tenant ID' },
|
||||||
|
{ status: 400 },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
// Validate locale
|
// Validate locale
|
||||||
const validLocales = ['de', 'en']
|
const validLocales = ['de', 'en']
|
||||||
const locale = localeParam && validLocales.includes(localeParam) ? localeParam : 'de'
|
const locale = localeParam && validLocales.includes(localeParam) ? localeParam : 'de'
|
||||||
|
|
@ -70,15 +86,6 @@ export async function GET(request: NextRequest) {
|
||||||
Math.max(1, parseInt(limitParam || String(DEFAULT_LIMIT), 10) || DEFAULT_LIMIT),
|
Math.max(1, parseInt(limitParam || String(DEFAULT_LIMIT), 10) || DEFAULT_LIMIT),
|
||||||
MAX_LIMIT
|
MAX_LIMIT
|
||||||
)
|
)
|
||||||
const tenantId = tenantParam ? parseInt(tenantParam, 10) : undefined
|
|
||||||
|
|
||||||
// Validate tenant ID if provided
|
|
||||||
if (tenantParam && (isNaN(tenantId!) || tenantId! < 1)) {
|
|
||||||
return NextResponse.json(
|
|
||||||
{ error: 'Invalid tenant ID' },
|
|
||||||
{ status: 400 }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get payload instance
|
// Get payload instance
|
||||||
const payload = await getPayload({ config })
|
const payload = await getPayload({ config })
|
||||||
|
|
|
||||||
|
|
@ -53,6 +53,22 @@ export async function GET(request: NextRequest) {
|
||||||
const offsetParam = searchParams.get('offset')
|
const offsetParam = searchParams.get('offset')
|
||||||
const localeParam = searchParams.get('locale')?.trim()
|
const localeParam = searchParams.get('locale')?.trim()
|
||||||
|
|
||||||
|
// Tenant is required for tenant isolation
|
||||||
|
if (!tenantParam) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: 'Tenant ID is required. Use ?tenant=<id> parameter.' },
|
||||||
|
{ status: 400 },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const tenantId = parseInt(tenantParam, 10)
|
||||||
|
if (isNaN(tenantId) || tenantId < 1) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: 'Invalid tenant ID' },
|
||||||
|
{ status: 400 },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
// Validate locale
|
// Validate locale
|
||||||
const validLocales = ['de', 'en']
|
const validLocales = ['de', 'en']
|
||||||
const locale = localeParam && validLocales.includes(localeParam) ? localeParam : 'de'
|
const locale = localeParam && validLocales.includes(localeParam) ? localeParam : 'de'
|
||||||
|
|
@ -87,15 +103,6 @@ export async function GET(request: NextRequest) {
|
||||||
MAX_LIMIT
|
MAX_LIMIT
|
||||||
)
|
)
|
||||||
const offset = Math.max(0, parseInt(offsetParam || '0', 10) || 0)
|
const offset = Math.max(0, parseInt(offsetParam || '0', 10) || 0)
|
||||||
const tenantId = tenantParam ? parseInt(tenantParam, 10) : undefined
|
|
||||||
|
|
||||||
// Validate tenant ID if provided
|
|
||||||
if (tenantParam && (isNaN(tenantId!) || tenantId! < 1)) {
|
|
||||||
return NextResponse.json(
|
|
||||||
{ error: 'Invalid tenant ID' },
|
|
||||||
{ status: 400 }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get payload instance
|
// Get payload instance
|
||||||
const payload = await getPayload({ config })
|
const payload = await getPayload({ config })
|
||||||
|
|
|
||||||
|
|
@ -51,6 +51,22 @@ export async function GET(request: NextRequest) {
|
||||||
const limitParam = searchParams.get('limit')
|
const limitParam = searchParams.get('limit')
|
||||||
const localeParam = searchParams.get('locale')?.trim()
|
const localeParam = searchParams.get('locale')?.trim()
|
||||||
|
|
||||||
|
// Tenant is required for tenant isolation
|
||||||
|
if (!tenantParam) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: 'Tenant ID is required. Use ?tenant=<id> parameter.' },
|
||||||
|
{ status: 400 },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const tenantId = parseInt(tenantParam, 10)
|
||||||
|
if (isNaN(tenantId) || tenantId < 1) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: 'Invalid tenant ID' },
|
||||||
|
{ status: 400 },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
// Validate locale
|
// Validate locale
|
||||||
const validLocales = ['de', 'en']
|
const validLocales = ['de', 'en']
|
||||||
const locale = localeParam && validLocales.includes(localeParam) ? localeParam : 'de'
|
const locale = localeParam && validLocales.includes(localeParam) ? localeParam : 'de'
|
||||||
|
|
@ -80,15 +96,6 @@ export async function GET(request: NextRequest) {
|
||||||
Math.max(1, parseInt(limitParam || String(DEFAULT_LIMIT), 10) || DEFAULT_LIMIT),
|
Math.max(1, parseInt(limitParam || String(DEFAULT_LIMIT), 10) || DEFAULT_LIMIT),
|
||||||
MAX_LIMIT
|
MAX_LIMIT
|
||||||
)
|
)
|
||||||
const tenantId = tenantParam ? parseInt(tenantParam, 10) : undefined
|
|
||||||
|
|
||||||
// Validate tenant ID if provided
|
|
||||||
if (tenantParam && (isNaN(tenantId!) || tenantId! < 1)) {
|
|
||||||
return NextResponse.json(
|
|
||||||
{ error: 'Invalid tenant ID' },
|
|
||||||
{ status: 400 }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get payload instance
|
// Get payload instance
|
||||||
const payload = await getPayload({ config })
|
const payload = await getPayload({ config })
|
||||||
|
|
|
||||||
|
|
@ -80,25 +80,12 @@ function getTenantIdFromQuery(req: PayloadRequest): number | null {
|
||||||
* method resolved the tenant ID.
|
* method resolved the tenant ID.
|
||||||
*/
|
*/
|
||||||
export const tenantScopedPublicRead: Access = async ({ req }) => {
|
export const tenantScopedPublicRead: Access = async ({ req }) => {
|
||||||
// Authentifizierte Admins dürfen alles lesen
|
const hasUser = !!req.user
|
||||||
if (req.user) {
|
const hostTenantId = await getTenantIdFromHost(req)
|
||||||
return true
|
const queryTenantId = getTenantIdFromQuery(req)
|
||||||
}
|
const tenantId = hostTenantId ?? queryTenantId
|
||||||
|
|
||||||
// Anonyme Requests: Tenant aus Domain oder Query-Parameter ermitteln
|
return hasUser ? true : tenantId ? { tenant: { equals: tenantId } } : false
|
||||||
const tenantId = (await getTenantIdFromHost(req)) ?? getTenantIdFromQuery(req)
|
|
||||||
|
|
||||||
if (!tenantId) {
|
|
||||||
// Weder gültige Domain noch Tenant-Parameter → kein Zugriff
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Nur Dokumente des eigenen Tenants zurückgeben
|
|
||||||
return {
|
|
||||||
tenant: {
|
|
||||||
equals: tenantId,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue