openvibers/openvibe.community Commits
Staff powers from the contracts staff map (D05, ADR-022): a browser viewer is paste staff with staff.moderation.pastes and discussion staff with staff.moderation.discussions, from the role or the staff_caps Network issues (issued claims win); discussionStaff reads the discussion capability. Same answers for every role today. test/staff-map.test.js
Changes
@@@ -35,12 +35,12 @@ function serviceHas(viewer, capabilityId) {}/**-- * Staff for discussions (comments, forum, relay admin): an admin/global_mod browser, or a++ * Staff for discussions (comments, forum, relay admin): a browser holding staff.moderation.discussions, or a* service that vouches for a staff person with X-OV-Staff: 1 and holds community.comment.moderate.*/function discussionStaff(viewer) {if (!viewer) return false;-- if (viewer.kind === 'user') return !!viewer.staff;++ if (viewer.kind === 'user') return !!(viewer.discussionStaff !== undefined ? viewer.discussionStaff : viewer.staff);return viewer.kind === 'service' && !!viewer.vouchesStaff && serviceHas(viewer, 'community.comment.moderate');}@@@ -26,6 +26,8 @@ const { extractToken, claimsToUser, decodeJwtPayload } = require('../auth/routesconst { checkCapability } = require('./capabilities');const { ids, capabilities, serviceAuth, http } = contracts;++const { staff: staffMap } = require('openvibe-contracts');++// The roles that hold staff moderation today (kept for callers; gates ask staffMap.can instead).const STAFF_ROLES = new Set(['admin', 'global_mod']);const PRINCIPAL_SUB = /^(svc|app|mod):/;const AUDIENCE = 'openvibe.community';@@@ -111,7 +113,10 @@ function createViewerResolver({ auth, config, network }) {if (subject && network) { try { network.rememberClaims(subject, claims); } catch { /* display cache only */ } }const user = claimsToUser(claims);if (subject && !user.subject_id) user.subject_id = subject;-- return { kind: 'user', subject, staff: STAFF_ROLES.has(claims.role), origin: 'user', user, token };++ // Staff powers come from the contracts staff map (ADR-022): the role, or Network's issued staff_caps.++ const staff = staffMap.can(claims, 'staff.moderation.pastes');++ const discussionStaff = staffMap.can(claims, 'staff.moderation.discussions');++ return { kind: 'user', subject, staff, discussionStaff, origin: 'user', user, token };}/**nnew file mode 100644@@@ -0,0 +1,29 @@++'use strict';++/** Community's staff powers come from the contracts staff map: the role, or the staff_caps Network issues. */++const assert = require('assert');++const { ids } = require('openvibe-contracts');++const { boot, check, done } = require('./helpers/app');++++(async () => {++ const t = await boot({ authority: 'community' });++ const net = t.network;++ const { createViewerResolver } = require('../server/identity/viewer');++ const viewers = createViewerResolver({ auth: t.app.locals.auth, config: require('../server/config'), network: null });++ const resolve = (token) => viewers.resolve({ headers: { authorization: `Bearer ${token}` }, get: () => undefined, cookies: {} });++ const tok = (claims) => net.sign({ id: Math.floor(Math.random() * 1e6), subject_id: ids.newId('user'), username: 'x', display_name: 'X', ...claims });++++ await check('a global moderator is paste and discussion staff', async () => {++ const v = await resolve(tok({ role: 'global_mod' }));++ assert.deepStrictEqual([v.staff, v.discussionStaff], [true, true]);++ });++ await check('an ordinary user is neither', async () => {++ const v = await resolve(tok({ role: 'user' }));++ assert.deepStrictEqual([v.staff, v.discussionStaff], [false, false]);++ });++ await check('issued staff_caps win over the role: discussions only', async () => {++ const v = await resolve(tok({ role: 'user', staff_caps: ['staff.moderation.discussions'] }));++ assert.deepStrictEqual([v.staff, v.discussionStaff], [false, true]);++ assert.strictEqual(require('../server/identity/capabilities').discussionStaff(v), true);++ });++ done();++})();
@@ -35,12 +35,12 @@ function serviceHas(viewer, capabilityId) {
@@ -35,12 +35,12 @@ function serviceHas(viewer, capabilityId) {
}
}
/**
/**
- * Staff for discussions (comments, forum, relay admin): an admin/global_mod browser, or a
+ * Staff for discussions (comments, forum, relay admin): a browser holding staff.moderation.discussions, or a
* service that vouches for a staff person with X-OV-Staff: 1 and holds community.comment.moderate.
* service that vouches for a staff person with X-OV-Staff: 1 and holds community.comment.moderate.
*/
*/
function discussionStaff(viewer) {
function discussionStaff(viewer) {
if (!viewer) return false;
if (!viewer) return false;
- if (viewer.kind === 'user') return !!viewer.staff;
+ if (viewer.kind === 'user') return !!(viewer.discussionStaff !== undefined ? viewer.discussionStaff : viewer.staff);
return viewer.kind === 'service' && !!viewer.vouchesStaff && serviceHas(viewer, 'community.comment.moderate');
return viewer.kind === 'service' && !!viewer.vouchesStaff && serviceHas(viewer, 'community.comment.moderate');
}
}
@@ -26,6 +26,8 @@ const { extractToken, claimsToUser, decodeJwtPayload } = require('../auth/routes
@@ -26,6 +26,8 @@ const { extractToken, claimsToUser, decodeJwtPayload } = require('../auth/routes
const { checkCapability } = require('./capabilities');
const { checkCapability } = require('./capabilities');
const { ids, capabilities, serviceAuth, http } = contracts;
const { ids, capabilities, serviceAuth, http } = contracts;
+const { staff: staffMap } = require('openvibe-contracts');
+// The roles that hold staff moderation today (kept for callers; gates ask staffMap.can instead).
const STAFF_ROLES = new Set(['admin', 'global_mod']);
const STAFF_ROLES = new Set(['admin', 'global_mod']);
const PRINCIPAL_SUB = /^(svc|app|mod):/;
const PRINCIPAL_SUB = /^(svc|app|mod):/;
const AUDIENCE = 'openvibe.community';
const AUDIENCE = 'openvibe.community';
@@ -111,7 +113,10 @@ function createViewerResolver({ auth, config, network }) {
@@ -111,7 +113,10 @@ function createViewerResolver({ auth, config, network }) {
if (subject && network) { try { network.rememberClaims(subject, claims); } catch { /* display cache only */ } }
if (subject && network) { try { network.rememberClaims(subject, claims); } catch { /* display cache only */ } }
const user = claimsToUser(claims);
const user = claimsToUser(claims);
if (subject && !user.subject_id) user.subject_id = subject;
if (subject && !user.subject_id) user.subject_id = subject;
- return { kind: 'user', subject, staff: STAFF_ROLES.has(claims.role), origin: 'user', user, token };
+ // Staff powers come from the contracts staff map (ADR-022): the role, or Network's issued staff_caps.
+ const staff = staffMap.can(claims, 'staff.moderation.pastes');
+ const discussionStaff = staffMap.can(claims, 'staff.moderation.discussions');
+ return { kind: 'user', subject, staff, discussionStaff, origin: 'user', user, token };
}
}
/**
/**
new file mode 100644
new file mode 100644
@@ -0,0 +1,29 @@
@@ -0,0 +1,29 @@
+'use strict';
+/** Community's staff powers come from the contracts staff map: the role, or the staff_caps Network issues. */
+const assert = require('assert');
+const { ids } = require('openvibe-contracts');
+const { boot, check, done } = require('./helpers/app');
+
+(async () => {
+ const t = await boot({ authority: 'community' });
+ const net = t.network;
+ const { createViewerResolver } = require('../server/identity/viewer');
+ const viewers = createViewerResolver({ auth: t.app.locals.auth, config: require('../server/config'), network: null });
+ const resolve = (token) => viewers.resolve({ headers: { authorization: `Bearer ${token}` }, get: () => undefined, cookies: {} });
+ const tok = (claims) => net.sign({ id: Math.floor(Math.random() * 1e6), subject_id: ids.newId('user'), username: 'x', display_name: 'X', ...claims });
+
+ await check('a global moderator is paste and discussion staff', async () => {
+ const v = await resolve(tok({ role: 'global_mod' }));
+ assert.deepStrictEqual([v.staff, v.discussionStaff], [true, true]);
+ });
+ await check('an ordinary user is neither', async () => {
+ const v = await resolve(tok({ role: 'user' }));
+ assert.deepStrictEqual([v.staff, v.discussionStaff], [false, false]);
+ });
+ await check('issued staff_caps win over the role: discussions only', async () => {
+ const v = await resolve(tok({ role: 'user', staff_caps: ['staff.moderation.discussions'] }));
+ assert.deepStrictEqual([v.staff, v.discussionStaff], [false, true]);
+ assert.strictEqual(require('../server/identity/capabilities').discussionStaff(v), true);
+ });
+ done();
+})();