diff --git a/apps/api-service/src/routes/enhance.ts b/apps/api-service/src/routes/enhance.ts index 7cd7373..a55c89e 100644 --- a/apps/api-service/src/routes/enhance.ts +++ b/apps/api-service/src/routes/enhance.ts @@ -7,6 +7,8 @@ import { PromptEnhancementResponse, } from "../types/api"; import { body, validationResult } from "express-validator"; +import { validateApiKey } from "../middleware/auth/validateApiKey"; +import { rateLimitByApiKey } from "../middleware/auth/rateLimiter"; export const enhanceRouter: RouterType = Router(); @@ -88,6 +90,9 @@ const logEnhanceRequest = (req: Request, _res: Response, next: Function) => { enhanceRouter.post( "/enhance", + // Authentication middleware + validateApiKey, + rateLimitByApiKey, validateEnhanceRequest, logEnhanceRequest, diff --git a/apps/api-service/src/routes/textToImage.ts b/apps/api-service/src/routes/textToImage.ts index 0a44896..c83fed9 100644 --- a/apps/api-service/src/routes/textToImage.ts +++ b/apps/api-service/src/routes/textToImage.ts @@ -10,6 +10,8 @@ import { logEnhancementResult, } from "../middleware/promptEnhancement"; import { asyncHandler } from "../middleware/errorHandler"; +import { validateApiKey } from "../middleware/auth/validateApiKey"; +import { rateLimitByApiKey } from "../middleware/auth/rateLimiter"; import { GenerateImageResponse } from "../types/api"; export const textToImageRouter: RouterType = Router(); @@ -21,6 +23,10 @@ let imageGenService: ImageGenService; */ textToImageRouter.post( "/text-to-image", + // Authentication middleware + validateApiKey, + rateLimitByApiKey, + // JSON validation middleware logTextToImageRequest, validateTextToImageRequest, diff --git a/docs/api/README.md b/docs/api/README.md index 1efca50..7895d98 100644 --- a/docs/api/README.md +++ b/docs/api/README.md @@ -52,17 +52,47 @@ curl -X POST http://localhost:3000/api/generate \ ## Rate Limits +All authenticated endpoints (those requiring API keys) are rate limited: + - **Per API Key:** 100 requests per hour -- Rate limit information included in response headers: - - `X-RateLimit-Limit`: Maximum requests per window - - `X-RateLimit-Remaining`: Requests remaining - - `X-RateLimit-Reset`: When the limit resets (ISO 8601) -- **429 Too Many Requests:** Returned when limit exceeded +- **Applies to:** + - `POST /api/generate` + - `POST /api/text-to-image` + - `POST /api/enhance` +- **Not rate limited:** + - Public endpoints (`GET /health`, `GET /api/info`) + - Bootstrap endpoint (`POST /api/bootstrap/initial-key`) + - Admin endpoints (require master key, but no rate limit) + - Image serving endpoints (`GET /api/images/*`) + +Rate limit information included in response headers: +- `X-RateLimit-Limit`: Maximum requests per window +- `X-RateLimit-Remaining`: Requests remaining +- `X-RateLimit-Reset`: When the limit resets (ISO 8601) + +**429 Too Many Requests:** Returned when limit exceeded with `Retry-After` header --- ## Endpoints +### Overview + +| Endpoint | Method | Authentication | Rate Limit | Description | +|----------|--------|----------------|------------|-------------| +| `/health` | GET | None | No | Health check | +| `/api/info` | GET | None | No | API information | +| `/api/bootstrap/initial-key` | POST | None (one-time) | No | Create first master key | +| `/api/admin/keys` | POST | Master Key | No | Create new API keys | +| `/api/admin/keys` | GET | Master Key | No | List all API keys | +| `/api/admin/keys/:keyId` | DELETE | Master Key | No | Revoke API key | +| `/api/generate` | POST | API Key | 100/hour | Generate images with files | +| `/api/text-to-image` | POST | API Key | 100/hour | Generate images (JSON only) | +| `/api/enhance` | POST | API Key | 100/hour | Enhance text prompts | +| `/api/images/*` | GET | None | No | Serve generated images | + +--- + ### Authentication & Admin #### `POST /api/bootstrap/initial-key` @@ -227,6 +257,7 @@ Returns API metadata and configuration limits. Generate images from text prompts with optional reference images. **Authentication:** API key required (master or project) +**Rate Limit:** 100 requests per hour per API key **Content-Type:** `multipart/form-data` @@ -302,6 +333,7 @@ curl -X POST http://localhost:3000/api/generate \ Generate images from text prompts only using JSON payload. Simplified endpoint for text-only requests without file uploads. **Authentication:** API key required (master or project) +**Rate Limit:** 100 requests per hour per API key **Content-Type:** `application/json` @@ -391,6 +423,7 @@ curl -X POST http://localhost:3000/api/text-to-image \ Enhance and optimize text prompts for better image generation results. **Authentication:** API key required (master or project) +**Rate Limit:** 100 requests per hour per API key **Content-Type:** `application/json` @@ -462,19 +495,26 @@ Enhance and optimize text prompts for better image generation results. ## Common Error Messages -### Authentication Errors +### Authentication Errors (401) - `"Missing API key"` - No X-API-Key header provided - `"Invalid API key"` - The provided API key is invalid, expired, or revoked -- `"Master key required"` - This endpoint requires a master API key -- `"Bootstrap not allowed"` - API keys already exist, cannot bootstrap again +- **Affected endpoints:** `/api/generate`, `/api/text-to-image`, `/api/enhance`, `/api/admin/*` -### Validation Errors +### Authorization Errors (403) +- `"Master key required"` - This endpoint requires a master API key (not project key) +- `"Bootstrap not allowed"` - API keys already exist, cannot bootstrap again +- **Affected endpoints:** `/api/admin/*`, `/api/bootstrap/initial-key` + +### Validation Errors (400) - `"Prompt is required"` - Missing or empty prompt parameter - `"Reference image validation failed"` - Invalid file format or size - `"Validation failed"` - Parameter validation error -### Rate Limiting +### Rate Limiting Errors (429) - `"Rate limit exceeded"` - Too many requests, retry after specified time +- **Applies to:** `/api/generate`, `/api/text-to-image`, `/api/enhance` +- **Rate limit:** 100 requests per hour per API key +- **Response includes:** `Retry-After` header with seconds until reset ### Server Errors - `"Server configuration error"` - Missing GEMINI_API_KEY or database connection diff --git a/docs/api/api.rest b/docs/api/api.rest index 8e1fa5c..6ea428d 100644 --- a/docs/api/api.rest +++ b/docs/api/api.rest @@ -1,4 +1,8 @@ @base = http://localhost:3000 +# Replace with your actual API key (e.g., bnt_abc123...) +@apiKey = bnt_d0da2d441cd2f22a0ec13897629b4438cc723f0bcb320d646a41ed05a985fdf8 +# Replace with your master key for admin endpoints +@masterKey = bnt_71475a11d69344ff9db2236ff4f10cfca34512b29c7ac1a74f73c156d708e226 ### Health @@ -11,10 +15,42 @@ GET {{base}}/health GET {{base}}/api/info -### enhance +### Bootstrap - Create First Master Key (One-time only) + +POST {{base}}/api/bootstrap/initial-key + + +### Admin - Create New API Key (Requires Master Key) + +POST {{base}}/api/admin/keys +Content-Type: application/json +X-API-Key: {{masterKey}} + +{ + "type": "project", + "projectId": "my-project", + "name": "My Project Key", + "expiresInDays": 90 +} + + +### Admin - List All API Keys (Requires Master Key) + +GET {{base}}/api/admin/keys +X-API-Key: {{masterKey}} + + +### Admin - Revoke API Key (Requires Master Key) + +DELETE {{base}}/api/admin/keys/KEY_ID_HERE +X-API-Key: {{masterKey}} + + +### Enhance Prompt (Requires API Key) POST {{base}}/api/enhance Content-Type: application/json +X-API-Key: {{apiKey}} { "prompt": "Два мага сражаются в снежном лесу. У одного из них в руках посох, из которого вырывается молния, а другой маг защищается щитом из льда. Вокруг них падают снежинки, и на заднем плане видны заснеженные деревья и горы.", @@ -30,10 +66,11 @@ Content-Type: application/json } -### Generate image from text +### Generate Image from Text (Requires API Key) POST {{base}}/api/text-to-image Content-Type: application/json +X-API-Key: {{apiKey}} { "prompt": "A majestic eagle soaring over snow-capped mountains", @@ -41,9 +78,11 @@ Content-Type: application/json } -### Generate Image with Files +### Generate Image with Files (Requires API Key) + POST {{base}}/api/generate Content-Type: multipart/form-data; boundary=----WebKitFormBoundary +X-API-Key: {{apiKey}} ------WebKitFormBoundary Content-Disposition: form-data; name="prompt"