openvibers/openvibe.chat Commits
openvibe-contracts v0.32.0: chat.message.send is chat's own capability, checked with a literal id
Changes
@@@ -263,8 +263,9 @@ Introduced here and registered in `openvibe-contracts` v0.13.0:`chat.message.send` — a service or app principal whose `/internal/live/calls` op sends a chatmessage (a `saveChatMessage` write, the deploy notice, or a pushed `chat`/`dm` frame) must hold itbeside `chat.live_bridge.write`; without it that op is refused (`capability.denied`) and the rest of--the batch runs. Contracts still lists it as owned by `live`; it needs re-owning to `chat` and adding--to the chat manifest (then the check uses a literal id the contracts check can see). Planned families:++the batch runs. It is owned by `chat` and listed in the chat manifest since `openvibe-contracts`++v0.30.2 (so is the `chat.preferences` user module since v0.32.0); the check names it literally, so++the contracts check enforces it. Planned families:`chat.room.*`, `chat.message.*`, `chat.dm.*`, `chat.moderation.*`, `chat.tts.*`, `chat.call.*`.Events: `chat.message.created`, `chat.message.deleted`, `chat.dm.created`, `chat.moderation.action`@@@ -7,8 +7,8 @@"runtime": "node 22, express, ws, better-sqlite3","port": 4400,"domain": "openvibe.live (/ws/chat, /api/chat/, /api/dm/, /api/tts/, /api/sounds via an nginx include in openvibe.live's vhost); openvibe.chat is still the OpenVibe.Sites placeholder",-- "contracts": "openvibe-contracts v0.13.0",-- "capabilities": ["chat.live_bridge.write", "chat.presence.read"],++ "contracts": "openvibe-contracts v0.32.0",++ "capabilities": ["chat.live_bridge.write", "chat.message.send", "chat.presence.read"],"capabilitiesUsed": ["live.chat_context.read", "live.chat_effects.write", "live.chat_mirror.write", "events.event.publish", "events.subscription.manage", "vip.entitlement.check", "network.modules.read", "network.modules.write"],"capabilitiesRegistered": true,"events": ["chat.message.created", "chat.message.deleted", "chat.dm.created", "chat.moderation.action"],@@@ -16,7 +16,7 @@"express": "^4.21.2","express-rate-limit": "^7.5.0","multer": "^2.0.2",-- "openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.13.0",++ "openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.32.0","openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.5.0","ws": "^8.18.0"},@@@ -955,9 +955,9 @@}},"node_modules/openvibe-contracts": {-- "version": "0.13.0",-- "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.13.0",-- "integrity": "sha512-MeIGJYHQsbr6ISeEErduUw/PD52X5IBclH2ht/G/t4lSX5X4o2AwcZYLEzOZrcbaXQyplgC7r86RCGFvi1ifUA==",++ "version": "0.32.0",++ "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.32.0",++ "integrity": "sha512-ntN58SIG03YDxbhsiIzOM5GRH19jRMHZUYWI3xU2VwovExagF5DdwApLVfezsu84UTJ+V2Jfn+LWDc+Oz8ab+g==","license": "AGPL-3.0-only","dependencies": {"ajv": "^8.17.1",@@@ -27,7 +27,7 @@"express": "^4.21.2","express-rate-limit": "^7.5.0","multer": "^2.0.2",-- "openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.13.0",++ "openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.32.0","openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.5.0","ws": "^8.18.0"},@@@ -75,10 +75,8 @@ const SERVER_OPS = {// alerts, call invites through sendDm), TTS, channel sounds, moderation writes and invalidations// need only the bridge capability.//--// The id is held in a constant on purpose: the contracts registry still lists chat.message.send as--// owned by live (it predates Wave 6), and openvibe-contracts-check refuses a literal guard of a--// capability another service owns. Once Contracts re-owns it to chat and adds it to the chat--// manifest, check it with a literal so the CI check sees it.++// chat.message.send is chat's own capability (openvibe-contracts >= 0.30.2 lists it in the chat++// manifest), so the check below names it literally and openvibe-contracts-check enforces it.const MESSAGE_SEND = 'chat.message.send';const FRAME_OPS = new Set(['broadcastToStream', 'broadcastToChannelRoom', 'broadcastGlobal', 'broadcastAll', 'forwardToGlobal',@@@ -221,7 +219,7 @@ function createBridge({ chatServer }) {const boot = String((req.body && req.body.boot) || '').slice(0, 64) || 'unknown';const ops = Array.isArray(req.body && req.body.ops) ? req.body.ops.slice(0, 500) : [];const results = [];-- const mayMessage = capabilities.check({ cap: req.principal && req.principal.cap }, MESSAGE_SEND);++ const mayMessage = capabilities.check({ cap: req.principal && req.principal.cap }, 'chat.message.send');for (const o of ops) {if (!mayMessage.allowed && sendsMessage(String(o.op || ''), o.args)) {console.warn(`[Bridge] ${o.op}${o.op === 'db' && Array.isArray(o.args) ? `.${o.args[0]}` : ''} refused: ${req.principal && req.principal.sub} lacks ${MESSAGE_SEND}`);@@@ -42,8 +42,9 @@ async function boot({ env = {} } = {}) {fs.mkdirSync(path.join(tmp, 'sounds'));const { serviceAuth } = require('openvibe-contracts');let jti = 0;++ // An app token (sub app:…) carries its developer project and env (identity.service-token-claims 1.2.0).const serviceToken = (cap, { aud = 'openvibe.chat', sub = 'svc:live' } = {}) =>-- serviceAuth.signServiceToken({ iss: ISS, sub, actor_type: sub.startsWith('app:') ? 'app' : 'service', aud: [aud], cap, iat: now(), exp: now() + 300, jti: `tok_test_${++jti}` }, keys.privateKey);++ serviceAuth.signServiceToken({ iss: ISS, sub, actor_type: sub.startsWith('app:') ? 'app' : 'service', ...(sub.startsWith('app:') ? { project_id: 'prj_01J8Z3Q4R5S6T7V8W9X0Y1Z2A3', env: 'production' } : {}), aud: [aud], cap, iat: now(), exp: now() + 300, jti: `tok_test_${++jti}` }, keys.privateKey);// ── Stub Network ──const tokenRequests = [];
@@ -263,8 +263,9 @@ Introduced here and registered in `openvibe-contracts` v0.13.0:
@@ -263,8 +263,9 @@ Introduced here and registered in `openvibe-contracts` v0.13.0:
`chat.message.send` — a service or app principal whose `/internal/live/calls` op sends a chat
`chat.message.send` — a service or app principal whose `/internal/live/calls` op sends a chat
message (a `saveChatMessage` write, the deploy notice, or a pushed `chat`/`dm` frame) must hold it
message (a `saveChatMessage` write, the deploy notice, or a pushed `chat`/`dm` frame) must hold it
beside `chat.live_bridge.write`; without it that op is refused (`capability.denied`) and the rest of
beside `chat.live_bridge.write`; without it that op is refused (`capability.denied`) and the rest of
-the batch runs. Contracts still lists it as owned by `live`; it needs re-owning to `chat` and adding
-to the chat manifest (then the check uses a literal id the contracts check can see). Planned families:
+the batch runs. It is owned by `chat` and listed in the chat manifest since `openvibe-contracts`
+v0.30.2 (so is the `chat.preferences` user module since v0.32.0); the check names it literally, so
+the contracts check enforces it. Planned families:
`chat.room.*`, `chat.message.*`, `chat.dm.*`, `chat.moderation.*`, `chat.tts.*`, `chat.call.*`.
`chat.room.*`, `chat.message.*`, `chat.dm.*`, `chat.moderation.*`, `chat.tts.*`, `chat.call.*`.
Events: `chat.message.created`, `chat.message.deleted`, `chat.dm.created`, `chat.moderation.action`
Events: `chat.message.created`, `chat.message.deleted`, `chat.dm.created`, `chat.moderation.action`
@@ -7,8 +7,8 @@
@@ -7,8 +7,8 @@
"runtime": "node 22, express, ws, better-sqlite3",
"runtime": "node 22, express, ws, better-sqlite3",
"port": 4400,
"port": 4400,
"domain": "openvibe.live (/ws/chat, /api/chat/, /api/dm/, /api/tts/, /api/sounds via an nginx include in openvibe.live's vhost); openvibe.chat is still the OpenVibe.Sites placeholder",
"domain": "openvibe.live (/ws/chat, /api/chat/, /api/dm/, /api/tts/, /api/sounds via an nginx include in openvibe.live's vhost); openvibe.chat is still the OpenVibe.Sites placeholder",
- "contracts": "openvibe-contracts v0.13.0",
- "capabilities": ["chat.live_bridge.write", "chat.presence.read"],
+ "contracts": "openvibe-contracts v0.32.0",
+ "capabilities": ["chat.live_bridge.write", "chat.message.send", "chat.presence.read"],
"capabilitiesUsed": ["live.chat_context.read", "live.chat_effects.write", "live.chat_mirror.write", "events.event.publish", "events.subscription.manage", "vip.entitlement.check", "network.modules.read", "network.modules.write"],
"capabilitiesUsed": ["live.chat_context.read", "live.chat_effects.write", "live.chat_mirror.write", "events.event.publish", "events.subscription.manage", "vip.entitlement.check", "network.modules.read", "network.modules.write"],
"capabilitiesRegistered": true,
"capabilitiesRegistered": true,
"events": ["chat.message.created", "chat.message.deleted", "chat.dm.created", "chat.moderation.action"],
"events": ["chat.message.created", "chat.message.deleted", "chat.dm.created", "chat.moderation.action"],
@@ -16,7 +16,7 @@
@@ -16,7 +16,7 @@
"express": "^4.21.2",
"express": "^4.21.2",
"express-rate-limit": "^7.5.0",
"express-rate-limit": "^7.5.0",
"multer": "^2.0.2",
"multer": "^2.0.2",
- "openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.13.0",
+ "openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.32.0",
"openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.5.0",
"openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.5.0",
"ws": "^8.18.0"
"ws": "^8.18.0"
},
},
@@ -955,9 +955,9 @@
@@ -955,9 +955,9 @@
}
}
},
},
"node_modules/openvibe-contracts": {
"node_modules/openvibe-contracts": {
- "version": "0.13.0",
- "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.13.0",
- "integrity": "sha512-MeIGJYHQsbr6ISeEErduUw/PD52X5IBclH2ht/G/t4lSX5X4o2AwcZYLEzOZrcbaXQyplgC7r86RCGFvi1ifUA==",
+ "version": "0.32.0",
+ "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.32.0",
+ "integrity": "sha512-ntN58SIG03YDxbhsiIzOM5GRH19jRMHZUYWI3xU2VwovExagF5DdwApLVfezsu84UTJ+V2Jfn+LWDc+Oz8ab+g==",
"license": "AGPL-3.0-only",
"license": "AGPL-3.0-only",
"dependencies": {
"dependencies": {
"ajv": "^8.17.1",
"ajv": "^8.17.1",
@@ -27,7 +27,7 @@
@@ -27,7 +27,7 @@
"express": "^4.21.2",
"express": "^4.21.2",
"express-rate-limit": "^7.5.0",
"express-rate-limit": "^7.5.0",
"multer": "^2.0.2",
"multer": "^2.0.2",
- "openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.13.0",
+ "openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.32.0",
"openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.5.0",
"openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.5.0",
"ws": "^8.18.0"
"ws": "^8.18.0"
},
},
@@ -75,10 +75,8 @@ const SERVER_OPS = {
@@ -75,10 +75,8 @@ const SERVER_OPS = {
// alerts, call invites through sendDm), TTS, channel sounds, moderation writes and invalidations
// alerts, call invites through sendDm), TTS, channel sounds, moderation writes and invalidations
// need only the bridge capability.
// need only the bridge capability.
//
//
-// The id is held in a constant on purpose: the contracts registry still lists chat.message.send as
-// owned by live (it predates Wave 6), and openvibe-contracts-check refuses a literal guard of a
-// capability another service owns. Once Contracts re-owns it to chat and adds it to the chat
-// manifest, check it with a literal so the CI check sees it.
+// chat.message.send is chat's own capability (openvibe-contracts >= 0.30.2 lists it in the chat
+// manifest), so the check below names it literally and openvibe-contracts-check enforces it.
const MESSAGE_SEND = 'chat.message.send';
const MESSAGE_SEND = 'chat.message.send';
const FRAME_OPS = new Set([
const FRAME_OPS = new Set([
'broadcastToStream', 'broadcastToChannelRoom', 'broadcastGlobal', 'broadcastAll', 'forwardToGlobal',
'broadcastToStream', 'broadcastToChannelRoom', 'broadcastGlobal', 'broadcastAll', 'forwardToGlobal',
@@ -221,7 +219,7 @@ function createBridge({ chatServer }) {
@@ -221,7 +219,7 @@ function createBridge({ chatServer }) {
const boot = String((req.body && req.body.boot) || '').slice(0, 64) || 'unknown';
const boot = String((req.body && req.body.boot) || '').slice(0, 64) || 'unknown';
const ops = Array.isArray(req.body && req.body.ops) ? req.body.ops.slice(0, 500) : [];
const ops = Array.isArray(req.body && req.body.ops) ? req.body.ops.slice(0, 500) : [];
const results = [];
const results = [];
- const mayMessage = capabilities.check({ cap: req.principal && req.principal.cap }, MESSAGE_SEND);
+ const mayMessage = capabilities.check({ cap: req.principal && req.principal.cap }, 'chat.message.send');
for (const o of ops) {
for (const o of ops) {
if (!mayMessage.allowed && sendsMessage(String(o.op || ''), o.args)) {
if (!mayMessage.allowed && sendsMessage(String(o.op || ''), o.args)) {
console.warn(`[Bridge] ${o.op}${o.op === 'db' && Array.isArray(o.args) ? `.${o.args[0]}` : ''} refused: ${req.principal && req.principal.sub} lacks ${MESSAGE_SEND}`);
console.warn(`[Bridge] ${o.op}${o.op === 'db' && Array.isArray(o.args) ? `.${o.args[0]}` : ''} refused: ${req.principal && req.principal.sub} lacks ${MESSAGE_SEND}`);
@@ -42,8 +42,9 @@ async function boot({ env = {} } = {}) {
@@ -42,8 +42,9 @@ async function boot({ env = {} } = {}) {
fs.mkdirSync(path.join(tmp, 'sounds'));
fs.mkdirSync(path.join(tmp, 'sounds'));
const { serviceAuth } = require('openvibe-contracts');
const { serviceAuth } = require('openvibe-contracts');
let jti = 0;
let jti = 0;
+ // An app token (sub app:…) carries its developer project and env (identity.service-token-claims 1.2.0).
const serviceToken = (cap, { aud = 'openvibe.chat', sub = 'svc:live' } = {}) =>
const serviceToken = (cap, { aud = 'openvibe.chat', sub = 'svc:live' } = {}) =>
- serviceAuth.signServiceToken({ iss: ISS, sub, actor_type: sub.startsWith('app:') ? 'app' : 'service', aud: [aud], cap, iat: now(), exp: now() + 300, jti: `tok_test_${++jti}` }, keys.privateKey);
+ serviceAuth.signServiceToken({ iss: ISS, sub, actor_type: sub.startsWith('app:') ? 'app' : 'service', ...(sub.startsWith('app:') ? { project_id: 'prj_01J8Z3Q4R5S6T7V8W9X0Y1Z2A3', env: 'production' } : {}), aud: [aud], cap, iat: now(), exp: now() + 300, jti: `tok_test_${++jti}` }, keys.privateKey);
// ── Stub Network ──
// ── Stub Network ──
const tokenRequests = [];
const tokenRequests = [];