openvibers/openvibe.billing Commits

Vibes Pay: third-party apps reach /api/v1/vibes/ on the public host (#192)

OP OpenVibers committed 8 days ago · 98d78fa6ae · 2 files, +16 −2

Every /api/v1/vibes route takes an app token (billing.vibes.charge or billing.vibes.read), so it is the one /api/v1
prefix served publicly, in its own rate-limit zone (an app's server makes charges on behalf of many people from one
address). The rest of /api/v1 stays loopback only.

Changes

ddiff --git a/deploy/nginx/billing.openvibe.network.conf b/deploy/nginx/billing.openvibe.network.conf
iindex 71ed072..4e2df68 100644
---- a/deploy/nginx/billing.openvibe.network.conf
++++ b/deploy/nginx/billing.openvibe.network.conf
@@@ -14,6 +14,8 @@
limit_req_zone $binary_remote_addr zone=ovbilling_hooks:10m rate=20r/s;
limit_req_zone $binary_remote_addr zone=ovbilling_console:10m rate=5r/s;
++# Vibes Pay's app API (D12): an app's server creates charges for many people from one address, so it has its own zone.
++limit_req_zone $binary_remote_addr zone=ovbilling_apps:10m rate=20r/s;
server {
listen 80;
@@@ -113,7 +115,15 @@ server {
# Service-to-service (OpenVibe.Events → 127.0.0.1:4600): never public.
location ^~ /internal/ { return 404; }
-- # The service API: loopback only. Never proxied from the public host.
++ # Vibes Pay for third-party apps (D12, docs/vibes-pay.md): every /api/v1/vibes route takes an app token
++ # (billing.vibes.charge or billing.vibes.read) and nothing else, so it is the one /api/v1 prefix the public host
++ # serves. An app's own server calls it from anywhere: openvibe-sdk/vibes, or plain HTTP.
++ location ^~ /api/v1/vibes/ {
++ limit_req zone=ovbilling_apps burst=40 nodelay;
++ proxy_pass http://127.0.0.1:4600;
++ }
++
++ # The rest of the service API: loopback only. Never proxied from the public host.
location ^~ /api/v1 { deny all; }
location ^~ /api/ { return 404; }
ddiff --git a/test/nginx-vhost.test.js b/test/nginx-vhost.test.js
iindex 0fac359..ccc019b 100644
---- a/test/nginx-vhost.test.js
++++ b/test/nginx-vhost.test.js
@@@ -48,4 +48,8 @@ const topups = tls.match(/location = \/api\/me\/credits\/topups \{([^}]*)\}/);
assert.ok(topups && /proxy_pass http:\/\/127\.0\.0\.1:4600;/.test(topups[1]) && /limit_req /.test(topups[1]), 'a rate-limited location for POST /api/me/credits/topups');
assert.ok(tls.indexOf(topups[0]) < tls.indexOf('location ^~ /api/ { return 404; }'), 'before the /api 404');
assert.match(tls, /location \^~ \/api\/v1 \{ deny all; \}/);
--console.log('nginx vhost: the policy is indexable, the rest noindex; /api/me/vibes, /api/me/balance, /api/me/balance/transfers, /api/me/vibes/from-balance, /api/me/activity, /api/me/credits and /api/me/credits/topups public, /api/v1 loopback only');
++// Vibes Pay's app API is the one public /api/v1 prefix, rate limited in its own zone, before the deny.
++const vibesApi = tls.match(/location \^~ \/api\/v1\/vibes\/ \{([^}]*)\}/);
++assert.ok(vibesApi && /limit_req zone=ovbilling_apps/.test(vibesApi[1]) && /proxy_pass http:\/\/127\.0\.0\.1:4600;/.test(vibesApi[1]), 'a rate-limited public location for /api/v1/vibes/');
++assert.ok(tls.indexOf(vibesApi[0]) < tls.indexOf('location ^~ /api/v1 { deny all; }'), 'before the /api/v1 deny');
++console.log('nginx vhost: the policy is indexable, the rest noindex; /api/me/vibes, /api/me/balance, /api/me/balance/transfers, /api/me/vibes/from-balance, /api/me/activity, /api/me/credits and /api/me/credits/topups public; /api/v1/vibes/ public to apps (app tokens); the rest of /api/v1 loopback only');