backend: webdav tokens

This commit is contained in:
2025-10-29 00:12:58 +01:00
parent b4282c2b4b
commit d9b2297eca
12 changed files with 707 additions and 119 deletions
+1
View File
@@ -707,6 +707,7 @@ fn truncate_all(conn: &mut PgConnection) -> Result<()> {
jobs, \
refresh_tokens, \
tags, \
webdav_tokens, \
user_memberships, \
users, \
tenants \
+153
View File
@@ -0,0 +1,153 @@
mod common;
use anyhow::Result;
use axum::body::Body;
use axum::http::{header, Method, Request, StatusCode};
use backend::models::WebdavToken;
use backend::routes::webdav;
use backend::schema::webdav_tokens;
use base64::engine::general_purpose::STANDARD as BASE64;
use base64::Engine;
use common::{acquire_db_lock, body_to_vec, TestApp};
use diesel::prelude::*;
use serde::Deserialize;
use serde_json::json;
use tower::ServiceExt;
use uuid::Uuid;
#[derive(Deserialize)]
struct TokenInfo {
id: Uuid,
label: Option<String>,
last_used_at: Option<String>,
revoked_at: Option<String>,
}
#[derive(Deserialize)]
struct CreateTokenResponse {
token: String,
#[serde(rename = "token_info")]
info: TokenInfo,
}
#[tokio::test]
async fn webdav_token_api_crud() -> Result<()> {
let _guard = acquire_db_lock().await;
let app = TestApp::new().await?;
let username = "alice";
let password = "correct horse battery";
app.insert_user(username, password, "admin").await?;
let access_token = app.login_token(username, password).await?;
let create_response = app
.post_json(
"/api/profile/webdav-tokens",
&json!({ "label": "dav" }),
Some(&access_token),
)
.await?;
assert_eq!(create_response.status(), StatusCode::CREATED);
let create_body = body_to_vec(create_response.into_body()).await?;
let created: CreateTokenResponse = serde_json::from_slice(&create_body)?;
let token_id = created.info.id;
assert_eq!(created.info.label.as_deref(), Some("dav"));
assert!(created.info.last_used_at.is_none());
let list_response = app
.get("/api/profile/webdav-tokens", Some(&access_token))
.await?;
assert_eq!(list_response.status(), StatusCode::OK);
let list_body = body_to_vec(list_response.into_body()).await?;
let listed: Vec<TokenInfo> = serde_json::from_slice(&list_body)?;
assert_eq!(listed.len(), 1);
assert_eq!(listed[0].id, token_id);
let delete_response = app
.delete(
&format!("/api/profile/webdav-tokens/{token_id}"),
Some(&access_token),
)
.await?;
assert_eq!(delete_response.status(), StatusCode::NO_CONTENT);
let list_after = app
.get("/api/profile/webdav-tokens", Some(&access_token))
.await?;
let list_after_body = body_to_vec(list_after.into_body()).await?;
let listed_after: Vec<TokenInfo> = serde_json::from_slice(&list_after_body)?;
assert_eq!(listed_after.len(), 1);
assert_eq!(listed_after[0].id, token_id);
assert!(listed_after[0].revoked_at.is_some());
app.cleanup().await?;
Ok(())
}
#[tokio::test]
async fn webdav_basic_auth_uses_tokens() -> Result<()> {
let _guard = acquire_db_lock().await;
let app = TestApp::new().await?;
let username = "bruce";
let password = "wayne";
app.insert_user(username, password, "admin").await?;
let access_token = app.login_token(username, password).await?;
let create_response = app
.post_json(
"/api/profile/webdav-tokens",
&json!({ "label": "webdav" }),
Some(&access_token),
)
.await?;
let create_body = body_to_vec(create_response.into_body()).await?;
let created: CreateTokenResponse = serde_json::from_slice(&create_body)?;
let token_id = created.info.id;
let router = webdav::create_router().with_state(app.state.clone());
let auth_header = format!(
"Basic {}",
BASE64.encode(format!("{}:{}", username, created.token))
);
let propfind = Method::from_bytes(b"PROPFIND")?;
let success_request = Request::builder()
.method(propfind.clone())
.uri("/")
.header(header::AUTHORIZATION, auth_header.clone())
.header("depth", "0")
.body(Body::empty())?;
let response = router.clone().oneshot(success_request).await?;
assert_eq!(response.status(), StatusCode::MULTI_STATUS);
let used = app
.with_conn(move |conn| {
let record = webdav_tokens::table
.find(token_id)
.first::<WebdavToken>(conn)?;
Ok::<_, anyhow::Error>(record.last_used_at)
})
.await?;
assert!(used.is_some());
let delete_response = app
.delete(
&format!("/api/profile/webdav-tokens/{token_id}"),
Some(&access_token),
)
.await?;
assert_eq!(delete_response.status(), StatusCode::NO_CONTENT);
let failure_request = Request::builder()
.method(propfind)
.uri("/")
.header(header::AUTHORIZATION, auth_header)
.header("depth", "0")
.body(Body::empty())?;
let response = router.oneshot(failure_request).await?;
assert_eq!(response.status(), StatusCode::UNAUTHORIZED);
app.cleanup().await?;
Ok(())
}