This commit is contained in:
Stefan Melmuk 2024-04-27 07:04:33 +08:00 committed by GitHub
commit ef9b91f34f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 263 additions and 189 deletions

193
Cargo.lock generated
View File

@ -86,6 +86,45 @@ dependencies = [
"password-hash",
]
[[package]]
name = "asn1-rs"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "30ff05a702273012438132f449575dbc804e27b2f3cbe3069aa237d26c98fa33"
dependencies = [
"asn1-rs-derive",
"asn1-rs-impl",
"displaydoc",
"nom",
"num-traits",
"rusticata-macros",
"thiserror",
"time",
]
[[package]]
name = "asn1-rs-derive"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "db8b7511298d5b7784b40b092d9e9dcd3a627a5707e4b5e507931ab0d44eeebf"
dependencies = [
"proc-macro2",
"quote",
"syn 1.0.109",
"synstructure",
]
[[package]]
name = "asn1-rs-impl"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2777730b2039ac0f95f093556e61b6d26cebed5393ca6f152717777cec3a42ed"
dependencies = [
"proc-macro2",
"quote",
"syn 1.0.109",
]
[[package]]
name = "async-channel"
version = "1.9.0"
@ -378,6 +417,17 @@ version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b"
[[package]]
name = "base64urlsafedata"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "18b3d30abb74120a9d5267463b9e0045fdccc4dd152e7249d966612dc1721384"
dependencies = [
"base64 0.21.7",
"serde",
"serde_json",
]
[[package]]
name = "bigdecimal"
version = "0.4.3"
@ -581,6 +631,23 @@ dependencies = [
"stacker",
]
[[package]]
name = "compact_jwt"
version = "0.2.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7aa76ef19968577838a34d02848136bb9b6bdbfd7675fb968fe9c931bc434b33"
dependencies = [
"base64 0.13.1",
"base64urlsafedata",
"hex",
"openssl",
"serde",
"serde_json",
"tracing",
"url",
"uuid",
]
[[package]]
name = "concurrent-queue"
version = "2.5.0"
@ -767,6 +834,20 @@ version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c297a1c74b71ae29df00c3e22dd9534821d60eb9af5a0192823fa2acea70c2a"
[[package]]
name = "der-parser"
version = "7.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fe398ac75057914d7d07307bf67dc7f3f574a26783b4fc7805a20ffa9f506e82"
dependencies = [
"asn1-rs",
"displaydoc",
"nom",
"num-bigint",
"num-traits",
"rusticata-macros",
]
[[package]]
name = "deranged"
version = "0.3.11"
@ -886,6 +967,17 @@ dependencies = [
"subtle",
]
[[package]]
name = "displaydoc"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "487585f4d0c6655fe74905e2504d8ad6908e4db67f744eb140876906c2f3175d"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.58",
]
[[package]]
name = "dotenvy"
version = "0.15.7"
@ -1387,6 +1479,12 @@ version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024"
[[package]]
name = "hex"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
[[package]]
name = "hickory-proto"
version = "0.24.1"
@ -2191,6 +2289,15 @@ dependencies = [
"memchr",
]
[[package]]
name = "oid-registry"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38e20717fa0541f39bd146692035c37bedfa532b3e5071b35761082407546b2a"
dependencies = [
"asn1-rs",
]
[[package]]
name = "once_cell"
version = "1.19.0"
@ -3021,6 +3128,15 @@ version = "0.1.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76"
[[package]]
name = "rusticata-macros"
version = "4.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "faf0c4a6ece9950b9abdb62b1cfcf2a68b3b67a10ba445b3bb85be2a293d0632"
dependencies = [
"nom",
]
[[package]]
name = "rustix"
version = "0.37.27"
@ -3195,10 +3311,10 @@ dependencies = [
]
[[package]]
name = "serde_cbor"
version = "0.11.2"
name = "serde_cbor_2"
version = "0.12.0-dev"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2bef2ebfde456fb76bbcf9f59315333decc4fda0b2b44b420243c11e0f5ec1f5"
checksum = "b46d75f449e01f1eddbe9b00f432d616fbbd899b809c837d0fbc380496a0dd55"
dependencies = [
"half",
"serde",
@ -3425,6 +3541,18 @@ version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160"
[[package]]
name = "synstructure"
version = "0.12.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f"
dependencies = [
"proc-macro2",
"quote",
"syn 1.0.109",
"unicode-xid",
]
[[package]]
name = "syslog"
version = "6.1.1"
@ -3932,6 +4060,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a183cf7feeba97b4dd1c0d46788634f6221d87fa961b305bed08c851829efcc0"
dependencies = [
"getrandom",
"serde",
]
[[package]]
@ -4003,6 +4132,7 @@ dependencies = [
"url",
"uuid",
"webauthn-rs",
"webauthn-rs-core",
"which",
"yubico",
]
@ -4141,21 +4271,52 @@ dependencies = [
[[package]]
name = "webauthn-rs"
version = "0.3.2"
version = "0.4.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "90b266eccb4b32595876f5c73ea443b0516da0b1df72ca07bc08ed9ba7f96ec1"
checksum = "2db00711c712414e93b019c4596315085792215bc2ac2d5872f9e8913b0a6316"
dependencies = [
"base64urlsafedata",
"serde",
"tracing",
"url",
"uuid",
"webauthn-rs-core",
]
[[package]]
name = "webauthn-rs-core"
version = "0.4.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "294c78c83f12153a51e1cf1e6970b5da1397645dada39033a9c3173a8fc4fc2b"
dependencies = [
"base64 0.13.1",
"base64urlsafedata",
"compact_jwt",
"der-parser",
"nom",
"openssl",
"rand",
"serde",
"serde_cbor",
"serde_derive",
"serde_cbor_2",
"serde_json",
"thiserror",
"tracing",
"url",
"uuid",
"webauthn-rs-proto",
"x509-parser",
]
[[package]]
name = "webauthn-rs-proto"
version = "0.4.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d24e638361a63ba5c0a0be6a60229490fcdf33740ed63df5bb6bdb627b52a138"
dependencies = [
"base64urlsafedata",
"serde",
"serde_json",
"url",
]
[[package]]
@ -4418,6 +4579,24 @@ version = "0.0.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d135d17ab770252ad95e9a872d365cf3090e3be864a34ab46f48555993efc904"
[[package]]
name = "x509-parser"
version = "0.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9fb9bace5b5589ffead1afb76e43e34cff39cd0f3ce7e170ae0c29e53b88eb1c"
dependencies = [
"asn1-rs",
"base64 0.13.1",
"data-encoding",
"der-parser",
"lazy_static",
"nom",
"oid-registry",
"rusticata-macros",
"thiserror",
"time",
]
[[package]]
name = "yansi"
version = "1.0.1"

View File

@ -109,7 +109,8 @@ totp-lite = "2.0.1"
yubico = { version = "0.11.0", features = ["online-tokio"], default-features = false }
# WebAuthn libraries
webauthn-rs = "0.3.2"
webauthn-rs = { version = "0.4.8", features = ["danger-allow-state-serialisation", "danger-credential-internals", "resident-key-support"] }
webauthn-rs-core = { version = "0.4.9" }
# Handling of URL's for WebAuthn and favicons
url = "2.5.0"

View File

@ -1,8 +1,7 @@
use rocket::serde::json::Json;
use rocket::Route;
use serde_json::Value;
use url::Url;
use webauthn_rs::{base64_data::Base64UrlSafeData, proto::*, AuthenticationState, RegistrationState, Webauthn};
use webauthn_rs::prelude::*;
use crate::{
api::{
@ -16,7 +15,7 @@ use crate::{
},
error::Error,
util::NumberOrString,
CONFIG,
CONFIG, WEBAUTHN,
};
pub fn routes() -> Vec<Route> {
@ -45,64 +44,6 @@ pub struct U2FRegistration {
pub migrated: Option<bool>,
}
struct WebauthnConfig {
url: String,
origin: Url,
rpid: String,
}
impl WebauthnConfig {
fn load() -> Webauthn<Self> {
let domain = CONFIG.domain();
let domain_origin = CONFIG.domain_origin();
Webauthn::new(Self {
rpid: Url::parse(&domain).map(|u| u.domain().map(str::to_owned)).ok().flatten().unwrap_or_default(),
url: domain,
origin: Url::parse(&domain_origin).unwrap(),
})
}
}
impl webauthn_rs::WebauthnConfig for WebauthnConfig {
fn get_relying_party_name(&self) -> &str {
&self.url
}
fn get_origin(&self) -> &Url {
&self.origin
}
fn get_relying_party_id(&self) -> &str {
&self.rpid
}
/// We have WebAuthn configured to discourage user verification
/// if we leave this enabled, it will cause verification issues when a keys send UV=1.
/// Upstream (the library they use) ignores this when set to discouraged, so we should too.
fn get_require_uv_consistency(&self) -> bool {
false
}
}
#[derive(Debug, Serialize, Deserialize)]
pub struct WebauthnRegistration {
pub id: i32,
pub name: String,
pub migrated: bool,
pub credential: Credential,
}
impl WebauthnRegistration {
fn to_json(&self) -> Value {
json!({
"Id": self.id,
"Name": self.name,
"migrated": self.migrated,
})
}
}
#[post("/two-factor/get-webauthn", data = "<data>")]
async fn get_webauthn(data: JsonUpcase<PasswordOrOtpData>, headers: Headers, mut conn: DbConn) -> JsonResult {
if !CONFIG.domain_set() {
@ -135,21 +76,16 @@ async fn generate_webauthn_challenge(
data.validate(&user, false, &mut conn).await?;
let registrations = get_webauthn_registrations(&user.uuid, &mut conn)
let registrations: Vec<Base64UrlSafeData> = get_webauthn_registrations(&user.uuid, &mut conn)
.await?
.1
.into_iter()
.map(|r| r.credential.cred_id) // We return the credentialIds to the clients to avoid double registering
.map(|r| r.security_key.cred_id().clone()) // We return the credentialIds to the clients to avoid double registering
.collect();
let (challenge, state) = WebauthnConfig::load().generate_challenge_register_options(
user.uuid.as_bytes().to_vec(),
user.email,
user.name,
Some(registrations),
None,
None,
)?;
let user_uuid = Uuid::parse_str(&user.uuid)?;
let (challenge, state) =
WEBAUTHN.start_securitykey_registration(user_uuid, &user.email, &user.name, Some(registrations), None, None)?;
let type_ = TwoFactorType::WebauthnRegisterChallenge;
TwoFactor::new(user.uuid, type_, serde_json::to_string(&state)?).save(&mut conn).await?;
@ -165,90 +101,11 @@ async fn generate_webauthn_challenge(
struct EnableWebauthnData {
Id: NumberOrString, // 1..5
Name: String,
DeviceResponse: RegisterPublicKeyCredentialCopy,
DeviceResponse: RegisterPublicKeyCredential,
MasterPasswordHash: Option<String>,
Otp: Option<String>,
}
// This is copied from RegisterPublicKeyCredential to change the Response objects casing
#[derive(Debug, Deserialize)]
#[allow(non_snake_case)]
struct RegisterPublicKeyCredentialCopy {
pub Id: String,
pub RawId: Base64UrlSafeData,
pub Response: AuthenticatorAttestationResponseRawCopy,
pub Type: String,
}
// This is copied from AuthenticatorAttestationResponseRaw to change clientDataJSON to clientDataJson
#[derive(Debug, Deserialize)]
#[allow(non_snake_case)]
pub struct AuthenticatorAttestationResponseRawCopy {
pub AttestationObject: Base64UrlSafeData,
pub ClientDataJson: Base64UrlSafeData,
}
impl From<RegisterPublicKeyCredentialCopy> for RegisterPublicKeyCredential {
fn from(r: RegisterPublicKeyCredentialCopy) -> Self {
Self {
id: r.Id,
raw_id: r.RawId,
response: AuthenticatorAttestationResponseRaw {
attestation_object: r.Response.AttestationObject,
client_data_json: r.Response.ClientDataJson,
},
type_: r.Type,
}
}
}
// This is copied from PublicKeyCredential to change the Response objects casing
#[derive(Debug, Deserialize)]
#[allow(non_snake_case)]
pub struct PublicKeyCredentialCopy {
pub Id: String,
pub RawId: Base64UrlSafeData,
pub Response: AuthenticatorAssertionResponseRawCopy,
pub Extensions: Option<AuthenticationExtensionsClientOutputsCopy>,
pub Type: String,
}
// This is copied from AuthenticatorAssertionResponseRaw to change clientDataJSON to clientDataJson
#[derive(Debug, Deserialize)]
#[allow(non_snake_case)]
pub struct AuthenticatorAssertionResponseRawCopy {
pub AuthenticatorData: Base64UrlSafeData,
pub ClientDataJson: Base64UrlSafeData,
pub Signature: Base64UrlSafeData,
pub UserHandle: Option<Base64UrlSafeData>,
}
#[derive(Debug, Deserialize)]
#[allow(non_snake_case)]
pub struct AuthenticationExtensionsClientOutputsCopy {
#[serde(default)]
pub Appid: bool,
}
impl From<PublicKeyCredentialCopy> for PublicKeyCredential {
fn from(r: PublicKeyCredentialCopy) -> Self {
Self {
id: r.Id,
raw_id: r.RawId,
response: AuthenticatorAssertionResponseRaw {
authenticator_data: r.Response.AuthenticatorData,
client_data_json: r.Response.ClientDataJson,
signature: r.Response.Signature,
user_handle: r.Response.UserHandle,
},
extensions: r.Extensions.map(|e| AuthenticationExtensionsClientOutputs {
appid: e.Appid,
}),
type_: r.Type,
}
}
}
#[post("/two-factor/webauthn", data = "<data>")]
async fn activate_webauthn(data: JsonUpcase<EnableWebauthnData>, headers: Headers, mut conn: DbConn) -> JsonResult {
let data: EnableWebauthnData = data.into_inner().data;
@ -265,7 +122,7 @@ async fn activate_webauthn(data: JsonUpcase<EnableWebauthnData>, headers: Header
let type_ = TwoFactorType::WebauthnRegisterChallenge as i32;
let state = match TwoFactor::find_by_user_and_type(&user.uuid, type_, &mut conn).await {
Some(tf) => {
let state: RegistrationState = serde_json::from_str(&tf.data)?;
let state: SecurityKeyRegistration = serde_json::from_str(&tf.data)?;
tf.delete(&mut conn).await?;
state
}
@ -273,8 +130,7 @@ async fn activate_webauthn(data: JsonUpcase<EnableWebauthnData>, headers: Header
};
// Verify the credentials with the saved state
let (credential, _data) =
WebauthnConfig::load().register_credential(&data.DeviceResponse.into(), &state, |_| Ok(false))?;
let security_key = WEBAUTHN.finish_securitykey_registration(&data.DeviceResponse, &state)?;
let mut registrations: Vec<_> = get_webauthn_registrations(&user.uuid, &mut conn).await?.1;
// TODO: Check for repeated ID's
@ -283,7 +139,7 @@ async fn activate_webauthn(data: JsonUpcase<EnableWebauthnData>, headers: Header
name: data.Name,
migrated: false,
credential,
security_key,
});
// Save the registrations and return them
@ -348,7 +204,7 @@ async fn delete_webauthn(data: JsonUpcase<DeleteU2FData>, headers: Headers, mut
Err(_) => err!("Error parsing U2F data"),
};
data.retain(|r| r.reg.key_handle != removed_item.credential.cred_id);
data.retain(|r| r.reg.key_handle != removed_item.security_key.cred_id().0);
let new_data_str = serde_json::to_string(&data)?;
u2f.data = new_data_str;
@ -364,6 +220,25 @@ async fn delete_webauthn(data: JsonUpcase<DeleteU2FData>, headers: Headers, mut
})))
}
#[derive(Debug, Serialize, Deserialize)]
pub struct WebauthnRegistration {
pub id: i32,
pub name: String,
pub migrated: bool,
pub security_key: SecurityKey,
}
impl WebauthnRegistration {
fn to_json(&self) -> Value {
json!({
"Id": self.id,
"Name": self.name,
"migrated": self.migrated,
})
}
}
pub async fn get_webauthn_registrations(
user_uuid: &str,
conn: &mut DbConn,
@ -377,16 +252,16 @@ pub async fn get_webauthn_registrations(
pub async fn generate_webauthn_login(user_uuid: &str, conn: &mut DbConn) -> JsonResult {
// Load saved credentials
let creds: Vec<Credential> =
get_webauthn_registrations(user_uuid, conn).await?.1.into_iter().map(|r| r.credential).collect();
let creds: Vec<SecurityKey> =
get_webauthn_registrations(user_uuid, conn).await?.1.into_iter().map(|r| r.security_key).collect();
if creds.is_empty() {
err!("No Webauthn devices registered")
}
// Generate a challenge based on the credentials
let ext = RequestAuthenticationExtensions::builder().appid(format!("{}/app-id.json", &CONFIG.domain())).build();
let (response, state) = WebauthnConfig::load().generate_challenge_authenticate_options(creds, Some(ext))?;
//let ext = RequestAuthenticationExtensions::builder().appid(format!("{}/app-id.json", &CONFIG.domain())).build();
let (response, state) = WEBAUTHN.start_securitykey_authentication(&creds)?; //, Some(ext))?;
// Save the challenge state for later validation
TwoFactor::new(user_uuid.into(), TwoFactorType::WebauthnLoginChallenge, serde_json::to_string(&state)?)
@ -401,7 +276,7 @@ pub async fn validate_webauthn_login(user_uuid: &str, response: &str, conn: &mut
let type_ = TwoFactorType::WebauthnLoginChallenge as i32;
let state = match TwoFactor::find_by_user_and_type(user_uuid, type_, conn).await {
Some(tf) => {
let state: AuthenticationState = serde_json::from_str(&tf.data)?;
let state: SecurityKeyAuthentication = serde_json::from_str(&tf.data)?;
tf.delete(conn).await?;
state
}
@ -413,19 +288,17 @@ pub async fn validate_webauthn_login(user_uuid: &str, response: &str, conn: &mut
),
};
let rsp: crate::util::UpCase<PublicKeyCredentialCopy> = serde_json::from_str(response)?;
let rsp: PublicKeyCredential = rsp.data.into();
let rsp: PublicKeyCredential = serde_json::from_str(response)?;
// let rsp: PublicKeyCredential = rsp.data.into();
let mut registrations = get_webauthn_registrations(user_uuid, conn).await?.1;
// If the credential we received is migrated from U2F, enable the U2F compatibility
//let use_u2f = registrations.iter().any(|r| r.migrated && r.credential.cred_id == rsp.raw_id.0);
let (cred_id, auth_data) = WebauthnConfig::load().authenticate_credential(&rsp, &state)?;
let auth_result = WEBAUTHN.finish_securitykey_authentication(&rsp, &state)?;
for reg in &mut registrations {
if &reg.credential.cred_id == cred_id {
reg.credential.counter = auth_data.counter;
if reg.security_key.cred_id() == auth_result.cred_id()
&& reg.security_key.update_credential(&auth_result).is_some()
{
TwoFactor::new(user_uuid.to_string(), TwoFactorType::Webauthn, serde_json::to_string(&registrations)?)
.save(conn)
.await?;

View File

@ -5,6 +5,7 @@ use std::sync::RwLock;
use job_scheduler_ng::Schedule;
use once_cell::sync::Lazy;
use reqwest::Url;
use webauthn_rs::{Webauthn, WebauthnBuilder};
use crate::{
db::DbConnType,
@ -24,6 +25,23 @@ pub static CONFIG: Lazy<Config> = Lazy::new(|| {
})
});
pub static WEBAUTHN: Lazy<Webauthn> = Lazy::new(|| {
let domain = CONFIG.domain();
let domain_origin = CONFIG.domain_origin();
let android_app_url = Url::parse("android:apk-key-hash:dUGFzUzf3lmHSLBDBIv+WaFyZMI").unwrap();
let ios_app_url = Url::parse("ios:bundle-id:com.8bit.bitwarden").unwrap();
let rp_id = Url::parse(&domain).map(|u| u.domain().map(str::to_owned)).ok().flatten().unwrap_or_default();
let rp_name = domain;
let rp_origin = Url::parse(&domain_origin).unwrap();
let builder = WebauthnBuilder::new(&rp_id, &rp_origin)
.expect("Invalid configuration")
.rp_name(&rp_name)
.append_allowed_origin(&ios_app_url)
.append_allowed_origin(&android_app_url);
builder.build().expect("Invalid configuration")
});
pub type Pass = String;
macro_rules! make_config {

View File

@ -159,7 +159,7 @@ impl TwoFactor {
use crate::api::core::two_factor::webauthn::U2FRegistration;
use crate::api::core::two_factor::webauthn::{get_webauthn_registrations, WebauthnRegistration};
use webauthn_rs::proto::*;
use webauthn_rs_core::proto::*;
for mut u2f in u2f_factors {
let mut regs: Vec<U2FRegistration> = serde_json::from_str(&u2f.data)?;
@ -183,22 +183,23 @@ impl TwoFactor {
type_: COSEAlgorithm::ES256,
key: COSEKeyType::EC_EC2(COSEEC2Key {
curve: ECDSACurve::SECP256R1,
x,
y,
x: x.to_vec().into(),
y: y.to_vec().into(),
}),
};
let credential = CredentialV3 {
counter: reg.counter,
verified: false,
cred: key,
cred_id: reg.reg.key_handle.clone(),
registration_policy: UserVerificationPolicy::Preferred,
};
let new_reg = WebauthnRegistration {
id: reg.id,
migrated: true,
name: reg.name.clone(),
credential: Credential {
counter: reg.counter,
verified: false,
cred: key,
cred_id: reg.reg.key_handle.clone(),
registration_policy: UserVerificationPolicy::Discouraged,
},
security_key: (Credential::from(credential).into()),
};
webauthn_regs.push(new_reg);

View File

@ -52,7 +52,8 @@ use rocket::error::Error as RocketErr;
use serde_json::{Error as SerdeErr, Value};
use std::io::Error as IoErr;
use std::time::SystemTimeError as TimeErr;
use webauthn_rs::error::WebauthnError as WebauthnErr;
use uuid::Error as UuidErr;
use webauthn_rs::prelude::WebauthnError as WebauthnErr;
use yubico::yubicoerror::YubicoError as YubiErr;
#[derive(Serialize)]
@ -87,6 +88,7 @@ make_error! {
Smtp(SmtpErr): _has_source, _api_error,
OpenSSL(SSLErr): _has_source, _api_error,
Rocket(RocketErr): _has_source, _api_error,
Uuid(UuidErr): _has_source, _api_error,
DieselCon(DieselConErr): _has_source, _api_error,
Webauthn(WebauthnErr): _has_source, _api_error,

View File

@ -53,7 +53,7 @@ mod util;
use crate::api::purge_auth_requests;
use crate::api::{WS_ANONYMOUS_SUBSCRIPTIONS, WS_USERS};
pub use config::CONFIG;
pub use config::{CONFIG, WEBAUTHN};
pub use error::{Error, MapResult};
use rocket::data::{Limits, ToByteUnit};
use std::sync::Arc;