openvibers/openvibe.search Commits

Bound subscribe's delivery secret to Events' 32-256 limit (#7)

OP OpenVibers committed 4 Oct 2026 · 13d7f2a140 · 2 files, +40 −2

checks run by GitHub CI (test) · CI ✓ 3/3 · review SHIP by OpenCode Hy3

https://github.com/OpenVibers/OpenVibe.Search/pull/7

Changes

ddiff --git a/scripts/subscribe.js b/scripts/subscribe.js
iindex 2349ee0..528d689 100644
---- a/scripts/subscribe.js
++++ b/scripts/subscribe.js
@@@ -5,7 +5,8 @@
*
* node scripts/subscribe.js [--endpoint http://127.0.0.1:4710/internal/events] [--pattern '*.index_document.*']
*
-- * Reads the environment (.env or /etc/openvibe/search.env):
++ * Reads the environment (`.env` here; the service itself gets /etc/openvibe/search.env from systemd,
++ * so on the box run `sudo node --env-file=/etc/openvibe/search.env scripts/subscribe.js …`):
* EVENTS_URL, OV_NETWORK_INTERNAL_URL, OV_OAUTH_CLIENT_ID, OV_OAUTH_CLIENT_SECRET (the search
* principal needs events.subscription.manage for audience openvibe.events)
* SEARCH_EVENTS_SECRET the delivery signing secret; the first value is handed to Events, so
@@@ -26,7 +27,11 @@ const opt = (name, d) => { const i = args.indexOf(`--${name}`); return i >= 0 ?
const pattern = opt('pattern', '*.index_document.*');
const secret = config.events.webhookSecrets[0];
if (!config.events.url) throw new Error('EVENTS_URL is not set');
-- if (!secret || secret.length < 32) throw new Error('SEARCH_EVENTS_SECRET must be set (32+ characters) before subscribing');
++ // Events accepts a delivery secret of 32..256 characters and answers 422 for anything longer
++ // (POST /api/v1/subscriptions), so refuse it here before the token exchange, where it is visible.
++ if (!secret || secret.length < 32 || secret.length > 256) {
++ throw new Error('SEARCH_EVENTS_SECRET must be set (32 to 256 characters) before subscribing');
++ }
if (!config.oauth.clientSecret) throw new Error('OV_OAUTH_CLIENT_SECRET is not set');
const tokens = serviceAuth.createTokenClient({
tokenUrl: `${config.networkInternalUrl}/oauth/token`,
ddiff --git a/test/subscribe.test.js b/test/subscribe.test.js
nnew file mode 100644
iindex 0000000..ef51485
---- /dev/null
++++ b/test/subscribe.test.js
@@@ -0,0 +1,33 @@
++'use strict';
++// scripts/subscribe.js validates SEARCH_EVENTS_SECRET before it talks to Network or Events: Events
++// accepts 32..256 characters, so a secret outside that range is refused here with a clear message
++// instead of a late 422. Each case exits before any network call (EVENTS_URL points nowhere useful).
++const assert = require('assert');
++const { spawnSync } = require('child_process');
++const path = require('path');
++const { suite } = require('./helpers');
++
++const t = suite('subscribe');
++const script = path.join(__dirname, '..', 'scripts', 'subscribe.js');
++
++function run(env) {
++ return spawnSync(process.execPath, [script], {
++ encoding: 'utf8',
++ env: { ...process.env, EVENTS_URL: 'http://127.0.0.1:1', OV_OAUTH_CLIENT_SECRET: 'not-a-real-secret', ...env },
++ });
++}
++
++t('refuses a secret Events would reject as too long (over 256 characters)', () => {
++ const r = run({ SEARCH_EVENTS_SECRET: 'a'.repeat(300) });
++ assert.strictEqual(r.status, 1, `expected exit 1, got ${r.status}\n${r.stderr}`);
++ assert.match(r.stderr, /SEARCH_EVENTS_SECRET must be set \(32 to 256 characters\)/);
++ assert.doesNotMatch(r.stderr, /token endpoint|fetch failed/, 'must fail before any network call');
++});
++
++t('refuses a secret shorter than 32 characters', () => {
++ const r = run({ SEARCH_EVENTS_SECRET: 'too-short' });
++ assert.strictEqual(r.status, 1, `expected exit 1, got ${r.status}\n${r.stderr}`);
++ assert.match(r.stderr, /SEARCH_EVENTS_SECRET must be set \(32 to 256 characters\)/);
++});
++
++t.run();