mirror of
https://github.com/complexcaresolutions/cms.c2sgmbh.git
synced 2026-03-17 20:54:11 +00:00
- Add Products collection with comprehensive fields (pricing, inventory, SEO, CTA) - Add ProductCategories collection with hierarchical structure - Implement CI/CD pipeline with GitHub Actions (lint, typecheck, test, build, e2e) - Add access control test utilities and unit tests - Fix Posts API to include category field for backwards compatibility - Update ESLint config with ignores for migrations and admin components - Add centralized access control functions in src/lib/access - Add db-direct.sh utility script for database access 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
398 lines
13 KiB
TypeScript
398 lines
13 KiB
TypeScript
/**
|
|
* Field-Level Access Control Unit Tests
|
|
*
|
|
* Tests for field-level access control patterns.
|
|
* Covers: SMTP password protection, sensitive field hiding, tenant membership
|
|
*
|
|
* IMPORTANT: These tests import the actual field access functions from @/lib/access
|
|
* to ensure regressions in live collections are detected.
|
|
*/
|
|
|
|
import { describe, it, expect, vi } from 'vitest'
|
|
import type { FieldAccess, PayloadRequest } from 'payload'
|
|
import {
|
|
createSuperAdmin,
|
|
createTenantUser,
|
|
createMockPayloadRequest,
|
|
TEST_USERS,
|
|
} from '../../helpers/access-control-test-utils'
|
|
|
|
// ============================================================================
|
|
// Import REAL field access functions from centralized library
|
|
// ============================================================================
|
|
|
|
import {
|
|
neverReadable,
|
|
superAdminOnlyField,
|
|
authenticatedOnlyField,
|
|
tenantMemberField,
|
|
} from '@/lib/access'
|
|
|
|
// ============================================================================
|
|
// Field Access Function Types
|
|
// ============================================================================
|
|
|
|
interface MockFieldAccessArgs {
|
|
req: ReturnType<typeof createMockPayloadRequest>
|
|
doc?: Record<string, unknown>
|
|
data?: Record<string, unknown>
|
|
siblingData?: Record<string, unknown>
|
|
}
|
|
|
|
// ============================================================================
|
|
// Execute Field Access Helper
|
|
// ============================================================================
|
|
|
|
async function executeFieldAccess(
|
|
accessFn: FieldAccess,
|
|
args: MockFieldAccessArgs,
|
|
): Promise<boolean> {
|
|
const result = await accessFn({
|
|
req: args.req as unknown as PayloadRequest,
|
|
doc: args.doc,
|
|
data: args.data,
|
|
siblingData: args.siblingData,
|
|
})
|
|
|
|
return result
|
|
}
|
|
|
|
// ============================================================================
|
|
// SMTP Password Field Tests (neverReadable)
|
|
// ============================================================================
|
|
|
|
describe('SMTP Password Field Access (neverReadable)', () => {
|
|
describe('Read Access', () => {
|
|
it('blocks read for super admin', async () => {
|
|
const request = createMockPayloadRequest(TEST_USERS.superAdmin)
|
|
const result = await executeFieldAccess(neverReadable, { req: request })
|
|
|
|
expect(result).toBe(false)
|
|
})
|
|
|
|
it('blocks read for regular user', async () => {
|
|
const request = createMockPayloadRequest(TEST_USERS.porwollUser)
|
|
const result = await executeFieldAccess(neverReadable, { req: request })
|
|
|
|
expect(result).toBe(false)
|
|
})
|
|
|
|
it('blocks read for anonymous user', async () => {
|
|
const request = createMockPayloadRequest(null)
|
|
const result = await executeFieldAccess(neverReadable, { req: request })
|
|
|
|
expect(result).toBe(false)
|
|
})
|
|
|
|
it('ensures password never appears in API response', async () => {
|
|
// This tests the intent: no one should ever read the SMTP password via API
|
|
const users = [
|
|
TEST_USERS.superAdmin,
|
|
TEST_USERS.porwollUser,
|
|
TEST_USERS.multiTenantUser,
|
|
null,
|
|
]
|
|
|
|
for (const user of users) {
|
|
const request = createMockPayloadRequest(user)
|
|
const result = await executeFieldAccess(neverReadable, { req: request })
|
|
expect(result).toBe(false)
|
|
}
|
|
})
|
|
})
|
|
})
|
|
|
|
// ============================================================================
|
|
// Super Admin Only Field Tests
|
|
// ============================================================================
|
|
|
|
describe('Super Admin Only Field Access (superAdminOnlyField)', () => {
|
|
it('grants access to super admin', async () => {
|
|
const request = createMockPayloadRequest(TEST_USERS.superAdmin)
|
|
const result = await executeFieldAccess(superAdminOnlyField, { req: request })
|
|
|
|
expect(result).toBe(true)
|
|
})
|
|
|
|
it('denies access to regular tenant user', async () => {
|
|
const request = createMockPayloadRequest(TEST_USERS.porwollUser)
|
|
const result = await executeFieldAccess(superAdminOnlyField, { req: request })
|
|
|
|
expect(result).toBe(false)
|
|
})
|
|
|
|
it('denies access to multi-tenant user without super admin', async () => {
|
|
const request = createMockPayloadRequest(TEST_USERS.multiTenantUser)
|
|
const result = await executeFieldAccess(superAdminOnlyField, { req: request })
|
|
|
|
expect(result).toBe(false)
|
|
})
|
|
|
|
it('denies access to anonymous user', async () => {
|
|
const request = createMockPayloadRequest(null)
|
|
const result = await executeFieldAccess(superAdminOnlyField, { req: request })
|
|
|
|
expect(result).toBe(false)
|
|
})
|
|
})
|
|
|
|
// ============================================================================
|
|
// Authenticated Field Tests
|
|
// ============================================================================
|
|
|
|
describe('Authenticated Field Access (authenticatedOnlyField)', () => {
|
|
it('grants access to any authenticated user', async () => {
|
|
const users = [
|
|
TEST_USERS.superAdmin,
|
|
TEST_USERS.porwollUser,
|
|
TEST_USERS.c2sUser,
|
|
TEST_USERS.multiTenantUser,
|
|
]
|
|
|
|
for (const user of users) {
|
|
const request = createMockPayloadRequest(user)
|
|
const result = await executeFieldAccess(authenticatedOnlyField, { req: request })
|
|
expect(result).toBe(true)
|
|
}
|
|
})
|
|
|
|
it('denies access to anonymous user', async () => {
|
|
const request = createMockPayloadRequest(null)
|
|
const result = await executeFieldAccess(authenticatedOnlyField, { req: request })
|
|
|
|
expect(result).toBe(false)
|
|
})
|
|
})
|
|
|
|
// ============================================================================
|
|
// Tenant Member Field Tests
|
|
// ============================================================================
|
|
|
|
describe('Tenant Member Field Access (tenantMemberField)', () => {
|
|
describe('Authentication Checks', () => {
|
|
it('denies anonymous users', async () => {
|
|
const request = createMockPayloadRequest(null)
|
|
const result = await executeFieldAccess(tenantMemberField, {
|
|
req: request,
|
|
doc: { tenant: 1 },
|
|
})
|
|
|
|
expect(result).toBe(false)
|
|
})
|
|
|
|
it('grants super admin access to any tenant document', async () => {
|
|
const request = createMockPayloadRequest(TEST_USERS.superAdmin)
|
|
const result = await executeFieldAccess(tenantMemberField, {
|
|
req: request,
|
|
doc: { tenant: 999 }, // Arbitrary tenant
|
|
})
|
|
|
|
expect(result).toBe(true)
|
|
})
|
|
})
|
|
|
|
describe('Tenant Membership', () => {
|
|
it('grants user access to own tenant document', async () => {
|
|
const request = createMockPayloadRequest(TEST_USERS.porwollUser) // tenant 1
|
|
const result = await executeFieldAccess(tenantMemberField, {
|
|
req: request,
|
|
doc: { tenant: 1 },
|
|
})
|
|
|
|
expect(result).toBe(true)
|
|
})
|
|
|
|
it('denies user access to other tenant document', async () => {
|
|
const request = createMockPayloadRequest(TEST_USERS.porwollUser) // tenant 1 only
|
|
const result = await executeFieldAccess(tenantMemberField, {
|
|
req: request,
|
|
doc: { tenant: 4 }, // c2s tenant
|
|
})
|
|
|
|
expect(result).toBe(false)
|
|
})
|
|
|
|
it('handles tenant as object format in document', async () => {
|
|
const request = createMockPayloadRequest(TEST_USERS.porwollUser)
|
|
const result = await executeFieldAccess(tenantMemberField, {
|
|
req: request,
|
|
doc: { tenant: { id: 1 } },
|
|
})
|
|
|
|
expect(result).toBe(true)
|
|
})
|
|
|
|
it('multi-tenant user has access to all assigned tenants', async () => {
|
|
const request = createMockPayloadRequest(TEST_USERS.multiTenantUser) // tenants 1, 4, 5
|
|
|
|
const results = await Promise.all([
|
|
executeFieldAccess(tenantMemberField, { req: request, doc: { tenant: 1 } }),
|
|
executeFieldAccess(tenantMemberField, { req: request, doc: { tenant: 4 } }),
|
|
executeFieldAccess(tenantMemberField, { req: request, doc: { tenant: 5 } }),
|
|
executeFieldAccess(tenantMemberField, { req: request, doc: { tenant: 999 } }),
|
|
])
|
|
|
|
expect(results).toEqual([true, true, true, false])
|
|
})
|
|
})
|
|
|
|
describe('Edge Cases', () => {
|
|
it('returns false when doc has no tenant', async () => {
|
|
const request = createMockPayloadRequest(TEST_USERS.porwollUser)
|
|
const result = await executeFieldAccess(tenantMemberField, {
|
|
req: request,
|
|
doc: {},
|
|
})
|
|
|
|
expect(result).toBe(false)
|
|
})
|
|
|
|
it('returns false when doc is undefined', async () => {
|
|
const request = createMockPayloadRequest(TEST_USERS.porwollUser)
|
|
const result = await executeFieldAccess(tenantMemberField, {
|
|
req: request,
|
|
doc: undefined,
|
|
})
|
|
|
|
expect(result).toBe(false)
|
|
})
|
|
})
|
|
})
|
|
|
|
// ============================================================================
|
|
// Security: Sensitive Data Protection Tests
|
|
// ============================================================================
|
|
|
|
describe('Sensitive Data Protection', () => {
|
|
// List of sensitive field patterns that should never be readable via API
|
|
const sensitiveFieldPatterns = [
|
|
{ name: 'SMTP Password (neverReadable)', access: neverReadable },
|
|
]
|
|
|
|
describe.each(sensitiveFieldPatterns)('$name field', ({ access }) => {
|
|
it('is never readable by super admin', async () => {
|
|
const request = createMockPayloadRequest(TEST_USERS.superAdmin)
|
|
const result = await executeFieldAccess(access, { req: request })
|
|
expect(result).toBe(false)
|
|
})
|
|
|
|
it('is never readable by regular user', async () => {
|
|
const request = createMockPayloadRequest(TEST_USERS.porwollUser)
|
|
const result = await executeFieldAccess(access, { req: request })
|
|
expect(result).toBe(false)
|
|
})
|
|
|
|
it('is never readable by anonymous user', async () => {
|
|
const request = createMockPayloadRequest(null)
|
|
const result = await executeFieldAccess(access, { req: request })
|
|
expect(result).toBe(false)
|
|
})
|
|
})
|
|
|
|
describe('Defense in Depth', () => {
|
|
it('sensitive field access is stateless (no bypass via repeated requests)', async () => {
|
|
const request = createMockPayloadRequest(TEST_USERS.superAdmin)
|
|
|
|
// Multiple attempts should all fail
|
|
for (let i = 0; i < 5; i++) {
|
|
const result = await executeFieldAccess(neverReadable, { req: request })
|
|
expect(result).toBe(false)
|
|
}
|
|
})
|
|
|
|
it('sensitive field access ignores document context', async () => {
|
|
const request = createMockPayloadRequest(TEST_USERS.superAdmin)
|
|
const result = await executeFieldAccess(neverReadable, {
|
|
req: request,
|
|
doc: { isSuperAdmin: true, owner: 1, password: 'secret' },
|
|
})
|
|
|
|
expect(result).toBe(false)
|
|
})
|
|
})
|
|
})
|
|
|
|
// ============================================================================
|
|
// Field Access with Document Context (tenantMemberField)
|
|
// ============================================================================
|
|
|
|
describe('Field Access with Document Context', () => {
|
|
it('grants access to document within user tenant', async () => {
|
|
const user = createTenantUser([1], { id: 5 })
|
|
const request = createMockPayloadRequest(user)
|
|
const result = await executeFieldAccess(tenantMemberField, {
|
|
req: request,
|
|
doc: { tenant: 1 },
|
|
})
|
|
|
|
expect(result).toBe(true)
|
|
})
|
|
|
|
it('denies access to document outside user tenant', async () => {
|
|
const user = createTenantUser([1], { id: 5 })
|
|
const request = createMockPayloadRequest(user)
|
|
const result = await executeFieldAccess(tenantMemberField, {
|
|
req: request,
|
|
doc: { tenant: 4 }, // Different tenant
|
|
})
|
|
|
|
expect(result).toBe(false)
|
|
})
|
|
|
|
it('grants access to super admin regardless of tenant', async () => {
|
|
const request = createMockPayloadRequest(TEST_USERS.superAdmin)
|
|
const result = await executeFieldAccess(tenantMemberField, {
|
|
req: request,
|
|
doc: { tenant: 999 }, // Any tenant
|
|
})
|
|
|
|
expect(result).toBe(true)
|
|
})
|
|
})
|
|
|
|
// ============================================================================
|
|
// Comprehensive Field Access Matrix
|
|
// ============================================================================
|
|
|
|
describe('Field Access Matrix', () => {
|
|
const allUsers = [
|
|
{ name: 'Super Admin', user: TEST_USERS.superAdmin },
|
|
{ name: 'Porwoll User', user: TEST_USERS.porwollUser },
|
|
{ name: 'Multi-Tenant User', user: TEST_USERS.multiTenantUser },
|
|
{ name: 'Anonymous', user: null },
|
|
]
|
|
|
|
describe('neverReadable', () => {
|
|
it.each(allUsers)('$name cannot read field', async ({ user }) => {
|
|
const request = createMockPayloadRequest(user)
|
|
const result = await executeFieldAccess(neverReadable, { req: request })
|
|
expect(result).toBe(false)
|
|
})
|
|
})
|
|
|
|
describe('superAdminOnlyField', () => {
|
|
it.each(allUsers)('$name access is correctly evaluated', async ({ user }) => {
|
|
const request = createMockPayloadRequest(user)
|
|
const result = await executeFieldAccess(superAdminOnlyField, { req: request })
|
|
|
|
if (user?.isSuperAdmin) {
|
|
expect(result).toBe(true)
|
|
} else {
|
|
expect(result).toBe(false)
|
|
}
|
|
})
|
|
})
|
|
|
|
describe('authenticatedOnlyField', () => {
|
|
it.each(allUsers)('$name access is correctly evaluated', async ({ user }) => {
|
|
const request = createMockPayloadRequest(user)
|
|
const result = await executeFieldAccess(authenticatedOnlyField, { req: request })
|
|
|
|
if (user !== null) {
|
|
expect(result).toBe(true)
|
|
} else {
|
|
expect(result).toBe(false)
|
|
}
|
|
})
|
|
})
|
|
})
|