openvibers/openvibe.billing Commits
Vibes Pay: third-party apps reach /api/v1/vibes/ on the public host (#192)
Changes
@@@ -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; }@@@ -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');
@@ -14,6 +14,8 @@
@@ -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_hooks:10m rate=20r/s;
limit_req_zone $binary_remote_addr zone=ovbilling_console:10m rate=5r/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 {
server {
listen 80;
listen 80;
@@ -113,7 +115,15 @@ server {
@@ -113,7 +115,15 @@ server {
# Service-to-service (OpenVibe.Events → 127.0.0.1:4600): never public.
# Service-to-service (OpenVibe.Events → 127.0.0.1:4600): never public.
location ^~ /internal/ { return 404; }
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/v1 { deny all; }
location ^~ /api/ { return 404; }
location ^~ /api/ { return 404; }
@@ -48,4 +48,8 @@ const topups = tls.match(/location = \/api\/me\/credits\/topups \{([^}]*)\}/);
@@ -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(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.ok(tls.indexOf(topups[0]) < tls.indexOf('location ^~ /api/ { return 404; }'), 'before the /api 404');
assert.match(tls, /location \^~ \/api\/v1 \{ deny all; \}/);
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');