about summary refs log tree commit diff
diff options
context:
space:
mode:
authorVika <vika@fireburn.ru>2022-09-19 19:01:06 +0300
committerVika <vika@fireburn.ru>2022-09-19 19:01:06 +0300
commit53c868691a09b84d71f724d23a09d1fb89368792 (patch)
treee5593882f3c9d4141a2cb796d48dc98b84411d20
parentde105ec7a56752c152e3020fa53a0e13206f4cb4 (diff)
downloadkittybox-53c868691a09b84d71f724d23a09d1fb89368792.tar.zst
Make webauthn and openssl optional
-rw-r--r--kittybox-rs/Cargo.toml5
-rw-r--r--kittybox-rs/src/indieauth/backend.rs12
-rw-r--r--kittybox-rs/src/indieauth/backend/fs.rs10
-rw-r--r--kittybox-rs/src/indieauth/mod.rs19
-rw-r--r--kittybox.nix12
5 files changed, 48 insertions, 10 deletions
diff --git a/kittybox-rs/Cargo.toml b/kittybox-rs/Cargo.toml
index c9b98a2..6b0057f 100644
--- a/kittybox-rs/Cargo.toml
+++ b/kittybox-rs/Cargo.toml
@@ -7,9 +7,10 @@ default-run = "kittybox"
 autobins = false
 
 [features]
-default = ["openssl"]
+default = ["rustls"]
 #util = ["anyhow"]
 #migration = ["util"]
+webauthn = ["openssl", "dep:webauthn"]
 openssl = ["reqwest/native-tls-vendored", "reqwest/native-tls-alpn"]
 rustls = ["reqwest/rustls-tls-webpki-roots"]
 cli = ["clap"]
@@ -85,7 +86,7 @@ tracing-tree = "0.2.1"
 tracing-subscriber = { version = "0.3.11", features = ["env-filter", "json"] }
 tower-http = { version = "0.3.3", features = ["trace", "cors", "catch-panic"] }
 tower = { version = "0.4.12", features = ["tracing"] }
-webauthn = { version = "0.4.5", package = "webauthn-rs", features = ["danger-allow-state-serialisation"] }
+webauthn = { version = "0.4.5", package = "webauthn-rs", features = ["danger-allow-state-serialisation"], optional = true }
 [dependencies.tokio]
 version = "^1.16.1"
 features = ["full", "tracing"] # TODO determine if my app doesn't need some features
diff --git a/kittybox-rs/src/indieauth/backend.rs b/kittybox-rs/src/indieauth/backend.rs
index 8b0c10a..534bcfb 100644
--- a/kittybox-rs/src/indieauth/backend.rs
+++ b/kittybox-rs/src/indieauth/backend.rs
@@ -9,7 +9,6 @@ type Result<T> = std::io::Result<T>;
 pub mod fs;
 pub use fs::FileBackend;
 
-
 #[async_trait::async_trait]
 pub trait AuthBackend: Clone + Send + Sync + 'static {
     // Authorization code management.
@@ -44,7 +43,10 @@ pub trait AuthBackend: Clone + Send + Sync + 'static {
     /// Enroll a password credential for a user. Only one password
     /// credential must exist for a given user.
     async fn enroll_password(&self, website: &url::Url, password: String) -> Result<()>;
+    /// List currently enrolled credential types for a given user.
+    async fn list_user_credential_types(&self, website: &url::Url) -> Result<Vec<EnrolledCredential>>;
     // WebAuthn credential management.
+    #[cfg(feature = "webauthn")]
     /// Enroll a WebAuthn authenticator public key for this user.
     /// Multiple public keys may be saved for one user, corresponding
     /// to different authenticators used by them.
@@ -53,8 +55,10 @@ pub trait AuthBackend: Clone + Send + Sync + 'static {
     /// updated version after using
     /// [webauthn::prelude::Passkey::update_credential()].
     async fn enroll_webauthn(&self, website: &url::Url, credential: webauthn::prelude::Passkey) -> Result<()>;
+    #[cfg(feature = "webauthn")]
     /// List currently enrolled WebAuthn authenticators for a given user.
     async fn list_webauthn_pubkeys(&self, website: &url::Url) -> Result<Vec<webauthn::prelude::Passkey>>;
+    #[cfg(feature = "webauthn")]
     /// Persist registration challenge state for a little while so it
     /// can be used later.
     ///
@@ -65,6 +69,7 @@ pub trait AuthBackend: Clone + Send + Sync + 'static {
         website: &url::Url,
         state: webauthn::prelude::PasskeyRegistration
     ) -> Result<String>;
+    #[cfg(feature = "webauthn")]
     /// Retrieve a persisted registration challenge.
     ///
     /// The challenge should be deleted after retrieval.
@@ -73,6 +78,7 @@ pub trait AuthBackend: Clone + Send + Sync + 'static {
         website: &url::Url,
         challenge_id: &str
     ) -> Result<webauthn::prelude::PasskeyRegistration>;
+    #[cfg(feature = "webauthn")]
     /// Persist authentication challenge state for a little while so
     /// it can be used later.
     ///
@@ -86,6 +92,7 @@ pub trait AuthBackend: Clone + Send + Sync + 'static {
         website: &url::Url,
         state: webauthn::prelude::PasskeyAuthentication
     ) -> Result<String>;
+    #[cfg(feature = "webauthn")]
     /// Retrieve a persisted authentication challenge.
     ///
     /// The challenge should be deleted after retrieval.
@@ -94,6 +101,5 @@ pub trait AuthBackend: Clone + Send + Sync + 'static {
         website: &url::Url,
         challenge_id: &str
     ) -> Result<webauthn::prelude::PasskeyAuthentication>;
-    /// List currently enrolled credential types for a given user.
-    async fn list_user_credential_types(&self, website: &url::Url) -> Result<Vec<EnrolledCredential>>;
+
 }
diff --git a/kittybox-rs/src/indieauth/backend/fs.rs b/kittybox-rs/src/indieauth/backend/fs.rs
index fbfa0f7..57bc3bd 100644
--- a/kittybox-rs/src/indieauth/backend/fs.rs
+++ b/kittybox-rs/src/indieauth/backend/fs.rs
@@ -7,6 +7,7 @@ use kittybox_indieauth::{
 };
 use serde::de::DeserializeOwned;
 use tokio::{task::spawn_blocking, io::AsyncReadExt};
+#[cfg(feature = "webauthn")]
 use webauthn::prelude::{Passkey, PasskeyRegistration, PasskeyAuthentication};
 
 const CODE_LENGTH: usize = 16;
@@ -343,15 +344,18 @@ impl AuthBackend for FileBackend {
     }
 
     // WebAuthn credential management.
+    #[cfg(feature = "webauthn")]
     async fn enroll_webauthn(&self, website: &url::Url, credential: Passkey) -> Result<()> {
         todo!()
     }
 
+    #[cfg(feature = "webauthn")]
     async fn list_webauthn_pubkeys(&self, website: &url::Url) -> Result<Vec<Passkey>> {
         // TODO stub!
         Ok(vec![])
     }
 
+    #[cfg(feature = "webauthn")]
     async fn persist_registration_challenge(
         &self,
         website: &url::Url,
@@ -360,6 +364,7 @@ impl AuthBackend for FileBackend {
         todo!()
     }
 
+    #[cfg(feature = "webauthn")]
     async fn retrieve_registration_challenge(
         &self,
         website: &url::Url,
@@ -368,6 +373,7 @@ impl AuthBackend for FileBackend {
         todo!()
     }
 
+    #[cfg(feature = "webauthn")]
     async fn persist_authentication_challenge(
         &self,
         website: &url::Url,
@@ -376,6 +382,7 @@ impl AuthBackend for FileBackend {
         todo!()
     }
 
+    #[cfg(feature = "webauthn")]
     async fn retrieve_authentication_challenge(
         &self,
         website: &url::Url,
@@ -392,12 +399,13 @@ impl AuthBackend for FileBackend {
                                   .join("password"))
             .await
         {
-            Ok(metadata) => creds.push(EnrolledCredential::Password),
+            Ok(_) => creds.push(EnrolledCredential::Password),
             Err(err) => if err.kind() != std::io::ErrorKind::NotFound {
                 return Err(err)
             }
         }
 
+        #[cfg(feature = "webauthn")]
         if !self.list_webauthn_pubkeys(website).await?.is_empty() {
             creds.push(EnrolledCredential::WebAuthn);
         }
diff --git a/kittybox-rs/src/indieauth/mod.rs b/kittybox-rs/src/indieauth/mod.rs
index adf669e..67f4a43 100644
--- a/kittybox-rs/src/indieauth/mod.rs
+++ b/kittybox-rs/src/indieauth/mod.rs
@@ -17,6 +17,7 @@ use kittybox_indieauth::{
 };
 
 pub mod backend;
+#[cfg(feature = "webauthn")]
 mod webauthn;
 use backend::AuthBackend;
 
@@ -111,6 +112,7 @@ async fn authorization_endpoint_get<A: AuthBackend, D: Storage + 'static>(
 #[serde(untagged)]
 enum Credential {
     Password(String),
+    #[cfg(feature = "webauthn")]
     WebAuthn(::webauthn::prelude::PublicKeyCredential)
 }
 
@@ -128,6 +130,7 @@ async fn verify_credential<A: AuthBackend>(
 ) -> std::io::Result<bool> {
     match credential {
         Credential::Password(password) => auth.verify_password(website, password).await,
+        #[cfg(feature = "webauthn")]
         Credential::WebAuthn(credential) => webauthn::verify(
             auth,
             website,
@@ -145,8 +148,12 @@ async fn authorization_endpoint_confirm<A: AuthBackend>(
     cookies: CookieJar,
 ) -> Response {
     tracing::debug!("Received authorization confirmation from user");
+    #[cfg(feature = "webauthn")]
     let challenge_id = cookies.get(webauthn::CHALLENGE_ID_COOKIE)
         .map(|cookie| cookie.value());
+    #[cfg(not(feature = "webauthn"))]
+    let challenge_id = None;
+
     let website = format!("https://{}/", host).parse().unwrap();
     let AuthorizationConfirmation {
         authorization_method: credential,
@@ -195,6 +202,7 @@ async fn authorization_endpoint_confirm<A: AuthBackend>(
     // opaque response instead that is completely useless
     (StatusCode::NO_CONTENT,
      [("Location", location.as_str())],
+     #[cfg(feature = "webauthn")]
      cookies.remove(Cookie::named(webauthn::CHALLENGE_ID_COOKIE))
     )
         .into_response()
@@ -309,7 +317,7 @@ async fn token_endpoint_post<A: AuthBackend, D: Storage + 'static>(
                 .unwrap()
                 .as_secs()
                 .into()
-        }    
+        }
     }
 
     #[inline]
@@ -521,7 +529,7 @@ async fn token_endpoint_post<A: AuthBackend, D: Storage + 'static>(
                 tracing::error!("Error revoking refresh token: {}", err);
                 return StatusCode::INTERNAL_SERVER_ERROR.into_response();
             }
-            
+
             GrantResponse::AccessToken {
                 me: data.me,
                 profile,
@@ -695,8 +703,13 @@ pub fn router<A: AuthBackend, D: Storage + 'static>(backend: A, db: D) -> axum::
                 .route(
                     "/userinfo",
                     get(userinfo_endpoint_get::<A, D>))
+
                 .route("/webauthn/pre_register",
-                       get(webauthn::webauthn_pre_register::<A, D>))
+                       get(
+                           #[cfg(feature = "webauthn")] webauthn::webauthn_pre_register::<A, D>,
+                           #[cfg(not(feature = "webauthn"))] || async { axum::http::StatusCode::NOT_FOUND }
+                       )
+                )
                 .layer(tower_http::cors::CorsLayer::new()
                        .allow_methods([
                            axum::http::Method::GET,
diff --git a/kittybox.nix b/kittybox.nix
index e61843b..397e057 100644
--- a/kittybox.nix
+++ b/kittybox.nix
@@ -1,4 +1,9 @@
-{ stdenv, lib, openssl, zlib, pkg-config, protobuf, naersk, lld, mold }:
+{ stdenv, lib, naersk, lld, mold
+, openssl, zlib, pkg-config, protobuf
+, useWebAuthn ? false }:
+
+assert useWebAuthn -> openssl != null && pkg-config != null;
+
 naersk.buildPackage {
   pname = "kittybox";
   version = "0.1.0";
@@ -6,6 +11,11 @@ naersk.buildPackage {
   src = ./kittybox-rs;
 
   doCheck = stdenv.hostPlatform == stdenv.targetPlatform;
+  cargoOptions = x: x ++ (lib.optionals useWebAuthn [
+    "--no-default-features" "--features=\"webauthn\""
+  ]);
+  buildInputs = lib.optional useWebAuthn openssl;
+  nativeBuildInputs = lib.optional useWebAuthn pkg-config;
 
   meta = with lib.meta; {
     maintainers = with lib.maintainers; [ vikanezrimaya ];