openvibers/openvibe.network Commits
Retire the block importer and obsolete internal key fixtures (#22)
Changes
@@@ -2,7 +2,7 @@/*** Shared by the operator scripts: `parseArgs(argv, { flags, values })`, the `--flag` / `--key value`* parsing they all use. On PostgreSQL (plan T2, ADR-035) the scripts open the service database through-- * DATABASE_URL themselves (scripts/secrets-out-of-db.js, scripts/import-blocks.js); the old++ * DATABASE_URL themselves (for example, scripts/secrets-out-of-db.js); the old* better-sqlite3 helpers (dbPath, dropToOwnerOf, the sqlite online backup) are gone with the file.*/const path = require('path');@@@ -329,7 +329,7 @@ async function bootAndCheck(target, dir, { timeoutMs = 120000 } = {}) {PATH: process.env.PATH, HOME: dir, PORT: String(port), HOST: '127.0.0.1',NODE_ENV: target.store === 'pglite' ? 'test' : 'production',JWT_PRIVATE_KEY: path.join(dir, 'keys', 'private.pem'), JWT_PUBLIC_KEY: path.join(dir, 'keys', 'public.pem'),-- AVATAR_PATH: path.join(dir, 'avatars'), INTERNAL_API_KEY: crypto.randomBytes(24).toString('hex'),++ AVATAR_PATH: path.join(dir, 'avatars'),OV_NETWORK_INTERNAL_URL: `http://127.0.0.1:${port}`,OV_LIVE_INTERNAL_URL: 'http://127.0.0.1:9', OV_TOOLS_INTERNAL_URL: 'http://127.0.0.1:9', OV_GAMES_INTERNAL_URL: 'http://127.0.0.1:9',OV_MEDIA_INTERNAL_URL: 'http://127.0.0.1:9', OV_AI_INTERNAL_URL: 'http://127.0.0.1:9',@@@ -336,7 +336,7 @@ function userRouter(requireAuth) {return router;}--/** Service routes; mounted under /internal (after requireInternalKey) with principal guards. */++/** Service routes; mounted under /internal after the requireServiceToken gate, with principal guards. */function serviceRoutes(router, principals) {const nsOf = (req) => req.params.ns;const resolveSubject = async (db, s) => (/^usr_[0-9A-HJKMNP-TV-Z]{26}$/.test(s) && await db.prepare('SELECT 1 FROM users WHERE subject_id = ?').get(s) ? s : null);@@@ -5,7 +5,7 @@* POST /internal/events/stream-live Live's direct call (server/internal/routes.js), with the* follower list in the body (follower_network_ids)* live.stream.started via Events server/notifications/events-consumer.js; the followers are-- * read from Network's active user_follows rows++ * read from Network's own active user_follows rows** Both claim the same persisted per-streamer announcement window (stream_live_announcements, keyed* by the streamer's NETWORK user id), so while Live still makes the direct call and Network also@@@ -69,7 +69,7 @@ async function dumpAnalytics(db) {const ISSUER = 'https://openvibe.network';const closed = 'http://127.0.0.1:9'; // remote dashboards: refused at once, never a local dev serverconst config = {-- internalKey: 'k'.repeat(32), baseUrl: ISSUER, loginUrl: ISSUER, networkUrl: ISSUER,++ baseUrl: ISSUER, loginUrl: ISSUER, networkUrl: ISSUER,jwt: { issuer: ISSUER, accessTokenExpiry: '1h', refreshTokenExpiry: '30d' },services: { live: { internalUrl: closed }, tools: { internalUrl: closed }, games: { internalUrl: closed }, media: { internalUrl: closed } },};@@@ -28,7 +28,7 @@ for (const bad of ['http://openvibe.media/x.png', 'javascript:alert(1)', 'https:// Network's own service token for the push (plan T2): live.avatar.write, audience openvibe.live.const { privateKey, publicKey } = require('crypto').generateKeyPairSync('rsa', { modulusLength: 2048, publicKeyEncoding: { type: 'spki', format: 'pem' }, privateKeyEncoding: { type: 'pkcs8', format: 'pem' } });const selfToken = require('../server/identity/self-token').createSelfTokens({ privateKey, issuer: 'https://openvibe.network' });-- const svc = createAvatarService({ db, config: { internalKey: 'k'.repeat(24), services: {} }, selfToken, requireAuth: (req, _res, next) => { req.user = { id: 1, username: 'Goosely' }; next(); } });++ const svc = createAvatarService({ db, config: { services: {} }, selfToken, requireAuth: (req, _res, next) => { req.user = { id: 1, username: 'Goosely' }; next(); } });assert.equal((await svc.fromSite({ user_id: 1, avatar_url: `${M}/f/a.png`, origin: 'live' })).changed, true);assert.equal(pushes.length, 0, 'a change that came from Live is not echoed back to Live');await svc.apply(1, 'Goosely', `${M}/f/b.png`, 'network'); assert.equal(pushes.length, 1); assert.equal(pushes[0].avatar_url, `${M}/f/b.png`, 'a change made here is pushed to the sites');@@@ -17,7 +17,6 @@ const { getDb } = require('../server/db/database');(async () => {const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'ov-blocks-'));--const dbFile = path.join(dir, 'network.db');const log = console.log; console.log = () => {};const db = getDb();console.log = log;@@@ -39,7 +38,7 @@ await db.prepare("INSERT INTO users (id, username, password_hash, is_anon) VALUEconst GUEST_SUBJECT = (await db.prepare('SELECT subject_id FROM users WHERE id = 5').get()).subject_id;const keys = crypto.generateKeyPairSync('rsa', { modulusLength: 2048, publicKeyEncoding: { type: 'spki', format: 'pem' }, privateKeyEncoding: { type: 'pkcs8', format: 'pem' } });--const config = { internalKey: 'legacy-key', jwt: { issuer: 'https://openvibe.network', accessTokenExpiry: '1h' } };++const config = { jwt: { issuer: 'https://openvibe.network', accessTokenExpiry: '1h' } };const { signToken } = require('../server/auth/routes');const requireAuth = require('../server/auth/session').makeRequireAuth(() => ({ db, publicKey: keys.publicKey, config }), signToken);const blocks = require('../server/identity/blocks');@@@ -28,7 +28,7 @@ const CAROL = ids.newId('user'), DAVE = ids.newId('user');await db.prepare('INSERT INTO users (id, username, password_hash, subject_id, role) VALUES (20, ?, ?, ?, ?), (21, ?, ?, ?, ?)').run('carol', 'x', CAROL, 'streamer', 'dave', 'x', DAVE, 'user');const keys = crypto.generateKeyPairSync('rsa', { modulusLength: 2048, publicKeyEncoding: { type: 'spki', format: 'pem' }, privateKeyEncoding: { type: 'pkcs8', format: 'pem' } });--const config = { internalKey: 'legacy-key', jwt: { issuer: 'https://openvibe.network', accessTokenExpiry: '1h' } };++const config = { jwt: { issuer: 'https://openvibe.network', accessTokenExpiry: '1h' } };const { signToken } = require('../server/auth/routes');const SECRET = 's'.repeat(40);const consumer = await createEventsConsumer({ db, notifications: new NotificationService(db), secrets: SECRET, log: { log() {}, warn() {}, error() {} } });@@@ -34,7 +34,7 @@ app.use(express.json());app.use(express.urlencoded({ extended: true }));app.locals.db = db;// No developer settings at all: the code defaults apply (as with DEV_SANDBOX_* unset).--app.locals.config = { baseUrl: ISSUER, loginUrl: ISSUER, internalKey: 'legacy-key', jwt: { issuer: ISSUER, accessTokenExpiry: '1h' } };++app.locals.config = { baseUrl: ISSUER, loginUrl: ISSUER, jwt: { issuer: ISSUER, accessTokenExpiry: '1h' } };app.locals.privateKey = keys.privateKey;app.locals.publicKey = keys.publicKey;app.use('/oauth', require('../server/auth/oauth-routes'));@@@ -37,7 +37,7 @@ app.use(express.json());app.use(express.urlencoded({ extended: true }));app.locals.db = db;app.locals.config = {-- baseUrl: ISSUER, loginUrl: ISSUER, internalKey: 'legacy-key',++ baseUrl: ISSUER, loginUrl: ISSUER,jwt: { issuer: ISSUER, accessTokenExpiry: '1h' },developer: { credentialOverlapS: 60 },};@@@ -35,7 +35,7 @@ app.use(express.json());app.use(express.urlencoded({ extended: true }));app.locals.db = db;app.locals.config = {-- baseUrl: ISSUER, loginUrl: ISSUER, internalKey: 'legacy-key',++ baseUrl: ISSUER, loginUrl: ISSUER,jwt: { issuer: ISSUER, accessTokenExpiry: '1h' },// No sandbox allowance here, so every grant goes through the staff-set allowance (the defaults// are covered by test/developer-defaults.test.js).@@@ -1,4 +1,5 @@'use strict';++process.env.FOLLOWS_AUTHORITY = 'live';// ADR-030 step 4, plan T2 "Follows": products write follows through /internal/follows (network.follows.write,// service token only), and go-live notifications take the followers from Network's own graph only (no call to// Live, whatever the environment says); a channel with no Network account announces nothing.@@@ -28,7 +29,7 @@ await db.prepare('INSERT INTO users (id, username, password_hash, subject_id, ro.run('carol', 'x', CAROL, 'streamer', 'dave', 'x', DAVE, 'user', 'erin', 'x', ERIN, 'user');const keys = crypto.generateKeyPairSync('rsa', { modulusLength: 2048, publicKeyEncoding: { type: 'spki', format: 'pem' }, privateKeyEncoding: { type: 'pkcs8', format: 'pem' } });--const config = { internalKey: 'legacy-key', jwt: { issuer: 'https://openvibe.network', accessTokenExpiry: '1h' } };++const config = { jwt: { issuer: 'https://openvibe.network', accessTokenExpiry: '1h' } };const { signToken } = require('../server/auth/routes');const requireAuth = require('../server/auth/session').makeRequireAuth(() => ({ db, publicKey: keys.publicKey, config }), signToken);const follows = require('../server/identity/follows');@@@ -74,7 +75,6 @@ const server = http.createServer(app);assert.deepStrictEqual(evs.map((e) => e.event_type), ['network.follow.created', 'network.follow.created', 'network.follow.deleted', 'network.follow.created']);// ── Go-live: Network's followers, no Live call (even with Live's old switch set) ──-- process.env.FOLLOWS_AUTHORITY = 'live';const started = {event_id: ids.newId('event'), event_type: 'live.stream.started', version: 1, source: 'live',actor: { type: 'user', id: CAROL }, subject: { type: 'stream', id: '801', revision: 1 }, visibility: 'public', priority: 'important',@@@ -31,7 +31,7 @@ await db.prepare(`INSERT INTO users (id, username, display_name, password_hash,await db.prepare("INSERT INTO users (id, username, password_hash, is_anon) VALUES (5, 'anon1234', 'x', 1)").run();const keys = crypto.generateKeyPairSync('rsa', { modulusLength: 2048, publicKeyEncoding: { type: 'spki', format: 'pem' }, privateKeyEncoding: { type: 'pkcs8', format: 'pem' } });--const config = { internalKey: 'legacy-key', jwt: { issuer: 'https://openvibe.network', accessTokenExpiry: '1h' } };++const config = { jwt: { issuer: 'https://openvibe.network', accessTokenExpiry: '1h' } };const { signToken } = require('../server/auth/routes');const requireAuth = require('../server/auth/session').makeRequireAuth(() => ({ db, publicKey: keys.publicKey, config }), signToken);const follows = require('../server/identity/follows');@@@ -8,7 +8,7 @@ const { siteForHost } = require('../server/frame/sites');(async () => {const db = getDb();--const svc = await createFrameService(db, { internalKey: 'change-me-in-production', services: {} }, null);++const svc = await createFrameService(db, { services: {} }, null);assert.equal(siteForHost('yt.openvibe.tools').id, 'tools');assert.equal(siteForHost('evil.example'), null);@@@ -49,7 +49,7 @@ for (let i = 0; i < 6; i++) await ins.run(1, 'tools', 'dns');});await Promise.all([live, ai].map(s => new Promise(r => s.listen(0, '127.0.0.1', r))));const { privateKey } = crypto.generateKeyPairSync('rsa', { modulusLength: 2048, privateKeyEncoding: { type: 'pkcs8', format: 'pem' }, publicKeyEncoding: { type: 'spki', format: 'pem' } });-- const cfg = { internalKey: 'a-real-internal-key', services: { live: { internalUrl: `http://127.0.0.1:${live.address().port}` } } };++ const cfg = { services: { live: { internalUrl: `http://127.0.0.1:${live.address().port}` } } };const aiUrl = `http://127.0.0.1:${ai.address().port}`;const db2 = db; // the process's database (test/helpers/pg-preload.mjs); the copy cache lives hereconst copySvc = async (d) => await createFrameService(d, cfg, null, { privateKey, issuer: 'https://openvibe.network', aiUrl });@@@ -36,7 +36,7 @@ const keys = crypto.generateKeyPairSync('rsa', { modulusLength: 2048, publicKeyEconst app = express();app.use(express.urlencoded({ extended: true }));app.locals.db = db;--app.locals.config = { internalKey: 'legacy-key', jwt: { issuer: 'https://openvibe.network', accessTokenExpiry: '1h' } };++app.locals.config = { jwt: { issuer: 'https://openvibe.network', accessTokenExpiry: '1h' } };app.locals.privateKey = keys.privateKey;app.locals.publicKey = keys.publicKey;app.use('/oauth', require('../server/auth/oauth-routes'));@@@ -32,7 +32,7 @@ async function bootServer({ env = {}, child = false, timeoutMs = 60000 } = {}) {NODE_ENV: 'test', PORT: String(port), HOST: '127.0.0.1', PGLITE_DIR: path.join(dir, 'pglite'),AVATAR_PATH: path.join(dir, 'avatars'),JWT_PRIVATE_KEY: path.join(dir, 'none.pem'), JWT_PUBLIC_KEY: path.join(dir, 'none.pub.pem'),-- BOOTSTRAP_PROFILE: 'local-dev', INTERNAL_API_KEY: 'k'.repeat(40),++ BOOTSTRAP_PROFILE: 'local-dev',OV_NETWORK_INTERNAL_URL: `http://127.0.0.1:${port}`,OV_LIVE_INTERNAL_URL: 'http://127.0.0.1:9', OV_TOOLS_INTERNAL_URL: 'http://127.0.0.1:9',OV_GAMES_INTERNAL_URL: 'http://127.0.0.1:9', OV_MEDIA_INTERNAL_URL: 'http://127.0.0.1:9',@@@ -29,7 +29,7 @@ await db.prepare("INSERT INTO users (id, username, password_hash, is_anon) VALUEconst clock = { offset: 0 };const now = () => Date.now() + clock.offset;const keys = crypto.generateKeyPairSync('rsa', { modulusLength: 2048, publicKeyEncoding: { type: 'spki', format: 'pem' }, privateKeyEncoding: { type: 'pkcs8', format: 'pem' } });--const config = { internalKey: 'legacy-key', jwt: { issuer: 'https://openvibe.network', accessTokenExpiry: '1h' } };++const config = { jwt: { issuer: 'https://openvibe.network', accessTokenExpiry: '1h' } };const requireAuth = require('../server/auth/session').makeRequireAuth(() => ({ db, publicKey: keys.publicKey, config }), signToken);const app = express();@@@ -27,7 +27,7 @@ const keys = crypto.generateKeyPairSync('rsa', { modulusLength: 2048, publicKeyEconst app = express();app.use(express.urlencoded({ extended: true }));app.locals.db = db;--app.locals.config = { internalKey: 'legacy-key', jwt: { issuer: 'https://openvibe.network', accessTokenExpiry: '1h' } };++app.locals.config = { jwt: { issuer: 'https://openvibe.network', accessTokenExpiry: '1h' } };app.locals.privateKey = keys.privateKey;app.locals.publicKey = keys.publicKey;app.use('/oauth', require('../server/auth/oauth-routes'));@@@ -28,7 +28,7 @@ metrics.instrument(app, { service: 'network', release: 'abc123def456', registry:app.use(express.json());app.use(express.urlencoded({ extended: true }));app.locals.db = db;--app.locals.config = { internalKey: 'legacy-key', jwt: { issuer: ISSUER, accessTokenExpiry: '1h' } };++app.locals.config = { jwt: { issuer: ISSUER, accessTokenExpiry: '1h' } };app.locals.privateKey = keys.privateKey;app.locals.publicKey = keys.publicKey;app.use('/oauth/token', observability.tokenEndpointMetrics);@@@ -23,7 +23,7 @@ const app = express();app.use(express.json());app.locals.db = db;app.locals.config = {-- baseUrl: ISSUER, loginUrl: ISSUER, internalKey: 'legacy-key',++ baseUrl: ISSUER, loginUrl: ISSUER,jwt: { issuer: ISSUER, accessTokenExpiry: '1h' },developer: { sandboxAudiences: 'openvibe.media', sandboxAllowance: '', credentialOverlapS: 60 },};@@@ -29,7 +29,7 @@ const keys = crypto.generateKeyPairSync('rsa', { modulusLength: 2048, publicKeyEconst app = express();app.use(express.urlencoded({ extended: true }));app.locals.db = db;--app.locals.config = { internalKey: 'legacy-key', jwt: { issuer: 'https://openvibe.network', accessTokenExpiry: '1h' } };++app.locals.config = { jwt: { issuer: 'https://openvibe.network', accessTokenExpiry: '1h' } };app.locals.privateKey = keys.privateKey;app.locals.publicKey = keys.publicKey;app.use('/oauth', require('../server/auth/oauth-routes'));@@@ -121,7 +121,7 @@ const out = (...a) => process.stdout.write(a.join(' ') + '\n');}assert.deepStrictEqual(bad, [], `private reads answered:\n${bad.join('\n')}`);// A service principal reaches only the /internal routes its capabilities name.-- assert.strictEqual(st['service GET /internal/integrations/github-token'], 403, 'github-token needs the legacy key or its own capability');++ assert.strictEqual(st['service GET /internal/integrations/github-token'], 403, 'github-token needs its own capability');// Deleted with the X-Internal-Key retirement (plan T2, no caller left): no longer routes at all.for (const p of ['/internal/stats', `/internal/users/${users.alice.id}`, `/internal/users/${users.alice.id}/linked-accounts`, '/internal/anon-list', `/internal/notifications/unread/${users.alice.id}`]) {assert.strictEqual(st[`service GET ${p}`], undefined, `${p} is gone`);@@@ -63,7 +63,7 @@ const out = (...a) => process.stdout.write(a.join(' ') + '\n');'/oauth/client-info?client_id=live', `/oauth/client-info?client_id=${dev.PA.app}`, '/api/auth/anon-identities', '/sso/check', '/fedcm/accounts'];const paths = crawler.pathsFor(w.routes, values, { method: 'get', query: 'limit=5&all=1&debug=1&include=secret', extra });const tokenOf = (h) => String((h && h.authorization) || '').replace(/^Bearer /, '');-- const people = { ...w.callers, key: { 'x-internal-key': w.sentinels.INTERNAL_API_KEY } };++ const people = { ...w.callers, key: { 'x-internal-key': 'retired-key' } };// What each caller must never see: every secret, plus every other caller's credential. Staff// admins and the owner list verification keys (they make them to hand out); nobody else sees one.const needlesFor = (who) => {@@@ -137,7 +137,7 @@ const out = (...a) => process.stdout.write(a.join(' ') + '\n');}// Wrong credentials of every kind on the paths that take them.const wrong = {-- 'retired internal key': { 'x-internal-key': w.sentinels.INTERNAL_API_KEY },++ 'retired internal key': { 'x-internal-key': 'retired-key' },'forged bearer': { authorization: `Bearer ${jwt.sign({ sub: users.owner.id, id: users.owner.id }, 'guess', { issuer: w.issuer })}` },'none alg bearer': { authorization: `Bearer ${jwt.sign({ sub: users.owner.id, id: users.owner.id }, null, { algorithm: 'none' })}` },'setup token guess': { 'x-setup-token': 'x', authorization: 'Bearer x' },@@@ -26,7 +26,6 @@ const S = (name) => `sentinel-not-a-secret-${name}`;/** Every secret-typed environment variable Network (or a module it loads) reads, with its sentinel. */function sentinelEnv() {return {-- INTERNAL_API_KEY: S('internal-api-key'),SETUP_TOKEN: S('setup-token'),NETWORK_EVENTS_SECRET: S('network-events-secret'),OV_LIVE_WEBHOOK_SECRET: S('live-webhook-secret'),@@@ -136,7 +135,7 @@ async function buildWorld({ label = 'sec', env: extraEnv = {} } = {}) {const shownOnce = []; // responses that legitimately carry a secret once (checked for no-store by the suites)const once = (what, r) => { shownOnce.push({ what, status: r.status, cache: r.headers.get('cache-control') || '' }); return r; };-- // Service principal tokens (client_credentials) and the legacy key.++ // Service principal tokens (client_credentials).async function serviceToken(clientId, audience = 'openvibe.network') {const r = once(`service token ${clientId}`, await call(null, 'POST', '/oauth/token', new URLSearchParams({ grant_type: 'client_credentials', client_id: clientId, client_secret: clientSecrets[clientId], audience })));if (r.status !== 200) throw new Error(`service token for ${clientId}: ${r.status} ${r.text}`);@@@ -23,7 +23,7 @@ for (const c of ['host', 'live']) await db.prepare('UPDATE oauth_clients SET cliawait db.prepare("INSERT INTO users (id, username, password_hash, subject_id, role) VALUES (1, 'boss', 'x', 'usr_01JAB2C3D4E5F6G7H8J9K0MNPA', 'admin'), (2, 'pat', 'x', 'usr_01JAB2C3D4E5F6G7H8J9K0MNPB', 'user')").run();const keys = crypto.generateKeyPairSync('rsa', { modulusLength: 2048, publicKeyEncoding: { type: 'spki', format: 'pem' }, privateKeyEncoding: { type: 'pkcs8', format: 'pem' } });--const config = { internalKey: 'legacy-key', jwt: { issuer: 'https://openvibe.network', accessTokenExpiry: '1h' } };++const config = { jwt: { issuer: 'https://openvibe.network', accessTokenExpiry: '1h' } };const { signToken } = require('../server/auth/routes');const requireAuth = require('../server/auth/session').makeRequireAuth(() => ({ db, publicKey: keys.publicKey, config }), signToken);const app = express();@@@ -20,7 +20,7 @@ const db = getDb();console.log = log;await db.prepare("INSERT INTO users (id, username, password_hash, role) VALUES (1, 'ann', 'x', 'user'), (2, 'bob', 'x', 'user'), (3, 'boss', 'x', 'admin')").run();const keys = crypto.generateKeyPairSync('rsa', { modulusLength: 2048, publicKeyEncoding: { type: 'spki', format: 'pem' }, privateKeyEncoding: { type: 'pkcs8', format: 'pem' } });--const config = { internalKey: 'legacy-key', jwt: { issuer: 'https://openvibe.network', accessTokenExpiry: '1h' } };++const config = { jwt: { issuer: 'https://openvibe.network', accessTokenExpiry: '1h' } };const { signToken } = require('../server/auth/routes');const requireAuth = require('../server/auth/session').makeRequireAuth(() => ({ db, publicKey: keys.publicKey, config }), signToken);const requireAdmin = (req, res, next) => (req.user && req.user.role === 'admin' ? next() : res.status(403).json({ error: 'admin only' }));
@@ -2,7 +2,7 @@
@@ -2,7 +2,7 @@
/**
/**
* Shared by the operator scripts: `parseArgs(argv, { flags, values })`, the `--flag` / `--key value`
* Shared by the operator scripts: `parseArgs(argv, { flags, values })`, the `--flag` / `--key value`
* parsing they all use. On PostgreSQL (plan T2, ADR-035) the scripts open the service database through
* parsing they all use. On PostgreSQL (plan T2, ADR-035) the scripts open the service database through
- * DATABASE_URL themselves (scripts/secrets-out-of-db.js, scripts/import-blocks.js); the old
+ * DATABASE_URL themselves (for example, scripts/secrets-out-of-db.js); the old
* better-sqlite3 helpers (dbPath, dropToOwnerOf, the sqlite online backup) are gone with the file.
* better-sqlite3 helpers (dbPath, dropToOwnerOf, the sqlite online backup) are gone with the file.
*/
*/
const path = require('path');
const path = require('path');
@@ -329,7 +329,7 @@ async function bootAndCheck(target, dir, { timeoutMs = 120000 } = {}) {
@@ -329,7 +329,7 @@ async function bootAndCheck(target, dir, { timeoutMs = 120000 } = {}) {
PATH: process.env.PATH, HOME: dir, PORT: String(port), HOST: '127.0.0.1',
PATH: process.env.PATH, HOME: dir, PORT: String(port), HOST: '127.0.0.1',
NODE_ENV: target.store === 'pglite' ? 'test' : 'production',
NODE_ENV: target.store === 'pglite' ? 'test' : 'production',
JWT_PRIVATE_KEY: path.join(dir, 'keys', 'private.pem'), JWT_PUBLIC_KEY: path.join(dir, 'keys', 'public.pem'),
JWT_PRIVATE_KEY: path.join(dir, 'keys', 'private.pem'), JWT_PUBLIC_KEY: path.join(dir, 'keys', 'public.pem'),
- AVATAR_PATH: path.join(dir, 'avatars'), INTERNAL_API_KEY: crypto.randomBytes(24).toString('hex'),
+ AVATAR_PATH: path.join(dir, 'avatars'),
OV_NETWORK_INTERNAL_URL: `http://127.0.0.1:${port}`,
OV_NETWORK_INTERNAL_URL: `http://127.0.0.1:${port}`,
OV_LIVE_INTERNAL_URL: 'http://127.0.0.1:9', OV_TOOLS_INTERNAL_URL: 'http://127.0.0.1:9', OV_GAMES_INTERNAL_URL: 'http://127.0.0.1:9',
OV_LIVE_INTERNAL_URL: 'http://127.0.0.1:9', OV_TOOLS_INTERNAL_URL: 'http://127.0.0.1:9', OV_GAMES_INTERNAL_URL: 'http://127.0.0.1:9',
OV_MEDIA_INTERNAL_URL: 'http://127.0.0.1:9', OV_AI_INTERNAL_URL: 'http://127.0.0.1:9',
OV_MEDIA_INTERNAL_URL: 'http://127.0.0.1:9', OV_AI_INTERNAL_URL: 'http://127.0.0.1:9',
@@ -336,7 +336,7 @@ function userRouter(requireAuth) {
@@ -336,7 +336,7 @@ function userRouter(requireAuth) {
return router;
return router;
}
}
-/** Service routes; mounted under /internal (after requireInternalKey) with principal guards. */
+/** Service routes; mounted under /internal after the requireServiceToken gate, with principal guards. */
function serviceRoutes(router, principals) {
function serviceRoutes(router, principals) {
const nsOf = (req) => req.params.ns;
const nsOf = (req) => req.params.ns;
const resolveSubject = async (db, s) => (/^usr_[0-9A-HJKMNP-TV-Z]{26}$/.test(s) && await db.prepare('SELECT 1 FROM users WHERE subject_id = ?').get(s) ? s : null);
const resolveSubject = async (db, s) => (/^usr_[0-9A-HJKMNP-TV-Z]{26}$/.test(s) && await db.prepare('SELECT 1 FROM users WHERE subject_id = ?').get(s) ? s : null);
@@ -5,7 +5,7 @@
@@ -5,7 +5,7 @@
* POST /internal/events/stream-live Live's direct call (server/internal/routes.js), with the
* POST /internal/events/stream-live Live's direct call (server/internal/routes.js), with the
* follower list in the body (follower_network_ids)
* follower list in the body (follower_network_ids)
* live.stream.started via Events server/notifications/events-consumer.js; the followers are
* live.stream.started via Events server/notifications/events-consumer.js; the followers are
- * read from Network's active user_follows rows
+ * read from Network's own active user_follows rows
*
*
* Both claim the same persisted per-streamer announcement window (stream_live_announcements, keyed
* Both claim the same persisted per-streamer announcement window (stream_live_announcements, keyed
* by the streamer's NETWORK user id), so while Live still makes the direct call and Network also
* by the streamer's NETWORK user id), so while Live still makes the direct call and Network also
@@ -69,7 +69,7 @@ async function dumpAnalytics(db) {
@@ -69,7 +69,7 @@ async function dumpAnalytics(db) {
const ISSUER = 'https://openvibe.network';
const ISSUER = 'https://openvibe.network';
const closed = 'http://127.0.0.1:9'; // remote dashboards: refused at once, never a local dev server
const closed = 'http://127.0.0.1:9'; // remote dashboards: refused at once, never a local dev server
const config = {
const config = {
- internalKey: 'k'.repeat(32), baseUrl: ISSUER, loginUrl: ISSUER, networkUrl: ISSUER,
+ baseUrl: ISSUER, loginUrl: ISSUER, networkUrl: ISSUER,
jwt: { issuer: ISSUER, accessTokenExpiry: '1h', refreshTokenExpiry: '30d' },
jwt: { issuer: ISSUER, accessTokenExpiry: '1h', refreshTokenExpiry: '30d' },
services: { live: { internalUrl: closed }, tools: { internalUrl: closed }, games: { internalUrl: closed }, media: { internalUrl: closed } },
services: { live: { internalUrl: closed }, tools: { internalUrl: closed }, games: { internalUrl: closed }, media: { internalUrl: closed } },
};
};
@@ -28,7 +28,7 @@ for (const bad of ['http://openvibe.media/x.png', 'javascript:alert(1)', 'https:
@@ -28,7 +28,7 @@ for (const bad of ['http://openvibe.media/x.png', 'javascript:alert(1)', 'https:
// Network's own service token for the push (plan T2): live.avatar.write, audience openvibe.live.
// Network's own service token for the push (plan T2): live.avatar.write, audience openvibe.live.
const { privateKey, publicKey } = require('crypto').generateKeyPairSync('rsa', { modulusLength: 2048, publicKeyEncoding: { type: 'spki', format: 'pem' }, privateKeyEncoding: { type: 'pkcs8', format: 'pem' } });
const { privateKey, publicKey } = require('crypto').generateKeyPairSync('rsa', { modulusLength: 2048, publicKeyEncoding: { type: 'spki', format: 'pem' }, privateKeyEncoding: { type: 'pkcs8', format: 'pem' } });
const selfToken = require('../server/identity/self-token').createSelfTokens({ privateKey, issuer: 'https://openvibe.network' });
const selfToken = require('../server/identity/self-token').createSelfTokens({ privateKey, issuer: 'https://openvibe.network' });
- const svc = createAvatarService({ db, config: { internalKey: 'k'.repeat(24), services: {} }, selfToken, requireAuth: (req, _res, next) => { req.user = { id: 1, username: 'Goosely' }; next(); } });
+ const svc = createAvatarService({ db, config: { services: {} }, selfToken, requireAuth: (req, _res, next) => { req.user = { id: 1, username: 'Goosely' }; next(); } });
assert.equal((await svc.fromSite({ user_id: 1, avatar_url: `${M}/f/a.png`, origin: 'live' })).changed, true);
assert.equal((await svc.fromSite({ user_id: 1, avatar_url: `${M}/f/a.png`, origin: 'live' })).changed, true);
assert.equal(pushes.length, 0, 'a change that came from Live is not echoed back to Live');
assert.equal(pushes.length, 0, 'a change that came from Live is not echoed back to Live');
await svc.apply(1, 'Goosely', `${M}/f/b.png`, 'network'); assert.equal(pushes.length, 1); assert.equal(pushes[0].avatar_url, `${M}/f/b.png`, 'a change made here is pushed to the sites');
await svc.apply(1, 'Goosely', `${M}/f/b.png`, 'network'); assert.equal(pushes.length, 1); assert.equal(pushes[0].avatar_url, `${M}/f/b.png`, 'a change made here is pushed to the sites');
@@ -17,7 +17,6 @@ const { getDb } = require('../server/db/database');
@@ -17,7 +17,6 @@ const { getDb } = require('../server/db/database');
(async () => {
(async () => {
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'ov-blocks-'));
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'ov-blocks-'));
-const dbFile = path.join(dir, 'network.db');
const log = console.log; console.log = () => {};
const log = console.log; console.log = () => {};
const db = getDb();
const db = getDb();
console.log = log;
console.log = log;
@@ -39,7 +38,7 @@ await db.prepare("INSERT INTO users (id, username, password_hash, is_anon) VALUE
@@ -39,7 +38,7 @@ await db.prepare("INSERT INTO users (id, username, password_hash, is_anon) VALUE
const GUEST_SUBJECT = (await db.prepare('SELECT subject_id FROM users WHERE id = 5').get()).subject_id;
const GUEST_SUBJECT = (await db.prepare('SELECT subject_id FROM users WHERE id = 5').get()).subject_id;
const keys = crypto.generateKeyPairSync('rsa', { modulusLength: 2048, publicKeyEncoding: { type: 'spki', format: 'pem' }, privateKeyEncoding: { type: 'pkcs8', format: 'pem' } });
const keys = crypto.generateKeyPairSync('rsa', { modulusLength: 2048, publicKeyEncoding: { type: 'spki', format: 'pem' }, privateKeyEncoding: { type: 'pkcs8', format: 'pem' } });
-const config = { internalKey: 'legacy-key', jwt: { issuer: 'https://openvibe.network', accessTokenExpiry: '1h' } };
+const config = { jwt: { issuer: 'https://openvibe.network', accessTokenExpiry: '1h' } };
const { signToken } = require('../server/auth/routes');
const { signToken } = require('../server/auth/routes');
const requireAuth = require('../server/auth/session').makeRequireAuth(() => ({ db, publicKey: keys.publicKey, config }), signToken);
const requireAuth = require('../server/auth/session').makeRequireAuth(() => ({ db, publicKey: keys.publicKey, config }), signToken);
const blocks = require('../server/identity/blocks');
const blocks = require('../server/identity/blocks');
@@ -28,7 +28,7 @@ const CAROL = ids.newId('user'), DAVE = ids.newId('user');
@@ -28,7 +28,7 @@ const CAROL = ids.newId('user'), DAVE = ids.newId('user');
await db.prepare('INSERT INTO users (id, username, password_hash, subject_id, role) VALUES (20, ?, ?, ?, ?), (21, ?, ?, ?, ?)').run('carol', 'x', CAROL, 'streamer', 'dave', 'x', DAVE, 'user');
await db.prepare('INSERT INTO users (id, username, password_hash, subject_id, role) VALUES (20, ?, ?, ?, ?), (21, ?, ?, ?, ?)').run('carol', 'x', CAROL, 'streamer', 'dave', 'x', DAVE, 'user');
const keys = crypto.generateKeyPairSync('rsa', { modulusLength: 2048, publicKeyEncoding: { type: 'spki', format: 'pem' }, privateKeyEncoding: { type: 'pkcs8', format: 'pem' } });
const keys = crypto.generateKeyPairSync('rsa', { modulusLength: 2048, publicKeyEncoding: { type: 'spki', format: 'pem' }, privateKeyEncoding: { type: 'pkcs8', format: 'pem' } });
-const config = { internalKey: 'legacy-key', jwt: { issuer: 'https://openvibe.network', accessTokenExpiry: '1h' } };
+const config = { jwt: { issuer: 'https://openvibe.network', accessTokenExpiry: '1h' } };
const { signToken } = require('../server/auth/routes');
const { signToken } = require('../server/auth/routes');
const SECRET = 's'.repeat(40);
const SECRET = 's'.repeat(40);
const consumer = await createEventsConsumer({ db, notifications: new NotificationService(db), secrets: SECRET, log: { log() {}, warn() {}, error() {} } });
const consumer = await createEventsConsumer({ db, notifications: new NotificationService(db), secrets: SECRET, log: { log() {}, warn() {}, error() {} } });
@@ -34,7 +34,7 @@ app.use(express.json());
@@ -34,7 +34,7 @@ app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.use(express.urlencoded({ extended: true }));
app.locals.db = db;
app.locals.db = db;
// No developer settings at all: the code defaults apply (as with DEV_SANDBOX_* unset).
// No developer settings at all: the code defaults apply (as with DEV_SANDBOX_* unset).
-app.locals.config = { baseUrl: ISSUER, loginUrl: ISSUER, internalKey: 'legacy-key', jwt: { issuer: ISSUER, accessTokenExpiry: '1h' } };
+app.locals.config = { baseUrl: ISSUER, loginUrl: ISSUER, jwt: { issuer: ISSUER, accessTokenExpiry: '1h' } };
app.locals.privateKey = keys.privateKey;
app.locals.privateKey = keys.privateKey;
app.locals.publicKey = keys.publicKey;
app.locals.publicKey = keys.publicKey;
app.use('/oauth', require('../server/auth/oauth-routes'));
app.use('/oauth', require('../server/auth/oauth-routes'));
@@ -37,7 +37,7 @@ app.use(express.json());
@@ -37,7 +37,7 @@ app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.use(express.urlencoded({ extended: true }));
app.locals.db = db;
app.locals.db = db;
app.locals.config = {
app.locals.config = {
- baseUrl: ISSUER, loginUrl: ISSUER, internalKey: 'legacy-key',
+ baseUrl: ISSUER, loginUrl: ISSUER,
jwt: { issuer: ISSUER, accessTokenExpiry: '1h' },
jwt: { issuer: ISSUER, accessTokenExpiry: '1h' },
developer: { credentialOverlapS: 60 },
developer: { credentialOverlapS: 60 },
};
};
@@ -35,7 +35,7 @@ app.use(express.json());
@@ -35,7 +35,7 @@ app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.use(express.urlencoded({ extended: true }));
app.locals.db = db;
app.locals.db = db;
app.locals.config = {
app.locals.config = {
- baseUrl: ISSUER, loginUrl: ISSUER, internalKey: 'legacy-key',
+ baseUrl: ISSUER, loginUrl: ISSUER,
jwt: { issuer: ISSUER, accessTokenExpiry: '1h' },
jwt: { issuer: ISSUER, accessTokenExpiry: '1h' },
// No sandbox allowance here, so every grant goes through the staff-set allowance (the defaults
// No sandbox allowance here, so every grant goes through the staff-set allowance (the defaults
// are covered by test/developer-defaults.test.js).
// are covered by test/developer-defaults.test.js).
@@ -1,4 +1,5 @@
@@ -1,4 +1,5 @@
'use strict';
'use strict';
+process.env.FOLLOWS_AUTHORITY = 'live';
// ADR-030 step 4, plan T2 "Follows": products write follows through /internal/follows (network.follows.write,
// ADR-030 step 4, plan T2 "Follows": products write follows through /internal/follows (network.follows.write,
// service token only), and go-live notifications take the followers from Network's own graph only (no call to
// service token only), and go-live notifications take the followers from Network's own graph only (no call to
// Live, whatever the environment says); a channel with no Network account announces nothing.
// Live, whatever the environment says); a channel with no Network account announces nothing.
@@ -28,7 +29,7 @@ await db.prepare('INSERT INTO users (id, username, password_hash, subject_id, ro
@@ -28,7 +29,7 @@ await db.prepare('INSERT INTO users (id, username, password_hash, subject_id, ro
.run('carol', 'x', CAROL, 'streamer', 'dave', 'x', DAVE, 'user', 'erin', 'x', ERIN, 'user');
.run('carol', 'x', CAROL, 'streamer', 'dave', 'x', DAVE, 'user', 'erin', 'x', ERIN, 'user');
const keys = crypto.generateKeyPairSync('rsa', { modulusLength: 2048, publicKeyEncoding: { type: 'spki', format: 'pem' }, privateKeyEncoding: { type: 'pkcs8', format: 'pem' } });
const keys = crypto.generateKeyPairSync('rsa', { modulusLength: 2048, publicKeyEncoding: { type: 'spki', format: 'pem' }, privateKeyEncoding: { type: 'pkcs8', format: 'pem' } });
-const config = { internalKey: 'legacy-key', jwt: { issuer: 'https://openvibe.network', accessTokenExpiry: '1h' } };
+const config = { jwt: { issuer: 'https://openvibe.network', accessTokenExpiry: '1h' } };
const { signToken } = require('../server/auth/routes');
const { signToken } = require('../server/auth/routes');
const requireAuth = require('../server/auth/session').makeRequireAuth(() => ({ db, publicKey: keys.publicKey, config }), signToken);
const requireAuth = require('../server/auth/session').makeRequireAuth(() => ({ db, publicKey: keys.publicKey, config }), signToken);
const follows = require('../server/identity/follows');
const follows = require('../server/identity/follows');
@@ -74,7 +75,6 @@ const server = http.createServer(app);
@@ -74,7 +75,6 @@ const server = http.createServer(app);
assert.deepStrictEqual(evs.map((e) => e.event_type), ['network.follow.created', 'network.follow.created', 'network.follow.deleted', 'network.follow.created']);
assert.deepStrictEqual(evs.map((e) => e.event_type), ['network.follow.created', 'network.follow.created', 'network.follow.deleted', 'network.follow.created']);
// ── Go-live: Network's followers, no Live call (even with Live's old switch set) ──
// ── Go-live: Network's followers, no Live call (even with Live's old switch set) ──
- process.env.FOLLOWS_AUTHORITY = 'live';
const started = {
const started = {
event_id: ids.newId('event'), event_type: 'live.stream.started', version: 1, source: 'live',
event_id: ids.newId('event'), event_type: 'live.stream.started', version: 1, source: 'live',
actor: { type: 'user', id: CAROL }, subject: { type: 'stream', id: '801', revision: 1 }, visibility: 'public', priority: 'important',
actor: { type: 'user', id: CAROL }, subject: { type: 'stream', id: '801', revision: 1 }, visibility: 'public', priority: 'important',
@@ -31,7 +31,7 @@ await db.prepare(`INSERT INTO users (id, username, display_name, password_hash,
@@ -31,7 +31,7 @@ await db.prepare(`INSERT INTO users (id, username, display_name, password_hash,
await db.prepare("INSERT INTO users (id, username, password_hash, is_anon) VALUES (5, 'anon1234', 'x', 1)").run();
await db.prepare("INSERT INTO users (id, username, password_hash, is_anon) VALUES (5, 'anon1234', 'x', 1)").run();
const keys = crypto.generateKeyPairSync('rsa', { modulusLength: 2048, publicKeyEncoding: { type: 'spki', format: 'pem' }, privateKeyEncoding: { type: 'pkcs8', format: 'pem' } });
const keys = crypto.generateKeyPairSync('rsa', { modulusLength: 2048, publicKeyEncoding: { type: 'spki', format: 'pem' }, privateKeyEncoding: { type: 'pkcs8', format: 'pem' } });
-const config = { internalKey: 'legacy-key', jwt: { issuer: 'https://openvibe.network', accessTokenExpiry: '1h' } };
+const config = { jwt: { issuer: 'https://openvibe.network', accessTokenExpiry: '1h' } };
const { signToken } = require('../server/auth/routes');
const { signToken } = require('../server/auth/routes');
const requireAuth = require('../server/auth/session').makeRequireAuth(() => ({ db, publicKey: keys.publicKey, config }), signToken);
const requireAuth = require('../server/auth/session').makeRequireAuth(() => ({ db, publicKey: keys.publicKey, config }), signToken);
const follows = require('../server/identity/follows');
const follows = require('../server/identity/follows');
@@ -8,7 +8,7 @@ const { siteForHost } = require('../server/frame/sites');
@@ -8,7 +8,7 @@ const { siteForHost } = require('../server/frame/sites');
(async () => {
(async () => {
const db = getDb();
const db = getDb();
-const svc = await createFrameService(db, { internalKey: 'change-me-in-production', services: {} }, null);
+const svc = await createFrameService(db, { services: {} }, null);
assert.equal(siteForHost('yt.openvibe.tools').id, 'tools');
assert.equal(siteForHost('yt.openvibe.tools').id, 'tools');
assert.equal(siteForHost('evil.example'), null);
assert.equal(siteForHost('evil.example'), null);
@@ -49,7 +49,7 @@ for (let i = 0; i < 6; i++) await ins.run(1, 'tools', 'dns');
@@ -49,7 +49,7 @@ for (let i = 0; i < 6; i++) await ins.run(1, 'tools', 'dns');
});
});
await Promise.all([live, ai].map(s => new Promise(r => s.listen(0, '127.0.0.1', r))));
await Promise.all([live, ai].map(s => new Promise(r => s.listen(0, '127.0.0.1', r))));
const { privateKey } = crypto.generateKeyPairSync('rsa', { modulusLength: 2048, privateKeyEncoding: { type: 'pkcs8', format: 'pem' }, publicKeyEncoding: { type: 'spki', format: 'pem' } });
const { privateKey } = crypto.generateKeyPairSync('rsa', { modulusLength: 2048, privateKeyEncoding: { type: 'pkcs8', format: 'pem' }, publicKeyEncoding: { type: 'spki', format: 'pem' } });
- const cfg = { internalKey: 'a-real-internal-key', services: { live: { internalUrl: `http://127.0.0.1:${live.address().port}` } } };
+ const cfg = { services: { live: { internalUrl: `http://127.0.0.1:${live.address().port}` } } };
const aiUrl = `http://127.0.0.1:${ai.address().port}`;
const aiUrl = `http://127.0.0.1:${ai.address().port}`;
const db2 = db; // the process's database (test/helpers/pg-preload.mjs); the copy cache lives here
const db2 = db; // the process's database (test/helpers/pg-preload.mjs); the copy cache lives here
const copySvc = async (d) => await createFrameService(d, cfg, null, { privateKey, issuer: 'https://openvibe.network', aiUrl });
const copySvc = async (d) => await createFrameService(d, cfg, null, { privateKey, issuer: 'https://openvibe.network', aiUrl });
@@ -36,7 +36,7 @@ const keys = crypto.generateKeyPairSync('rsa', { modulusLength: 2048, publicKeyE
@@ -36,7 +36,7 @@ const keys = crypto.generateKeyPairSync('rsa', { modulusLength: 2048, publicKeyE
const app = express();
const app = express();
app.use(express.urlencoded({ extended: true }));
app.use(express.urlencoded({ extended: true }));
app.locals.db = db;
app.locals.db = db;
-app.locals.config = { internalKey: 'legacy-key', jwt: { issuer: 'https://openvibe.network', accessTokenExpiry: '1h' } };
+app.locals.config = { jwt: { issuer: 'https://openvibe.network', accessTokenExpiry: '1h' } };
app.locals.privateKey = keys.privateKey;
app.locals.privateKey = keys.privateKey;
app.locals.publicKey = keys.publicKey;
app.locals.publicKey = keys.publicKey;
app.use('/oauth', require('../server/auth/oauth-routes'));
app.use('/oauth', require('../server/auth/oauth-routes'));
@@ -32,7 +32,7 @@ async function bootServer({ env = {}, child = false, timeoutMs = 60000 } = {}) {
@@ -32,7 +32,7 @@ async function bootServer({ env = {}, child = false, timeoutMs = 60000 } = {}) {
NODE_ENV: 'test', PORT: String(port), HOST: '127.0.0.1', PGLITE_DIR: path.join(dir, 'pglite'),
NODE_ENV: 'test', PORT: String(port), HOST: '127.0.0.1', PGLITE_DIR: path.join(dir, 'pglite'),
AVATAR_PATH: path.join(dir, 'avatars'),
AVATAR_PATH: path.join(dir, 'avatars'),
JWT_PRIVATE_KEY: path.join(dir, 'none.pem'), JWT_PUBLIC_KEY: path.join(dir, 'none.pub.pem'),
JWT_PRIVATE_KEY: path.join(dir, 'none.pem'), JWT_PUBLIC_KEY: path.join(dir, 'none.pub.pem'),
- BOOTSTRAP_PROFILE: 'local-dev', INTERNAL_API_KEY: 'k'.repeat(40),
+ BOOTSTRAP_PROFILE: 'local-dev',
OV_NETWORK_INTERNAL_URL: `http://127.0.0.1:${port}`,
OV_NETWORK_INTERNAL_URL: `http://127.0.0.1:${port}`,
OV_LIVE_INTERNAL_URL: 'http://127.0.0.1:9', OV_TOOLS_INTERNAL_URL: 'http://127.0.0.1:9',
OV_LIVE_INTERNAL_URL: 'http://127.0.0.1:9', OV_TOOLS_INTERNAL_URL: 'http://127.0.0.1:9',
OV_GAMES_INTERNAL_URL: 'http://127.0.0.1:9', OV_MEDIA_INTERNAL_URL: 'http://127.0.0.1:9',
OV_GAMES_INTERNAL_URL: 'http://127.0.0.1:9', OV_MEDIA_INTERNAL_URL: 'http://127.0.0.1:9',
@@ -29,7 +29,7 @@ await db.prepare("INSERT INTO users (id, username, password_hash, is_anon) VALUE
@@ -29,7 +29,7 @@ await db.prepare("INSERT INTO users (id, username, password_hash, is_anon) VALUE
const clock = { offset: 0 };
const clock = { offset: 0 };
const now = () => Date.now() + clock.offset;
const now = () => Date.now() + clock.offset;
const keys = crypto.generateKeyPairSync('rsa', { modulusLength: 2048, publicKeyEncoding: { type: 'spki', format: 'pem' }, privateKeyEncoding: { type: 'pkcs8', format: 'pem' } });
const keys = crypto.generateKeyPairSync('rsa', { modulusLength: 2048, publicKeyEncoding: { type: 'spki', format: 'pem' }, privateKeyEncoding: { type: 'pkcs8', format: 'pem' } });
-const config = { internalKey: 'legacy-key', jwt: { issuer: 'https://openvibe.network', accessTokenExpiry: '1h' } };
+const config = { jwt: { issuer: 'https://openvibe.network', accessTokenExpiry: '1h' } };
const requireAuth = require('../server/auth/session').makeRequireAuth(() => ({ db, publicKey: keys.publicKey, config }), signToken);
const requireAuth = require('../server/auth/session').makeRequireAuth(() => ({ db, publicKey: keys.publicKey, config }), signToken);
const app = express();
const app = express();
@@ -27,7 +27,7 @@ const keys = crypto.generateKeyPairSync('rsa', { modulusLength: 2048, publicKeyE
@@ -27,7 +27,7 @@ const keys = crypto.generateKeyPairSync('rsa', { modulusLength: 2048, publicKeyE
const app = express();
const app = express();
app.use(express.urlencoded({ extended: true }));
app.use(express.urlencoded({ extended: true }));
app.locals.db = db;
app.locals.db = db;
-app.locals.config = { internalKey: 'legacy-key', jwt: { issuer: 'https://openvibe.network', accessTokenExpiry: '1h' } };
+app.locals.config = { jwt: { issuer: 'https://openvibe.network', accessTokenExpiry: '1h' } };
app.locals.privateKey = keys.privateKey;
app.locals.privateKey = keys.privateKey;
app.locals.publicKey = keys.publicKey;
app.locals.publicKey = keys.publicKey;
app.use('/oauth', require('../server/auth/oauth-routes'));
app.use('/oauth', require('../server/auth/oauth-routes'));
@@ -28,7 +28,7 @@ metrics.instrument(app, { service: 'network', release: 'abc123def456', registry:
@@ -28,7 +28,7 @@ metrics.instrument(app, { service: 'network', release: 'abc123def456', registry:
app.use(express.json());
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.use(express.urlencoded({ extended: true }));
app.locals.db = db;
app.locals.db = db;
-app.locals.config = { internalKey: 'legacy-key', jwt: { issuer: ISSUER, accessTokenExpiry: '1h' } };
+app.locals.config = { jwt: { issuer: ISSUER, accessTokenExpiry: '1h' } };
app.locals.privateKey = keys.privateKey;
app.locals.privateKey = keys.privateKey;
app.locals.publicKey = keys.publicKey;
app.locals.publicKey = keys.publicKey;
app.use('/oauth/token', observability.tokenEndpointMetrics);
app.use('/oauth/token', observability.tokenEndpointMetrics);
@@ -23,7 +23,7 @@ const app = express();
@@ -23,7 +23,7 @@ const app = express();
app.use(express.json());
app.use(express.json());
app.locals.db = db;
app.locals.db = db;
app.locals.config = {
app.locals.config = {
- baseUrl: ISSUER, loginUrl: ISSUER, internalKey: 'legacy-key',
+ baseUrl: ISSUER, loginUrl: ISSUER,
jwt: { issuer: ISSUER, accessTokenExpiry: '1h' },
jwt: { issuer: ISSUER, accessTokenExpiry: '1h' },
developer: { sandboxAudiences: 'openvibe.media', sandboxAllowance: '', credentialOverlapS: 60 },
developer: { sandboxAudiences: 'openvibe.media', sandboxAllowance: '', credentialOverlapS: 60 },
};
};
@@ -29,7 +29,7 @@ const keys = crypto.generateKeyPairSync('rsa', { modulusLength: 2048, publicKeyE
@@ -29,7 +29,7 @@ const keys = crypto.generateKeyPairSync('rsa', { modulusLength: 2048, publicKeyE
const app = express();
const app = express();
app.use(express.urlencoded({ extended: true }));
app.use(express.urlencoded({ extended: true }));
app.locals.db = db;
app.locals.db = db;
-app.locals.config = { internalKey: 'legacy-key', jwt: { issuer: 'https://openvibe.network', accessTokenExpiry: '1h' } };
+app.locals.config = { jwt: { issuer: 'https://openvibe.network', accessTokenExpiry: '1h' } };
app.locals.privateKey = keys.privateKey;
app.locals.privateKey = keys.privateKey;
app.locals.publicKey = keys.publicKey;
app.locals.publicKey = keys.publicKey;
app.use('/oauth', require('../server/auth/oauth-routes'));
app.use('/oauth', require('../server/auth/oauth-routes'));
@@ -121,7 +121,7 @@ const out = (...a) => process.stdout.write(a.join(' ') + '\n');
@@ -121,7 +121,7 @@ const out = (...a) => process.stdout.write(a.join(' ') + '\n');
}
}
assert.deepStrictEqual(bad, [], `private reads answered:\n${bad.join('\n')}`);
assert.deepStrictEqual(bad, [], `private reads answered:\n${bad.join('\n')}`);
// A service principal reaches only the /internal routes its capabilities name.
// A service principal reaches only the /internal routes its capabilities name.
- assert.strictEqual(st['service GET /internal/integrations/github-token'], 403, 'github-token needs the legacy key or its own capability');
+ assert.strictEqual(st['service GET /internal/integrations/github-token'], 403, 'github-token needs its own capability');
// Deleted with the X-Internal-Key retirement (plan T2, no caller left): no longer routes at all.
// Deleted with the X-Internal-Key retirement (plan T2, no caller left): no longer routes at all.
for (const p of ['/internal/stats', `/internal/users/${users.alice.id}`, `/internal/users/${users.alice.id}/linked-accounts`, '/internal/anon-list', `/internal/notifications/unread/${users.alice.id}`]) {
for (const p of ['/internal/stats', `/internal/users/${users.alice.id}`, `/internal/users/${users.alice.id}/linked-accounts`, '/internal/anon-list', `/internal/notifications/unread/${users.alice.id}`]) {
assert.strictEqual(st[`service GET ${p}`], undefined, `${p} is gone`);
assert.strictEqual(st[`service GET ${p}`], undefined, `${p} is gone`);
@@ -63,7 +63,7 @@ const out = (...a) => process.stdout.write(a.join(' ') + '\n');
@@ -63,7 +63,7 @@ const out = (...a) => process.stdout.write(a.join(' ') + '\n');
'/oauth/client-info?client_id=live', `/oauth/client-info?client_id=${dev.PA.app}`, '/api/auth/anon-identities', '/sso/check', '/fedcm/accounts'];
'/oauth/client-info?client_id=live', `/oauth/client-info?client_id=${dev.PA.app}`, '/api/auth/anon-identities', '/sso/check', '/fedcm/accounts'];
const paths = crawler.pathsFor(w.routes, values, { method: 'get', query: 'limit=5&all=1&debug=1&include=secret', extra });
const paths = crawler.pathsFor(w.routes, values, { method: 'get', query: 'limit=5&all=1&debug=1&include=secret', extra });
const tokenOf = (h) => String((h && h.authorization) || '').replace(/^Bearer /, '');
const tokenOf = (h) => String((h && h.authorization) || '').replace(/^Bearer /, '');
- const people = { ...w.callers, key: { 'x-internal-key': w.sentinels.INTERNAL_API_KEY } };
+ const people = { ...w.callers, key: { 'x-internal-key': 'retired-key' } };
// What each caller must never see: every secret, plus every other caller's credential. Staff
// What each caller must never see: every secret, plus every other caller's credential. Staff
// admins and the owner list verification keys (they make them to hand out); nobody else sees one.
// admins and the owner list verification keys (they make them to hand out); nobody else sees one.
const needlesFor = (who) => {
const needlesFor = (who) => {
@@ -137,7 +137,7 @@ const out = (...a) => process.stdout.write(a.join(' ') + '\n');
@@ -137,7 +137,7 @@ const out = (...a) => process.stdout.write(a.join(' ') + '\n');
}
}
// Wrong credentials of every kind on the paths that take them.
// Wrong credentials of every kind on the paths that take them.
const wrong = {
const wrong = {
- 'retired internal key': { 'x-internal-key': w.sentinels.INTERNAL_API_KEY },
+ 'retired internal key': { 'x-internal-key': 'retired-key' },
'forged bearer': { authorization: `Bearer ${jwt.sign({ sub: users.owner.id, id: users.owner.id }, 'guess', { issuer: w.issuer })}` },
'forged bearer': { authorization: `Bearer ${jwt.sign({ sub: users.owner.id, id: users.owner.id }, 'guess', { issuer: w.issuer })}` },
'none alg bearer': { authorization: `Bearer ${jwt.sign({ sub: users.owner.id, id: users.owner.id }, null, { algorithm: 'none' })}` },
'none alg bearer': { authorization: `Bearer ${jwt.sign({ sub: users.owner.id, id: users.owner.id }, null, { algorithm: 'none' })}` },
'setup token guess': { 'x-setup-token': 'x', authorization: 'Bearer x' },
'setup token guess': { 'x-setup-token': 'x', authorization: 'Bearer x' },
@@ -26,7 +26,6 @@ const S = (name) => `sentinel-not-a-secret-${name}`;
@@ -26,7 +26,6 @@ const S = (name) => `sentinel-not-a-secret-${name}`;
/** Every secret-typed environment variable Network (or a module it loads) reads, with its sentinel. */
/** Every secret-typed environment variable Network (or a module it loads) reads, with its sentinel. */
function sentinelEnv() {
function sentinelEnv() {
return {
return {
- INTERNAL_API_KEY: S('internal-api-key'),
SETUP_TOKEN: S('setup-token'),
SETUP_TOKEN: S('setup-token'),
NETWORK_EVENTS_SECRET: S('network-events-secret'),
NETWORK_EVENTS_SECRET: S('network-events-secret'),
OV_LIVE_WEBHOOK_SECRET: S('live-webhook-secret'),
OV_LIVE_WEBHOOK_SECRET: S('live-webhook-secret'),
@@ -136,7 +135,7 @@ async function buildWorld({ label = 'sec', env: extraEnv = {} } = {}) {
@@ -136,7 +135,7 @@ async function buildWorld({ label = 'sec', env: extraEnv = {} } = {}) {
const shownOnce = []; // responses that legitimately carry a secret once (checked for no-store by the suites)
const shownOnce = []; // responses that legitimately carry a secret once (checked for no-store by the suites)
const once = (what, r) => { shownOnce.push({ what, status: r.status, cache: r.headers.get('cache-control') || '' }); return r; };
const once = (what, r) => { shownOnce.push({ what, status: r.status, cache: r.headers.get('cache-control') || '' }); return r; };
- // Service principal tokens (client_credentials) and the legacy key.
+ // Service principal tokens (client_credentials).
async function serviceToken(clientId, audience = 'openvibe.network') {
async function serviceToken(clientId, audience = 'openvibe.network') {
const r = once(`service token ${clientId}`, await call(null, 'POST', '/oauth/token', new URLSearchParams({ grant_type: 'client_credentials', client_id: clientId, client_secret: clientSecrets[clientId], audience })));
const r = once(`service token ${clientId}`, await call(null, 'POST', '/oauth/token', new URLSearchParams({ grant_type: 'client_credentials', client_id: clientId, client_secret: clientSecrets[clientId], audience })));
if (r.status !== 200) throw new Error(`service token for ${clientId}: ${r.status} ${r.text}`);
if (r.status !== 200) throw new Error(`service token for ${clientId}: ${r.status} ${r.text}`);
@@ -23,7 +23,7 @@ for (const c of ['host', 'live']) await db.prepare('UPDATE oauth_clients SET cli
@@ -23,7 +23,7 @@ for (const c of ['host', 'live']) await db.prepare('UPDATE oauth_clients SET cli
await db.prepare("INSERT INTO users (id, username, password_hash, subject_id, role) VALUES (1, 'boss', 'x', 'usr_01JAB2C3D4E5F6G7H8J9K0MNPA', 'admin'), (2, 'pat', 'x', 'usr_01JAB2C3D4E5F6G7H8J9K0MNPB', 'user')").run();
await db.prepare("INSERT INTO users (id, username, password_hash, subject_id, role) VALUES (1, 'boss', 'x', 'usr_01JAB2C3D4E5F6G7H8J9K0MNPA', 'admin'), (2, 'pat', 'x', 'usr_01JAB2C3D4E5F6G7H8J9K0MNPB', 'user')").run();
const keys = crypto.generateKeyPairSync('rsa', { modulusLength: 2048, publicKeyEncoding: { type: 'spki', format: 'pem' }, privateKeyEncoding: { type: 'pkcs8', format: 'pem' } });
const keys = crypto.generateKeyPairSync('rsa', { modulusLength: 2048, publicKeyEncoding: { type: 'spki', format: 'pem' }, privateKeyEncoding: { type: 'pkcs8', format: 'pem' } });
-const config = { internalKey: 'legacy-key', jwt: { issuer: 'https://openvibe.network', accessTokenExpiry: '1h' } };
+const config = { jwt: { issuer: 'https://openvibe.network', accessTokenExpiry: '1h' } };
const { signToken } = require('../server/auth/routes');
const { signToken } = require('../server/auth/routes');
const requireAuth = require('../server/auth/session').makeRequireAuth(() => ({ db, publicKey: keys.publicKey, config }), signToken);
const requireAuth = require('../server/auth/session').makeRequireAuth(() => ({ db, publicKey: keys.publicKey, config }), signToken);
const app = express();
const app = express();
@@ -20,7 +20,7 @@ const db = getDb();
@@ -20,7 +20,7 @@ const db = getDb();
console.log = log;
console.log = log;
await db.prepare("INSERT INTO users (id, username, password_hash, role) VALUES (1, 'ann', 'x', 'user'), (2, 'bob', 'x', 'user'), (3, 'boss', 'x', 'admin')").run();
await db.prepare("INSERT INTO users (id, username, password_hash, role) VALUES (1, 'ann', 'x', 'user'), (2, 'bob', 'x', 'user'), (3, 'boss', 'x', 'admin')").run();
const keys = crypto.generateKeyPairSync('rsa', { modulusLength: 2048, publicKeyEncoding: { type: 'spki', format: 'pem' }, privateKeyEncoding: { type: 'pkcs8', format: 'pem' } });
const keys = crypto.generateKeyPairSync('rsa', { modulusLength: 2048, publicKeyEncoding: { type: 'spki', format: 'pem' }, privateKeyEncoding: { type: 'pkcs8', format: 'pem' } });
-const config = { internalKey: 'legacy-key', jwt: { issuer: 'https://openvibe.network', accessTokenExpiry: '1h' } };
+const config = { jwt: { issuer: 'https://openvibe.network', accessTokenExpiry: '1h' } };
const { signToken } = require('../server/auth/routes');
const { signToken } = require('../server/auth/routes');
const requireAuth = require('../server/auth/session').makeRequireAuth(() => ({ db, publicKey: keys.publicKey, config }), signToken);
const requireAuth = require('../server/auth/session').makeRequireAuth(() => ({ db, publicKey: keys.publicKey, config }), signToken);
const requireAdmin = (req, res, next) => (req.user && req.user.role === 'admin' ? next() : res.status(403).json({ error: 'admin only' }));
const requireAdmin = (req, res, next) => (req.user && req.user.role === 'admin' ? next() : res.status(403).json({ error: 'admin only' }));