diff --git a/stellar-payment-platform/server.js b/stellar-payment-platform/server.js index 8b32451..762f3ba 100644 --- a/stellar-payment-platform/server.js +++ b/stellar-payment-platform/server.js @@ -439,6 +439,18 @@ app.get('/metrics', async (req, res) => { } }); + +/** + * @openapi + * /federation: + * get: + * tags: + * - v1 + * description: GET /federation + * responses: + * 200: + * description: Success + */ app.get('/federation', ipLimiter, etagCache, validateSchema({ query: federationQuerySchema }), async (req, res, next) => { const { q: queryValue, type } = req.query; @@ -623,6 +635,18 @@ const verifyFreighterRegistrationSignature = ({ * - Validates that provided signature(s) meet minimum threshold * - Ensures authorization requirements are satisfied */ + +/** + * @openapi + * /register: + * post: + * tags: + * - v1 + * description: POST /register + * responses: + * 200: + * description: Success + */ app.post('/register', ipLimiter, idempotencyMiddleware(redisClient), requireJson, validateSchema({ body: registerBodySchema }), async (req, res, next) => { // registerBodySchema has already guaranteed that username is a trimmed // 3-20 character alphanumeric string and address is a non-empty trimmed diff --git a/stellar-payment-platform/src/routes/v1/paymentRoutes.js b/stellar-payment-platform/src/routes/v1/paymentRoutes.js index 68a4395..e26bfa5 100644 --- a/stellar-payment-platform/src/routes/v1/paymentRoutes.js +++ b/stellar-payment-platform/src/routes/v1/paymentRoutes.js @@ -11,12 +11,26 @@ const { idempotencyMiddleware } = require('../../../middleware/idempotency'); module.exports = (redisClient) => { const router = express.Router(); + + router.use(idempotencyMiddleware(redisClient)); // ── Idempotency protection for payment intent creation (POST /payments/bulk). // Duplicate submissions within 24h return the originally created intents. ── router.use(idempotencyMiddleware(redisClient)); // POST /payments/bulk + +/** + * @openapi + * /payments/bulk: + * post: + * tags: + * - v1 + * description: POST /payments/bulk + * responses: + * 200: + * description: Success + */ router.post('/payments/bulk', requireJson, validateSchema({ body: bulkPaymentSchema }), asyncHandler(async (req, res, next) => { const intents = req.body; diff --git a/stellar-payment-platform/src/routes/v1/statsRoutes.js b/stellar-payment-platform/src/routes/v1/statsRoutes.js index c32415b..84297a8 100644 --- a/stellar-payment-platform/src/routes/v1/statsRoutes.js +++ b/stellar-payment-platform/src/routes/v1/statsRoutes.js @@ -11,6 +11,18 @@ const { fetchAdminStats } = require('../../services/statsService'); module.exports = (redisClient) => { const router = express.Router(); + +/** + * @openapi + * /stats: + * get: + * tags: + * - v1 + * description: GET /stats + * responses: + * 200: + * description: Success + */ router.get('/stats', etagCache, asyncHandler(async (req, res, next) => { try { const stats = await getCachedStats(redisClient, () => fetchAdminStats(prisma, poolGet)); diff --git a/stellar-payment-platform/src/routes/v1/userRoutes.js b/stellar-payment-platform/src/routes/v1/userRoutes.js index e164232..df9d8da 100644 --- a/stellar-payment-platform/src/routes/v1/userRoutes.js +++ b/stellar-payment-platform/src/routes/v1/userRoutes.js @@ -359,7 +359,7 @@ router.delete('/register/:username', asyncHandler(async (req, res, next) => { where: { username }, data: { deletedAt: new Date() }, }); - + // Invalidate any stale federation cache entries invalidateFederationCache(username, existing.address); }); @@ -513,6 +513,18 @@ const totalPages = Math.ceil(totalCount / limit); } })); + +/** + * @openapi + * /users: + * get: + * tags: + * - v1 + * description: GET /users + * responses: + * 200: + * description: Success + */ router.get('/users', etagCache, validateSchema({ query: usersQuerySchema }), asyncHandler(async (req, res, next) => { const { limit: cursorLimit, cursor, invalid: invalidCursor } = parseCursorQuery(req.query); const { page, limit, skip } = parsePagination(req.query); diff --git a/stellar-payment-platform/src/routes/v1/webhookRoutes.js b/stellar-payment-platform/src/routes/v1/webhookRoutes.js index 7921d02..3237735 100644 --- a/stellar-payment-platform/src/routes/v1/webhookRoutes.js +++ b/stellar-payment-platform/src/routes/v1/webhookRoutes.js @@ -18,6 +18,8 @@ module.exports = (redisClient) => { // 2xx response. Read-only GET /webhooks is ignored. ──────────────────────── router.use(idempotencyMiddleware(redisClient)); + + const DEFAULT_FEDERATION_DOMAIN = 'localhost'; const authenticateWebhookCall = (req) => @@ -169,7 +171,6 @@ router.post('/webhooks/verify-test', asyncHandler(async (req, res, next) => { } })); - /** * @openapi * /webhooks: @@ -392,6 +393,33 @@ router.delete('/webhooks/:id', asyncHandler(async (req, res, next) => { } })); + router.post('/webhooks/verify-test', (req, res) => { + const { secret, payload } = req.body; + const signature = req.headers['x-webhook-signature']; + + if (!secret || !payload) { + return res.status(400).json({ error: 'Missing secret or payload' }); + } + + const expectedSignature = crypto.createHmac('sha256', secret).update(payload).digest('hex'); + + if (signature === expectedSignature) { + return res.status(200).json({ + ok: true, + valid: true, + message: 'Signature verification succeeded', + expectedSignature, + }); + } else { + return res.status(401).json({ + ok: false, + valid: false, + error: { code: 'INVALID_WEBHOOK_SIGNATURE' }, + receivedSignature: signature, + }); + } + }); + router.all('/webhooks', (req, res) => { if (req.method !== 'GET' && req.method !== 'POST') { return res.status(405).json({ error: 'Method Not Allowed' });