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, last_used_at: Option, revoked_at: Option, } #[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 = 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 = 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::(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(()) }