use axum::{ extract::{Path, Query, State}, http::StatusCode, Json, }; use chrono::{DateTime, NaiveDateTime}; use serde::{Deserialize, Serialize}; use uuid::Uuid; use crate::auth::{ passkeys::PasskeySummary, webdav_tokens::{ create_webdav_token as issue_token, list_webdav_tokens as load_tokens, revoke_webdav_token as revoke_token, }, TenantScopedConn, }; use crate::error::{AppError, AppResult}; use crate::models::WebdavToken; use crate::state::AppState; use crate::utils::{db::no_content, time::to_iso}; #[derive(Debug, Serialize)] pub struct WebdavTokenResponse { pub id: Uuid, pub tenant_id: Uuid, pub label: Option, pub created_at: String, pub last_used_at: Option, pub expires_at: Option, pub revoked_at: Option, } #[derive(Debug, Serialize)] pub struct WebdavTokenCreatedResponse { pub token: String, pub token_info: WebdavTokenResponse, } #[derive(Debug, Deserialize)] pub struct CreateWebdavTokenRequest { pub label: Option, pub expires_at: Option, } #[derive(Debug, Deserialize)] pub struct RevokePasskeyQuery { #[serde(default)] pub reason: Option, } pub async fn list_passkeys( State(state): State, TenantScopedConn { mut conn, user_id, .. }: TenantScopedConn, ) -> AppResult>> { let service = state .passkeys .as_ref() .ok_or_else(|| AppError::bad_request("passkey support is disabled"))?; let passkeys = service.list_for_user(&mut conn, user_id)?; Ok(Json(passkeys)) } pub async fn list_webdav_tokens( TenantScopedConn { mut conn, tenant_id, user_id, .. }: TenantScopedConn, ) -> AppResult>> { let tokens = load_tokens(&mut conn, user_id, Some(tenant_id))?; let responses = tokens.into_iter().map(webdav_token_to_response).collect(); Ok(Json(responses)) } pub async fn create_webdav_token( TenantScopedConn { mut conn, tenant_id, user_id, .. }: TenantScopedConn, Json(payload): Json, ) -> AppResult<(StatusCode, Json)> { let expires_at = match payload.expires_at { Some(ref value) => Some(parse_timestamp(value)?), None => None, }; let issued = issue_token( &mut conn, user_id, tenant_id, payload.label.clone(), expires_at, )?; let response = WebdavTokenCreatedResponse { token: issued.token, token_info: webdav_token_to_response(issued.record), }; Ok((StatusCode::CREATED, Json(response))) } pub async fn delete_webdav_token( TenantScopedConn { mut conn, user_id, .. }: TenantScopedConn, Path(token_id): Path, ) -> AppResult { revoke_token(&mut conn, token_id, user_id)?; no_content() } pub async fn delete_passkey( State(state): State, TenantScopedConn { mut conn, user_id, .. }: TenantScopedConn, Path(passkey_id): Path, Query(query): Query, ) -> AppResult { let service = state .passkeys .as_ref() .ok_or_else(|| AppError::bad_request("passkey support is disabled"))?; service.revoke_passkey(&mut conn, user_id, passkey_id, query.reason)?; no_content() } fn webdav_token_to_response(token: WebdavToken) -> WebdavTokenResponse { WebdavTokenResponse { id: token.id, tenant_id: token.tenant_id, label: token.label, created_at: to_iso(token.created_at), last_used_at: token.last_used_at.map(to_iso), expires_at: token.expires_at.map(to_iso), revoked_at: token.revoked_at.map(to_iso), } } fn parse_timestamp(value: &str) -> AppResult { let dt = DateTime::parse_from_rfc3339(value) .map_err(|_| AppError::bad_request("invalid expires_at timestamp"))?; Ok(dt.naive_utc()) }