Skip to content

Allow documents to be uploaded from a public task - #5

Merged
Klaas-Ritense merged 1 commit into
mainfrom
feat/public-task-document-upload
Aug 31, 2026
Merged

Klaas-Ritense merged 1 commit into
mainfrom
feat/public-task-document-upload

Conversation

@Klaas-Ritense

Copy link
Copy Markdown
Member

Upload fields now work in the public form. A form containing a Valtimo File Upload, a Documenten API upload field or a plain Form.io file component renders a file picker, and the files an applicant chooses end up in the case the same way they would have from a task inside Valtimo. Nothing changes in the form definition.

How it fits together

  1. Valtimo's upload components are Angular elements the plain Form.io renderer draws as nothing, so they are rewritten to Form.io's file component before the definition leaves the server.
  2. POST /api/v1/public-task/{publicTaskId}/attachment parks the file in temporary resource storage and answers with its resource id.
  3. That id travels back in the submission, where UploadField raises a TemporaryResourceSubmittedEvent — so a Documenten API "store uploaded document" task or the S3 listener keeps working without knowing a public task was involved.

Bounding an unauthenticated endpoint

The limits live on the Create Public Task process link rather than in application properties, so an administrator sets them where the rest of the task is configured. Each falls back to a bounded default rather than to "no limit", and all are read when the task is created — editing the process link does not change the terms of a link already sent out. An upload field can narrow them with Form.io's own Maximum File Size and File Pattern, never widen them.

  • The attachment counter is a conditional update rather than a read-then-write, so simultaneous uploads cannot both take the last slot. A refused file gives its slot back.
  • The type a file is held to is what its content is detected as, not what the request claims — a renamed executable is still an executable.
  • Filenames are stripped of paths and invisible characters. U+202E alone makes factuur<RLO>fdp.exe read as factuurexe.pdf.
  • A submission may only point at files this task uploaded for this case.

Metadata

Metadata on the process link is written on every file the task uploads. The Documenten API builds the whole document out of it and cannot default informatieobjecttype or titel, which inside Valtimo are collected by a component the public page cannot render.

Values are resolved against the case. Valtimo resolves placeholders only in action properties that are strings, and this one is a map, so PluginService skipped it entirely and stored pv:... literally — the plugin now resolves the values itself, one at a time so an unresolvable value costs only its own key. The screen offers a value selector for the value column and a plain text field for the key.

Review notes

  • documentation/plugin.md is now written for people configuring the plugin in the Valtimo UI; developer and ops material moved to documentation/developer.md.
  • Before deploying: spring.servlet.multipart.max-file-size caps every upload before the plugin sees it, valtimo.upload.accepted-mime-types is the application-wide floor on file types, and temporary resources are purged after valtimo.temporaryResourceStorage.retentionInMinutes (default 60) — a public form is often left open longer than that. All three are covered in developer.md.
  • Two things found while reviewing and not addressed here, both pre-existing: two concurrent submissions can both pass the availability check before either writes isCompletedByPublicTask, and there is no rate limiting in front of the upload endpoint — a leaked link is a cheap request that costs a form prefill per attempt.

Backend tests (including the Postgres-backed ITs) and the Angular build pass. The stack was brought up and the public endpoints smoke-tested; the value selector was checked in the running admin UI.

A public form could not accept files. Three things were in the way, of
which only two were missing.

Valtimo's upload components (valtimo-file, documenten-api-file) are
Angular components of the Valtimo front end, so the plain Form.io
renderer the public page uses drew nothing at all for them. They are now
rewritten to Form.io's own file component before the definition leaves
the server, which keeps the mapping in one testable place instead of in
the page.

There was no upload target an applicant could reach: Valtimo's
/api/v1/resource/temp is authenticated, and the public page has no
token. POST /api/v1/public-task/{publicTaskId}/attachment now parks the
file in temporary resource storage and answers with its resource id.

The third thing turned out to need nothing: the id travels back in the
submission, where Valtimo's UploadField raises a
TemporaryResourceSubmittedEvent for it, exactly as for an upload from a
task inside Valtimo. Whatever already handles those - a Documenten API
"store uploaded document" task, the S3 listener - keeps working without
knowing that a public task was involved.

The endpoint is unauthenticated by design, so what it accepts is bounded
rather than trusted. The bounds are configured per process link rather
than in application properties, so that an administrator sets them where
the rest of the task is configured: Maximum number of attachments,
Maximum file size and Accepted file types, each falling back to a
bounded default rather than to "no limit". They are read when the public
task is created and kept with it, so editing a process link does not
change the terms of a link that has already been sent out. An upload
field can narrow them further with Form.io's own Maximum File Size and
File Pattern, never widen them.

The attachment count is a conditional update on a counter rather than a
read followed by a write, so simultaneous uploads cannot both take the
last slot; a refused file gives its slot back. The type a file is held
to is the type its content is detected as, not the one the request
claims, so a renamed executable is still an executable. Filenames are
stripped of paths and of invisible characters - U+202E alone makes
"factuur<RLO>fdp.exe" read as "factuurexe.pdf" - before they reach
metadata that later becomes, for instance, a bestandsnaam in the
Documenten API. A submission may only point at files that this task
uploaded for this case.

Metadata on the process link is written on every file the task uploads,
for whatever picks the file up: the Documenten API builds the whole
document out of it and cannot default informatieobjecttype or titel,
which inside Valtimo are collected by a component the public page cannot
render. Its values are resolved against the case, so they can follow it
rather than being fixed per process link. Valtimo resolves placeholders
only in action properties that are strings, and this one is a map, so
the plugin resolves the values itself; the screen offers a value
selector for them.

Worth knowing when deploying this: temporary resources are purged after
valtimo.temporaryResourceStorage.retentionInMinutes, which defaults to
60. A public form is often left open longer than that, and an applicant
would lose their attachments before pressing submit.
spring.servlet.multipart.max-file-size caps every upload before the
plugin sees it, and valtimo.upload.accepted-mime-types is the
application-wide floor on file types. documentation/developer.md covers
these, along with what a replacement HTML template has to carry over.
@Klaas-Ritense
Klaas-Ritense merged commit 8761eaf into main Aug 31, 2026
5 checks passed
@Klaas-Ritense
Klaas-Ritense deleted the feat/public-task-document-upload branch August 31, 2026 07:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant