use std::str::FromStr;
use crate::PublicKey;
#[allow(deprecated, reason = "Internal use of deprecated public API")]
use crate::PubkyCookieAuthFlow;
use crate::{
Capabilities, ClientId, DelegatedGrantCredentialState, EventCursor, EventStreamBuilder,
GrantCredential, Pkdns, PubkyGrantAuthFlow, PubkyHttpClient, PubkySession, PubkySigner,
PublicStorage, Result,
actors::AuthFlowKind,
deep_links::{DeepLink, XCallbackParams},
errors::AuthError,
};
#[cfg(not(target_arch = "wasm32"))]
use crate::errors::RequestError;
#[cfg(not(target_arch = "wasm32"))]
use std::path::Path;
#[derive(Clone, Debug)]
pub struct Pubky {
client: PubkyHttpClient,
}
impl Pubky {
pub fn new() -> Result<Self> {
Ok(Self {
client: PubkyHttpClient::new()?,
})
}
pub fn testnet() -> Result<Self> {
Ok(Self {
client: PubkyHttpClient::testnet()?,
})
}
#[must_use]
pub const fn with_client(client: PubkyHttpClient) -> Self {
Self { client }
}
#[allow(
deprecated,
reason = "Cookie flow is intentionally exposed via this facade while deprecated"
)]
pub fn start_cookie_auth_flow(
&self,
caps: &Capabilities,
auth_kind: AuthFlowKind,
) -> Result<PubkyCookieAuthFlow> {
PubkyCookieAuthFlow::builder(caps, auth_kind)
.client(self.client.clone())
.start()
}
pub fn start_grant_auth_flow(
&self,
caps: &Capabilities,
auth_kind: AuthFlowKind,
client_id: ClientId,
) -> Result<PubkyGrantAuthFlow> {
PubkyGrantAuthFlow::builder(caps, auth_kind, client_id)
.client(self.client.clone())
.start()
}
#[allow(
deprecated,
reason = "Cookie flow is intentionally exposed via this facade while deprecated"
)]
pub fn resume_cookie_auth_flow(&self, authorization_url: &str) -> Result<PubkyCookieAuthFlow> {
let (caps, relay, secret, auth_kind, x_callback) = parse_auth_deep_link(authorization_url)?;
PubkyCookieAuthFlow::builder(&caps, auth_kind)
.client_secret(secret)
.relay(relay)
.x_callback(x_callback)
.client(self.client.clone())
.start()
}
#[must_use]
pub fn signer(&self, keypair: crate::Keypair) -> PubkySigner {
PubkySigner {
client: self.client.clone(),
keypair,
}
}
#[must_use]
pub fn public_storage(&self) -> PublicStorage {
PublicStorage {
client: self.client.clone(),
}
}
#[must_use]
pub fn pkdns(&self) -> Pkdns {
Pkdns::with_client(self.client.clone())
}
pub async fn get_homeserver_of(
&self,
user_public_key: &PublicKey,
) -> Result<Option<PublicKey>> {
Pkdns::with_client(self.client.clone())
.get_homeserver_of(user_public_key)
.await
}
#[must_use]
pub fn event_stream_for_user(
&self,
user: &PublicKey,
cursor: Option<EventCursor>,
) -> EventStreamBuilder {
EventStreamBuilder::for_user(self.client.clone(), user, cursor)
}
#[must_use]
pub fn event_stream_for(&self, homeserver: &PublicKey) -> EventStreamBuilder {
EventStreamBuilder::for_homeserver(self.client.clone(), homeserver)
}
#[cfg(not(target_arch = "wasm32"))]
pub async fn session_from_file<P: AsRef<Path>>(&self, path: P) -> Result<PubkySession> {
PubkySession::from_secret_file(path.as_ref(), Some(self.client.clone())).await
}
pub async fn restore_session(&self, token: &str) -> Result<PubkySession> {
if GrantCredential::is_secret_token(token) {
return PubkySession::import_grant_secret(token, Some(self.client.clone())).await;
}
PubkySession::import_secret(token, Some(self.client.clone())).await
}
#[doc(hidden)]
pub async fn restore_delegated_grant_session(
&self,
state: DelegatedGrantCredentialState,
sign: crate::DelegatedSignFn,
) -> Result<PubkySession> {
let credential = GrantCredential::import_delegated_state(state, &self.client, sign).await?;
Ok(PubkySession::from_grant_credential(
self.client.clone(),
credential,
))
}
#[cfg(not(target_arch = "wasm32"))]
pub fn signer_from_recovery_file<P: AsRef<Path>>(
&self,
path: P,
passphrase: &str,
) -> Result<PubkySigner> {
use pubky_common::recovery_file::decrypt_recovery_file;
let bytes = std::fs::read(path.as_ref()).map_err(|e| RequestError::Validation {
message: format!("failed to read recovery file: {e}"),
})?;
let kp =
decrypt_recovery_file(&bytes, passphrase).map_err(|e| RequestError::Validation {
message: format!("failed to decrypt recovery file: {e}"),
})?;
Ok(self.signer(kp))
}
#[inline]
#[must_use]
pub const fn client(&self) -> &PubkyHttpClient {
&self.client
}
}
fn parse_auth_deep_link(
url: &str,
) -> Result<(
Capabilities,
url::Url,
[u8; 32],
AuthFlowKind,
XCallbackParams,
)> {
let deep_link = DeepLink::from_str(url)
.map_err(|e| AuthError::Validation(format!("Failed to parse authorization URL: {e}")))?;
match &deep_link {
DeepLink::Signin(s) => Ok((
s.params().capabilities.clone(),
s.params().relay.clone(),
s.params().secret,
AuthFlowKind::signin(),
s.x_callback().clone(),
)),
DeepLink::Signup(s) => Ok((
s.params().capabilities.clone(),
s.params().relay.clone(),
s.params().secret,
AuthFlowKind::signup(
s.params().homeserver.clone(),
s.params().signup_token.clone(),
),
s.x_callback().clone(),
)),
DeepLink::SigninGrant(_) | DeepLink::SignupGrant(_) => Err(AuthError::Validation(
"grant auth flows cannot be resumed from the authorization URL alone; the PoP client private key is required and is not encoded in the deep link."
.into(),
)
.into()),
DeepLink::DirectSignup(_) => Err(AuthError::Validation(
"Direct signup URLs cannot be resumed as cookie auth flows.".into(),
)
.into()),
DeepLink::SeedExport(_) => {
Err(AuthError::Validation("Only signin and signup URLs can be resumed.".into()).into())
}
}
}