feat(sharing): master switch for public share links, and a policy tab that reads in decision order #352

Merged
Cordy merged 10 commits from sharing-page into main 2026-08-28 01:21:11 +00:00
Owner

Built test-first. Three commits: RED (field + tests, one failing), GREEN (four lines of enforcement), then the UI.

The switch

settings.ShareLinksDisabled turns off creation of new public share links instance-wide. shareCreate rejects with 400 policy: public share links are turned off on this server, checked before the password and expiry rules, so there is no combination of other settings that lets a link through.

Stored negative, deliberately. A ShareLinksEnabled bool would default to Go's zero value — false — and every existing instance would silently lose share links on upgrade, because its settings.json has no such key. ShareUploadsForbidden already solves this the same way. The UI inverts both, so the labels read the right way round without a config migration.

Three tests, each pinning a claim that can break independently:

Test Claim
TestShareLinksDisabledRejectsCreation the API refuses, not merely the dialog
TestShareLinksEnabledWhenUnset the zero value leaves sharing on
TestShareLinksDisabledLeavesExistingLinks disabling does not revoke

Only the first was red before the enforcement commit; the other two were written to fail loudly if the implementation broke behaviour it had no business touching. That was checked, not assumed — the RED run asserted the enforcement test failed on its want 400 assertion and that the other two already passed.

The policy tab

Rows now run capability → constraint → defaults → timings: whether links may exist at all, whether they must carry a password, whether they may take uploads, whether uploads are on by default, then the two expiry ceilings. Previously the two dropdowns came first and the switches after, which is schema order, not decision order.

Forbid upload links becomes Allow upload links — same stored field, inverted in the UI. Labels shortened now that the page title supplies the context (Default share expiryDefault expiry). Page description changed from "Instance-wide share link policy." to "Who may create share links, and the limits the server enforces on them."

Nine i18n keys upserted across four languages; two that lost their last caller (sdNoUpload, shareForbidUploads) were removed, and the splice asserted no references survive.

Not in this PR

Non-operator visibility — a regular user still discovers the policy by being rejected after filling in the dialog. That is #351, with the three candidate designs and the argument against each written up.

Verification

go build ./..., go vet, go test ./internal/api/... ./internal/settings/... all pass. node --check on the extracted script blocks passes. Row order asserted programmatically rather than eyeballed.

Built test-first. Three commits: RED (field + tests, one failing), GREEN (four lines of enforcement), then the UI. ## The switch `settings.ShareLinksDisabled` turns off creation of new public share links instance-wide. `shareCreate` rejects with `400 policy: public share links are turned off on this server`, checked **before** the password and expiry rules, so there is no combination of other settings that lets a link through. **Stored negative, deliberately.** A `ShareLinksEnabled bool` would default to Go's zero value — `false` — and every existing instance would silently lose share links on upgrade, because its `settings.json` has no such key. `ShareUploadsForbidden` already solves this the same way. The UI inverts both, so the labels read the right way round without a config migration. Three tests, each pinning a claim that can break independently: | Test | Claim | |---|---| | `TestShareLinksDisabledRejectsCreation` | the API refuses, not merely the dialog | | `TestShareLinksEnabledWhenUnset` | the zero value leaves sharing **on** | | `TestShareLinksDisabledLeavesExistingLinks` | disabling does not revoke | Only the first was red before the enforcement commit; the other two were written to fail loudly if the implementation broke behaviour it had no business touching. That was checked, not assumed — the RED run asserted the enforcement test failed on its `want 400` assertion and that the other two already passed. ## The policy tab Rows now run **capability → constraint → defaults → timings**: whether links may exist at all, whether they must carry a password, whether they may take uploads, whether uploads are on by default, then the two expiry ceilings. Previously the two dropdowns came first and the switches after, which is schema order, not decision order. `Forbid upload links` becomes `Allow upload links` — same stored field, inverted in the UI. Labels shortened now that the page title supplies the context (`Default share expiry` → `Default expiry`). Page description changed from "Instance-wide share link policy." to "Who may create share links, and the limits the server enforces on them." Nine i18n keys upserted across four languages; two that lost their last caller (`sdNoUpload`, `shareForbidUploads`) were removed, and the splice asserted no references survive. ## Not in this PR Non-operator visibility — a regular user still discovers the policy by being rejected after filling in the dialog. That is #351, with the three candidate designs and the argument against each written up. ## Verification `go build ./...`, `go vet`, `go test ./internal/api/... ./internal/settings/...` all pass. `node --check` on the extracted script blocks passes. Row order asserted programmatically rather than eyeballed.
Adds settings.ShareLinksDisabled and three tests. Only the enforcement
test fails; the other two already hold and are here to pin behaviour
the implementation must not break:

  - the zero value must leave sharing ON, since an upgrading instance
    has no such key in settings.json;
  - disabling must not revoke links that already exist.

Field is stored negative for exactly that first reason.
Four lines in the existing policy block. Checked first, so a disabled
instance refuses before the password and expiry rules have anything to
say -- there is no combination of other settings that lets a link
through.

Create-time only: the two tests pinning the zero value and the
no-revoke promise still pass untouched.
feat(admin): rebuild the Sharing policy tab
All checks were successful
ci / test-and-build (pull_request) Successful in 41s
fa338c915f
Rows now run capability, constraint, defaults, timings: whether links may
exist at all, whether they must carry a password, whether they may take
uploads, whether uploads are on by default, then the two expiry ceilings.
Reading the page top to bottom now follows the decision, not the schema.

Public share links is the new master switch. Allow upload links replaces
Forbid upload links: both settings stay stored negative, and the UI
inverts them, so no config migration is needed for a label that reads
the right way round.
Cordy merged commit a7edff2ecc into main 2026-08-28 01:21:11 +00:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: Cordy/Cairn#352
No description provided.