cms.c2sgmbh/tests/unit/access-control/field-access.unit.spec.ts
Martin Porwoll da735cab46 feat: add Products and ProductCategories collections with CI/CD pipeline
- 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>
2025-12-12 21:36:26 +00:00

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)
}
})
})
})