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..06e4cad 100644 --- a/server.js +++ b/server.js @@ -10,23 +10,56 @@ 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' })); + +// Enhanced CORS configuration for Swagger UI app.use(cors({ origin: process.env.ALLOWED_ORIGINS ? process.env.ALLOWED_ORIGINS.split(',').map(o => o.trim()) - : ['http://localhost:3000'], - methods: ['GET', 'POST'], - allowedHeaders: ['Content-Type', 'Authorization'] + : [`${protocol}://${apiDomain}`], + 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 } })); @@ -79,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', diff --git a/swagger.config.js b/swagger.config.js index 6fc5eda..1ba5137 100644 --- a/swagger.config.js +++ b/swagger.config.js @@ -1,5 +1,16 @@ +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'; + +// 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', @@ -18,12 +29,8 @@ const options = { }, servers: [ { - url: 'http://localhost:3000', - description: 'Development server' - }, - { - url: 'https://api.example.com', - description: 'Production server' + url: serverUrl, + description: isProduction ? 'Production server' : 'Development server' } ], components: { @@ -323,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'