mirror of
https://github.com/xtr-dev/payload-feature-flags.git
synced 2025-12-10 02:43:25 +00:00
Fix test suite and improve security documentation
- 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 <noreply@anthropic.com>
This commit is contained in:
70
README.md
70
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.
|
**Important:** The plugin uses Payload's native REST API for the collection, which respects all access control rules.
|
||||||
|
|
||||||
## Usage
|
## 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<string, { data: any; expiry: number }>()
|
||||||
|
private ttl = 5 * 60 * 1000 // 5 minutes
|
||||||
|
|
||||||
|
async get(key: string, fetcher: () => Promise<any>) {
|
||||||
|
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
|
## Migration
|
||||||
|
|
||||||
### Disabling the Plugin
|
### Disabling the Plugin
|
||||||
|
|||||||
@@ -1,11 +1,9 @@
|
|||||||
import type { Payload } from 'payload'
|
import type { Payload } from 'payload'
|
||||||
|
|
||||||
import config from '@payload-config'
|
import config from '@payload-config'
|
||||||
import { createPayloadRequest, getPayload } from 'payload'
|
import { getPayload } from 'payload'
|
||||||
import { afterAll, beforeAll, describe, expect, test } from 'vitest'
|
import { afterAll, beforeAll, describe, expect, test } from 'vitest'
|
||||||
|
|
||||||
import { customEndpointHandler } from '../src/endpoints/customEndpointHandler.js'
|
|
||||||
|
|
||||||
let payload: Payload
|
let payload: Payload
|
||||||
|
|
||||||
afterAll(async () => {
|
afterAll(async () => {
|
||||||
@@ -17,21 +15,6 @@ beforeAll(async () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
describe('Plugin integration tests', () => {
|
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 () => {
|
test('can create post with custom text field added by plugin', async () => {
|
||||||
const post = await payload.create({
|
const post = await payload.create({
|
||||||
collection: 'posts',
|
collection: 'posts',
|
||||||
|
|||||||
Reference in New Issue
Block a user