Staff report: signed file URLs via APIv3 Attachment.get
APIv4 Attachment isn't exposed on this Civi install (confirmed in the
June 2026 upload-spike notes), so the Attachment.get call we shipped at
63e73e7 silently returned nothing and we fell through to the bare
/civicrm/file URL — which crashes Civi on a null fcs JWT decode.
APIv3 Attachment.get IS exposed and returns the signed URL with fcs
baked in (verified against id=150 in the user's API Explorer):
"url": "https://.../civicrm/file?reset=1&id=150&fcs=<JWT>"
Changes:
- lib/civicrm.ts: add a civi3() helper that calls /civicrm/ajax/rest with
AuthX headers, normalizing v3's array-or-keyed-object values shape into
a plain array.
- app/api/staff/report/route.ts: replace the dead v4 Attachment.get with
civi3("Attachment", "get", { id: {IN: [...]}, return: ["id","url"] }).
Each file's url goes into the value payload as before, so the frontend
needs no change.
Fallback chain remains intact: if Attachment.get fails (auth, endpoint
unavailable, etc.) the frontend still uses the /civicrm/webform-mw/file
extension route from b65bc6d/41467bd.
This commit is contained in:
@@ -13,7 +13,7 @@
|
||||
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
import { isStaffKeyValid } from "@/lib/staff-auth";
|
||||
import { civi } from "@/lib/civicrm";
|
||||
import { civi, civi3 } from "@/lib/civicrm";
|
||||
import { mapCustomFieldRow } from "@/lib/staff-field-mapping.mjs";
|
||||
import type {
|
||||
StaffReportPayload,
|
||||
@@ -352,13 +352,11 @@ async function buildLivePayload(orgId: number): Promise<StaffReportPayload> {
|
||||
|
||||
const rows = activityRes.values ?? [];
|
||||
|
||||
// Civi serves uploaded files at /civicrm/file?id=X&eid=Y&fcs=<JWT>; the fcs
|
||||
// is a JWT signed with Civi's site key. Without it, the file handler
|
||||
// crashes on a null JWT decode. We don't have the site key on this side,
|
||||
// so ask Civi for signed URLs via APIv4 Attachment.get and pass them
|
||||
// straight through to the client. If Attachment.get doesn't expose `url`
|
||||
// on this Civi version, the frontend falls back to a bare /civicrm/file
|
||||
// URL (still broken, but no worse than before).
|
||||
// Civi serves uploaded files at /civicrm/file?id=X&fcs=<JWT>; the fcs is
|
||||
// an HS256 JWT signed with the site key. Without it, /civicrm/file
|
||||
// crashes on a null JWT decode. APIv4 Attachment isn't exposed on this
|
||||
// install, but APIv3 Attachment.get is — and it returns `url` with the
|
||||
// fcs already baked in. We pass the URL straight through to the client.
|
||||
const fileIds = new Set<number>();
|
||||
const collectId = (v: unknown) => {
|
||||
if (v === null || v === undefined || v === "") return;
|
||||
@@ -377,23 +375,25 @@ async function buildLivePayload(orgId: number): Promise<StaffReportPayload> {
|
||||
const urlByFileId = new Map<number, string>();
|
||||
if (fileIds.size > 0) {
|
||||
try {
|
||||
const attachRes = await civi<{ id: number; url?: string }>("Attachment", "get", {
|
||||
select: ["id", "url"],
|
||||
where: [["id", "IN", Array.from(fileIds)]],
|
||||
// Bypass permission checks: we already gated this whole route on
|
||||
// STAFF_REPORT_KEY, and we want every file the org's activities
|
||||
// reference, regardless of which contact "owns" them.
|
||||
checkPermissions: false,
|
||||
limit: 0,
|
||||
});
|
||||
const attachRes = await civi3<{ id: string | number; url?: string }>(
|
||||
"Attachment",
|
||||
"get",
|
||||
{
|
||||
// APIv3 takes id as an IN-clause via the {IN: [...]} operator object.
|
||||
id: { IN: Array.from(fileIds) },
|
||||
return: ["id", "url"],
|
||||
options: { limit: 0 },
|
||||
},
|
||||
);
|
||||
for (const a of attachRes.values ?? []) {
|
||||
if (typeof a.url === "string" && a.url.length > 0) {
|
||||
urlByFileId.set(a.id, a.url);
|
||||
const fid = Number(a.id);
|
||||
if (Number.isFinite(fid) && typeof a.url === "string" && a.url.length > 0) {
|
||||
urlByFileId.set(fid, a.url);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn(
|
||||
"[staff/report] Attachment.get failed; file links will lack fcs:",
|
||||
"[staff/report] Attachment.get (v3) failed; file links will lack fcs:",
|
||||
e instanceof Error ? e.message : String(e),
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user