Skip to content

Commit 9ca3fd4

Browse files
authored
fix: register xid record after participant creation, not before (#2540)
createXidRecord was called inside _handleUserIdentification before any participant row existed. Migration 000015 added a composite FK constraint: FOREIGN KEY (zid, pid) REFERENCES participants(zid, pid) With this constraint, inserting an xid record with pid=null (because no participant exists yet) either fails or leaves the record permanently unlinked from a participant. When the error propagates through ensureParticipantOptional's catch block, pid is set to -1 and all subsequent votes and comments return 500. Move createXidRecord to after _getOrCreateParticipant in _ensureParticipantInternal, where both uid and pid are known and the participant row already exists. This mirrors the correct ordering already used by _joinWithZidOrSuzinvite. This is the root cause fix for #2538. A minimal interim fix (try/catch) was shipped separately in #2539.
1 parent 40cccfd commit 9ca3fd4

1 file changed

Lines changed: 25 additions & 13 deletions

File tree

server/src/auth/ensure-participant.ts

Lines changed: 25 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import { Response, NextFunction } from "express";
1818
import { addParticipantAndMetadata } from "../participant";
1919
import { checkLegacyCookieAndIssueJWT } from "./legacyCookies";
2020
import { createAnonUser } from "./create-user";
21-
import { createXidRecord, getXidRecord, isXidAllowed } from "../xids";
21+
import { createXidRecord, getXidRecord, isXidAllowed, xidExists } from "../xids";
2222
import { failJson } from "../utils/fail";
2323
import { getConversationInfo, getZidFromConversationId } from "../conversation";
2424
import { getPidPromise } from "../user";
@@ -194,20 +194,12 @@ async function _handleUserIdentification(
194194
return existingXidRecords[0].uid;
195195
}
196196

197-
// Create new anonymous user for this XID
197+
// Create new anonymous user for this XID.
198+
// Note: we do NOT call createXidRecord here — the participant row doesn't
199+
// exist yet, so the FK (zid, pid) → participants(zid, pid) can't be
200+
// satisfied. xid registration happens after participant creation below.
198201
const newUid = await createAnonUser();
199202

200-
// Create XID record linking the XID to the new user
201-
await createXidRecord(
202-
req.p.xid,
203-
conv.owner,
204-
newUid,
205-
zid,
206-
undefined,
207-
undefined,
208-
undefined
209-
);
210-
211203
return newUid;
212204
}
213205

@@ -464,6 +456,26 @@ async function _ensureParticipantInternal(
464456
const participantResult = await _getOrCreateParticipant(zid, uid, pid, req);
465457
pid = participantResult.pid;
466458
isNewlyCreatedParticipant = participantResult.isNewlyCreated;
459+
460+
// Register the xid record now that the participant row exists.
461+
// This mirrors the order used by _joinWithZidOrSuzinvite and ensures
462+
// the FK (zid, pid) → participants(zid, pid) can be satisfied.
463+
// See: https://github.com/compdemocracy/polis/issues/2538
464+
if (req.p.xid && isNewlyCreatedUser && pid !== undefined) {
465+
const conv = await getConversationInfo(zid);
466+
const alreadyExists = await xidExists(req.p.xid, conv.owner, uid);
467+
if (!alreadyExists) {
468+
await createXidRecord(
469+
req.p.xid,
470+
conv.owner,
471+
uid,
472+
zid,
473+
req.p.x_profile_image_url,
474+
req.p.x_name,
475+
req.p.x_email
476+
);
477+
}
478+
}
467479
} else if ((pid === undefined || pid === -1) && uid !== undefined) {
468480
// Just look up existing participant if we have a uid
469481
const existingPid = await getPidPromise(zid, uid, true);

0 commit comments

Comments
 (0)