use base64::{engine::general_purpose::URL_SAFE, Engine as _};
use derive_builder::Builder;
use serde::{Deserialize, Serialize};
use std::borrow::Cow;
use crate::api::rest_endpoint_prelude::*;
use crate::api::RestEndpoint;
use crate::auth::auth_helper::AuthHelper;
use crate::config;
use crate::types::{ApiVersion, ServiceType};
use super::PasskeyError;
#[derive(Builder, Debug, Clone, Serialize)]
#[builder(setter(strip_option))]
pub struct Passkey<'a> {
#[builder(setter(into))]
user_id: Cow<'a, str>,
}
#[derive(Builder, Debug, Clone)]
#[builder(setter(strip_option))]
pub struct AuthStartRequest<'a> {
#[builder(setter(into))]
passkey: Passkey<'a>,
}
impl<'a> AuthStartRequest<'a> {
pub fn builder() -> AuthStartRequestBuilder<'a> {
AuthStartRequestBuilder::default()
}
}
impl RestEndpoint for AuthStartRequest<'_> {
fn method(&self) -> http::Method {
http::Method::POST
}
fn endpoint(&self) -> Cow<'static, str> {
"auth/passkey/start".to_string().into()
}
fn body(&self) -> Result<Option<(&'static str, Vec<u8>)>, BodyError> {
let mut params = JsonBodyParams::default();
params.push("passkey", serde_json::to_value(&self.passkey)?);
params.into_body()
}
fn service_type(&self) -> ServiceType {
ServiceType::Identity
}
fn api_version(&self) -> Option<ApiVersion> {
Some(ApiVersion::new(4, 0))
}
}
pub async fn get_init_auth_ep<A: AuthHelper>(
config: &config::CloudConfig,
_auth_helper: &mut A,
) -> Result<impl RestEndpoint, PasskeyError> {
if let Some(auth) = &config.auth {
let mut ep = AuthStartRequest::builder();
let mut passkey = PasskeyBuilder::default();
if let Some(val) = &auth.user_id {
passkey.user_id(val.clone());
} else {
return Err(PasskeyError::MissingAuthData);
}
ep.passkey(passkey.build()?);
return Ok(ep.build()?);
}
Err(PasskeyError::MissingAuthData)
}
#[derive(Clone, Debug, Deserialize, PartialEq)]
pub struct PasskeyAuthenticationStartResponse {
pub public_key: PublicKeyCredentialRequestOptions,
pub mediation: Option<Mediation>,
}
#[derive(Clone, Debug, Deserialize, PartialEq)]
pub struct PublicKeyCredentialRequestOptions {
pub allow_credentials: Vec<AllowCredentials>,
pub challenge: String,
pub extensions: Option<RequestAuthenticationExtensions>,
pub hints: Option<Vec<PublicKeyCredentialHint>>,
pub rp_id: String,
pub timeout: Option<u32>,
pub user_verification: UserVerificationPolicy,
}
#[derive(Clone, Debug, Deserialize, PartialEq)]
pub enum Mediation {
Conditional,
}
#[derive(Clone, Debug, Deserialize, PartialEq)]
pub struct AllowCredentials {
pub id: String,
pub transports: Option<Vec<AuthenticatorTransport>>,
pub type_: String,
}
#[derive(Clone, Debug, Deserialize, PartialEq)]
pub enum AuthenticatorTransport {
Ble,
Hybrid,
Internal,
Nfc,
Test,
Unknown,
Usb,
}
#[derive(Clone, Debug, Deserialize, PartialEq)]
pub enum UserVerificationPolicy {
Required,
Preferred,
DiscouragedDoNotUse,
}
#[derive(Clone, Debug, Deserialize, PartialEq)]
pub enum PublicKeyCredentialHint {
ClientDevice,
Hybrid,
SecurityKey,
}
#[derive(Clone, Debug, Deserialize, PartialEq)]
pub struct RequestAuthenticationExtensions {
pub appid: Option<String>,
pub hmac_get_secret: Option<HmacGetSecretInput>,
pub uvm: Option<bool>,
}
#[derive(Clone, Debug, Deserialize, PartialEq)]
pub struct HmacGetSecretInput {
pub output1: String,
pub output2: Option<String>,
}
impl TryFrom<HmacGetSecretInput> for webauthn_rs_proto::extensions::HmacGetSecretInput {
type Error = PasskeyError;
fn try_from(val: HmacGetSecretInput) -> Result<Self, Self::Error> {
Ok(Self {
output1: URL_SAFE.decode(val.output1)?.into(),
output2: val
.output2
.map(|s2| URL_SAFE.decode(s2))
.transpose()?
.map(Into::into),
})
}
}
impl TryFrom<RequestAuthenticationExtensions>
for webauthn_rs_proto::extensions::RequestAuthenticationExtensions
{
type Error = PasskeyError;
fn try_from(val: RequestAuthenticationExtensions) -> Result<Self, Self::Error> {
Ok(Self {
appid: val.appid,
hmac_get_secret: val.hmac_get_secret.map(TryInto::try_into).transpose()?,
uvm: val.uvm,
})
}
}
impl From<AuthenticatorTransport> for webauthn_rs_proto::options::AuthenticatorTransport {
fn from(val: AuthenticatorTransport) -> Self {
match val {
AuthenticatorTransport::Ble => webauthn_rs_proto::options::AuthenticatorTransport::Ble,
AuthenticatorTransport::Hybrid => {
webauthn_rs_proto::options::AuthenticatorTransport::Hybrid
}
AuthenticatorTransport::Internal => {
webauthn_rs_proto::options::AuthenticatorTransport::Internal
}
AuthenticatorTransport::Nfc => webauthn_rs_proto::options::AuthenticatorTransport::Nfc,
AuthenticatorTransport::Test => {
webauthn_rs_proto::options::AuthenticatorTransport::Test
}
AuthenticatorTransport::Unknown => {
webauthn_rs_proto::options::AuthenticatorTransport::Unknown
}
AuthenticatorTransport::Usb => webauthn_rs_proto::options::AuthenticatorTransport::Usb,
}
}
}
impl From<UserVerificationPolicy> for webauthn_rs_proto::options::UserVerificationPolicy {
fn from(val: UserVerificationPolicy) -> Self {
match val {
UserVerificationPolicy::Required => {
webauthn_rs_proto::options::UserVerificationPolicy::Required
}
UserVerificationPolicy::Preferred => {
webauthn_rs_proto::options::UserVerificationPolicy::Preferred
}
UserVerificationPolicy::DiscouragedDoNotUse => {
webauthn_rs_proto::options::UserVerificationPolicy::Discouraged_DO_NOT_USE
}
}
}
}
impl From<PublicKeyCredentialHint> for webauthn_rs_proto::options::PublicKeyCredentialHints {
fn from(val: PublicKeyCredentialHint) -> Self {
match val {
PublicKeyCredentialHint::ClientDevice => {
webauthn_rs_proto::options::PublicKeyCredentialHints::ClientDevice
}
PublicKeyCredentialHint::Hybrid => {
webauthn_rs_proto::options::PublicKeyCredentialHints::Hybrid
}
PublicKeyCredentialHint::SecurityKey => {
webauthn_rs_proto::options::PublicKeyCredentialHints::SecurityKey
}
}
}
}
impl TryFrom<AllowCredentials> for webauthn_rs_proto::options::AllowCredentials {
type Error = PasskeyError;
fn try_from(val: AllowCredentials) -> Result<Self, Self::Error> {
Ok(Self {
id: URL_SAFE.decode(val.id)?.into(),
transports: val
.transports
.map(|tr| tr.into_iter().map(Into::into).collect::<Vec<_>>()),
type_: val.type_,
})
}
}
impl TryFrom<PublicKeyCredentialRequestOptions>
for webauthn_rs_proto::auth::PublicKeyCredentialRequestOptions
{
type Error = PasskeyError;
fn try_from(val: PublicKeyCredentialRequestOptions) -> Result<Self, Self::Error> {
Ok(Self {
allow_credentials: val
.allow_credentials
.into_iter()
.map(TryInto::try_into)
.collect::<Result<Vec<_>, _>>()?,
challenge: URL_SAFE.decode(val.challenge)?.into(),
extensions: val.extensions.map(TryInto::try_into).transpose()?,
hints: val
.hints
.map(|hints| hints.into_iter().map(Into::into).collect::<Vec<_>>()),
rp_id: val.rp_id,
timeout: val.timeout,
user_verification: val.user_verification.into(),
})
}
}
impl TryFrom<PasskeyAuthenticationStartResponse>
for webauthn_authenticator_rs::prelude::RequestChallengeResponse
{
type Error = PasskeyError;
fn try_from(val: PasskeyAuthenticationStartResponse) -> Result<Self, Self::Error> {
Ok(Self {
public_key: val.public_key.try_into()?,
mediation: val.mediation.map(|med| match med {
Mediation::Conditional => webauthn_rs_proto::auth::Mediation::Conditional,
}),
})
}
}