openvibers/openvibe.network Commits
Agent Streams S4: the node grant for Chat (#602)
Changes
@@@ -1025,7 +1025,7 @@ Every change also sends the person a Network notification, so a swap nobody askeMIT (`license` in [package.json](package.json)).<!-- versions:start -->--- openvibe-contracts: v0.300.0++- openvibe-contracts: v0.301.0- openvibe-sdk: v0.91.0- openvibe-shared: v4.122.0<!-- versions:end -->@@@ -6,7 +6,7 @@"runtime": "node 22, express, PostgreSQL 18 (openvibe-sdk/db, async) + Valkey 9","port": 4000,"domain": "openvibe.network (with auth., api., admin., my. and themes.openvibe.network in its manifest)",-- "contracts": "openvibe-contracts v0.300.0",++ "contracts": "openvibe-contracts v0.301.0","sdk": "openvibe-sdk v0.91.0","shared": "openvibe-shared v4.122.0","capabilities": [@@@ -20,7 +20,7 @@"iovalkey": "^0.4.0","jsonwebtoken": "^9.0.0","multer": "^1.4.5-lts.1",-- "openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.300.0",++ "openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.301.0","openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.91.0","openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v4.122.0","pg": "^8.23.0",@@@ -2303,9 +2303,9 @@}},"node_modules/openvibe-contracts": {-- "version": "0.300.0",-- "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.300.0",-- "integrity": "sha512-0Vw23wJc0G/EDQ8Hei3lbm5UdLzZ1njZBK9K1/yKwGpMJK/YYknaPgTkU+sOhIw/47kMm9VsUilNS7X+M3x6Lw==",++ "version": "0.301.0",++ "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.301.0",++ "integrity": "sha512-f/T1fk9WfVte/kPR0Hqe2k5TJ1Bd3CC2UHA1USzssj4pGfPMMY3fCs6cr02TKolmwiDJKL94oGkCk4nfkL+FGg==","license": "AGPL-3.0-only","dependencies": {"ajv": "^8.17.1",@@@ -24,7 +24,7 @@"iovalkey": "^0.4.0","jsonwebtoken": "^9.0.0","multer": "^1.4.5-lts.1",-- "openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.300.0",++ "openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.301.0","openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.91.0","openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v4.122.0","pg": "^8.23.0",@@@ -832,6 +832,8 @@ const DEFAULT_GRANTS = [// its owner (on_behalf_of). The machine acts for its owner, never for the person directly and never for anyone else.const NODE_GRANTS = [['openvibe.live', 'live.agent_stream.publish'],++ // Agent Streams S4: a desk asks OpenVibe.Chat for the sound of a line, in the owner's own voice.++ ['openvibe.chat', 'chat.tts.speak'],];// Grants withdrawn by decision; applied at every boot so an old default can't come back.@@@ -75,7 +75,17 @@ const keys = crypto.generateKeyPairSync('rsa', {assert.ok(contracts.validate('identity.service-token-claims@1', v.claims).valid,'the claims fit identity.service-token-claims@1');-- const other = await issue(principal, 'openvibe.chat');++ // Agent Streams S4: the desk asks Chat for the sound of a line, in the owner's voice, and for nothing else there.++ const chat = await issue(principal, 'openvibe.chat');++ assert.strictEqual(chat.status, 200, JSON.stringify(chat.body));++ const vc = contracts.serviceAuth.verifyServiceToken(chat.body.access_token, {++ publicKey: keys.publicKey, issuer: ISSUER, audience: 'openvibe.chat',++ });++ assert.ok(vc.ok, vc.reason);++ assert.deepStrictEqual(vc.claims.cap, ['chat.tts.speak'], 'Chat gets the voice capability and nothing else');++ assert.strictEqual(vc.claims.on_behalf_of, OWNER, 'the voice is the owner\'s');++++ const other = await issue(principal, 'openvibe.media');assert.deepStrictEqual([other.status, other.body.error], [400, 'invalid_scope'], 'no other audience');const website = await issue(principal, 'openvibe.website');@@@ -107,7 +107,7 @@ try {assert.strictEqual((await row(A.principal)).last_seen_at, seen, 'not rewritten within 60 s');});-- await check('the audience is Network, the pairing service or Connect, nothing else', async () => {++ await check('the audience is Network, the pairing service, Connect or a service the machine holds a grant for, nothing else', async () => {const r = await tokenCall(A.principal, A.credential, 'openvibe.bot');assert.strictEqual(r.status, 200, r.text);const v = serviceAuth.verifyServiceToken(r.body.access_token, { publicKey: keys.publicKey, issuer: ISSUER, audience: 'openvibe.bot' });@@@ -120,7 +120,7 @@ try {assert.ok(vw.ok, vw.reason);assert.deepStrictEqual([vw.claims.sub, vw.claims.aud, vw.claims.cap], [`node:${A.principal}`, ['openvibe.website'], []]);assert.strictEqual((await call('PUT', '/api/v1/node/self/capabilities', doc(A.node_id), bearer(w.body.access_token))).status, 401, 'no use on Network');-- for (const aud of ['openvibe.chat', 'openvibe.network.evil', 'openvibe.websites', 'connect.openvibe.website', '', undefined]) {++ for (const aud of ['openvibe.media', 'openvibe.network.evil', 'openvibe.websites', 'connect.openvibe.website', '', undefined]) {const bad = await tokenCall(A.principal, A.credential, aud);assert.deepStrictEqual([bad.status, bad.body.error], [400, 'invalid_scope'], String(aud));}
@@ -1025,7 +1025,7 @@ Every change also sends the person a Network notification, so a swap nobody aske
@@ -1025,7 +1025,7 @@ Every change also sends the person a Network notification, so a swap nobody aske
MIT (`license` in [package.json](package.json)).
MIT (`license` in [package.json](package.json)).
<!-- versions:start -->
<!-- versions:start -->
-- openvibe-contracts: v0.300.0
+- openvibe-contracts: v0.301.0
- openvibe-sdk: v0.91.0
- openvibe-sdk: v0.91.0
- openvibe-shared: v4.122.0
- openvibe-shared: v4.122.0
<!-- versions:end -->
<!-- versions:end -->
@@ -6,7 +6,7 @@
@@ -6,7 +6,7 @@
"runtime": "node 22, express, PostgreSQL 18 (openvibe-sdk/db, async) + Valkey 9",
"runtime": "node 22, express, PostgreSQL 18 (openvibe-sdk/db, async) + Valkey 9",
"port": 4000,
"port": 4000,
"domain": "openvibe.network (with auth., api., admin., my. and themes.openvibe.network in its manifest)",
"domain": "openvibe.network (with auth., api., admin., my. and themes.openvibe.network in its manifest)",
- "contracts": "openvibe-contracts v0.300.0",
+ "contracts": "openvibe-contracts v0.301.0",
"sdk": "openvibe-sdk v0.91.0",
"sdk": "openvibe-sdk v0.91.0",
"shared": "openvibe-shared v4.122.0",
"shared": "openvibe-shared v4.122.0",
"capabilities": [
"capabilities": [
@@ -20,7 +20,7 @@
@@ -20,7 +20,7 @@
"iovalkey": "^0.4.0",
"iovalkey": "^0.4.0",
"jsonwebtoken": "^9.0.0",
"jsonwebtoken": "^9.0.0",
"multer": "^1.4.5-lts.1",
"multer": "^1.4.5-lts.1",
- "openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.300.0",
+ "openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.301.0",
"openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.91.0",
"openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.91.0",
"openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v4.122.0",
"openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v4.122.0",
"pg": "^8.23.0",
"pg": "^8.23.0",
@@ -2303,9 +2303,9 @@
@@ -2303,9 +2303,9 @@
}
}
},
},
"node_modules/openvibe-contracts": {
"node_modules/openvibe-contracts": {
- "version": "0.300.0",
- "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.300.0",
- "integrity": "sha512-0Vw23wJc0G/EDQ8Hei3lbm5UdLzZ1njZBK9K1/yKwGpMJK/YYknaPgTkU+sOhIw/47kMm9VsUilNS7X+M3x6Lw==",
+ "version": "0.301.0",
+ "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.301.0",
+ "integrity": "sha512-f/T1fk9WfVte/kPR0Hqe2k5TJ1Bd3CC2UHA1USzssj4pGfPMMY3fCs6cr02TKolmwiDJKL94oGkCk4nfkL+FGg==",
"license": "AGPL-3.0-only",
"license": "AGPL-3.0-only",
"dependencies": {
"dependencies": {
"ajv": "^8.17.1",
"ajv": "^8.17.1",
@@ -24,7 +24,7 @@
@@ -24,7 +24,7 @@
"iovalkey": "^0.4.0",
"iovalkey": "^0.4.0",
"jsonwebtoken": "^9.0.0",
"jsonwebtoken": "^9.0.0",
"multer": "^1.4.5-lts.1",
"multer": "^1.4.5-lts.1",
- "openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.300.0",
+ "openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.301.0",
"openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.91.0",
"openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.91.0",
"openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v4.122.0",
"openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v4.122.0",
"pg": "^8.23.0",
"pg": "^8.23.0",
@@ -832,6 +832,8 @@ const DEFAULT_GRANTS = [
@@ -832,6 +832,8 @@ const DEFAULT_GRANTS = [
// its owner (on_behalf_of). The machine acts for its owner, never for the person directly and never for anyone else.
// its owner (on_behalf_of). The machine acts for its owner, never for the person directly and never for anyone else.
const NODE_GRANTS = [
const NODE_GRANTS = [
['openvibe.live', 'live.agent_stream.publish'],
['openvibe.live', 'live.agent_stream.publish'],
+ // Agent Streams S4: a desk asks OpenVibe.Chat for the sound of a line, in the owner's own voice.
+ ['openvibe.chat', 'chat.tts.speak'],
];
];
// Grants withdrawn by decision; applied at every boot so an old default can't come back.
// Grants withdrawn by decision; applied at every boot so an old default can't come back.
@@ -75,7 +75,17 @@ const keys = crypto.generateKeyPairSync('rsa', {
@@ -75,7 +75,17 @@ const keys = crypto.generateKeyPairSync('rsa', {
assert.ok(contracts.validate('identity.service-token-claims@1', v.claims).valid,
assert.ok(contracts.validate('identity.service-token-claims@1', v.claims).valid,
'the claims fit identity.service-token-claims@1');
'the claims fit identity.service-token-claims@1');
- const other = await issue(principal, 'openvibe.chat');
+ // Agent Streams S4: the desk asks Chat for the sound of a line, in the owner's voice, and for nothing else there.
+ const chat = await issue(principal, 'openvibe.chat');
+ assert.strictEqual(chat.status, 200, JSON.stringify(chat.body));
+ const vc = contracts.serviceAuth.verifyServiceToken(chat.body.access_token, {
+ publicKey: keys.publicKey, issuer: ISSUER, audience: 'openvibe.chat',
+ });
+ assert.ok(vc.ok, vc.reason);
+ assert.deepStrictEqual(vc.claims.cap, ['chat.tts.speak'], 'Chat gets the voice capability and nothing else');
+ assert.strictEqual(vc.claims.on_behalf_of, OWNER, 'the voice is the owner\'s');
+
+ const other = await issue(principal, 'openvibe.media');
assert.deepStrictEqual([other.status, other.body.error], [400, 'invalid_scope'], 'no other audience');
assert.deepStrictEqual([other.status, other.body.error], [400, 'invalid_scope'], 'no other audience');
const website = await issue(principal, 'openvibe.website');
const website = await issue(principal, 'openvibe.website');
@@ -107,7 +107,7 @@ try {
@@ -107,7 +107,7 @@ try {
assert.strictEqual((await row(A.principal)).last_seen_at, seen, 'not rewritten within 60 s');
assert.strictEqual((await row(A.principal)).last_seen_at, seen, 'not rewritten within 60 s');
});
});
- await check('the audience is Network, the pairing service or Connect, nothing else', async () => {
+ await check('the audience is Network, the pairing service, Connect or a service the machine holds a grant for, nothing else', async () => {
const r = await tokenCall(A.principal, A.credential, 'openvibe.bot');
const r = await tokenCall(A.principal, A.credential, 'openvibe.bot');
assert.strictEqual(r.status, 200, r.text);
assert.strictEqual(r.status, 200, r.text);
const v = serviceAuth.verifyServiceToken(r.body.access_token, { publicKey: keys.publicKey, issuer: ISSUER, audience: 'openvibe.bot' });
const v = serviceAuth.verifyServiceToken(r.body.access_token, { publicKey: keys.publicKey, issuer: ISSUER, audience: 'openvibe.bot' });
@@ -120,7 +120,7 @@ try {
@@ -120,7 +120,7 @@ try {
assert.ok(vw.ok, vw.reason);
assert.ok(vw.ok, vw.reason);
assert.deepStrictEqual([vw.claims.sub, vw.claims.aud, vw.claims.cap], [`node:${A.principal}`, ['openvibe.website'], []]);
assert.deepStrictEqual([vw.claims.sub, vw.claims.aud, vw.claims.cap], [`node:${A.principal}`, ['openvibe.website'], []]);
assert.strictEqual((await call('PUT', '/api/v1/node/self/capabilities', doc(A.node_id), bearer(w.body.access_token))).status, 401, 'no use on Network');
assert.strictEqual((await call('PUT', '/api/v1/node/self/capabilities', doc(A.node_id), bearer(w.body.access_token))).status, 401, 'no use on Network');
- for (const aud of ['openvibe.chat', 'openvibe.network.evil', 'openvibe.websites', 'connect.openvibe.website', '', undefined]) {
+ for (const aud of ['openvibe.media', 'openvibe.network.evil', 'openvibe.websites', 'connect.openvibe.website', '', undefined]) {
const bad = await tokenCall(A.principal, A.credential, aud);
const bad = await tokenCall(A.principal, A.credential, aud);
assert.deepStrictEqual([bad.status, bad.body.error], [400, 'invalid_scope'], String(aud));
assert.deepStrictEqual([bad.status, bad.body.error], [400, 'invalid_scope'], String(aud));
}
}