openvibers/openvibe.network Commits
Sign-in redirects get their own rate limit: /oauth/authorize (every site's sign-in and silent check, no password) shared the password endpoints' 5 a minute and refused 304 sign-ins from 15 visitors today; now 60 a minute, sign-in and registration keep 5 (#290)
Changes
@@@ -15,6 +15,11 @@ limit_req_zone $binary_remote_addr zone=openvibenetwork_auth:10m rate=5r/m;# Token issuance for apps (client credentials, code exchange, refresh): not a password check, so not the# sign-in limit — a developer's app needs a few tokens a minute (one per audience) and got 503s at 5/min.limit_req_zone $binary_remote_addr zone=openvibenetwork_token:10m rate=60r/m;++# /oauth/authorize answers every site's sign-in, and every silent "already signed in?" check as a visitor opens another++# OpenVibe site (about 10,000 a day): it checks no password, it redirects with the session the browser has. It shared++# the password endpoints' 5 a minute, which refused 304 sign-ins from 15 visitors on 2026-10-20 (the owner among them,++# signing in to openvibe.host); it has its own, generous zone.++limit_req_zone $binary_remote_addr zone=openvibenetwork_authorize:10m rate=60r/m;# ── HTTP → HTTPS redirect ──────────────────────────────────server {@@@ -73,8 +78,21 @@ server {proxy_set_header X-Forwarded-Proto $scheme;}-- # ── Auth endpoints (strict rate limit) ──────────────────-- location ~ ^/(api/auth/(register|login|anon-session)|oauth/authorize) {++ # ── Sign-in redirects (their own limit, above) ──────────++ location = /oauth/authorize {++ limit_req zone=openvibenetwork_authorize burst=60 nodelay;++ limit_req_status 429;++ proxy_pass http://127.0.0.1:4000;++ proxy_http_version 1.1;++ proxy_set_header Host $host;++ proxy_set_header X-Real-IP $remote_addr;++ proxy_set_header X-Forwarded-For $remote_addr;++ proxy_set_header CF-Connecting-IP $remote_addr;++ proxy_set_header X-Forwarded-Proto $scheme;++ }++++ # ── Password and account endpoints (strict rate limit) ──++ location ~ ^/api/auth/(register|login|anon-session) {limit_req zone=openvibenetwork_auth burst=5 nodelay;limit_req_status 429;proxy_pass http://127.0.0.1:4000;@@@ -23,10 +23,17 @@ const DEFAULT_SERVICE_MAP = {rateZones: [{ name: 'openvibenetwork_api', rate: '10r/s' },{ name: 'openvibenetwork_auth', rate: '5r/m' },++ { name: 'openvibenetwork_authorize', rate: '60r/m' },],locations: [{-- match: '~ ^/(api/auth/(register|login|anon-session)|oauth/(token|authorize))',++ // Every site's sign-in and silent check: no password here, so not the password endpoints' 5 a minute.++ match: '= /oauth/authorize',++ rateZone: 'openvibenetwork_authorize',++ rateBurst: 60,++ },++ {++ match: '~ ^/(api/auth/(register|login|anon-session)|oauth/token)',rateZone: 'openvibenetwork_auth',rateBurst: 5,},@@@ -13,4 +13,12 @@ assert.ok(src.includes("app.use('/api/auth/', rateLimit({ windowMs: 15 * 60_000,assert.ok(src.includes("app.use('/api/v1/node-pairing', rateLimit({ windowMs: 60_000, max: 10 })"), 'pairing stays at 10 a minute');const conf = fs.readFileSync(path.join(__dirname, '../deploy/nginx/openvibe.network.conf'), 'utf8');assert.ok(/zone=openvibenetwork_api:10m rate=10r\/s/.test(conf), "nginx's /api/ zone is the 10 r/s the app now matches");++// /oauth/authorize (every site's sign-in and silent check, no password) has its own zone: under the password++// endpoints' 5 a minute it refused 304 sign-ins from 15 visitors on 2026-10-20. Sign-in and registration keep 5.++assert.ok(/zone=openvibenetwork_authorize:10m rate=60r\/m;/.test(conf), 'authorize: 60 a minute per address');++const block = (re) => { const m = re.exec(conf); assert.ok(m, String(re)); return conf.slice(m.index, conf.indexOf('}', m.index)); };++assert.match(block(/location = \/oauth\/authorize \{/), /limit_req zone=openvibenetwork_authorize burst=60 nodelay;/);++const strict = /location ~ (\S+) \{\s*limit_req zone=openvibenetwork_auth burst=5 nodelay;/.exec(conf);++assert.ok(strict && /register\|login/.test(strict[1]) && !/authorize/.test(strict[1]), 'the strict zone covers sign-in and registration, not authorize');++assert.ok(/zone=openvibenetwork_auth:10m rate=5r\/m;/.test(conf));console.log('api rate limit: all checks passed');
@@ -15,6 +15,11 @@ limit_req_zone $binary_remote_addr zone=openvibenetwork_auth:10m rate=5r/m;
@@ -15,6 +15,11 @@ limit_req_zone $binary_remote_addr zone=openvibenetwork_auth:10m rate=5r/m;
# Token issuance for apps (client credentials, code exchange, refresh): not a password check, so not the
# Token issuance for apps (client credentials, code exchange, refresh): not a password check, so not the
# sign-in limit — a developer's app needs a few tokens a minute (one per audience) and got 503s at 5/min.
# sign-in limit — a developer's app needs a few tokens a minute (one per audience) and got 503s at 5/min.
limit_req_zone $binary_remote_addr zone=openvibenetwork_token:10m rate=60r/m;
limit_req_zone $binary_remote_addr zone=openvibenetwork_token:10m rate=60r/m;
+# /oauth/authorize answers every site's sign-in, and every silent "already signed in?" check as a visitor opens another
+# OpenVibe site (about 10,000 a day): it checks no password, it redirects with the session the browser has. It shared
+# the password endpoints' 5 a minute, which refused 304 sign-ins from 15 visitors on 2026-10-20 (the owner among them,
+# signing in to openvibe.host); it has its own, generous zone.
+limit_req_zone $binary_remote_addr zone=openvibenetwork_authorize:10m rate=60r/m;
# ── HTTP → HTTPS redirect ──────────────────────────────────
# ── HTTP → HTTPS redirect ──────────────────────────────────
server {
server {
@@ -73,8 +78,21 @@ server {
@@ -73,8 +78,21 @@ server {
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
- # ── Auth endpoints (strict rate limit) ──────────────────
- location ~ ^/(api/auth/(register|login|anon-session)|oauth/authorize) {
+ # ── Sign-in redirects (their own limit, above) ──────────
+ location = /oauth/authorize {
+ limit_req zone=openvibenetwork_authorize burst=60 nodelay;
+ limit_req_status 429;
+ proxy_pass http://127.0.0.1:4000;
+ proxy_http_version 1.1;
+ proxy_set_header Host $host;
+ proxy_set_header X-Real-IP $remote_addr;
+ proxy_set_header X-Forwarded-For $remote_addr;
+ proxy_set_header CF-Connecting-IP $remote_addr;
+ proxy_set_header X-Forwarded-Proto $scheme;
+ }
+
+ # ── Password and account endpoints (strict rate limit) ──
+ location ~ ^/api/auth/(register|login|anon-session) {
limit_req zone=openvibenetwork_auth burst=5 nodelay;
limit_req zone=openvibenetwork_auth burst=5 nodelay;
limit_req_status 429;
limit_req_status 429;
proxy_pass http://127.0.0.1:4000;
proxy_pass http://127.0.0.1:4000;
@@ -23,10 +23,17 @@ const DEFAULT_SERVICE_MAP = {
@@ -23,10 +23,17 @@ const DEFAULT_SERVICE_MAP = {
rateZones: [
rateZones: [
{ name: 'openvibenetwork_api', rate: '10r/s' },
{ name: 'openvibenetwork_api', rate: '10r/s' },
{ name: 'openvibenetwork_auth', rate: '5r/m' },
{ name: 'openvibenetwork_auth', rate: '5r/m' },
+ { name: 'openvibenetwork_authorize', rate: '60r/m' },
],
],
locations: [
locations: [
{
{
- match: '~ ^/(api/auth/(register|login|anon-session)|oauth/(token|authorize))',
+ // Every site's sign-in and silent check: no password here, so not the password endpoints' 5 a minute.
+ match: '= /oauth/authorize',
+ rateZone: 'openvibenetwork_authorize',
+ rateBurst: 60,
+ },
+ {
+ match: '~ ^/(api/auth/(register|login|anon-session)|oauth/token)',
rateZone: 'openvibenetwork_auth',
rateZone: 'openvibenetwork_auth',
rateBurst: 5,
rateBurst: 5,
},
},
@@ -13,4 +13,12 @@ assert.ok(src.includes("app.use('/api/auth/', rateLimit({ windowMs: 15 * 60_000,
@@ -13,4 +13,12 @@ assert.ok(src.includes("app.use('/api/auth/', rateLimit({ windowMs: 15 * 60_000,
assert.ok(src.includes("app.use('/api/v1/node-pairing', rateLimit({ windowMs: 60_000, max: 10 })"), 'pairing stays at 10 a minute');
assert.ok(src.includes("app.use('/api/v1/node-pairing', rateLimit({ windowMs: 60_000, max: 10 })"), 'pairing stays at 10 a minute');
const conf = fs.readFileSync(path.join(__dirname, '../deploy/nginx/openvibe.network.conf'), 'utf8');
const conf = fs.readFileSync(path.join(__dirname, '../deploy/nginx/openvibe.network.conf'), 'utf8');
assert.ok(/zone=openvibenetwork_api:10m rate=10r\/s/.test(conf), "nginx's /api/ zone is the 10 r/s the app now matches");
assert.ok(/zone=openvibenetwork_api:10m rate=10r\/s/.test(conf), "nginx's /api/ zone is the 10 r/s the app now matches");
+// /oauth/authorize (every site's sign-in and silent check, no password) has its own zone: under the password
+// endpoints' 5 a minute it refused 304 sign-ins from 15 visitors on 2026-10-20. Sign-in and registration keep 5.
+assert.ok(/zone=openvibenetwork_authorize:10m rate=60r\/m;/.test(conf), 'authorize: 60 a minute per address');
+const block = (re) => { const m = re.exec(conf); assert.ok(m, String(re)); return conf.slice(m.index, conf.indexOf('}', m.index)); };
+assert.match(block(/location = \/oauth\/authorize \{/), /limit_req zone=openvibenetwork_authorize burst=60 nodelay;/);
+const strict = /location ~ (\S+) \{\s*limit_req zone=openvibenetwork_auth burst=5 nodelay;/.exec(conf);
+assert.ok(strict && /register\|login/.test(strict[1]) && !/authorize/.test(strict[1]), 'the strict zone covers sign-in and registration, not authorize');
+assert.ok(/zone=openvibenetwork_auth:10m rate=5r\/m;/.test(conf));
console.log('api rate limit: all checks passed');
console.log('api rate limit: all checks passed');