fszontagh 1 месяц назад
Родитель
Сommit
80315618f0
1 измененных файлов с 156 добавлено и 0 удалено
  1. 156 0
      docs/nodes.md

+ 156 - 0
docs/nodes.md

@@ -269,6 +269,162 @@ Any node may set it, not only the last one to run, so adding a node to the end
 of a workflow cannot silently change what its API returns. If several set it,
 the last one wins and the runner logs that it happened.
 
+## Form Trigger
+
+`form-trigger` (category `triggers`) publishes an HTML form at a shareable URL.
+Submitting it starts the workflow - a form field lets a person do what a webhook
+otherwise needs a developer for.
+
+### Where it is served
+
+The form is served at `/webhook/{workflowId}`. If the node's `path` field is
+set, that path is appended and must match exactly, e.g.
+`/webhook/<id>/apply`; leave it empty to answer at the bare workflow URL. `GET`
+renders the form, `POST` submits it.
+
+**The workflow must be published and active.** The webhook route only ever
+runs the published version - not whatever is currently open in the editor -
+and it refuses to answer at all if the workflow's `active` flag is off (a
+plain `400 Workflow is not active`). A form that was saved but never
+published, or was unpublished after being turned off, answers nothing useful.
+This is the single most likely reason a form "doesn't work": open the
+workflow and confirm both Publish and Active.
+
+### Fields
+
+Each entry in the node's `fields` array is one question:
+
+| Key | Meaning |
+|-----|---------|
+| `name` | The key the workflow reads the answer under. Letters, digits and underscore, not starting with a digit. |
+| `label` | Shown next to the input. |
+| `type` | One of `text`, `textarea`, `number`, `select`, `checkbox`, `date`, `file`. |
+| `required` | Blocks submission (with the form re-rendered and an error) if left empty. |
+| `placeholder` | Placeholder text, for the text-like types. |
+| `options` | The choices, for a `select` field. |
+| `accept` | For a `file` field, e.g. `image/*`. This is a browser hint only, sent as the `accept` attribute on the `<input>` - it is **not enforced server-side**. Nothing stops a submission with a different file type arriving anyway. A form that must reject a wrong file type needs a node downstream that inspects the actual bytes, not this field. |
+| `maxSizeMb` | For a `file` field. `0` uses the server-wide limit; a nonzero value can only tighten that limit, never loosen it - a per-field maximum above the server limit is meaningless because the oversized request is already refused before this check runs. |
+
+### Reading the answers
+
+Answers land on the trigger node's output under `form`, keyed by field name:
+
+```
+{{ $node['Form'].form.email }}
+{{ $node['Form'].form.howMany }}
+```
+
+A `file` field arrives as a binary object, the same shape a download produces:
+
+```json
+{
+    "type": "binary",
+    "data": "<base64>",
+    "mimeType": "image/png",
+    "filename": "photo.png",
+    "size": 48213,
+    "checksum": "<sha256 hex>"
+}
+```
+
+`checksum` is the SHA-256 of the base64 text, not of the raw bytes - the same
+convention `http-request`'s download path uses for its own binary output, so
+the two are directly comparable and an uploaded file can be handed straight to
+any node that expects a downloaded one.
+
+### Response modes
+
+`responseMode` is `immediate` or `wait`:
+
+- **`immediate`** returns the thank-you page (`responseMessage`) right away and
+  lets the workflow run in the background. **Anything that takes more than
+  about 30 seconds must use this mode** - the alternative blocks the browser
+  and times out.
+- **`wait`** holds the browser connection open until the workflow finishes, so
+  a `respond-to-webhook` node downstream can shape the actual response (status,
+  headers, body) the way [Answering a Webhook](#answering-a-webhook) describes.
+  The wait is capped at 30 seconds; a run that has not finished by then answers
+  with a timeout rather than hanging the browser indefinitely.
+
+Immediate-mode submissions run on a small bounded worker pool inside the
+webserver rather than one thread per request, so a burst of traffic against a
+public form cannot exhaust it. The pool is `server.form_dispatch_threads`
+(default 4) and `server.form_dispatch_queue_capacity` (default 32) in
+`config/webserver.json` - unset in the shipped config, so a stock install runs
+on those defaults. When the queue is full, the form answers **503** with a
+plain "This form is busy right now. Please try again in a moment." page
+instead of a false thank-you, so a bounded burst is visible as a retry-able
+failure rather than a silently dropped submission.
+
+### Upload size
+
+Two limits apply, and a submission has to pass both:
+
+- `server.max_upload_mb` in `config/webserver.json` bounds every request body
+  the webserver accepts, uploads included - 32 MB by default. Anything larger
+  is refused before the form handler ever sees it.
+- The runner's own gRPC message-size limit, `max_message_size_mb` in
+  `config/runner.json`, bounds the same body again on its way from the
+  webserver to the runner as part of the execution payload - 64 MB by default,
+  deliberately set above the upload limit so it never re-imposes a lower cap
+  than the one already enforced at the HTTP layer.
+
+A field's own `maxSizeMb` (see Fields, above) can tighten either of these
+further but never raise them.
+
+### Password
+
+`password` is optional; leave it empty and anyone with the link can submit the
+form. Set it and a visitor must enter it once (checked with a constant-time
+comparison) before the form is shown; on success they get a signed, HttpOnly
+cookie scoped to that workflow's webhook path, good for one hour, so they are
+not asked again for the rest of the session.
+
+**State plainly what this is worth**: the password is stored as plain,
+readable text in the live workflow document. Anyone who can read the workflow
+- anyone with editor access to the project - can read it directly, no
+decryption involved. It is a gate against a link being forwarded somewhere it
+shouldn't go, not a secret and not a security boundary. Do not rely on it to
+keep a form private from someone who already has access to the workspace.
+
+### No captcha
+
+There is no captcha and no rate limiting on the form endpoint beyond the
+bounded worker pool described above. A public form can be submitted
+repeatedly by a script as easily as by a person. A form that cannot tolerate
+that should be given a password, at minimum, and treated as a mitigation
+rather than a fix.
+
+### Submissions are executions
+
+There is no separate submissions list. A submission is a workflow execution
+like any other and is visible wherever executions are - the workflow's
+execution history - with the form answers as the trigger node's output.
+
+### Two things to know before debugging one at 2am
+
+**The form endpoint reveals that a form exists at a URL, even to someone who
+doesn't know its password.** An unknown workflow id gets a `404`, a workflow
+that exists but has no form node falls through to a structurally different
+response, and a workflow with a form node behind a wrong password gets a
+`200` password prompt. These three cases are distinguishable, so a visitor can
+tell "there is a form here" from "there is nothing here" without ever seeing
+the password itself. This was a deliberate choice, not an oversight - closing
+it would mean reshaping how every webhook on the platform answers a request
+for an id it doesn't recognize, and workflow ids are random UUIDs, so there is
+nothing practical to enumerate against it in the first place.
+
+**A node with a config field literally named `password` - the form trigger
+included - permanently shows as having unpublished changes in the editor,**
+even immediately after publishing. The upstream database daemon encrypts
+fields named `password` when it writes a version-history snapshot, while a
+live read of the same document stays plaintext; the editor's unpublished-changes
+indicator compares those two, and an encrypted value never equals its own
+plaintext. This is pre-existing platform behaviour that affects any node
+config with a `password`-named field, not something specific to forms - it is
+safe to ignore the indicator on a form-trigger node once you've confirmed the
+publish itself succeeded.
+
 ## Multiple Inputs
 
 A node that joins two branches names its inputs: