Skip to content

feat(sources): allow editing CalDAV sources (URL, username, password, name)#73

Merged
olivierlambert merged 2 commits into
mainfrom
feat/source-edit
May 23, 2026
Merged

feat(sources): allow editing CalDAV sources (URL, username, password, name)#73
olivierlambert merged 2 commits into
mainfrom
feat/source-edit

Conversation

@olivierlambert
Copy link
Copy Markdown
Owner

Summary

Closes #72. Today the source row at `/dashboard/sources` supports test, sync, remove, and write-back-calendar selection — but not editing the connection itself. Typo'd URL? Username mistake? Periodic CalDAV password rotation? The only path was delete-and-readd, which loses the synced events cache, the write-back configuration, and any per-event-type calendar selections that referenced calendars under the old source.

This PR adds an Edit affordance on each source row (web) and a `calrs source update` command (CLI). Both are scoped to the current user's sources.

Web

  • New routes: `GET /dashboard/sources/{id}/edit` (form pre-filled from DB) and `POST /dashboard/sources/{id}/edit` (apply changes).
  • New "Edit" link added to each source row in `templates/dashboard_sources.html`, between Test and Remove.
  • `templates/source_form.html` reused with an `editing` boolean (mirrors the `event_type_form.html` pattern). When editing: the title/subtitle change, the password field is no longer `required` and shows "Leave blank to keep existing", and the form posts to the edit URL.
  • The handler scopes by `user_id` on both GET and POST, so you can't view or edit a source you don't own (redirect to `/dashboard/sources` on mismatch).

CLI

`calrs source update [--name ...] [--url ...] [--username ...] [--password]`

  • `` accepts a unique prefix (matching the existing convention in `Remove` and `Test`).
  • `--password` is a boolean flag; when set, the command prompts for the new password via `rpassword` (no echo, not in shell history). Useful for scripted password rotation.
  • Without `--password` the existing encrypted blob is preserved untouched.

Notable details

  • Password field is optional in both surfaces; empty means "keep existing". The web handler decrypts the stored blob to feed the connection-test client with the same credentials sync will use afterwards, so the test exercises the real auth path.
  • URL still passes `validate_caldav_url` (the existing SSRF guard) regardless of whether the connection test is skipped.
  • The "Skip connection test" checkbox still works on the edit form — same semantics as create.
  • After URL or username changes, both surfaces hint that a manual sync is recommended to refresh the discovered calendar list. No automatic re-sync — the discovery flow's `resolve_url()` already re-resolves hrefs from the server origin, so the next manual sync self-heals.

Tests

4 new web handler tests:

  • `edit_source_form_renders_prefilled` — GET shows form in editing mode with name/url/username pre-filled, password placeholder reads "Leave blank to keep existing"
  • `update_source_changes_fields_and_keeps_password_when_blank` — POST with empty password updates name/url/username, decrypted password unchanged
  • `update_source_rotates_password` — POST with non-empty password re-encrypts; decrypted blob matches new password
  • `update_source_404_for_other_user_source` — GET on another user's source redirects (cross-user scoping enforced)

583 tests total (up from 579). All green on pre-commit. Clippy clean.

Test plan

  • On `/dashboard/sources`, click Edit on an existing source. Form pre-fills with current values; password field shows "Leave blank to keep existing".
  • Change the display name, leave password blank, save. Source name updates; sync still works (proving password unchanged).
  • Edit again, enter a new password, save. Manual sync should still work (proving password rotation succeeded). If you rotate to a wrong password and the test wasn't skipped, the form re-renders with a connection-failed error.
  • Try the CLI: `calrs source update --name "New Name"` (no password change) and `calrs source update --password` (interactive prompt).
  • After editing the URL or username, verify the post-action hint says to run sync.

Verification gap I'm flagging

I have not driven the Edit button in a real browser. The handler logic is covered by the four unit tests on `update_source` plus the existing template-render tests; the JS-free form is unlikely to surprise. The browser-facing piece worth eyeballing is the post-edit redirect and the password-blank "Leave blank to keep existing" placeholder.

🤖 Generated with Claude Code

olivierlambert and others added 2 commits May 23, 2026 18:27
Closes #72. Today the source row supports test/sync/remove and
write-back-calendar selection but not editing the connection itself.
Typo'd URL? Username mistake? Periodic CalDAV password rotation? The
only path was delete-and-readd, which loses the synced events cache,
the write-back configuration, and any per-event-type calendar
selections that referenced calendars under the old source.

Adds an Edit affordance on each source row (web) and a `calrs source
update` command (CLI). The web POST and the CLI both go through small
DB updates that scope by user_id (web) or by id-prefix (CLI matching
existing convention).

Notable details:

- Password field is optional; empty means "keep existing". The web
  handler decrypts the stored blob to feed the connection-test client
  with credentials sync will use afterwards. The CLI uses an explicit
  --password flag that prompts via rpassword (so password isn't echoed
  and isn't visible in shell history).
- URL still passes validate_caldav_url (SSRF guard); the connection
  test still respects the existing "Skip connection test" checkbox.
- After URL or username changes, both surfaces hint that a manual
  sync is recommended to refresh the discovered calendar list. No
  automatic re-sync — the discovery flow's resolve_url() already
  re-resolves hrefs from the server origin, so the next manual sync
  self-heals.
- Edit button only shown for sources the user owns; the GET handler
  scopes by user_id and redirects on access mismatch.

Tests: 4 new web handler tests (form pre-fill, blank-password keeps
existing, password rotation re-encrypts, cross-user access blocked).
583 tests total (up from 579), all green on pre-commit. Clippy clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- CLI source update now runs validate_caldav_url on the new URL, mirroring
  the web handler. Closes a parity gap where the web path rejected private
  IPs but the CLI happily accepted them.
- Stop SELECT-ing and binding current_password_enc in the no-rotation arm;
  it was only used for a `let _ =` discard. Use a 4-tuple destructure
  instead.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@olivierlambert olivierlambert merged commit 62bf740 into main May 23, 2026
4 checks passed
@olivierlambert olivierlambert deleted the feat/source-edit branch May 23, 2026 16:29
olivierlambert added a commit that referenced this pull request May 24, 2026
A modernization pass on operator-facing surfaces: source connection
details are now editable instead of delete-and-readd; SMTP gains
env-var configuration and proper port-465 support; the From: mailbox
stops mangling addresses without display names; and team event-type
management permissions are tightened so management routes can't be
reached via the read-only availability surface.

Highlights:
- Edit CalDAV sources from the dashboard (`/dashboard/sources/{id}/edit`)
  or the CLI (`calrs source update <id-prefix>`); empty password keeps
  the existing one (#73, closes #72).
- SMTP via `CALRS_SMTP_*` env vars + new `tls_mode` (starttls/tls)
  fixing the port-465 hang for both env-var and DB-configured SMTP
  (#56, migration 052).
- Fix `Error: Invalid input` on SMTP test when `from_name` is unset
  by building the From: mailbox via `Mailbox::new` everywhere (#104).
- Team event-type management gated through a single
  `can_manage_event_type` check; `delete_invite` now strictly requires
  it (behaviour change for non-admin invite creators on internal
  events); 8 new regression tests on the manageability matrix (#55).

671 tests total, up from 650 in 1.11.0. See CHANGELOG.md for details.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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.

feat: edit existing CalDAV sources (URL, username, password, name)

1 participant