feat(18-01): write-only settings secrets, richeditor and tabs on settings forms (task 3)

- type: password on a settings form binds a lagoon.Encrypted column as a
  write-only secret (any other column fails boot naming the field): the
  GET omits it from data and reports stored, env or empty per field in
  meta.secrets; on PUT an absent key or "" keeps the value, null clears
  it, a non-string is a 422; the answer never carries a value
- cabana.SecretFallbacks (field to config key) gives the env state;
  cabana.SettingsMeta, SecretStored/SecretEnv/SecretEmpty exported
- settings forms accept richeditor: the Put sanitizes through seaglass
  like a controller save and returns meta.notices; repeater and
  dependsOn stay refused (settingsFormOnly next to controllerFormOnly)
- backend::lang secret.* phrases in en and pl
- admin SPA: PasswordField secret status line (stored with Remove stored
  value, env, empty, pending clear with Undo), SettingsFormView tabs with
  error-tab activation and the secrets/notices meta, settings payload
  that never sends an untouched secret; regenerated OpenAPI and TS
  types; rebuilt dist
- vault fixture, TestSettingsSecretCompile, Docker TestSettingsSecretSmoke
  and TestSettingsRichEditorSmoke, conform settings fixture with a
  password field; settings.md sections, forms.md notes, cabana README
This commit is contained in:
Jakub Zych
2026-10-13 11:36:12 +02:00
parent 0c8605123e
commit 1e841b4281
27 changed files with 1251 additions and 92 deletions

View File

@@ -298,6 +298,7 @@ func (MembersController) FormVirtualFields() []string {
- Every listed name must be a field of the form's `fields.yaml` with the type `password`, `text`, `textarea`, `number`, `checkbox`, `switch` or `dropdown`. Anything else stops the start-up.
- `type: password` is always a form-only field: a password field the controller does not list stops the start-up. The admin SPA shows it empty on every load, leaves an empty password out of an update (empty means unchanged), and clears it after a save. A confirmation is a second `password` field, compared by the `confirmed` rule on the server.
- Form-only fields are not available on settings forms or relation forms.
- On a settings form `type: password` is not a form-only field but a write-only secret backed by a `lagoon.Encrypted` column; see [Write-only secrets](settings.md#write-only-secrets).
## Permission editor
@@ -449,7 +450,7 @@ The value travels as a string of HTML, or null. Anything else in a save body is
- HTML the policy keeps whole is stored byte for byte, so a value that only differs in escaping is never rewritten.
- Otherwise the clean HTML is stored and the save still succeeds. The create or update response then carries `meta.notices` for the field: how many elements and attributes were removed with their names, how many comments, or that some markup changed. The admin shows them under the field.
The editor's own toolbar keeps to the same allowlist, but the server is the gate: a value posted by any client goes through the policy. The type is supported in controller forms only; settings forms and relation forms refuse it at start-up, so no save path stores an unsanitized value.
The editor's own toolbar keeps to the same allowlist, but the server is the gate: a value posted by any client goes through the policy. The type is supported in controller forms and on [settings forms](settings.md#rich-text-on-a-settings-page), whose save runs the same sanitizer; relation forms refuse it at start-up, so no save path stores an unsanitized value.
In the admin SPA the field is a TipTap editor with a toolbar for bold, italic, underline, a level 3 heading, bullet and numbered lists, quotes, links, undo and redo. The editor code loads on first use, so forms without a rich text field never download it.