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::error::PasskeyError;
#[derive(Builder, Clone, Debug, Deserialize, PartialEq, Serialize)]
#[builder(setter(into, strip_option))]
pub struct AuthFinishRequest<'a> {
id: Cow<'a, str>,
extensions: AuthenticationExtensionsClientOutputs<'a>,
raw_id: Cow<'a, str>,
response: AuthenticatorAssertionResponseRaw<'a>,
type_: Cow<'a, str>,
user_id: Cow<'a, str>,
}
impl RestEndpoint for AuthFinishRequest<'_> {
fn method(&self) -> http::Method {
http::Method::POST
}
fn endpoint(&self) -> Cow<'static, str> {
"auth/passkey/finish".to_string().into()
}
fn body(&self) -> Result<Option<(&'static str, Vec<u8>)>, BodyError> {
let mut params = JsonBodyParams::default();
params.push("id", &self.id);
params.push("extensions", serde_json::to_value(&self.extensions)?);
params.push("raw_id", &self.raw_id);
params.push("response", serde_json::to_value(&self.response)?);
params.push("type_", &self.type_);
params.push("user_id", &self.user_id);
params.into_body()
}
fn service_type(&self) -> ServiceType {
ServiceType::Identity
}
fn api_version(&self) -> Option<ApiVersion> {
Some(ApiVersion::new(4, 0))
}
}
#[derive(Builder, Clone, Debug, Deserialize, PartialEq, Serialize)]
#[builder(setter(into, strip_option))]
pub struct AuthenticatorAssertionResponseRaw<'a> {
pub authenticator_data: Cow<'a, str>,
pub client_data_json: Cow<'a, str>,
pub signature: Cow<'a, str>,
#[serde(skip_serializing_if = "Option::is_none")]
#[builder(default)]
pub user_handle: Option<Cow<'a, str>>,
}
#[derive(Builder, Clone, Debug, Deserialize, PartialEq, Serialize)]
#[builder(setter(into, strip_option))]
pub struct AuthenticationExtensionsClientOutputs<'a> {
#[serde(skip_serializing_if = "Option::is_none")]
#[builder(default)]
pub appid: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
#[builder(default)]
pub hmac_get_secret: Option<HmacGetSecretOutput<'a>>,
}
#[derive(Builder, Clone, Debug, Deserialize, PartialEq, Serialize)]
#[builder(setter(into, strip_option))]
pub struct HmacGetSecretOutput<'a> {
pub output1: Cow<'a, str>,
#[serde(skip_serializing_if = "Option::is_none")]
#[builder(default)]
pub output2: Option<Cow<'a, str>>,
}
impl TryFrom<webauthn_authenticator_rs::prelude::PublicKeyCredential>
for AuthFinishRequestBuilder<'_>
{
type Error = PasskeyError;
fn try_from(
value: webauthn_authenticator_rs::prelude::PublicKeyCredential,
) -> Result<Self, Self::Error> {
let mut req = AuthFinishRequestBuilder::default();
req.id(value.id);
req.raw_id(URL_SAFE.encode(value.raw_id));
req.type_(value.type_);
let mut ext_builder = AuthenticationExtensionsClientOutputsBuilder::default();
if let Some(appid) = value.extensions.appid {
ext_builder.appid(appid);
}
if let Some(ext) = &value.extensions.hmac_get_secret {
let mut hmac_out = HmacGetSecretOutputBuilder::default();
hmac_out.output1(URL_SAFE.encode(ext.output1.clone()));
if let Some(out2) = &ext.output2 {
hmac_out.output2(URL_SAFE.encode(out2));
}
ext_builder.hmac_get_secret(hmac_out.build()?);
}
req.extensions(ext_builder.build()?);
let mut rsp_builder = AuthenticatorAssertionResponseRawBuilder::default();
rsp_builder.authenticator_data(URL_SAFE.encode(value.response.authenticator_data));
rsp_builder.client_data_json(URL_SAFE.encode(value.response.client_data_json));
rsp_builder.signature(URL_SAFE.encode(value.response.signature));
if let Some(uh) = &value.response.user_handle {
rsp_builder.user_handle(URL_SAFE.encode(uh));
}
req.response(rsp_builder.build()?);
Ok(req)
}
}
pub async fn get_finish_auth_ep<'a, A: AuthHelper>(
config: &config::CloudConfig,
passkey_auth: webauthn_authenticator_rs::prelude::PublicKeyCredential,
_auth_helper: &mut A,
) -> Result<impl RestEndpoint + use<'a, A>, PasskeyError> {
if let Some(auth) = &config.auth {
let mut ep_builder: AuthFinishRequestBuilder = passkey_auth.try_into()?;
if let Some(val) = &auth.user_id {
ep_builder.user_id(val.clone());
} else {
return Err(PasskeyError::MissingAuthData);
}
return Ok(ep_builder.build()?);
}
Err(PasskeyError::MissingAuthData)
}