openvibers/openvibe.network Commits
Network: restore Space's events grant (a revoked default grant came back only by name) (#161)
Changes
@@@ -393,6 +393,14 @@ const REVOKED_GRANTS = [// Default grants whose namespaces changed: a row still exactly as the old default seeded it is moved to// the new list at boot, in this order (a row someone edited is left alone). Live's write grant lost// chat.preferences when the namespace moved to Chat (Wave 6), then chat.tts_defaults (contracts 0.41.0).++// Default grants that REVOKED_GRANTS once revoked and that are defaults again. The seed below inserts with ON CONFLICT++// DO NOTHING, so a revoked row would otherwise stay revoked; only these named rows are restored (a grant a person revoked++// through grants-admin is never touched). Each entry must also be in DEFAULT_GRANTS and no longer in REVOKED_GRANTS.++const RESTORED_GRANTS = [++ // T20 step 2 (2026-10-12): Space publishes its static hosting events again (revoked when the forum left, 10-08).++ ['space', 'events.event.publish', 'openvibe.events'],++];++const CHANGED_DEFAULT_NAMESPACES = [['live', 'network.modules.write', SELF_AUDIENCE, ['chat.preferences', 'chat.tts_defaults', 'live.profile'], ['chat.tts_defaults', 'live.profile']],['live', 'network.modules.write', SELF_AUDIENCE, ['chat.tts_defaults', 'live.profile'], ['live.profile', 'live.stats']],@@@ -415,6 +423,9 @@ async function ensureSchema(db) {for (const [client, cap, aud] of REVOKED_GRANTS) {await db.prepare("UPDATE principal_grants SET revoked_at = ov_now() WHERE client_id = ? AND capability = ? AND audience = ? AND revoked_at IS NULL").run(client, cap, aud);}++ for (const [client, cap, aud] of RESTORED_GRANTS) {++ await db.prepare("UPDATE principal_grants SET revoked_at = NULL WHERE client_id = ? AND capability = ? AND audience = ? AND granted_by = 'default' AND revoked_at IS NOT NULL").run(client, cap, aud);++ }for (const [client, cap, aud, was, now] of CHANGED_DEFAULT_NAMESPACES) {await db.prepare("UPDATE principal_grants SET namespaces = ? WHERE client_id = ? AND capability = ? AND audience = ? AND granted_by = 'default' AND namespaces = ?").run(JSON.stringify(now), client, cap, aud, JSON.stringify(was));@@@ -562,4 +573,4 @@ function guard(capability, { ownApp, namespace } = {}) {};}--module.exports = { ensureSchema, issueToken, guard, grantsFor, recordDecision, DEFAULT_GRANTS, REVOKED_GRANTS, TOKEN_TTL_S, SELF_AUDIENCE };++module.exports = { ensureSchema, issueToken, guard, grantsFor, recordDecision, DEFAULT_GRANTS, REVOKED_GRANTS, RESTORED_GRANTS, TOKEN_TTL_S, SELF_AUDIENCE };nnew file mode 100644@@@ -0,0 +1,35 @@++'use strict';++// A default grant that code revoked (REVOKED_GRANTS) and later made a default again comes back at boot only when it is++// named in RESTORED_GRANTS; a grant a person revoked stays revoked. (2026-10-12: Space's events.event.publish stayed++// revoked after Network#160 because the seed inserts with ON CONFLICT DO NOTHING.)++const assert = require('assert');++const { getDb } = require('../server/db/database');++const principals = require('../server/identity/principals');++++(async () => {++ const db = await getDb();++ await principals.ensureSchema(db);++ const key = (g) => g.slice(0, 3).join(' ');++ const defaults = new Set(principals.DEFAULT_GRANTS.map(key));++ const revoked = new Set(principals.REVOKED_GRANTS.map(key));++ for (const g of principals.RESTORED_GRANTS) {++ assert.ok(defaults.has(key(g)), `${key(g)} is restored, so it must be a default grant`);++ assert.ok(!revoked.has(key(g)), `${key(g)} is restored, so it must not be revoked`);++ }++ const row = (c, cap, aud) => db.prepare('SELECT revoked_at FROM principal_grants WHERE client_id = ? AND capability = ? AND audience = ?').get(c, cap, aud);++ const [client, cap, aud] = principals.RESTORED_GRANTS[0];++ assert.ok(await db.prepare('SELECT 1 FROM oauth_clients WHERE client_id = ?').get(client), `the ${client} client is seeded`);++ {++ await db.prepare('UPDATE principal_grants SET revoked_at = ov_now() WHERE client_id = ? AND capability = ? AND audience = ?').run(client, cap, aud);++ await principals.ensureSchema(db);++ assert.strictEqual((await row(client, cap, aud)).revoked_at, null, 'the restored default grant is live again after boot');++ }++ // A default grant that is not in RESTORED_GRANTS and was revoked (by a person, through grants-admin) stays revoked.++ const other = principals.DEFAULT_GRANTS.find((g) => !principals.RESTORED_GRANTS.some((r) => key(r) === key(g)) && g[0] === 'host');++ await db.prepare('UPDATE principal_grants SET revoked_at = ov_now() WHERE client_id = ? AND capability = ? AND audience = ?').run(other[0], other[1], other[2]);++ await principals.ensureSchema(db);++ const kept = await row(other[0], other[1], other[2]);++ assert.ok(!kept || kept.revoked_at, 'a grant someone revoked is never restored by boot');++ console.log('restored grants: a named default grant comes back; any other revoked grant stays revoked');++ process.exit(0);++})().catch((err) => { console.error(err); process.exit(1); });
@@ -393,6 +393,14 @@ const REVOKED_GRANTS = [
@@ -393,6 +393,14 @@ const REVOKED_GRANTS = [
// Default grants whose namespaces changed: a row still exactly as the old default seeded it is moved to
// Default grants whose namespaces changed: a row still exactly as the old default seeded it is moved to
// the new list at boot, in this order (a row someone edited is left alone). Live's write grant lost
// the new list at boot, in this order (a row someone edited is left alone). Live's write grant lost
// chat.preferences when the namespace moved to Chat (Wave 6), then chat.tts_defaults (contracts 0.41.0).
// chat.preferences when the namespace moved to Chat (Wave 6), then chat.tts_defaults (contracts 0.41.0).
+// Default grants that REVOKED_GRANTS once revoked and that are defaults again. The seed below inserts with ON CONFLICT
+// DO NOTHING, so a revoked row would otherwise stay revoked; only these named rows are restored (a grant a person revoked
+// through grants-admin is never touched). Each entry must also be in DEFAULT_GRANTS and no longer in REVOKED_GRANTS.
+const RESTORED_GRANTS = [
+ // T20 step 2 (2026-10-12): Space publishes its static hosting events again (revoked when the forum left, 10-08).
+ ['space', 'events.event.publish', 'openvibe.events'],
+];
+
const CHANGED_DEFAULT_NAMESPACES = [
const CHANGED_DEFAULT_NAMESPACES = [
['live', 'network.modules.write', SELF_AUDIENCE, ['chat.preferences', 'chat.tts_defaults', 'live.profile'], ['chat.tts_defaults', 'live.profile']],
['live', 'network.modules.write', SELF_AUDIENCE, ['chat.preferences', 'chat.tts_defaults', 'live.profile'], ['chat.tts_defaults', 'live.profile']],
['live', 'network.modules.write', SELF_AUDIENCE, ['chat.tts_defaults', 'live.profile'], ['live.profile', 'live.stats']],
['live', 'network.modules.write', SELF_AUDIENCE, ['chat.tts_defaults', 'live.profile'], ['live.profile', 'live.stats']],
@@ -415,6 +423,9 @@ async function ensureSchema(db) {
@@ -415,6 +423,9 @@ async function ensureSchema(db) {
for (const [client, cap, aud] of REVOKED_GRANTS) {
for (const [client, cap, aud] of REVOKED_GRANTS) {
await db.prepare("UPDATE principal_grants SET revoked_at = ov_now() WHERE client_id = ? AND capability = ? AND audience = ? AND revoked_at IS NULL").run(client, cap, aud);
await db.prepare("UPDATE principal_grants SET revoked_at = ov_now() WHERE client_id = ? AND capability = ? AND audience = ? AND revoked_at IS NULL").run(client, cap, aud);
}
}
+ for (const [client, cap, aud] of RESTORED_GRANTS) {
+ await db.prepare("UPDATE principal_grants SET revoked_at = NULL WHERE client_id = ? AND capability = ? AND audience = ? AND granted_by = 'default' AND revoked_at IS NOT NULL").run(client, cap, aud);
+ }
for (const [client, cap, aud, was, now] of CHANGED_DEFAULT_NAMESPACES) {
for (const [client, cap, aud, was, now] of CHANGED_DEFAULT_NAMESPACES) {
await db.prepare("UPDATE principal_grants SET namespaces = ? WHERE client_id = ? AND capability = ? AND audience = ? AND granted_by = 'default' AND namespaces = ?")
await db.prepare("UPDATE principal_grants SET namespaces = ? WHERE client_id = ? AND capability = ? AND audience = ? AND granted_by = 'default' AND namespaces = ?")
.run(JSON.stringify(now), client, cap, aud, JSON.stringify(was));
.run(JSON.stringify(now), client, cap, aud, JSON.stringify(was));
@@ -562,4 +573,4 @@ function guard(capability, { ownApp, namespace } = {}) {
@@ -562,4 +573,4 @@ function guard(capability, { ownApp, namespace } = {}) {
};
};
}
}
-module.exports = { ensureSchema, issueToken, guard, grantsFor, recordDecision, DEFAULT_GRANTS, REVOKED_GRANTS, TOKEN_TTL_S, SELF_AUDIENCE };
+module.exports = { ensureSchema, issueToken, guard, grantsFor, recordDecision, DEFAULT_GRANTS, REVOKED_GRANTS, RESTORED_GRANTS, TOKEN_TTL_S, SELF_AUDIENCE };
new file mode 100644
new file mode 100644
@@ -0,0 +1,35 @@
@@ -0,0 +1,35 @@
+'use strict';
+// A default grant that code revoked (REVOKED_GRANTS) and later made a default again comes back at boot only when it is
+// named in RESTORED_GRANTS; a grant a person revoked stays revoked. (2026-10-12: Space's events.event.publish stayed
+// revoked after Network#160 because the seed inserts with ON CONFLICT DO NOTHING.)
+const assert = require('assert');
+const { getDb } = require('../server/db/database');
+const principals = require('../server/identity/principals');
+
+(async () => {
+ const db = await getDb();
+ await principals.ensureSchema(db);
+ const key = (g) => g.slice(0, 3).join(' ');
+ const defaults = new Set(principals.DEFAULT_GRANTS.map(key));
+ const revoked = new Set(principals.REVOKED_GRANTS.map(key));
+ for (const g of principals.RESTORED_GRANTS) {
+ assert.ok(defaults.has(key(g)), `${key(g)} is restored, so it must be a default grant`);
+ assert.ok(!revoked.has(key(g)), `${key(g)} is restored, so it must not be revoked`);
+ }
+ const row = (c, cap, aud) => db.prepare('SELECT revoked_at FROM principal_grants WHERE client_id = ? AND capability = ? AND audience = ?').get(c, cap, aud);
+ const [client, cap, aud] = principals.RESTORED_GRANTS[0];
+ assert.ok(await db.prepare('SELECT 1 FROM oauth_clients WHERE client_id = ?').get(client), `the ${client} client is seeded`);
+ {
+ await db.prepare('UPDATE principal_grants SET revoked_at = ov_now() WHERE client_id = ? AND capability = ? AND audience = ?').run(client, cap, aud);
+ await principals.ensureSchema(db);
+ assert.strictEqual((await row(client, cap, aud)).revoked_at, null, 'the restored default grant is live again after boot');
+ }
+ // A default grant that is not in RESTORED_GRANTS and was revoked (by a person, through grants-admin) stays revoked.
+ const other = principals.DEFAULT_GRANTS.find((g) => !principals.RESTORED_GRANTS.some((r) => key(r) === key(g)) && g[0] === 'host');
+ await db.prepare('UPDATE principal_grants SET revoked_at = ov_now() WHERE client_id = ? AND capability = ? AND audience = ?').run(other[0], other[1], other[2]);
+ await principals.ensureSchema(db);
+ const kept = await row(other[0], other[1], other[2]);
+ assert.ok(!kept || kept.revoked_at, 'a grant someone revoked is never restored by boot');
+ console.log('restored grants: a named default grant comes back; any other revoked grant stays revoked');
+ process.exit(0);
+})().catch((err) => { console.error(err); process.exit(1); });