-
Notifications
You must be signed in to change notification settings - Fork 43
Micol/file upload #1386
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Micol/file upload #1386
Conversation
| if (!isValid && filename?.endsWith(".ris")) { | ||
| this.logger.warn("UPLOAD_WARNING", { | ||
| reason: "RIS file had generic content type", | ||
| filename, | ||
| contentType, | ||
| }); | ||
| isValid = true; | ||
| } | ||
|
|
||
| // Fallback: allow .msg files with 'application/octet-stream' | ||
| if (!isValid && filename?.endsWith(".msg")) { | ||
| this.logger.warn("UPLOAD_WARNING", { | ||
| reason: "MSG file had generic content type", | ||
| filename, | ||
| contentType, | ||
| }); | ||
| isValid = true; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure this is needed - you can add custom accepted mime types via the form json. Does this work for your use case?
e.g.
{
"name": "applicationForm",
"options": {
"required": true,
"accept": "image/jpeg,image/png,application/pdf,application/vnd.oasis.opendocument.text",
"customValidationMessages": {
"string.empty": "Upload your application form"
}
},
"type": "FileUploadField",
"title": "Application form",
"schema": {}
},
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hi Jen will look into this, I didn;t actually write this bit and the onw below part of this PR was written by Suilman but will look into this
runner/src/server/utils/hmac.ts
Outdated
| /** | ||
| * Validates an HMAC signature | ||
| */ | ||
| export async function validateHmac( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this used? unit test if so please
runner/src/server/utils/hmac.ts
Outdated
| export async function createHmac(email: string, hmacKey: string) { | ||
| try { | ||
| // Get current timestamp | ||
| const currentTimestamp = Math.floor(Date.now() / 1000); | ||
|
|
||
| // Prepare the data for HMAC calculation | ||
| const dataToHash = email + currentTimestamp; | ||
|
|
||
| // Calculate the HMAC hash | ||
| const hmac = crypto | ||
| .createHmac("sha256", hmacKey) | ||
| .update(dataToHash) | ||
| .digest("hex"); | ||
|
|
||
| const expiryTimestamp = currentTimestamp + TIME_THRESHOLD; | ||
| const adjustedExpiryForDisplay = applyBSTIfRequired(expiryTimestamp); | ||
|
|
||
| const hmacExpiryTime = formatUnixTimestamp(adjustedExpiryForDisplay); | ||
|
|
||
| return [hmac, currentTimestamp, hmacExpiryTime]; | ||
| } catch (error) { | ||
| console.error("Error creating HMAC:", error); | ||
| throw error; | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this used? Unit test if so. If not, let's keep it out of the codebase
I would suggest setting your servers to UTC and validate on this - or using something like date-fns or temporal to resolve any timezone related issues.
| const [hmacSignature, requestTime] = await createHmacRaw( | ||
| request.yar.id, | ||
| hmacKey | ||
| ); | ||
|
|
||
| return this.parsedDocumentUploadResponse(responseData); | ||
| const securityHeaders = { | ||
| "X-Request-ID": request.yar.id.toString(), | ||
| "X-HMAC-Signature": hmacSignature.toString(), | ||
| "X-HMAC-Time": requestTime.toString(), | ||
| }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This appears to be implementation detail of your document upload api - could you wrap this section in if (hmacKey) { or if(requiresSecurityHeaders) { please? not all existing APIs need this
| const id = request.params?.id; | ||
| const forms = request.server?.app?.forms; | ||
| const model = id && forms?.[id]; | ||
| const hmacKey = model?.def?.fileUploadHmacSharedKey; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
doesn't look like this was added to the schema?
do you have a hmac key per form? or is this shared across all your forms? would consider adding this as an env var if shared across all forms
| if (!config.documentUploadApiUrl) { | ||
| server.registerService([ | ||
| Schmervice.withName("uploadService", MockUploadService), | ||
| ]); | ||
| } else { | ||
| server.registerService([UploadService]); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This needs to stay in. MockUploadService is used when you are running the application without a document upload api configured. Unit and e2e tests use this too.
| // TODO: I didn't want to introduce a breaking change, but maybe the versioning should be handled differently | ||
| return `${baseUrl}`; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed, versioning should be handled by the config but will require all consumers of XGovFormBuilder to change their config if they want to upgrade. Currently your implementation is a breaking change. You will need to add /v1 to base url
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, what it is currently is that it calls the end poin v1/files and doesn't allow you to call any other endpoint should I leave it like this an open a ticket somewhere for someone to introduce this necessary breaking change?
Refactor: Make document upload service configurable per form
Summary
Refactored the document upload service to support form-specific upload endpoints, making it reusable across different forms rather than being tied to a single implementation.
Changes
1. Schema Updates (
model/src/schema/schema.ts)Type Definition:
Validation:
Forms can now optionally specify a custom document upload API URL.
2. Upload Service Refactoring (
runner/src/server/services/upload/uploadService.ts)Key improvements:
buildFormData()- Creates FormData from file streamsgetUploadUrl()- Resolves the upload URL with fallback logicvalidateUploadUrl()- Validates URL is configured before uploadbuildUploadRequest()- Constructs request with security headersURL Resolution:
Falls back to global config if form-specific URL is not provided.
Validation:
Now throws a clear error if no upload URL is configured (either in form definition or global config), preventing silent failures.
Removed:
MockUploadServiceclass - mocking strategy didn't follow separation of concerns principlesMockUploadService- Note this was happening silently and hence a possible risk section in the code3. Additional Changes and Bug Fixes
validateContentType()for .msg files (previously said "RIS file")runner/src/server/plugins/engine/models/FormModel.tsImpact
✅ Non-breaking: Existing forms continue to work with global
⚠️ Required:
config.documentUploadApiUrl✅ New capability: Forms can specify custom upload endpoints via
documentUploadApiUrlconfig.documentUploadApiUrlmust be set in default.js or all file uploads will failTesting
Manual testing completed:
Testing Checklist
Scenario 1: File upload service works as expected when
documentUploadApiUrlis set indefault.jsScenario 2: File upload service fails gracefully when no
documentUploadApiUrlis set indefault.jsand no form-specific URL is configuredDocument upload API URL is not configured. Please set documentUploadApiUrl in config or model definitionScenario 3: File upload service correctly retrieves the form-specific URL when no
documentUploadApiUrlis set indefault.jsbut one is configured in the form definitionScenario 4: File upload service prioritizes form-specific URL when both
default.jsand form definition havedocumentUploadApiUrlconfiguredVerified:
Removed mock service code is not referenced elsewhere
Existing upload flows remain functional
Follow-up Work
the following to can be found in a conversation in the comments below
the reason I didn't make this directly is because it would involve changing environment variables in production please view conversation with @kitttang below
/v1/files) configurable if needed