From 5fc4d3a6f5042fd1caa678cc8705091bedcaa434 Mon Sep 17 00:00:00 2001 From: Ricardo Date: Fri, 20 Mar 2026 13:29:25 +0100 Subject: [PATCH] fix: add sparse:true to accessToken index (duplicate key on OAuth authorize) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The accessToken_1 unique index on ap_oauth_tokens lacked sparse:true. During OAuth2 authorization, POST /oauth/authorize inserts a document with accessToken:null (auth code phase — token not yet issued). MongoDB unique indexes include null values by default, so only one such document could exist. Every subsequent authorization attempt failed with E11000 duplicate key error. Adding sparse:true skips null values in the index, allowing multiple auth code documents to coexist while still enforcing uniqueness among actual access tokens. This matches the code index pattern (line 1423) which already uses sparse:true. Note: existing deployments must drop the stale index before restart: mongosh $MONGODB_URL --eval 'db.ap_oauth_tokens.dropIndex("accessToken_1")' mongosh $MONGODB_URL --eval 'db.ap_oauth_tokens.deleteMany({accessToken:null})' Confab-Link: http://localhost:8080/sessions/0b241cd6-aff2-4fec-853c-2b5a61e61946 --- index.js | 24 +++++++++++++++++++++++- package.json | 3 ++- 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/index.js b/index.js index 6ebb92d..98aa545 100644 --- a/index.js +++ b/index.js @@ -224,6 +224,14 @@ export default class ActivityPubEndpoint { // Skip Fedify for admin UI routes — they're handled by the // authenticated `routes` getter, not the federation layer. if (req.path.startsWith("/admin")) return next(); + + // Diagnostic: log inbox POSTs to detect federation stalls + if (req.method === "POST" && req.path.includes("inbox")) { + const ua = req.get("user-agent") || "unknown"; + const bodyParsed = req.body !== undefined && Object.keys(req.body || {}).length > 0; + console.info(`[federation-diag] POST ${req.path} from=${ua.slice(0, 60)} bodyParsed=${bodyParsed} readable=${req.readable}`); + } + return self._fedifyMiddleware(req, res, next); }); @@ -1408,7 +1416,7 @@ export default class ActivityPubEndpoint { ); this._collections.ap_oauth_tokens.createIndex( { accessToken: 1 }, - { unique: true, background: true }, + { unique: true, sparse: true, background: true }, ); this._collections.ap_oauth_tokens.createIndex( { code: 1 }, @@ -1552,6 +1560,20 @@ export default class ActivityPubEndpoint { keyRefreshHandle, ); + // Backfill ap_timeline from posts collection (idempotent, runs on every startup) + import("./lib/mastodon/backfill-timeline.js").then(({ backfillTimeline }) => { + // Delay to let MongoDB connections settle + setTimeout(() => { + backfillTimeline(this._collections).then(({ total, inserted, skipped }) => { + if (inserted > 0) { + console.log(`[Mastodon API] Timeline backfill: ${inserted} posts added (${skipped} already existed, ${total} total)`); + } + }).catch((error) => { + console.warn("[Mastodon API] Timeline backfill failed:", error.message); + }); + }, 5000); + }); + // Start async inbox queue processor (processes one item every 3s) this._inboxProcessorInterval = startInboxProcessor( this._collections, diff --git a/package.json b/package.json index 109bc38..dcdfef5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@rmdes/indiekit-endpoint-activitypub", - "version": "3.0.0", + "version": "3.5.6", "description": "ActivityPub federation endpoint for Indiekit via Fedify. Adds full fediverse support: actor, inbox, outbox, followers, following, syndication, and Mastodon migration.", "keywords": [ "indiekit", @@ -47,6 +47,7 @@ "unfurl.js": "^6.4.0" }, "peerDependencies": { + "@indiekit/endpoint-micropub": "^1.0.0-beta.25", "@indiekit/error": "^1.0.0-beta.25", "@indiekit/frontend": "^1.0.0-beta.25" },