Changeset c66cbb4 in Klonkt
- Timestamp:
- 07/25/2026 08:22:15 AM (7 weeks ago)
- Branches:
- main
- Children:
- 8f520e0
- Parents:
- ad6f62a
- Files:
-
- 1 added
- 2 edited
-
src/routes/activitypub.js (modified) (1 diff)
-
src/services/ActivityPubService.js (modified) (3 diffs)
-
test/authorized-fetch.test.js (added)
Legend:
- Unmodified
- Added
- Removed
-
src/routes/activitypub.js
rad6f62a rc66cbb4 79 79 80 80 // ── Outbox ──────────────────────────────────────────────────────── 81 router.get('/ap/users/:slug/outbox', (req, res) => {81 router.get('/ap/users/:slug/outbox', async (req, res) => { 82 82 const site = publicSite(req.params.slug); 83 83 if (!site) return res.status(404).end(); 84 // Authorized fetch (FEP-633c §5.3 note): a committed guardian doing a SIGNED 85 // GET may read the ward's fan-only history too, without appearing as a 86 // follower. Unsigned / non-guardian callers get the public collection only. 87 let asGuardian = false; 88 if (req.headers['signature']) { 89 const verified = await AP.verifyRequest(req).catch(() => null); 90 asGuardian = !!(verified && AP.isWardGuardian(req.params.slug, verified.id)); 91 } 92 const fanClause = asGuardian ? '' : "AND (fan_only IS NULL OR fan_only = 0)"; 84 93 const posts = db.prepare( 85 94 `SELECT id, slug, title, content, cover_image_url, cover_video_url, nsfw, content_warning, published_at, created_at 86 FROM posts WHERE site_id = ? AND status = 'published' AND (fan_only IS NULL OR fan_only = 0)95 FROM posts WHERE site_id = ? AND status = 'published' ${fanClause} 87 96 ORDER BY COALESCE(published_at, created_at) DESC LIMIT 20` 88 97 ).all(site.id); 89 AP.sendAP(res, AP.buildOutbox(baseUrl(req), site, posts) );98 AP.sendAP(res, AP.buildOutbox(baseUrl(req), site, posts), asGuardian ? 'private, no-store' : undefined); 90 99 }); 91 100 -
src/services/ActivityPubService.js
rad6f62a rc66cbb4 144 144 145 145 const AP_CONTENT_TYPE = 'application/activity+json; charset=utf-8'; 146 export function sendAP(res, obj ) {146 export function sendAP(res, obj, cacheControl) { 147 147 res.type(AP_CONTENT_TYPE); 148 res.set('Cache-Control', 'public, max-age=120'); 148 // A per-caller (e.g. guardian-widened) view must not be publicly cached. 149 res.set('Cache-Control', cacheControl || 'public, max-age=120'); 149 150 res.send(JSON.stringify(obj)); 150 151 } … … 2901 2902 } 2902 2903 2904 // FEP-633c §5.3 note (authorized fetch): true when `actorUri` is a committed 2905 // guardian of the local ward `wardSlug` — so a signed GET from it may read the 2906 // ward's non-public history without the guardian appearing as a follower. 2907 export function isWardGuardian(wardSlug, actorUri) { 2908 try { return !!Guardianship.getRelation(wardSlug, 'ward', actorUri); } catch { return false; } 2909 } 2910 2903 2911 // FEP-633c §5.3: the guardians approved a gated follow of their ward. Send the 2904 2912 // Accept to the follower and record them, so delivery (incl. followers-only) … … 3248 3256 listOutbox, deliverOutboxDelete, deliverOutboxUpdate, deliverDirectNote, 3249 3257 webfingerResolve, followActor, resolveRemoteActor, unfollowActor, listFollowing, setAutoBoost, backfillFromOutbox, getTimeline, timelineAttachments, sendInteraction, voteOnPoll, voteOnRemotePoll, 3250 acceptGatedFollow, rejectGatedFollow, 3258 acceptGatedFollow, rejectGatedFollow, isWardGuardian, 3251 3259 parseOwnPoll, pollTally, ownPollView, deliverPollUpdate, maybeCrawlThread, sendReport, localMentionSlugs, 3252 3260 autoBoostCount, boostedCount, markBoosted, unmarkBoosted, markLiked, unmarkLiked, getTimelineReaction, upsertBoostedNote, getCirkelPosts, getCirkelMembers, selfHealTimeline,
Note:
See TracChangeset
for help on using the changeset viewer.
![(please configure the [header_logo] section in trac.ini)](/chrome/site/your_project_logo.png)