openvibers/openvibe.network Commits
/internal/events/stream-live accepts a service token with network.notifications.push
Changes
@@@ -30,7 +30,7 @@ function requireInternalKey(req, res, next) {return res.status(403).json({ error: 'Invalid or missing internal key' });}const principals = require('../identity/principals');--const TOKEN_ROUTES = new Set(['GET /identity/resolve', 'POST /identity/resolve-batch', 'POST /coins/credit', 'POST /coins/debit', 'POST /coins/transfer', 'POST /notifications/push', 'POST /notifications/push-bulk']);++const TOKEN_ROUTES = new Set(['GET /identity/resolve', 'POST /identity/resolve-batch', 'POST /coins/credit', 'POST /coins/debit', 'POST /coins/transfer', 'POST /notifications/push', 'POST /notifications/push-bulk', 'POST /events/stream-live']);const TOKEN_ROUTE_PATTERNS = [/^(GET|PUT) \/modules\/[a-z0-9_.]+\/[A-Za-z0-9_]+$/];const forApp = (req) => (req.body && req.body.app_id !== undefined ? String(req.body.app_id) : undefined);const forService = (req) => (req.body && req.body.service !== undefined ? String(req.body.service) : undefined);@@@ -306,7 +306,7 @@ router.post('/coins/transfer', principals.guard('network.coins.transfer', { ownA// followers + "all streamer" subscribers.// ═══════════════════════════════════════════════════════════════--router.post('/events/stream-live', async (req, res) => {++router.post('/events/stream-live', principals.guard('network.notifications.push'), async (req, res) => {const { streamer, stream, follower_network_ids } = req.body;if (!streamer?.username || !stream?.id) {return res.status(400).json({ error: 'streamer and stream objects required' });@@@ -113,6 +113,12 @@ const server = http.createServer(app);const cm = await token({ client_id: 'live', client_secret: 'live-secret', audience: 'openvibe.community' });assert.deepStrictEqual(cm.body.scope.split(' '), ['community.comment.moderate', 'community.comment.write', 'community.paste.create', 'community.paste.moderate', 'community.paste.write', 'community.pulse.write']);++ // Go-live fan-out accepts Live's token (network.notifications.push); a narrower token is refused.++ r = await post('/internal/events/stream-live', {}, { authorization: `Bearer ${full}` });++ assert.strictEqual(r.status, 400, 'guard passed, handler validated the body');++ r = await post('/internal/events/stream-live', {}, { authorization: `Bearer ${creditOnly}` });++ assert.strictEqual(r.status, 403);++// ── Legacy key keeps working ──r = await post('/internal/coins/credit', credit(), { 'x-internal-key': 'legacy-key' });assert.strictEqual(r.status, 200);
@@ -30,7 +30,7 @@ function requireInternalKey(req, res, next) {
@@ -30,7 +30,7 @@ function requireInternalKey(req, res, next) {
return res.status(403).json({ error: 'Invalid or missing internal key' });
return res.status(403).json({ error: 'Invalid or missing internal key' });
}
}
const principals = require('../identity/principals');
const principals = require('../identity/principals');
-const TOKEN_ROUTES = new Set(['GET /identity/resolve', 'POST /identity/resolve-batch', 'POST /coins/credit', 'POST /coins/debit', 'POST /coins/transfer', 'POST /notifications/push', 'POST /notifications/push-bulk']);
+const TOKEN_ROUTES = new Set(['GET /identity/resolve', 'POST /identity/resolve-batch', 'POST /coins/credit', 'POST /coins/debit', 'POST /coins/transfer', 'POST /notifications/push', 'POST /notifications/push-bulk', 'POST /events/stream-live']);
const TOKEN_ROUTE_PATTERNS = [/^(GET|PUT) \/modules\/[a-z0-9_.]+\/[A-Za-z0-9_]+$/];
const TOKEN_ROUTE_PATTERNS = [/^(GET|PUT) \/modules\/[a-z0-9_.]+\/[A-Za-z0-9_]+$/];
const forApp = (req) => (req.body && req.body.app_id !== undefined ? String(req.body.app_id) : undefined);
const forApp = (req) => (req.body && req.body.app_id !== undefined ? String(req.body.app_id) : undefined);
const forService = (req) => (req.body && req.body.service !== undefined ? String(req.body.service) : undefined);
const forService = (req) => (req.body && req.body.service !== undefined ? String(req.body.service) : undefined);
@@ -306,7 +306,7 @@ router.post('/coins/transfer', principals.guard('network.coins.transfer', { ownA
@@ -306,7 +306,7 @@ router.post('/coins/transfer', principals.guard('network.coins.transfer', { ownA
// followers + "all streamer" subscribers.
// followers + "all streamer" subscribers.
// ═══════════════════════════════════════════════════════════════
// ═══════════════════════════════════════════════════════════════
-router.post('/events/stream-live', async (req, res) => {
+router.post('/events/stream-live', principals.guard('network.notifications.push'), async (req, res) => {
const { streamer, stream, follower_network_ids } = req.body;
const { streamer, stream, follower_network_ids } = req.body;
if (!streamer?.username || !stream?.id) {
if (!streamer?.username || !stream?.id) {
return res.status(400).json({ error: 'streamer and stream objects required' });
return res.status(400).json({ error: 'streamer and stream objects required' });
@@ -113,6 +113,12 @@ const server = http.createServer(app);
@@ -113,6 +113,12 @@ const server = http.createServer(app);
const cm = await token({ client_id: 'live', client_secret: 'live-secret', audience: 'openvibe.community' });
const cm = await token({ client_id: 'live', client_secret: 'live-secret', audience: 'openvibe.community' });
assert.deepStrictEqual(cm.body.scope.split(' '), ['community.comment.moderate', 'community.comment.write', 'community.paste.create', 'community.paste.moderate', 'community.paste.write', 'community.pulse.write']);
assert.deepStrictEqual(cm.body.scope.split(' '), ['community.comment.moderate', 'community.comment.write', 'community.paste.create', 'community.paste.moderate', 'community.paste.write', 'community.pulse.write']);
+ // Go-live fan-out accepts Live's token (network.notifications.push); a narrower token is refused.
+ r = await post('/internal/events/stream-live', {}, { authorization: `Bearer ${full}` });
+ assert.strictEqual(r.status, 400, 'guard passed, handler validated the body');
+ r = await post('/internal/events/stream-live', {}, { authorization: `Bearer ${creditOnly}` });
+ assert.strictEqual(r.status, 403);
+
// ── Legacy key keeps working ──
// ── Legacy key keeps working ──
r = await post('/internal/coins/credit', credit(), { 'x-internal-key': 'legacy-key' });
r = await post('/internal/coins/credit', credit(), { 'x-internal-key': 'legacy-key' });
assert.strictEqual(r.status, 200);
assert.strictEqual(r.status, 200);