Fail closed in verifyChecksum; drop the Contact.get fallback
If Contact.validateChecksum threw for any reason, verifyChecksum retried with Contact.get plus an unknown `checksum` param. Our API key is privileged, so if Civi ever ignored that param the probe would resolve the contact and accept any checksum. Treat errors as invalid instead, and reject non-positive/non-integer contact ids up front.
This commit is contained in:
+11
-14
@@ -297,32 +297,29 @@ export async function civiMultipart<T = unknown>(
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate a contact checksum (cid + cs) against CiviCRM.
|
||||
* Validate a contact checksum (cid + cs) against CiviCRM via APIv4
|
||||
* Contact.validateChecksum.
|
||||
*
|
||||
* APIv4 exposes Contact.validateChecksum in newer Civi versions. For older
|
||||
* versions you may need to call Contact.get with the cs param and verify
|
||||
* the contact resolves. We use validateChecksum here and fall back to a
|
||||
* Contact.get probe if it returns a "missing API" error.
|
||||
* Fails closed: any API error is treated as an invalid checksum. There is
|
||||
* deliberately no fallback — our API key is privileged, so any probe that
|
||||
* merely checks "does the contact resolve" would accept every checksum.
|
||||
* /api/health confirms validateChecksum is available on the install.
|
||||
*/
|
||||
export async function verifyChecksum(cid: string, cs: string): Promise<boolean> {
|
||||
if (isStubMode()) {
|
||||
// STUB: any non-empty cs is "valid" so the UI can be exercised locally.
|
||||
return Boolean(cid && cs);
|
||||
}
|
||||
const contactId = Number(cid);
|
||||
if (!Number.isSafeInteger(contactId) || contactId <= 0) return false;
|
||||
try {
|
||||
const res = await civi<{ valid: boolean }>("Contact", "validateChecksum", {
|
||||
contactId: Number(cid),
|
||||
contactId,
|
||||
checksum: cs,
|
||||
});
|
||||
return Boolean(res.values?.[0]?.valid);
|
||||
} catch (e) {
|
||||
// Fallback: try Contact.get with the checksum as `cs` URL param. If the
|
||||
// contact resolves, the checksum is valid.
|
||||
const res = await civi<{ id: number }>("Contact", "get", {
|
||||
where: [["id", "=", Number(cid)]],
|
||||
select: ["id"],
|
||||
checksum: cs,
|
||||
});
|
||||
return Array.isArray(res.values) && res.values.length === 1;
|
||||
console.error("[civi] validateChecksum failed; treating as invalid:", e instanceof Error ? e.message : String(e));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user