Compare commits

...

10 Commits

Author SHA1 Message Date
Lennart
d59ae25eba v0.6.5 2025-07-22 16:57:08 +02:00
Lennart
d4daa35df6 auth: Make app token validation faster by supplying hint to the app token name 2025-07-22 16:48:04 +02:00
Lennart
ea43876410 auth: User faster app token hash 2025-07-22 16:10:19 +02:00
Lennart
18af1b9aa2 remove calendar-proxy from DAV header 2025-07-22 15:41:24 +02:00
Lennart
e69c75102c version 0.6.4 2025-07-22 10:55:28 +02:00
Lennart
09f1bd20ae close connection if request body might not have been consumed
hopefully fixes #77
2025-07-22 10:53:12 +02:00
Lennart
72f970a857 version 0.6.3 2025-07-20 13:39:25 +02:00
Lennart
08c250657e well-known: add second apple user agent 2025-07-20 13:38:57 +02:00
Lennart
b8ef2f1ba2 version 0.6.2 2025-07-20 13:16:42 +02:00
Lennart
c8adf60f48 version 0.6.1 2025-07-20 13:13:01 +02:00
8 changed files with 60 additions and 24 deletions

22
Cargo.lock generated
View File

@@ -2999,7 +2999,7 @@ dependencies = [
[[package]]
name = "rustical"
version = "0.6.0"
version = "0.6.5"
dependencies = [
"anyhow",
"argon2",
@@ -3042,7 +3042,7 @@ dependencies = [
[[package]]
name = "rustical_caldav"
version = "0.6.0"
version = "0.6.5"
dependencies = [
"async-std",
"async-trait",
@@ -3080,7 +3080,7 @@ dependencies = [
[[package]]
name = "rustical_carddav"
version = "0.6.0"
version = "0.6.5"
dependencies = [
"async-trait",
"axum",
@@ -3112,7 +3112,7 @@ dependencies = [
[[package]]
name = "rustical_dav"
version = "0.6.0"
version = "0.6.5"
dependencies = [
"async-trait",
"axum",
@@ -3137,7 +3137,7 @@ dependencies = [
[[package]]
name = "rustical_dav_push"
version = "0.6.0"
version = "0.6.5"
dependencies = [
"async-trait",
"axum",
@@ -3163,7 +3163,7 @@ dependencies = [
[[package]]
name = "rustical_frontend"
version = "0.6.0"
version = "0.6.5"
dependencies = [
"askama",
"askama_web",
@@ -3196,7 +3196,7 @@ dependencies = [
[[package]]
name = "rustical_ical"
version = "0.6.0"
version = "0.6.5"
dependencies = [
"axum",
"chrono",
@@ -3214,7 +3214,7 @@ dependencies = [
[[package]]
name = "rustical_oidc"
version = "0.6.0"
version = "0.6.5"
dependencies = [
"async-trait",
"axum",
@@ -3229,7 +3229,7 @@ dependencies = [
[[package]]
name = "rustical_store"
version = "0.6.0"
version = "0.6.5"
dependencies = [
"anyhow",
"async-trait",
@@ -3263,7 +3263,7 @@ dependencies = [
[[package]]
name = "rustical_store_sqlite"
version = "0.6.0"
version = "0.6.5"
dependencies = [
"async-trait",
"chrono",
@@ -3284,7 +3284,7 @@ dependencies = [
[[package]]
name = "rustical_xml"
version = "0.6.0"
version = "0.6.5"
dependencies = [
"quick-xml",
"thiserror 2.0.12",

View File

@@ -2,7 +2,7 @@
members = ["crates/*"]
[workspace.package]
version = "0.6.0"
version = "0.6.5"
edition = "2024"
description = "A CalDAV server"
repository = "https://github.com/lennart-k/rustical"

View File

@@ -51,7 +51,7 @@ impl<C: CalendarStore, S: SubscriptionStore> ResourceService for CalendarResourc
type Principal = Principal;
type PrincipalUri = CalDavPrincipalUri;
const DAV_HEADER: &str = "1, 3, access-control, calendar-access, calendar-proxy, webdav-push";
const DAV_HEADER: &str = "1, 3, access-control, calendar-access, webdav-push";
async fn get_resource(
&self,

View File

@@ -41,11 +41,6 @@ impl Resource for PrincipalResource {
Resourcetype(&[
ResourcetypeInner(Some(rustical_dav::namespace::NS_DAV), "collection"),
ResourcetypeInner(Some(rustical_dav::namespace::NS_DAV), "principal"),
// https://github.com/apple/ccs-calendarserver/blob/13c706b985fb728b9aab42dc0fef85aae21921c3/doc/Extensions/caldav-proxy.txt
// ResourcetypeInner(
// Some(rustical_dav::namespace::NS_CALENDARSERVER),
// "calendar-proxy-write",
// ),
])
}

View File

@@ -46,7 +46,7 @@ impl<AP: AuthenticationProvider, S: SubscriptionStore, CS: CalendarStore> Resour
type Principal = Principal;
type PrincipalUri = CalDavPrincipalUri;
const DAV_HEADER: &str = "1, 3, access-control, calendar-access, calendar-proxy";
const DAV_HEADER: &str = "1, 3, access-control, calendar-access";
async fn get_resource(
&self,

View File

@@ -56,9 +56,13 @@ pub async fn route_post_app_token<AP: AuthenticationProvider>(
assert!(!name.is_empty());
assert_eq!(user_id, user.id);
let token = generate_app_token();
auth_provider
let mut token_id = auth_provider
.add_app_token(&user.id, name.to_owned(), token.clone())
.await?;
// Get first 4 characters of token identifier
token_id.truncate(4);
// This will be a hint for the token validator which app token hash to verify against
let token = format!("{token_id}_{token}");
if apple {
let profile = AppleConfig {
token_name: name,

View File

@@ -149,8 +149,23 @@ impl AuthenticationProvider for SqlitePrincipalStore {
user_id: &str,
token: &str,
) -> Result<Option<Principal>, Error> {
#[instrument(skip(password))]
fn verify_password(password: &str, hash: &str) -> Result<(), password_auth::VerifyError> {
password_auth::verify_password(password, hash)
}
// Allow to specify the token id to use to make validation faster
// Doesn't match the whole length of the token id to keep the length in bounds
// Example: asd_selgkh
// where the app token id starts with asd and its value is selgkh
let (token_id_prefix, token) = token.split_once('_').unwrap_or(("", token));
for app_token in &self.get_app_tokens(user_id).await? {
if password_auth::verify_password(token, app_token.token.as_ref()).is_ok() {
// Wrong token id
if !app_token.id.starts_with(token_id_prefix) {
continue;
}
if verify_password(token, app_token.token.as_ref()).is_ok() {
return self.get_principal(user_id).await;
}
}
@@ -206,7 +221,10 @@ impl AuthenticationProvider for SqlitePrincipalStore {
None,
None,
Params {
rounds: 10,
// The app token has a high entropy so we are quite safe from quessing attacks
// Also if an attacker got access to the hashes they'd have already gotten
// access to the whole database.
rounds: 2,
..Default::default()
},
&salt,

View File

@@ -1,11 +1,13 @@
use crate::config::NextcloudLoginConfig;
use axum::Router;
use axum::body::Body;
use axum::body::{Body, HttpBody};
use axum::extract::Request;
use axum::middleware::Next;
use axum::response::{Redirect, Response};
use axum::routing::{any, options};
use axum_extra::TypedHeader;
use headers::{HeaderMapExt, UserAgent};
use http::header::CONNECTION;
use http::{HeaderValue, StatusCode};
use rustical_caldav::caldav_router;
use rustical_carddav::carddav_router;
@@ -60,7 +62,7 @@ pub fn make_app<AS: AddressbookStore, CS: CalendarStore, S: SubscriptionStore>(
.route(
"/.well-known/caldav",
any(async |TypedHeader(ua): TypedHeader<UserAgent>| {
if ua.as_str().contains("remindd") {
if ua.as_str().contains("remindd") || ua.as_str().contains("dataaccessd") {
// remindd is an Apple Calendar User Agent
// Even when explicitly configuring a principal URL in Apple Calendar Apple
// will not respect that configuration but call /.well-known/caldav,
@@ -178,4 +180,21 @@ pub fn make_app<AS: AddressbookStore, CS: CalendarStore, S: SubscriptionStore>(
},
),
)
.layer(axum::middleware::from_fn(
async |req: Request, next: Next| {
// Closes the connection if the request body might've not been fully consumed
// Otherwise subsequent requests reusing the connection might fail.
// See https://github.com/lennart-k/rustical/issues/77
let body_empty = req.body().is_end_stream();
let mut response = next.run(req).await;
if !body_empty
&& (response.status().is_server_error() || response.status().is_client_error())
{
response
.headers_mut()
.insert(CONNECTION, HeaderValue::from_static("close"));
}
response
},
))
}