diff --git a/apps/api-service/package.json b/apps/api-service/package.json index a4b707d..b24f419 100644 --- a/apps/api-service/package.json +++ b/apps/api-service/package.json @@ -4,7 +4,8 @@ "description": "Nano Banana Image Generation Service - REST API for AI-powered image generation using Gemini Flash Image model", "main": "dist/server.js", "scripts": { - "dev": "echo 'Logs will be saved to api-dev.log' && tsx --watch src/server.ts 2>&1 | tee api-dev.log", + "infra:up": "cd ../.. && docker compose up -d postgres minio storage-init", + "dev": "npm run infra:up && echo 'Logs will be saved to api-dev.log' && tsx --watch src/server.ts 2>&1 | tee api-dev.log", "start": "node dist/server.js", "build": "tsc", "typecheck": "tsc --noEmit", diff --git a/apps/api-service/src/middleware/auth/requireProjectKey.ts b/apps/api-service/src/middleware/auth/requireProjectKey.ts new file mode 100644 index 0000000..e65818b --- /dev/null +++ b/apps/api-service/src/middleware/auth/requireProjectKey.ts @@ -0,0 +1,42 @@ +import { Request, Response, NextFunction } from 'express'; + +/** + * Middleware to ensure only project keys can access generation endpoints + * Master keys are for admin purposes only + */ +export function requireProjectKey( + req: Request, + res: Response, + next: NextFunction +): void { + // This middleware assumes validateApiKey has already run and attached req.apiKey + if (!req.apiKey) { + res.status(401).json({ + error: 'Authentication required', + message: 'API key validation must be performed first', + }); + return; + } + + // Block master keys from generation endpoints + if (req.apiKey.keyType === 'master') { + res.status(403).json({ + error: 'Forbidden', + message: 'Master keys cannot be used for image generation. Please use a project-specific API key.', + }); + return; + } + + // Ensure project key has required IDs + if (!req.apiKey.projectId) { + res.status(400).json({ + error: 'Invalid API key', + message: 'Project key must be associated with a project', + }); + return; + } + + console.log(`[${new Date().toISOString()}] Project key validated for generation: ${req.apiKey.id}`); + + next(); +} diff --git a/apps/api-service/src/routes/generate.ts b/apps/api-service/src/routes/generate.ts index 1dcdeb3..3160b11 100644 --- a/apps/api-service/src/routes/generate.ts +++ b/apps/api-service/src/routes/generate.ts @@ -15,6 +15,7 @@ import { } from "../middleware/promptEnhancement"; import { asyncHandler } from "../middleware/errorHandler"; import { validateApiKey } from "../middleware/auth/validateApiKey"; +import { requireProjectKey } from "../middleware/auth/requireProjectKey"; import { rateLimitByApiKey } from "../middleware/auth/rateLimiter"; import { GenerateImageResponse } from "../types/api"; // Create router @@ -30,6 +31,7 @@ generateRouter.post( "/generate", // Authentication middleware validateApiKey, + requireProjectKey, rateLimitByApiKey, // File upload middleware @@ -64,8 +66,12 @@ generateRouter.post( const { prompt, filename } = req.body; const files = (req.files as Express.Multer.File[]) || []; + // Extract org/project IDs from validated API key + const orgId = req.apiKey?.organizationId || undefined; + const projectId = req.apiKey?.projectId!; // Guaranteed by requireProjectKey middleware + console.log( - `[${timestamp}] [${requestId}] Starting image generation process`, + `[${timestamp}] [${requestId}] Starting image generation process for org:${orgId}, project:${projectId}`, ); try { @@ -102,6 +108,8 @@ generateRouter.post( const result = await imageGenService.generateImage({ prompt, filename, + orgId, + projectId, ...(referenceImages && { referenceImages }), }); diff --git a/apps/api-service/src/routes/textToImage.ts b/apps/api-service/src/routes/textToImage.ts index c83fed9..6563a3e 100644 --- a/apps/api-service/src/routes/textToImage.ts +++ b/apps/api-service/src/routes/textToImage.ts @@ -11,6 +11,7 @@ import { } from "../middleware/promptEnhancement"; import { asyncHandler } from "../middleware/errorHandler"; import { validateApiKey } from "../middleware/auth/validateApiKey"; +import { requireProjectKey } from "../middleware/auth/requireProjectKey"; import { rateLimitByApiKey } from "../middleware/auth/rateLimiter"; import { GenerateImageResponse } from "../types/api"; @@ -25,6 +26,7 @@ textToImageRouter.post( "/text-to-image", // Authentication middleware validateApiKey, + requireProjectKey, rateLimitByApiKey, // JSON validation middleware @@ -54,8 +56,12 @@ textToImageRouter.post( const requestId = req.requestId; const { prompt, filename } = req.body; + // Extract org/project IDs from validated API key + const orgId = req.apiKey?.organizationId || undefined; + const projectId = req.apiKey?.projectId!; // Guaranteed by requireProjectKey middleware + console.log( - `[${timestamp}] [${requestId}] Starting text-to-image generation process`, + `[${timestamp}] [${requestId}] Starting text-to-image generation process for org:${orgId}, project:${projectId}`, ); try { @@ -67,6 +73,8 @@ textToImageRouter.post( const result = await imageGenService.generateImage({ prompt, filename, + orgId, + projectId, }); // Log the result