From 783db83f15f98aed4554c28a6c6a637c84463ee6 Mon Sep 17 00:00:00 2001 From: Tofik Hasanov <72318342+tofikwest@users.noreply.github.com> Date: Thu, 5 Mar 2026 15:36:42 -0500 Subject: [PATCH] fix(evidence-forms): regenerate presigned URLs on submission retrieval (#2223) Presigned S3 download URLs (15-min TTL) were stored in the database at upload time. When users viewed submissions later, clicking attachments returned "AccessDenied - Request has expired". Now regenerate fresh presigned URLs when returning submissions via getFormWithSubmissions and getSubmission, following the same pattern already used in exportCsv. --- .../evidence-forms/evidence-forms.service.ts | 51 ++++++++++++++++++- 1 file changed, 49 insertions(+), 2 deletions(-) diff --git a/apps/api/src/evidence-forms/evidence-forms.service.ts b/apps/api/src/evidence-forms/evidence-forms.service.ts index 4b4c882de..d9ed50f4c 100644 --- a/apps/api/src/evidence-forms/evidence-forms.service.ts +++ b/apps/api/src/evidence-forms/evidence-forms.service.ts @@ -197,6 +197,34 @@ export class EvidenceFormsService { return fileBuffer; } + /** + * Walk submission data and regenerate fresh presigned URLs for any file fields. + * File fields are objects with { fileKey, downloadUrl, fileName }. + */ + private async refreshFileUrls( + data: Record, + ): Promise> { + const refreshed: Record = { ...data }; + + for (const [key, value] of Object.entries(refreshed)) { + if ( + value && + typeof value === 'object' && + 'fileKey' in value && + typeof (value as Record).fileKey === 'string' + ) { + const fileObj = value as Record; + const freshUrl = + await this.attachmentsService.getPresignedDownloadUrl( + fileObj.fileKey as string, + ); + refreshed[key] = { ...fileObj, downloadUrl: freshUrl }; + } + } + + return refreshed; + } + listForms() { return evidenceFormDefinitionList; } @@ -276,9 +304,21 @@ export class EvidenceFormsService { const paginated = filtered.slice(query.offset, query.offset + query.limit); + const submissionsWithFreshUrls = await Promise.all( + paginated.map(async (submission) => { + const refreshedData = await this.refreshFileUrls( + submission.data as Record, + ); + return normalizeSubmissionFormType({ + ...submission, + data: refreshedData, + }); + }), + ); + return { form: evidenceFormDefinitions[parsedType.data], - submissions: paginated.map(normalizeSubmissionFormType), + submissions: submissionsWithFreshUrls, total: filtered.length, }; } @@ -324,9 +364,16 @@ export class EvidenceFormsService { throw new NotFoundException('Submission not found'); } + const refreshedData = await this.refreshFileUrls( + submission.data as Record, + ); + return { form: evidenceFormDefinitions[parsedType.data], - submission: normalizeSubmissionFormType(submission), + submission: normalizeSubmissionFormType({ + ...submission, + data: refreshedData, + }), }; }