use anyhow::Result;
use atproto_identity::url::build_url;
use serde::{Deserialize, Serialize};
use std::iter;
use crate::{
client::{Auth, post_json},
errors::ClientError,
};
#[cfg_attr(debug_assertions, derive(Debug))]
#[derive(Serialize, Deserialize, Clone)]
pub struct CreateSessionRequest {
pub identifier: String,
pub password: String,
#[serde(skip_serializing_if = "Option::is_none", rename = "authFactorToken")]
pub auth_factor_token: Option<String>,
}
#[cfg_attr(debug_assertions, derive(Debug))]
#[derive(Deserialize, Clone)]
pub struct AppPasswordSession {
pub did: String,
pub handle: String,
pub email: String,
#[serde(rename = "accessJwt")]
pub access_jwt: String,
#[serde(rename = "refreshJwt")]
pub refresh_jwt: String,
}
#[cfg_attr(debug_assertions, derive(Debug))]
#[derive(Deserialize, Clone)]
pub struct RefreshSessionResponse {
pub did: String,
pub handle: String,
#[serde(rename = "accessJwt")]
pub access_jwt: String,
#[serde(rename = "refreshJwt")]
pub refresh_jwt: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub active: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub status: Option<String>,
}
#[cfg_attr(debug_assertions, derive(Debug))]
#[derive(Deserialize, Clone)]
pub struct AppPasswordResponse {
pub name: String,
pub password: String,
#[serde(rename = "createdAt")]
pub created_at: String,
}
pub async fn create_session(
http_client: &reqwest::Client,
base_url: &str,
identifier: &str,
password: &str,
auth_factor_token: Option<&str>,
) -> Result<AppPasswordSession> {
let url = build_url(
base_url,
"/xrpc/com.atproto.server.createSession",
iter::empty::<(&str, &str)>(),
)?
.to_string();
let request = CreateSessionRequest {
identifier: identifier.to_string(),
password: password.to_string(),
auth_factor_token: auth_factor_token.map(|s| s.to_string()),
};
let value = serde_json::to_value(request)?;
post_json(http_client, &url, value)
.await
.and_then(|value| serde_json::from_value(value).map_err(|err| err.into()))
}
pub async fn refresh_session(
http_client: &reqwest::Client,
base_url: &str,
refresh_token: &str,
) -> Result<RefreshSessionResponse> {
let url = build_url(
base_url,
"/xrpc/com.atproto.server.refreshSession",
iter::empty::<(&str, &str)>(),
)?
.to_string();
let mut headers = reqwest::header::HeaderMap::new();
headers.insert(
reqwest::header::AUTHORIZATION,
reqwest::header::HeaderValue::from_str(&format!("Bearer {}", refresh_token))?,
);
let response = http_client.post(&url).headers(headers).send().await?;
let value = response.json::<serde_json::Value>().await?;
serde_json::from_value(value).map_err(|err| err.into())
}
pub async fn create_app_password(
http_client: &reqwest::Client,
base_url: &str,
access_token: &str,
name: &str,
) -> Result<AppPasswordResponse> {
let url = build_url(
base_url,
"/xrpc/com.atproto.server.createAppPassword",
iter::empty::<(&str, &str)>(),
)?
.to_string();
let request_body = serde_json::json!({
"name": name
});
let mut headers = reqwest::header::HeaderMap::new();
headers.insert(
reqwest::header::AUTHORIZATION,
reqwest::header::HeaderValue::from_str(&format!("Bearer {}", access_token))?,
);
let response = http_client
.post(&url)
.headers(headers)
.json(&request_body)
.send()
.await?;
let value = response.json::<serde_json::Value>().await?;
serde_json::from_value(value).map_err(|err| err.into())
}
pub async fn delete_session(
http_client: &reqwest::Client,
auth: &Auth,
base_url: &str,
) -> Result<()> {
let app_auth = match auth {
Auth::AppPassword(app_auth) => app_auth,
_ => {
return Err(ClientError::InvalidAuthMethod {
method: "deleteSession requires AppPassword authentication".to_string(),
}
.into());
}
};
let url = build_url(
base_url,
"/xrpc/com.atproto.server.deleteSession",
iter::empty::<(&str, &str)>(),
)?
.to_string();
let mut headers = reqwest::header::HeaderMap::new();
headers.insert(
reqwest::header::AUTHORIZATION,
reqwest::header::HeaderValue::from_str(&format!("Bearer {}", app_auth.access_token))?,
);
let response = http_client
.post(&url)
.headers(headers)
.send()
.await
.map_err(|error| ClientError::HttpRequestFailed {
url: url.clone(),
error,
})?;
if response.status() == reqwest::StatusCode::OK {
Ok(())
} else {
Err(anyhow::anyhow!(
"deleteSession failed: expected 200 OK, got {}",
response.status()
))
}
}