You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
feat: security hardening, structured logging, and regression tests for 1.0
CSRF protection (double-submit cookie) on all 31 POST handlers, per-IP
rate limiting on booking endpoints, server-side input validation,
double-booking prevention via SQLite unique index + transactions,
crash-proof web handlers (37 unwrap removals), graceful shutdown,
and 50 structured tracing points across auth, bookings, CalDAV, admin,
and email delivery.
28 new regression tests (191 → 219) covering ICS timezone/location bugs,
input validation, CSRF token verification, and date range validation.
Updated docs: security.md, architecture.md, deployment.md, booking-flow.md,
introduction.md, README.md, CHANGELOG.md, and CLAUDE.md.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
-**ICS location field corruption** — LOCATION line in `.ics` calendar invites had trailing whitespace after CRLF, causing the ORGANIZER field to be interpreted as a continuation of LOCATION per RFC 5545 line folding rules. BlueMind and other strict CalDAV servers displayed the organizer info inside the location field.
90
+
-**ICS floating times** — DTSTART/DTEND in `.ics` invites used floating times (no timezone) instead of UTC. Events appeared at the wrong time for guests in different timezones. Now converts to UTC with `Z` suffix via `convert_to_utc()`.
91
+
-**Hardcoded UTC guest timezone** — `confirm_booking` and `approve_booking_by_token` handlers passed `"UTC"` as guest timezone instead of the actual stored timezone, causing ICS times in approval emails to be wrong.
92
+
-**Broken "Add source" link on dashboard overview** — pointed to `/dashboard/sources/add` instead of `/dashboard/sources/new`
93
+
94
+
### Added
95
+
96
+
-**Version display in sidebar** — calrs version shown at the bottom of the dashboard sidebar
@@ -237,6 +240,18 @@ File: `src/web/mod.rs`, templates in `templates/`
237
240
238
241
**CalDAV write-back:** Confirmed bookings are pushed to the host's CalDAV calendar (if `write_calendar_href` is configured on the source). On cancellation, the event is deleted from CalDAV.
239
242
243
+
**Security hardening (1.0):**
244
+
-**CSRF protection** — double-submit cookie pattern on all 31 POST handlers via `csrf_cookie_middleware`. Client-side JS injects `_csrf` hidden field. Multipart forms use query parameter.
245
+
-**Booking rate limiting** — per-IP (10 req / 5 min) on all 4 booking handlers. Uses `X-Forwarded-For`.
246
+
-**Input validation** — server-side on all booking forms (name 1–255, email format, notes max 5000, date max 365 days), registration, settings, avatar upload (content-type whitelist).
247
+
-**Double-booking prevention** — partial unique index `idx_bookings_no_overlap` on `(event_type_id, start_at)` + `BEGIN IMMEDIATE` transactions.
248
+
-**Crash-proof handlers** — all `.unwrap()` in web handlers replaced with proper error responses.
249
+
250
+
**Observability (1.0):**
251
+
-**Structured logging** — `tracing` crate with 50 log points across auth, bookings, CalDAV, admin, email, DB migrations. Configurable via `RUST_LOG` env var (default: `calrs=info,tower_http=info`).
Copy file name to clipboardExpand all lines: README.md
+45Lines changed: 45 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -135,6 +135,7 @@
135
135
-**SQLite storage** — single-file WAL-mode database, zero ops
136
136
-**CLI** — full command set for headless operation (init, source, sync, event-type, booking, config, user)
137
137
-**Single binary** — no runtime dependencies beyond the binary itself
138
+
-**Structured logging** — `tracing` + `tower-http` for request-level observability, configurable via `RUST_LOG`
138
139
139
140
## Install
140
141
@@ -335,6 +336,50 @@ Get certificates with certbot: `sudo certbot --nginx -d cal.example.com`.
335
336
336
337
> **Important:** Set `CALRS_BASE_URL` to your public URL (e.g. `https://cal.example.com`) so that OIDC redirect URIs and email links point to the right host.
337
338
339
+
## Observability
340
+
341
+
calrs uses structured logging via the `tracing` crate. All log output goes to stderr and is captured by systemd journal, Docker logs, or any log aggregator.
342
+
343
+
### Configuration
344
+
345
+
Set the log level via the `RUST_LOG` environment variable:
- No JavaScript framework — vanilla JS only where needed (timezone detection, provider presets)
140
+
- No JavaScript framework — vanilla JS only where needed (timezone detection, provider presets, CSRF token injection)
132
141
133
142
## Email
134
143
@@ -160,7 +169,7 @@ The approval request email includes Approve and Decline action buttons (table-ba
160
169
161
170
## Testing
162
171
163
-
calrs has an automated test suite with 147+ tests, run on every push and pull request via [GitHub Actions](https://github.com/olivierlambert/calrs/actions/workflows/ci.yml).
172
+
calrs has an automated test suite with 219 tests, run on every push and pull request via [GitHub Actions](https://github.com/olivierlambert/calrs/actions/workflows/ci.yml).
164
173
165
174
**What's tested:**
166
175
@@ -173,6 +182,8 @@ calrs has an automated test suite with 147+ tests, run on every push and pull re
Copy file name to clipboardExpand all lines: docs/src/booking-flow.md
+15-1Lines changed: 15 additions & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -21,7 +21,7 @@
21
21
|---|---|
22
22
|`confirmed`| Booking is active. Slot is blocked. Emails sent. |
23
23
|`pending`| Awaiting host approval (when `requires_confirmation` is on). |
24
-
|`cancelled`| Cancelled by host. Slot is freed. |
24
+
|`cancelled`| Cancelled by host or guest. Slot is freed. |
25
25
|`declined`| Declined by host (pending booking rejected). |
26
26
27
27
## Confirmation mode
@@ -48,6 +48,17 @@ From the dashboard, click **Cancel** on an upcoming booking:
48
48
3. Both guest and host receive cancellation emails with a `METHOD:CANCEL``.ics` attachment
49
49
4. If the booking was pushed to CalDAV, the event is deleted from the calendar
50
50
51
+
### Guest self-cancellation
52
+
53
+
Guests can cancel their own bookings via a link in the confirmation email:
54
+
55
+
1. Click the "Cancel booking" link in the email
56
+
2. Optionally enter a reason
57
+
3. Confirm the cancellation
58
+
4. Both guest and host are notified
59
+
60
+
The cancellation email correctly attributes who cancelled (host vs guest).
61
+
51
62
## Conflict detection
52
63
53
64
Before a booking is accepted, calrs checks for conflicts:
@@ -57,6 +68,8 @@ Before a booking is accepted, calrs checks for conflicts:
57
68
-**Buffer times** — the buffer before/after is included in the conflict window
58
69
-**Minimum notice** — slots too close to the current time are rejected
59
70
71
+
Additionally, a database-level unique index prevents two bookings from occupying the same slot, even if two guests submit simultaneously.
72
+
60
73
## CalDAV write-back
61
74
62
75
When a booking is confirmed (either directly or via approval), calrs can push the event to the host's CalDAV calendar. See [CalDAV Integration > Write-back](./caldav.md#caldav-write-back) for setup.
@@ -71,6 +84,7 @@ If SMTP is configured, calrs sends emails at these moments:
| Booking reminder | Reminder with cancel button | Reminder with details |
74
88
75
89
All emails are sent as **HTML with plain text fallback**. They include event title, date, time, timezone, location, and notes. The HTML templates are responsive and support dark mode in email clients that honor `prefers-color-scheme`.
0 commit comments