Close the adversarial review round: auth hardening, data fidelity against the platform context, backend grants and docker coherence
This commit is contained in:
@@ -28,13 +28,11 @@
|
||||
"moleculer-cron": "0.0.4",
|
||||
"moleculer-repl": "^0.6.6",
|
||||
"sharp": "0.31.3",
|
||||
"tsx": "^4.20.3",
|
||||
"url-join": "^4.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=22.12"
|
||||
},
|
||||
"packageManager": "pnpm@10.26.0+sha512.3b3f6c725ebe712506c0ab1ad4133cf86b1f4b687effce62a9b38b4d72e3954242e643190fc51fa1642949c735f403debd44f5cb0edd657abe63a8b6a7e1e402",
|
||||
"devDependencies": {
|
||||
"tsx": "^4.20.3"
|
||||
}
|
||||
"packageManager": "pnpm@10.26.0+sha512.3b3f6c725ebe712506c0ab1ad4133cf86b1f4b687effce62a9b38b4d72e3954242e643190fc51fa1642949c735f403debd44f5cb0edd657abe63a8b6a7e1e402"
|
||||
}
|
||||
|
||||
Generated
+3
-4
@@ -74,13 +74,12 @@ importers:
|
||||
sharp:
|
||||
specifier: 0.31.3
|
||||
version: 0.31.3
|
||||
url-join:
|
||||
specifier: ^4.0.1
|
||||
version: 4.0.1
|
||||
devDependencies:
|
||||
tsx:
|
||||
specifier: ^4.20.3
|
||||
version: 4.23.15
|
||||
url-join:
|
||||
specifier: ^4.0.1
|
||||
version: 4.0.1
|
||||
|
||||
packages:
|
||||
|
||||
|
||||
@@ -2,6 +2,14 @@ const urlJoin = require('url-join');
|
||||
const { PodCollectionsHandlerMixin } = require('@activitypods/app');
|
||||
const CONFIG = require('../config/config');
|
||||
|
||||
// Visibility decision: the attendees collection is readable by ANY visitor, including
|
||||
// unauthenticated ones (acl:agentClass foaf:Agent). A meeting point's participant count and
|
||||
// its list of participants must be visible to non-contacts — the app shows outsiders the
|
||||
// anonymous participants plus the total counter, and that is a product feature, not a leak.
|
||||
// Member URIs are not secret; the anonymity guarantee lives in each profile's own WAC (a
|
||||
// participant whose profile is not shared simply renders as anonymous), not here. The
|
||||
// reference welcometomyplace grants this to its announces group, but Festipod has no
|
||||
// announces concept, so the grant is public.
|
||||
module.exports = {
|
||||
name: 'attendees',
|
||||
mixins: [PodCollectionsHandlerMixin],
|
||||
@@ -13,5 +21,30 @@ module.exports = {
|
||||
summary: 'Meeting point attendees'
|
||||
},
|
||||
createWacGroup: true
|
||||
},
|
||||
actions: {
|
||||
async givePublicReadPermission(ctx) {
|
||||
const { collectionUri, actorUri } = ctx.params;
|
||||
|
||||
await ctx.call('pod-permissions.add', {
|
||||
uri: collectionUri,
|
||||
agentPredicate: 'acl:agentClass',
|
||||
agentUri: 'http://xmlns.com/foaf/0.1/Agent',
|
||||
mode: 'acl:Read',
|
||||
actorUri
|
||||
});
|
||||
}
|
||||
},
|
||||
hooks: {
|
||||
after: {
|
||||
async createAndAttach(ctx, res) {
|
||||
const { actorUri } = ctx.params;
|
||||
const collectionUri = res;
|
||||
|
||||
await this.actions.givePublicReadPermission({ collectionUri, actorUri }, { parentCtx: ctx });
|
||||
|
||||
return res;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -8,6 +8,29 @@ module.exports = {
|
||||
async started() {
|
||||
this.logger.info('Registration watcher subscribed');
|
||||
},
|
||||
methods: {
|
||||
/**
|
||||
* The Event's `apods:attendees` collection is attached asynchronously by
|
||||
* PodCollectionsHandlerMixin on create, so a Join/Leave processed in that window sees no
|
||||
* collection. Returning immediately would drop the activity forever (the client overlay
|
||||
* shows joined, nothing reconciles). Re-fetch the Event a bounded number of times until the
|
||||
* collection appears; only then give up.
|
||||
*/
|
||||
async fetchAttendeesCollectionUri(ctx, event, actorUri) {
|
||||
if (event['apods:attendees'] || !event.id) return event['apods:attendees'];
|
||||
|
||||
for (let attempt = 0; attempt < 3; attempt++) {
|
||||
await new Promise(resolve => setTimeout(resolve, 500));
|
||||
try {
|
||||
const { ok, body } = await ctx.call('pod-resources.get', { resourceUri: event.id, actorUri });
|
||||
if (ok && body && body['apods:attendees']) return body['apods:attendees'];
|
||||
} catch (e) {
|
||||
this.logger.warn(`Could not re-fetch ${event.id} while waiting for its attendees collection: ${e.message}`);
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
},
|
||||
activities: {
|
||||
join: {
|
||||
match: {
|
||||
@@ -36,7 +59,7 @@ module.exports = {
|
||||
},
|
||||
async onReceive(ctx, activity, actorUri) {
|
||||
const event = activity.object;
|
||||
const attendeesCollectionUri = event['apods:attendees'];
|
||||
const attendeesCollectionUri = await this.fetchAttendeesCollectionUri(ctx, event, actorUri);
|
||||
|
||||
if (!attendeesCollectionUri) {
|
||||
this.logger.warn(`No attendees collection on ${event.id}, skipping Join`);
|
||||
@@ -69,7 +92,7 @@ module.exports = {
|
||||
},
|
||||
async onReceive(ctx, activity, actorUri) {
|
||||
const event = activity.object;
|
||||
const attendeesCollectionUri = event['apods:attendees'];
|
||||
const attendeesCollectionUri = await this.fetchAttendeesCollectionUri(ctx, event, actorUri);
|
||||
|
||||
if (!attendeesCollectionUri) {
|
||||
this.logger.warn(`No attendees collection on ${event.id}, skipping Leave`);
|
||||
|
||||
Reference in New Issue
Block a user