From 118f1ee2ed8110f78fa21f5fec542b09b1aae836 Mon Sep 17 00:00:00 2001 From: Bas van den Aakster Date: Fri, 3 Oct 2025 14:24:10 +0200 Subject: [PATCH] Fix test suite and improve security documentation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Removed broken test that referenced deleted customEndpointHandler - Removed unused import for createPayloadRequest - Added production security best practices to README including API key authentication example - Added rate limiting example for production use - Added client-side caching recommendations for performance optimization 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- README.md | 70 +++++++++++++++++++++++++++++++++++++++++++++++++ dev/int.spec.ts | 19 +------------- 2 files changed, 71 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index 4a8e604..b76eb39 100644 --- a/README.md +++ b/README.md @@ -176,6 +176,44 @@ access: { } ``` +**Production Security Best Practices:** + +For production environments, consider implementing these additional security measures: + +```typescript +// Example: API key authentication for external services +collectionOverrides: { + access: { + read: ({ req }) => { + // Check for API key in headers for service-to-service calls + const apiKey = req.headers['x-api-key'] + if (apiKey && apiKey === process.env.FEATURE_FLAGS_API_KEY) { + return true + } + // Fall back to user authentication + return !!req.user + } + } +} +``` + +**Rate Limiting:** Use Payload's built-in rate limiting or implement middleware: + +```typescript +// Example with express-rate-limit +import rateLimit from 'express-rate-limit' + +const featureFlagLimiter = rateLimit({ + windowMs: 15 * 60 * 1000, // 15 minutes + max: 100, // Limit each IP to 100 requests per windowMs + standardHeaders: true, + legacyHeaders: false, +}) + +// Apply to your API routes +app.use('/api/feature-flags', featureFlagLimiter) +``` + **Important:** The plugin uses Payload's native REST API for the collection, which respects all access control rules. ## Usage @@ -448,6 +486,38 @@ if (flag.enabled && flag.variants) { } ``` +## Performance Considerations + +### Client-Side Caching + +For improved performance, consider implementing client-side caching when fetching feature flags: + +```typescript +// Example: Simple cache with TTL +class FeatureFlagCache { + private cache = new Map() + private ttl = 5 * 60 * 1000 // 5 minutes + + async get(key: string, fetcher: () => Promise) { + const cached = this.cache.get(key) + if (cached && cached.expiry > Date.now()) { + return cached.data + } + + const data = await fetcher() + this.cache.set(key, { data, expiry: Date.now() + this.ttl }) + return data + } +} + +const flagCache = new FeatureFlagCache() + +// Use with the hooks +const flags = await flagCache.get('all-flags', () => + fetch('/api/feature-flags?limit=1000').then(r => r.json()) +) +``` + ## Migration ### Disabling the Plugin diff --git a/dev/int.spec.ts b/dev/int.spec.ts index 04e6982..d8771bf 100644 --- a/dev/int.spec.ts +++ b/dev/int.spec.ts @@ -1,11 +1,9 @@ import type { Payload } from 'payload' import config from '@payload-config' -import { createPayloadRequest, getPayload } from 'payload' +import { getPayload } from 'payload' import { afterAll, beforeAll, describe, expect, test } from 'vitest' -import { customEndpointHandler } from '../src/endpoints/customEndpointHandler.js' - let payload: Payload afterAll(async () => { @@ -17,21 +15,6 @@ beforeAll(async () => { }) describe('Plugin integration tests', () => { - test('should query custom endpoint added by plugin', async () => { - const request = new Request('http://localhost:3000/api/my-plugin-endpoint', { - method: 'GET', - }) - - const payloadRequest = await createPayloadRequest({ config, request }) - const response = await customEndpointHandler(payloadRequest) - expect(response.status).toBe(200) - - const data = await response.json() - expect(data).toMatchObject({ - message: 'Hello from custom endpoint', - }) - }) - test('can create post with custom text field added by plugin', async () => { const post = await payload.create({ collection: 'posts',