Proving who somebody is and starting a session for them are separate operations. login_with_caldav does the first by asking the CalDAV server whether the credentials work; begin_session does the second and knows nothing about how the question was answered. OIDC arrives as a second way to reach begin_session, not as a second scheme threaded through everything -- which is what v1 had, with a JWT for most of the app and a separate SQLite session_token used only by the preferences API. The token lives in an HttpOnly cookie and nowhere else. v1 kept a JWT and the CalDAV password in localStorage, readable by any script on the origin, and re-sent the password in a header on every request. Here the password never leaves the server: it is encrypted with XChaCha20-Poly1305 and caldav_for is the only path back, handing out a client rather than a credential. Failed decryption is an error, not a subtly wrong password -- the AEAD tag is checked, so a tampered row surfaces here instead of as a mysterious CalDAV rejection later. A wrong password and an unreachable server stay distinct, because telling somebody their password is wrong when the server is down sends them to reset one that was fine. Errors carry a stable code alongside their message, so a client can branch on them. Internal ones say nothing about the inside of the server; the detail goes to the log. Tests go through router(), the same function main calls -- v1's suite rebuilt the route table and tested a copy until it stopped compiling. Skipping is now loud: a skipped test reports "ok", so run.sh sets RUNWAY_REQUIRE_CALDAV=1 and not running becomes a failure.
80 lines
1.8 KiB
TOML
80 lines
1.8 KiB
TOML
[workspace]
|
|
resolver = "3"
|
|
members = [
|
|
"crates/runway-core",
|
|
"crates/runway-caldav",
|
|
"crates/runway-server",
|
|
"crates/runway-web",
|
|
"crates/runway-cli",
|
|
]
|
|
|
|
[workspace.package]
|
|
version = "0.1.0"
|
|
edition = "2024"
|
|
license = "MIT"
|
|
repository = "https://github.com/connorjohnstone/runway"
|
|
|
|
[workspace.lints.rust]
|
|
dead_code = "deny"
|
|
unused_must_use = "deny"
|
|
unsafe_code = "forbid"
|
|
|
|
[workspace.lints.clippy]
|
|
# v1 had 65 of these. Tests opt out locally.
|
|
unwrap_used = "deny"
|
|
expect_used = "deny"
|
|
|
|
[workspace.dependencies]
|
|
# Internal
|
|
runway-core = { path = "crates/runway-core" }
|
|
runway-caldav = { path = "crates/runway-caldav" }
|
|
|
|
# Core data
|
|
chrono = { version = "0.4", default-features = false, features = ["serde", "clock", "std"] }
|
|
chrono-tz = { version = "0.10", features = ["serde"] }
|
|
serde = { version = "1", features = ["derive"] }
|
|
serde_json = "1"
|
|
uuid = { version = "1", features = ["v4", "serde"] }
|
|
|
|
# Calendar domain
|
|
icalendar = "0.17"
|
|
rrule = "0.14"
|
|
|
|
# Crypto
|
|
sha2 = "0.10"
|
|
chacha20poly1305 = "0.10"
|
|
rand = "0.9"
|
|
base64 = "0.22"
|
|
|
|
# Errors + logging
|
|
thiserror = "2"
|
|
tracing = "0.1"
|
|
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
|
|
|
# Backend
|
|
axum = "0.8"
|
|
axum-extra = { version = "0.10", features = ["cookie"] }
|
|
tokio = { version = "1", features = ["rt-multi-thread", "macros", "net", "signal"] }
|
|
tower = "0.5"
|
|
tower-http = { version = "0.6", features = ["cors", "trace"] }
|
|
sqlx = { version = "0.9", features = ["runtime-tokio", "sqlite", "chrono", "uuid", "migrate"] }
|
|
reqwest = { version = "0.12", default-features = false, features = ["json", "rustls-tls"] }
|
|
quick-xml = "0.42"
|
|
|
|
# Frontend
|
|
leptos = "0.8"
|
|
leptos_router = "0.8"
|
|
|
|
# CLI
|
|
clap = { version = "4", features = ["derive", "env"] }
|
|
iana-time-zone = "0.1"
|
|
|
|
# Dev
|
|
pretty_assertions = "1"
|
|
|
|
[profile.release]
|
|
opt-level = "z"
|
|
lto = true
|
|
codegen-units = 1
|
|
strip = true
|