88f23b7023
The backend served its settings dataset (including the app actor's private key) to anonymous callers, through the SPARQL endpoint and through the semapps LDP catch-all route, since WebACL is off. An allow-list mounted on every route now exposes only what the app and the pod need (/app, /as/actor/app and its Client ID Document, /catalog, the public key, access needs, the JSON-LD context, nodeinfo, the app inbox and the webhook endpoint); everything else answers 404. Every watcher-fed service (public read, catalog, attendees, contacts, registration) now decides on the activity and object re-read from the pod as the app, checks they belong to the outbox owner and that the stored object has the expected type, and never acts on the notification body. Decisions for one object are serialized and taken on a read made when acting, with a 30 s bound and an abort check before every write. The anonymous read follows the event's visibility both ways, attendees collection included; Festipod records the grants it makes and revokes only those, leaving any other public share untouched; an unreadable ACL changes nothing. A participant's attendance follows their latest act by the pod's timestamp, not the order notifications arrive. Known limits (no conditional write on the pod, a write in flight at the timeout) are documented in the code.
46 lines
2.1 KiB
JavaScript
46 lines
2.1 KiB
JavaScript
// The `createAndAttach` action every Festipod collection handler uses (attendees, contacts), in place
|
|
// of the one `PodCollectionsHandlerMixin` (@activitypods/app 2.3.2) gives them.
|
|
//
|
|
// The mixin's version calls `pod-collections.createAndAttach`, then derives a WAC group slug from the
|
|
// collection URI it returns. That call returns undefined when it attaches nothing — the resource
|
|
// already carries a value for the predicate (written by its creator or by another app), or the pod
|
|
// refused to create the collection — and the mixin then runs `new URL(undefined)`, which throws. The
|
|
// webhook call that runs the watcher is not awaited by the platform, so that throw ended the whole
|
|
// backend process (observed 2026-10-12, an event POSTed with `apods:attendees` already set). Here,
|
|
// nothing attached means no group to create.
|
|
|
|
/** The action definition; runs bound to a service with the mixin's settings and `getGroupSlugFromCollectionUri`. */
|
|
const guardedCreateAndAttach = {
|
|
async handler(ctx) {
|
|
const { resourceUri, actorUri } = ctx.params;
|
|
const { attachPredicate, collectionOptions, createWacGroup } = this.settings;
|
|
|
|
const collectionUri = await ctx.call('pod-collections.createAndAttach', {
|
|
resourceUri,
|
|
attachPredicate,
|
|
collectionOptions,
|
|
actorUri
|
|
});
|
|
if (!collectionUri) {
|
|
this.logger.info(`No ${attachPredicate} collection attached to ${resourceUri} (already carries one, or creation refused)`);
|
|
return undefined;
|
|
}
|
|
// The service's own follow-up for a collection it has just created (attendees: record it as
|
|
// Festipod's, see public-read-ledger.js) — right after the attach, so a failure of what follows
|
|
// (the WAC group) cannot skip it.
|
|
if (typeof this.onCollectionAttached === 'function') {
|
|
await this.onCollectionAttached(ctx, { resourceUri, collectionUri, actorUri });
|
|
}
|
|
|
|
if (createWacGroup) {
|
|
await ctx.call('pod-wac-groups.create', {
|
|
groupSlug: this.getGroupSlugFromCollectionUri(collectionUri),
|
|
actorUri
|
|
});
|
|
}
|
|
return collectionUri;
|
|
}
|
|
};
|
|
|
|
module.exports = { guardedCreateAndAttach };
|