]> git.proxmox.com Git - proxmox-backup.git/commitdiff
proxmox-rest-server: use new ServerAdapter trait instead of callbacks
authorDietmar Maurer <dietmar@proxmox.com>
Tue, 5 Oct 2021 09:01:05 +0000 (11:01 +0200)
committerDietmar Maurer <dietmar@proxmox.com>
Tue, 5 Oct 2021 09:13:10 +0000 (11:13 +0200)
Async callbacks are a PITA, so we now pass a single trait object which
implements check_auth and get_index.

proxmox-rest-server/examples/minimal-rest-server.rs
proxmox-rest-server/src/api_config.rs
proxmox-rest-server/src/lib.rs
proxmox-restore-daemon/src/main.rs
proxmox-restore-daemon/src/proxmox_restore_daemon/auth.rs
proxmox-restore-daemon/src/proxmox_restore_daemon/mod.rs
src/bin/proxmox-backup-api.rs
src/bin/proxmox-backup-proxy.rs
src/server/auth.rs

index b1ef933530479ce6cc323afe0dc4fdb968167a2b..6400fdbf1db1ad6372e0c42cae8d2689e7cfa667 100644 (file)
@@ -1,20 +1,24 @@
-use std::sync::{Arc, Mutex};
+use std::sync::Mutex;
 use std::collections::HashMap;
 use std::future::Future;
 use std::pin::Pin;
 
 use anyhow::{bail, format_err, Error};
 use lazy_static::lazy_static;
+use hyper::{Body, Response, Method};
+use http::request::Parts;
+use http::HeaderMap;
 
 use proxmox::api::{api, router::SubdirMap, Router, RpcEnvironmentType, UserInformation};
 use proxmox::list_subdirs_api_method;
-use proxmox_rest_server::{ApiAuth, ApiConfig, AuthError, RestServer, RestEnvironment};
-// Create a Dummy User info and auth system
-// Normally this would check and authenticate the user
+use proxmox_rest_server::{ServerAdapter, ApiConfig, AuthError, RestServer, RestEnvironment};
+
+// Create a Dummy User information system
 struct DummyUserInfo;
 
 impl UserInformation for DummyUserInfo {
     fn is_superuser(&self, _userid: &str) -> bool {
+        // Always return true here, so we have access to everthing
         true
     }
     fn is_group_member(&self, _userid: &str, group: &str) -> bool {
@@ -25,14 +29,17 @@ impl UserInformation for DummyUserInfo {
     }
 }
 
-struct DummyAuth;
+struct MinimalServer;
+
+// implement the server adapter
+impl ServerAdapter for MinimalServer {
 
-impl ApiAuth for DummyAuth {
-    fn check_auth<'a>(
-        &'a self,
-        _headers: &'a http::HeaderMap,
-        _method: &'a hyper::Method,
-    ) -> Pin<Box<dyn Future<Output = Result<(String, Box<dyn UserInformation + Sync + Send>), AuthError>> + Send + 'a>> {
+    // normally this would check and authenticate the user
+    fn check_auth(
+        &self,
+        _headers: &HeaderMap,
+        _method: &Method,
+    ) -> Pin<Box<dyn Future<Output = Result<(String, Box<dyn UserInformation + Sync + Send>), AuthError>> + Send>> {
         Box::pin(async move {
             // get some global/cached userinfo
             let userinfo: Box<dyn UserInformation + Sync + Send> = Box::new(DummyUserInfo);
@@ -40,21 +47,21 @@ impl ApiAuth for DummyAuth {
             Ok(("User".to_string(), userinfo))
         })
     }
-}
 
-// this should return the index page of the webserver
-// iow. what the user browses to
-
-fn get_index<'a>(
-    _env: RestEnvironment,
-    _parts: http::request::Parts,
-) -> Pin<Box<dyn Future<Output = http::Response<hyper::Body>> + Send + 'a>> {
-    Box::pin(async move {
-        // build an index page
-        http::Response::builder()
-            .body("hello world".into())
-            .unwrap()
-    })
+    // this should return the index page of the webserver
+    // iow. what the user browses to
+    fn get_index(
+        &self,
+        _env: RestEnvironment,
+        _parts: Parts,
+    ) -> Pin<Box<dyn Future<Output = Response<Body>> + Send>> {
+        Box::pin(async move {
+            // build an index page
+            http::Response::builder()
+                .body("hello world".into())
+                .unwrap()
+        })
+    }
 }
 
 // a few examples on how to do api calls with the Router
@@ -190,8 +197,7 @@ async fn run() -> Result<(), Error> {
         "/var/tmp/",
         &ROUTER,
         RpcEnvironmentType::PUBLIC,
-        Arc::new(DummyAuth {}),
-        &get_index,
+        MinimalServer,
     )?;
     let rest_server = RestServer::new(config);
 
index c7c71ec025b5b55192e9804440020eca3db2a33c..999901149b5371d2f5f08d21ffa5d47d11400b58 100644 (file)
@@ -3,7 +3,6 @@ use std::path::PathBuf;
 use std::time::SystemTime;
 use std::fs::metadata;
 use std::sync::{Arc, Mutex, RwLock};
-use std::future::Future;
 use std::pin::Pin;
 
 use anyhow::{bail, Error, format_err};
@@ -16,9 +15,8 @@ use serde::Serialize;
 use proxmox::api::{ApiMethod, Router, RpcEnvironmentType, UserInformation};
 use proxmox::tools::fs::{create_path, CreateOptions};
 
-use crate::{ApiAuth, AuthError, FileLogger, FileLogOptions, CommandSocket, RestEnvironment};
+use crate::{ServerAdapter, AuthError, FileLogger, FileLogOptions, CommandSocket, RestEnvironment};
 
-pub type GetIndexFn = &'static (dyn Fn(RestEnvironment, Parts) -> Pin<Box<dyn Future<Output = Response<Body>> + Send>> + Send + Sync);
 
 /// REST server configuration
 pub struct ApiConfig {
@@ -30,8 +28,7 @@ pub struct ApiConfig {
     template_files: RwLock<HashMap<String, (SystemTime, PathBuf)>>,
     request_log: Option<Arc<Mutex<FileLogger>>>,
     auth_log: Option<Arc<Mutex<FileLogger>>>,
-    api_auth: Arc<dyn ApiAuth + Send + Sync>,
-    get_index_fn: GetIndexFn,
+    adapter: Pin<Box<dyn ServerAdapter + Send + Sync>>,
 }
 
 impl ApiConfig {
@@ -53,8 +50,7 @@ impl ApiConfig {
         basedir: B,
         router: &'static Router,
         env_type: RpcEnvironmentType,
-        api_auth: Arc<dyn ApiAuth + Send + Sync>,
-        get_index_fn: GetIndexFn,
+        adapter: impl ServerAdapter + 'static,
     ) -> Result<Self, Error> {
         Ok(Self {
             basedir: basedir.into(),
@@ -65,8 +61,7 @@ impl ApiConfig {
             template_files: RwLock::new(HashMap::new()),
             request_log: None,
             auth_log: None,
-            api_auth,
-            get_index_fn,
+            adapter: Box::pin(adapter),
         })
     }
 
@@ -75,7 +70,7 @@ impl ApiConfig {
         rest_env: RestEnvironment,
         parts: Parts,
     ) -> Response<Body> {
-        (self.get_index_fn)(rest_env, parts).await
+        self.adapter.get_index(rest_env, parts).await
     }
 
     pub(crate) async fn check_auth(
@@ -83,7 +78,7 @@ impl ApiConfig {
         headers: &http::HeaderMap,
         method: &hyper::Method,
     ) -> Result<(String, Box<dyn UserInformation + Sync + Send>), AuthError> {
-        self.api_auth.check_auth(headers, method).await
+        self.adapter.check_auth(headers, method).await
     }
 
     pub(crate) fn find_method(
index bb29295cf21dd5d33712f3420b730e704d9fe32f..d72936c2ebcaea3a7ce3faaa848b4f4e81b1a496 100644 (file)
@@ -21,6 +21,9 @@ use std::pin::Pin;
 
 use anyhow::{bail, format_err, Error};
 use nix::unistd::Pid;
+use hyper::{Body, Response, Method};
+use http::request::Parts;
+use http::HeaderMap;
 
 use proxmox::tools::fd::Fd;
 use proxmox::sys::linux::procfs::PidStat;
@@ -70,17 +73,26 @@ impl From<Error> for AuthError {
     }
 }
 
-/// User Authentication trait
-pub trait ApiAuth {
+/// User Authentication and index/root page generation methods
+pub trait ServerAdapter: Send + Sync {
+
+    /// Returns the index/root page
+    fn get_index(
+        &self,
+        rest_env: RestEnvironment,
+        parts: Parts,
+    ) -> Pin<Box<dyn Future<Output = Response<Body>> + Send>>;
+
     /// Extract user credentials from headers and check them.
     ///
     /// If credenthials are valid, returns the username and a
     /// [UserInformation] object to query additional user data.
     fn check_auth<'a>(
         &'a self,
-        headers: &'a http::HeaderMap,
-        method: &'a hyper::Method,
+        headers: &'a HeaderMap,
+        method: &'a Method,
     ) -> Pin<Box<dyn Future<Output = Result<(String, Box<dyn UserInformation + Sync + Send>), AuthError>> + Send + 'a>>;
+
 }
 
 lazy_static::lazy_static!{
index 1e175abd9932957b1dd1aefa5414c45084db566b..6dd25ff5a0cb450680a5ab522e00ad019e9947fa 100644 (file)
@@ -7,23 +7,17 @@ use std::os::unix::{
 };
 use std::path::Path;
 use std::sync::{Arc, Mutex};
-use std::future::Future;
-use std::pin::Pin;
 
 use anyhow::{bail, format_err, Error};
 use lazy_static::lazy_static;
 use log::{error, info};
 use tokio::sync::mpsc;
 use tokio_stream::wrappers::ReceiverStream;
-use http::request::Parts;
-use http::Response;
-use hyper::{Body, StatusCode};
-use hyper::header;
 
 use proxmox::api::RpcEnvironmentType;
 
 use pbs_client::DEFAULT_VSOCK_PORT;
-use proxmox_rest_server::{ApiConfig, RestServer, RestEnvironment};
+use proxmox_rest_server::{ApiConfig, RestServer};
 
 mod proxmox_restore_daemon;
 use proxmox_restore_daemon::*;
@@ -93,29 +87,14 @@ fn setup_system_env() -> Result<(), Error> {
     Ok(())
 }
 
-fn get_index<'a>(
-    _env: RestEnvironment,
-    _parts: Parts,
-) -> Pin<Box<dyn Future<Output = http::Response<Body>> + Send + 'a>> {
-    Box::pin(async move {
-
-        let index = "<center><h1>Proxmox Backup Restore Daemon/h1></center>";
-
-        Response::builder()
-            .status(StatusCode::OK)
-            .header(header::CONTENT_TYPE, "text/html")
-            .body(index.into())
-            .unwrap()
-    })
-}
 
 async fn run() -> Result<(), Error> {
     watchdog_init();
 
-    let auth_config = Arc::new(
-        auth::ticket_auth().map_err(|err| format_err!("reading ticket file failed: {}", err))?,
-    );
-    let config = ApiConfig::new("", &ROUTER, RpcEnvironmentType::PUBLIC, auth_config, &get_index)?;
+    let adaptor = StaticAuthAdapter::new()
+        .map_err(|err| format_err!("reading ticket file failed: {}", err))?;
+
+    let config = ApiConfig::new("", &ROUTER, RpcEnvironmentType::PUBLIC, adaptor)?;
     let rest_server = RestServer::new(config);
 
     let vsock_fd = get_vsock_fd()?;
index faf57a3d3e6929f9a0826ede77a43b251738a691..b57552e1d3b42f800d14dd4f3fa0377237949a62 100644 (file)
@@ -5,10 +5,13 @@ use std::future::Future;
 use std::pin::Pin;
 
 use anyhow::{bail, format_err, Error};
+use hyper::{Body, Response, Method, StatusCode};
+use http::request::Parts;
+use http::HeaderMap;
 
 use proxmox::api::UserInformation;
 
-use proxmox_rest_server::{ApiAuth, AuthError};
+use proxmox_rest_server::{ServerAdapter, AuthError, RestEnvironment};
 
 const TICKET_FILE: &str = "/ticket";
 
@@ -22,15 +25,30 @@ impl UserInformation for SimpleUserInformation {
     fn lookup_privs(&self, _userid: &str, _path: &[&str]) -> u64 { 0 }
 }
 
-pub struct StaticAuth {
+pub struct StaticAuthAdapter {
     ticket: String,
 }
 
-impl ApiAuth for StaticAuth {
+impl StaticAuthAdapter {
+
+    pub fn new() -> Result<Self, Error> {
+        let mut ticket_file = File::open(TICKET_FILE)?;
+        let mut ticket = String::new();
+        let len = ticket_file.read_to_string(&mut ticket)?;
+        if len <= 0 {
+            bail!("invalid ticket: cannot be empty");
+        }
+        Ok(StaticAuthAdapter { ticket })
+    }
+}
+
+
+impl ServerAdapter for StaticAuthAdapter {
+
     fn check_auth<'a>(
         &'a self,
-        headers: &'a http::HeaderMap,
-        _method: &'a hyper::Method,
+        headers: &'a HeaderMap,
+        _method: &'a Method,
     ) -> Pin<Box<dyn Future<Output = Result<(String, Box<dyn UserInformation + Sync + Send>), AuthError>> + Send + 'a>> {
         Box::pin(async move {
 
@@ -47,14 +65,21 @@ impl ApiAuth for StaticAuth {
             }
         })
     }
-}
 
-pub fn ticket_auth() -> Result<StaticAuth, Error> {
-    let mut ticket_file = File::open(TICKET_FILE)?;
-    let mut ticket = String::new();
-    let len = ticket_file.read_to_string(&mut ticket)?;
-    if len <= 0 {
-        bail!("invalid ticket: cannot be empty");
+    fn get_index(
+        &self,
+        _env: RestEnvironment,
+        _parts: Parts,
+    ) -> Pin<Box<dyn Future<Output = http::Response<Body>> + Send>> {
+        Box::pin(async move {
+
+            let index = "<center><h1>Proxmox Backup Restore Daemon/h1></center>";
+
+            Response::builder()
+                .status(StatusCode::OK)
+                .header(hyper::header::CONTENT_TYPE, "text/html")
+                .body(index.into())
+                .unwrap()
+        })
     }
-    Ok(StaticAuth { ticket })
 }
index 58e2bb6e17da94d0d4616d84640e6d246723a675..570f208dbe618e744ea935914b6c1c57e22f90fd 100644 (file)
@@ -3,6 +3,7 @@ mod api;
 pub use api::*;
 
 pub mod auth;
+pub use auth::*;
 
 mod watchdog;
 pub use watchdog::*;
index cdfa79f330de0a5a53ad338af92e49fff4130f2b..a0eef382580c39d89c783ef8052d77f499468f70 100644 (file)
@@ -5,16 +5,17 @@ use anyhow::{bail, Error};
 use futures::*;
 use http::request::Parts;
 use http::Response;
-use hyper::{Body, StatusCode};
-use hyper::header;
+use hyper::{Body, Method, StatusCode};
+use http::HeaderMap;
 
 use proxmox::try_block;
 use proxmox::api::RpcEnvironmentType;
 use proxmox::tools::fs::CreateOptions;
+use proxmox::api::UserInformation;
 
-use proxmox_rest_server::{daemon, ApiConfig, RestServer, RestEnvironment};
+use proxmox_rest_server::{daemon, AuthError, ApiConfig, RestServer, RestEnvironment, ServerAdapter};
 
-use proxmox_backup::server::auth::default_api_auth;
+use proxmox_backup::server::auth::check_pbs_auth;
 use proxmox_backup::auth_helpers::*;
 use proxmox_backup::config;
 
@@ -27,20 +28,36 @@ fn main() {
     }
 }
 
-fn get_index<'a>(
-    _env: RestEnvironment,
-    _parts: Parts,
-) -> Pin<Box<dyn Future<Output = Response<Body>> + Send + 'a>> {
-    Box::pin(async move {
+struct ProxmoxBackupApiAdapter;
 
-        let index = "<center><h1>Proxmox Backup API Server</h1></center>";
+impl ServerAdapter for ProxmoxBackupApiAdapter {
 
-        Response::builder()
-            .status(StatusCode::OK)
-            .header(header::CONTENT_TYPE, "text/html")
-            .body(index.into())
-            .unwrap()
-    })
+    fn get_index(
+        &self,
+        _env: RestEnvironment,
+        _parts: Parts,
+    ) -> Pin<Box<dyn Future<Output = Response<Body>> + Send>> {
+        Box::pin(async move {
+
+            let index = "<center><h1>Proxmox Backup API Server</h1></center>";
+
+            Response::builder()
+                .status(StatusCode::OK)
+                .header(hyper::header::CONTENT_TYPE, "text/html")
+                .body(index.into())
+                .unwrap()
+        })
+    }
+
+    fn check_auth<'a>(
+        &'a self,
+        headers: &'a HeaderMap,
+        method: &'a Method,
+    ) -> Pin<Box<dyn Future<Output = Result<(String, Box<dyn UserInformation + Sync + Send>), AuthError>> + Send + 'a>> {
+        Box::pin(async move {
+            check_pbs_auth(headers, method).await
+        })
+    }
 }
 
 async fn run() -> Result<(), Error> {
@@ -78,8 +95,7 @@ async fn run() -> Result<(), Error> {
         pbs_buildcfg::JS_DIR,
         &proxmox_backup::api2::ROUTER,
         RpcEnvironmentType::PRIVILEGED,
-        default_api_auth(),
-        &get_index,
+        ProxmoxBackupApiAdapter,
     )?;
 
     let backup_user = pbs_config::backup_user()?;
index 57bd50bf68b2be5e3a520747c27e87a66b2fe452..8e4bdcacba42df6e1f7a838359cb49440d86bdb3 100644 (file)
@@ -15,21 +15,23 @@ use url::form_urlencoded;
 use openssl::ssl::{SslMethod, SslAcceptor, SslFiletype};
 use tokio_stream::wrappers::ReceiverStream;
 use serde_json::{json, Value};
+use http::{Method, HeaderMap};
 
 use proxmox::try_block;
-use proxmox::api::{RpcEnvironment, RpcEnvironmentType};
+use proxmox::api::{RpcEnvironment, RpcEnvironmentType, UserInformation};
 use proxmox::sys::linux::socket::set_tcp_keepalive;
 use proxmox::tools::fs::CreateOptions;
 
 use pbs_tools::task_log;
 use pbs_datastore::DataStore;
 use proxmox_rest_server::{
-    rotate_task_log_archive, extract_cookie , ApiConfig, RestServer, RestEnvironment, WorkerTask,
+    rotate_task_log_archive, extract_cookie , AuthError, ApiConfig, RestServer, RestEnvironment,
+    ServerAdapter, WorkerTask,
 };
 
 use proxmox_backup::{
     server::{
-        auth::default_api_auth,
+        auth::check_pbs_auth,
         jobstate::{
             self,
             Job,
@@ -81,6 +83,29 @@ fn main() -> Result<(), Error> {
 }
 
 
+struct ProxmoxBackupProxyAdapter;
+
+impl ServerAdapter for ProxmoxBackupProxyAdapter {
+
+    fn get_index(
+        &self,
+        env: RestEnvironment,
+        parts: Parts,
+    ) -> Pin<Box<dyn Future<Output = Response<Body>> + Send>> {
+        Box::pin(get_index_future(env, parts))
+    }
+
+    fn check_auth<'a>(
+        &'a self,
+        headers: &'a HeaderMap,
+        method: &'a Method,
+    ) -> Pin<Box<dyn Future<Output = Result<(String, Box<dyn UserInformation + Sync + Send>), AuthError>> + Send + 'a>> {
+        Box::pin(async move {
+            check_pbs_auth(headers, method).await
+        })
+    }
+}
+
 fn extract_lang_header(headers: &http::HeaderMap) -> Option<String> {
     if let Some(Ok(cookie)) = headers.get("COOKIE").map(|v| v.to_str()) {
         return extract_cookie(cookie, "PBSLangCookie");
@@ -88,13 +113,6 @@ fn extract_lang_header(headers: &http::HeaderMap) -> Option<String> {
     None
 }
 
-fn get_index<'a>(
-    env: RestEnvironment,
-    parts: Parts,
-) -> Pin<Box<dyn Future<Output = Response<Body>> + Send + 'a>> {
-    Box::pin(get_index_future(env, parts))
-}
-
 async fn get_index_future(
     env: RestEnvironment,
     parts: Parts,
@@ -191,8 +209,7 @@ async fn run() -> Result<(), Error> {
         pbs_buildcfg::JS_DIR,
         &proxmox_backup::api2::ROUTER,
         RpcEnvironmentType::PUBLIC,
-        default_api_auth(),
-        &get_index,
+        ProxmoxBackupProxyAdapter,
     )?;
 
     config.add_alias("novnc", "/usr/share/novnc-pve");
index d6c4a66fb525cfcab4c3cc3ce48d7f6752092dc2..2e6beac764f0b9183373e60fe2e13d67f622bf2f 100644 (file)
@@ -1,9 +1,5 @@
 //! Provides authentication primitives for the HTTP server
 
-use std::sync::Arc;
-use std::future::Future;
-use std::pin::Pin;
-
 use anyhow::format_err;
 
 use proxmox::api::UserInformation;
@@ -11,7 +7,7 @@ use proxmox::api::UserInformation;
 use pbs_tools::ticket::{self, Ticket};
 use pbs_config::{token_shadow, CachedUserInfo};
 use pbs_api_types::{Authid, Userid};
-use proxmox_rest_server::{ApiAuth, AuthError, extract_cookie};
+use proxmox_rest_server::{AuthError, extract_cookie};
 
 use crate::auth_helpers::*;
 
@@ -28,111 +24,93 @@ enum AuthData {
     ApiToken(String),
 }
 
-pub struct UserApiAuth {}
-pub fn default_api_auth() -> Arc<UserApiAuth> {
-    Arc::new(UserApiAuth {})
-}
-
-impl UserApiAuth {
-    fn extract_auth_data(headers: &http::HeaderMap) -> Option<AuthData> {
-        if let Some(raw_cookie) = headers.get(header::COOKIE) {
-            if let Ok(cookie) = raw_cookie.to_str() {
-                if let Some(ticket) = extract_cookie(cookie, "PBSAuthCookie") {
-                    let csrf_token = match headers.get("CSRFPreventionToken").map(|v| v.to_str()) {
-                        Some(Ok(v)) => Some(v.to_owned()),
-                        _ => None,
-                    };
-                    return Some(AuthData::User(UserAuthData { ticket, csrf_token }));
-                }
+fn extract_auth_data(headers: &http::HeaderMap) -> Option<AuthData> {
+    if let Some(raw_cookie) = headers.get(header::COOKIE) {
+        if let Ok(cookie) = raw_cookie.to_str() {
+            if let Some(ticket) = extract_cookie(cookie, "PBSAuthCookie") {
+                let csrf_token = match headers.get("CSRFPreventionToken").map(|v| v.to_str()) {
+                    Some(Ok(v)) => Some(v.to_owned()),
+                    _ => None,
+                };
+                return Some(AuthData::User(UserAuthData { ticket, csrf_token }));
             }
         }
+    }
 
-        match headers.get(header::AUTHORIZATION).map(|v| v.to_str()) {
-            Some(Ok(v)) => {
-                if v.starts_with("PBSAPIToken ") || v.starts_with("PBSAPIToken=") {
-                    Some(AuthData::ApiToken(v["PBSAPIToken ".len()..].to_owned()))
-                } else {
-                    None
-                }
+    match headers.get(header::AUTHORIZATION).map(|v| v.to_str()) {
+        Some(Ok(v)) => {
+            if v.starts_with("PBSAPIToken ") || v.starts_with("PBSAPIToken=") {
+                Some(AuthData::ApiToken(v["PBSAPIToken ".len()..].to_owned()))
+            } else {
+                None
             }
-            _ => None,
         }
+        _ => None,
     }
+}
 
-    async fn check_auth_async(
-        &self,
-        headers: &http::HeaderMap,
-        method: &hyper::Method,
-    ) -> Result<(String, Box<dyn UserInformation + Sync + Send>), AuthError> {
+pub async fn check_pbs_auth(
+    headers: &http::HeaderMap,
+    method: &hyper::Method,
+) -> Result<(String, Box<dyn UserInformation + Sync + Send>), AuthError> {
 
-        // fixme: make all IO async
+    // fixme: make all IO async
 
-        let user_info = CachedUserInfo::new()?;
+    let user_info = CachedUserInfo::new()?;
 
-        let auth_data = Self::extract_auth_data(headers);
-        match auth_data {
-            Some(AuthData::User(user_auth_data)) => {
-                let ticket = user_auth_data.ticket.clone();
-                let ticket_lifetime = ticket::TICKET_LIFETIME;
+    let auth_data = extract_auth_data(headers);
+    match auth_data {
+        Some(AuthData::User(user_auth_data)) => {
+            let ticket = user_auth_data.ticket.clone();
+            let ticket_lifetime = ticket::TICKET_LIFETIME;
 
-                let userid: Userid = Ticket::<super::ticket::ApiTicket>::parse(&ticket)?
-                    .verify_with_time_frame(public_auth_key(), "PBS", None, -300..ticket_lifetime)?
-                    .require_full()?;
+            let userid: Userid = Ticket::<super::ticket::ApiTicket>::parse(&ticket)?
+                .verify_with_time_frame(public_auth_key(), "PBS", None, -300..ticket_lifetime)?
+                .require_full()?;
 
-                let auth_id = Authid::from(userid.clone());
-                if !user_info.is_active_auth_id(&auth_id) {
-                    return Err(format_err!("user account disabled or expired.").into());
-                }
+            let auth_id = Authid::from(userid.clone());
+            if !user_info.is_active_auth_id(&auth_id) {
+                return Err(format_err!("user account disabled or expired.").into());
+            }
 
-                if method != hyper::Method::GET {
-                    if let Some(csrf_token) = &user_auth_data.csrf_token {
-                        verify_csrf_prevention_token(
-                            csrf_secret(),
-                            &userid,
-                            &csrf_token,
-                            -300,
-                            ticket_lifetime,
-                        )?;
-                    } else {
-                        return Err(format_err!("missing CSRF prevention token").into());
-                    }
+            if method != hyper::Method::GET {
+                if let Some(csrf_token) = &user_auth_data.csrf_token {
+                    verify_csrf_prevention_token(
+                        csrf_secret(),
+                        &userid,
+                        &csrf_token,
+                        -300,
+                        ticket_lifetime,
+                    )?;
+                } else {
+                    return Err(format_err!("missing CSRF prevention token").into());
                 }
+            }
 
-                Ok((auth_id.to_string(), Box::new(user_info)))
+            Ok((auth_id.to_string(), Box::new(user_info)))
+        }
+        Some(AuthData::ApiToken(api_token)) => {
+            let mut parts = api_token.splitn(2, ':');
+            let tokenid = parts
+                .next()
+                .ok_or_else(|| format_err!("failed to split API token header"))?;
+            let tokenid: Authid = tokenid.parse()?;
+
+            if !user_info.is_active_auth_id(&tokenid) {
+                return Err(format_err!("user account or token disabled or expired.").into());
             }
-            Some(AuthData::ApiToken(api_token)) => {
-                let mut parts = api_token.splitn(2, ':');
-                let tokenid = parts
-                    .next()
-                    .ok_or_else(|| format_err!("failed to split API token header"))?;
-                let tokenid: Authid = tokenid.parse()?;
-
-                if !user_info.is_active_auth_id(&tokenid) {
-                    return Err(format_err!("user account or token disabled or expired.").into());
-                }
 
-                let tokensecret = parts
-                    .next()
-                    .ok_or_else(|| format_err!("failed to split API token header"))?;
-                let tokensecret = percent_decode_str(tokensecret)
-                    .decode_utf8()
-                    .map_err(|_| format_err!("failed to decode API token header"))?;
+            let tokensecret = parts
+                .next()
+                .ok_or_else(|| format_err!("failed to split API token header"))?;
+            let tokensecret = percent_decode_str(tokensecret)
+                .decode_utf8()
+                .map_err(|_| format_err!("failed to decode API token header"))?;
 
-                token_shadow::verify_secret(&tokenid, &tokensecret)?;
+            token_shadow::verify_secret(&tokenid, &tokensecret)?;
 
-                Ok((tokenid.to_string(), Box::new(user_info)))
-            }
-            None => Err(AuthError::NoData),
+            Ok((tokenid.to_string(), Box::new(user_info)))
         }
-    }
-}
-
-impl ApiAuth for UserApiAuth {
-    fn check_auth<'a>(
-        &'a self,
-        headers: &'a http::HeaderMap,
-        method: &'a hyper::Method,
-    ) -> Pin<Box<dyn Future<Output = Result<(String, Box<dyn UserInformation + Sync + Send>), AuthError>> + Send + 'a>> {
-        Box::pin(self.check_auth_async(headers, method))
+        None => Err(AuthError::NoData),
     }
 }