use std::path::PathBuf;
use serde::{Deserialize, Serialize};
use tracing::debug;
use crate::{Result, prelude::*};
#[derive(Debug, Serialize)]
struct CreateChallengeRequest {
pub app_name: String,
}
#[derive(Debug, Deserialize)]
struct CreateChallengeResponse {
pub challenge_id: String,
}
#[derive(Debug, Serialize)]
struct CreateApiKeyRequest {
pub challenge_id: String,
pub code: String,
}
#[derive(Debug, Deserialize)]
struct CreateApiKeyResponse {
pub api_key: String,
}
#[doc(hidden)]
#[derive(Clone, Debug, Serialize)]
pub struct AuthStatus {
pub keystore: KeyStoreStatus,
pub http: HttpStatus,
#[cfg(feature = "grpc")]
pub grpc: GrpcStatus,
}
#[doc(hidden)]
#[derive(Clone, Debug, Serialize)]
pub struct HttpStatus {
pub url: String,
pub has_token: bool,
}
impl HttpStatus {
#[must_use]
pub fn is_authenticated(&self) -> bool {
self.has_token
}
}
#[cfg(feature = "grpc")]
#[doc(hidden)]
#[derive(Clone, Debug, Serialize)]
pub struct GrpcStatus {
pub endpoint: Option<String>,
pub has_account_key: bool,
pub has_session_token: bool,
}
#[cfg(feature = "grpc")]
impl GrpcStatus {
#[must_use]
pub fn is_authenticated(&self) -> bool {
self.has_account_key || self.has_session_token
}
}
#[derive(Clone, Debug, Serialize)]
pub struct KeyStoreStatus {
pub id: String,
pub service: String,
pub path: Option<std::path::PathBuf>,
}
impl AnytypeClient {
pub async fn create_auth_challenge(&self) -> Result<String> {
let request = CreateChallengeRequest {
app_name: self.config.app_name.clone(),
};
debug!("creating auth challenge ...");
let response: CreateChallengeResponse = self
.client
.post_unauthenticated("/v1/auth/challenges", &request)
.await?;
debug!("challenge received: {}", &response.challenge_id);
Ok(response.challenge_id)
}
pub async fn create_api_key(
&self,
challenge_id: &str,
code: impl Into<String>,
) -> Result<HttpCredentials> {
let request = CreateApiKeyRequest {
challenge_id: challenge_id.to_string(),
code: code.into(),
};
let response: CreateApiKeyResponse = self
.client
.post_unauthenticated("/v1/auth/api_keys", &request)
.await?;
Ok(HttpCredentials::new(response.api_key))
}
pub async fn authenticate_interactive<F>(&self, get_code: F, force_reauth: bool) -> Result<()>
where
F: FnOnce(&str) -> Result<String>,
{
if !force_reauth {
if self.client.has_key() {
debug!("client already has key - no need to re-authenticate");
return Ok(());
}
let creds = self.keystore.get_http_credentials()?;
if creds.has_creds() {
self.client.set_api_key(creds);
return Ok(());
}
}
debug!("beginning interactive authentication");
let challenge_id: String = self.create_auth_challenge().await?;
let code = get_code(&challenge_id)?;
let api_key = self.create_api_key(&challenge_id, code).await?;
self.keystore.update_http_credentials(&api_key)?;
self.set_api_key(api_key);
Ok(())
}
#[must_use]
pub fn get_key_store(&self) -> &KeyStore {
&self.keystore
}
pub fn clear_api_key(&self) {
self.client.clear_api_key();
}
pub fn set_api_key(&self, key: HttpCredentials) {
self.client.set_api_key(key);
}
pub fn logout(&self) -> Result<()> {
self.clear_api_key();
self.keystore.clear_all_credentials()?;
Ok(())
}
pub fn auth_status(&self) -> Result<AuthStatus, AnytypeError> {
let keystore = self.get_key_store();
let http_creds = keystore.get_http_credentials()?;
#[cfg(feature = "grpc")]
let grpc_creds = keystore.get_grpc_credentials()?;
let path = keystore
.store()
.as_any()
.downcast_ref::<db_keystore::DbKeyStore>()
.map(|store| PathBuf::from(&store.path()));
Ok(AuthStatus {
keystore: KeyStoreStatus {
id: keystore.id(),
service: keystore.service().to_string(),
path,
},
http: HttpStatus {
url: self.get_http_endpoint().to_string(),
has_token: http_creds.has_creds(),
},
#[cfg(feature = "grpc")]
grpc: GrpcStatus {
endpoint: self.get_grpc_endpoint(),
has_account_key: grpc_creds.has_account_key(),
has_session_token: grpc_creds.has_session_token(),
},
})
}
}