From 91259e98c6f2f2f47183663a8e5965e8cfddee9c Mon Sep 17 00:00:00 2001 From: Michilis Date: Tue, 15 Jul 2025 21:45:40 +0000 Subject: [PATCH 1/4] feat: add API_DOMAIN environment variable for production configuration - Add API_DOMAIN env var to set domain/IP for API in production - Update swagger.config.js to use dynamic server URLs - Update server.js CORS configuration to use API_DOMAIN - Update env.example with new API_DOMAIN field - Add comprehensive documentation in README.md - Fixes CORS issues in Swagger docs for production deployments --- README.md | 31 +++++++++++++++++++++++++++++++ env.example | 4 +++- server.js | 7 ++++++- swagger.config.js | 14 ++++++++------ 4 files changed, 48 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index ae13e14..4979d59 100644 --- a/README.md +++ b/README.md @@ -176,6 +176,9 @@ Edit `.env` file: PORT=3000 NODE_ENV=development +# API Domain/IP Configuration (for Swagger docs and CORS) +API_DOMAIN=localhost:3000 + # Security Configuration ALLOW_REDEEM_DOMAINS=ln.tips,getalby.com,wallet.mutinywallet.com API_SECRET=your-secret-key-here @@ -199,6 +202,34 @@ npm run dev npm start ``` +### Environment Variables + +#### API_DOMAIN Configuration +The `API_DOMAIN` environment variable is used to configure the correct domain/IP for your API in production. This affects: + +- **Swagger Documentation**: The "Try it out" feature will use the correct server URL +- **CORS Configuration**: Default CORS origins will use the correct protocol and domain +- **API Documentation**: Server URLs in the documentation will be accurate + +**Examples:** +```bash +# Development +API_DOMAIN=localhost:3000 + +# Production with domain +API_DOMAIN=api.yourdomain.com + +# Production with IP +API_DOMAIN=192.168.1.100:3000 + +# Production with custom port +API_DOMAIN=yourdomain.com:8080 +``` + +**Note**: The protocol (http/https) is automatically determined based on `NODE_ENV`: +- `NODE_ENV=development` → `http://` +- `NODE_ENV=production` → `https://` + The API will be available at `http://localhost:3000` ## 🔧 Configuration diff --git a/env.example b/env.example index c2fc6c7..8d0f0e4 100644 --- a/env.example +++ b/env.example @@ -2,10 +2,12 @@ PORT=3000 NODE_ENV=development +# API Domain/IP Configuration (for Swagger docs and CORS) +API_DOMAIN=localhost:3000 + # Security Configuration ALLOW_REDEEM_DOMAINS=* - # Default Lightning Address (used when no address is provided in redeem requests) DEFAULT_LIGHTNING_ADDRESS=admin@your-domain.com diff --git a/server.js b/server.js index 91dab90..09fdaf0 100644 --- a/server.js +++ b/server.js @@ -10,12 +10,17 @@ const redemptionService = require('./services/redemption'); const app = express(); const PORT = process.env.PORT || 3000; +// Get API domain for CORS configuration +const apiDomain = process.env.API_DOMAIN || 'localhost:3000'; +const isProduction = process.env.NODE_ENV === 'production'; +const protocol = isProduction ? 'https' : 'http'; + // Middleware app.use(express.json({ limit: '10mb' })); app.use(cors({ origin: process.env.ALLOWED_ORIGINS ? process.env.ALLOWED_ORIGINS.split(',').map(o => o.trim()) - : ['http://localhost:3000'], + : [`${protocol}://${apiDomain}`], methods: ['GET', 'POST'], allowedHeaders: ['Content-Type', 'Authorization'] })); diff --git a/swagger.config.js b/swagger.config.js index 6fc5eda..25a3732 100644 --- a/swagger.config.js +++ b/swagger.config.js @@ -1,5 +1,11 @@ +require('dotenv').config(); const swaggerJsdoc = require('swagger-jsdoc'); +// Get the API domain from environment variable, default to localhost:3000 +const apiDomain = process.env.API_DOMAIN || 'localhost:3000'; +const isProduction = process.env.NODE_ENV === 'production'; +const protocol = isProduction ? 'https' : 'http'; + const options = { definition: { openapi: '3.0.0', @@ -18,12 +24,8 @@ const options = { }, servers: [ { - url: 'http://localhost:3000', - description: 'Development server' - }, - { - url: 'https://api.example.com', - description: 'Production server' + url: `${protocol}://${apiDomain}`, + description: isProduction ? 'Production server' : 'Development server' } ], components: { From bf1f368a2479ecddc6f74ac935563e379b64e99a Mon Sep 17 00:00:00 2001 From: Michilis Date: Tue, 15 Jul 2025 21:56:55 +0000 Subject: [PATCH 2/4] fix: enhance CORS configuration for production with Nginx proxy - Add enhanced CORS headers for Swagger UI compatibility - Add OPTIONS method support for preflight requests - Add additional middleware for CORS preflight handling - Add debug endpoint /api/cors-test for CORS troubleshooting - Improve Swagger UI configuration for production deployment --- server.js | 34 +++++++++++++++++++++++++++++++--- swagger.config.js | 7 ++++++- 2 files changed, 37 insertions(+), 4 deletions(-) diff --git a/server.js b/server.js index 09fdaf0..0fe2d16 100644 --- a/server.js +++ b/server.js @@ -17,21 +17,49 @@ const protocol = isProduction ? 'https' : 'http'; // Middleware app.use(express.json({ limit: '10mb' })); + +// Enhanced CORS configuration for Swagger UI app.use(cors({ origin: process.env.ALLOWED_ORIGINS ? process.env.ALLOWED_ORIGINS.split(',').map(o => o.trim()) : [`${protocol}://${apiDomain}`], - methods: ['GET', 'POST'], - allowedHeaders: ['Content-Type', 'Authorization'] + methods: ['GET', 'POST', 'OPTIONS'], + allowedHeaders: ['Content-Type', 'Authorization', 'Accept', 'Origin', 'X-Requested-With'], + credentials: true, + optionsSuccessStatus: 200 })); +// Additional middleware for Swagger UI preflight requests +app.use((req, res, next) => { + if (req.method === 'OPTIONS') { + res.header('Access-Control-Allow-Origin', '*'); + res.header('Access-Control-Allow-Methods', 'GET, POST, OPTIONS'); + res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization, Accept, Origin, X-Requested-With'); + res.status(200).end(); + return; + } + next(); +}); + +// Debug endpoint to test CORS +app.get('/api/cors-test', (req, res) => { + res.json({ + success: true, + message: 'CORS test successful', + timestamp: new Date().toISOString(), + origin: req.headers.origin, + host: req.headers.host + }); +}); + // Swagger Documentation app.use('/docs', swaggerUi.serve, swaggerUi.setup(swaggerSpecs, { customCss: '.swagger-ui .topbar { display: none }', customSiteTitle: 'Cashu Redeem API Documentation', swaggerOptions: { filter: true, - showRequestHeaders: true + showRequestHeaders: true, + tryItOutEnabled: true } })); diff --git a/swagger.config.js b/swagger.config.js index 25a3732..c6718b2 100644 --- a/swagger.config.js +++ b/swagger.config.js @@ -6,6 +6,11 @@ const apiDomain = process.env.API_DOMAIN || 'localhost:3000'; const isProduction = process.env.NODE_ENV === 'production'; const protocol = isProduction ? 'https' : 'http'; +// For production behind Nginx, we need to ensure the URL doesn't include the internal port +const serverUrl = isProduction + ? `${protocol}://${apiDomain}` + : `${protocol}://${apiDomain}`; + const options = { definition: { openapi: '3.0.0', @@ -24,7 +29,7 @@ const options = { }, servers: [ { - url: `${protocol}://${apiDomain}`, + url: serverUrl, description: isProduction ? 'Production server' : 'Development server' } ], From 184f28fe5d30c639ec5eedc6a978474a86c7ebc9 Mon Sep 17 00:00:00 2001 From: Michilis Date: Tue, 15 Jul 2025 22:05:42 +0000 Subject: [PATCH 3/4] docs: remove GET / endpoint from Swagger documentation - Remove the General category and GET / endpoint from API docs - Keep the endpoint functional but hidden from Swagger UI - Clean up documentation to focus on core API endpoints --- server.js | 46 ---------------------------------------------- 1 file changed, 46 deletions(-) diff --git a/server.js b/server.js index 0fe2d16..06e4cad 100644 --- a/server.js +++ b/server.js @@ -112,52 +112,6 @@ function asyncHandler(fn) { // API Routes -/** - * @swagger - * /: - * get: - * summary: API Information - * description: Get basic information about the Cashu Redeem API - * tags: [General] - * responses: - * 200: - * description: API information - * content: - * application/json: - * schema: - * type: object - * properties: - * name: - * type: string - * example: "Cashu Redeem API" - * version: - * type: string - * example: "1.0.0" - * description: - * type: string - * example: "A production-grade API for redeeming Cashu tokens (ecash) to Lightning addresses" - * documentation: - * type: string - * example: "/docs" - * endpoints: - * type: object - * properties: - * decode: - * type: string - * example: "POST /api/decode" - * redeem: - * type: string - * example: "POST /api/redeem" - * validate: - * type: string - * example: "POST /api/validate-address" - * health: - * type: string - * example: "GET /api/health" - * github: - * type: string - * example: "https://github.com/yourusername/cashu-redeem-api" - */ app.get('/', (req, res) => { res.json({ name: 'Cashu Redeem API', From aa93c503412cd74913ac36bbc16d3ccec067bf1d Mon Sep 17 00:00:00 2001 From: Michilis Date: Tue, 15 Jul 2025 22:07:29 +0000 Subject: [PATCH 4/4] docs: remove empty General category from Swagger tags - Remove the General tag definition from swagger.config.js - Clean up the tags array to only include active categories - Eliminates empty General category from Swagger UI --- swagger.config.js | 4 ---- 1 file changed, 4 deletions(-) diff --git a/swagger.config.js b/swagger.config.js index c6718b2..1ba5137 100644 --- a/swagger.config.js +++ b/swagger.config.js @@ -330,10 +330,6 @@ const options = { } }, tags: [ - { - name: 'General', - description: 'General API information and utilities' - }, { name: 'Token Operations', description: 'Operations for decoding and redeeming Cashu tokens'