fix: add sparse:true to accessToken index (duplicate key on OAuth authorize)

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
This commit is contained in:
Ricardo
2026-03-20 13:29:25 +01:00
parent 2c0cfffd54
commit 5fc4d3a6f5
2 changed files with 25 additions and 2 deletions

View File

@@ -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,