Files
log/src/commands/auth.rs
2026-01-20 17:32:27 +09:00

139 lines
3.8 KiB
Rust

use anyhow::{Context, Result};
use serde::{Deserialize, Serialize};
use super::token::{self, Session};
use crate::lexicons::{self, com_atproto_server};
#[derive(Debug, Serialize)]
struct CreateSessionRequest {
identifier: String,
password: String,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
struct CreateSessionResponse {
did: String,
handle: String,
access_jwt: String,
refresh_jwt: String,
}
/// Login to ATProto PDS
pub async fn login(handle: &str, password: &str, pds: &str, is_bot: bool) -> Result<()> {
let client = reqwest::Client::new();
let url = lexicons::url(pds, &com_atproto_server::CREATE_SESSION);
let req = CreateSessionRequest {
identifier: handle.to_string(),
password: password.to_string(),
};
let account_type = if is_bot { "bot" } else { "user" };
println!("Logging in to {} as {} ({})...", pds, handle, account_type);
let res = client
.post(&url)
.json(&req)
.send()
.await
.context("Failed to send login request")?;
if !res.status().is_success() {
let status = res.status();
let body = res.text().await.unwrap_or_default();
anyhow::bail!("Login failed: {} - {}", status, body);
}
let session_res: CreateSessionResponse = res.json().await?;
let session = Session {
did: session_res.did,
handle: session_res.handle,
access_jwt: session_res.access_jwt,
refresh_jwt: session_res.refresh_jwt,
pds: Some(pds.to_string()),
};
if is_bot {
token::save_bot_session(&session)?;
} else {
token::save_session(&session)?;
}
println!("Logged in as {} ({})", session.handle, session.did);
Ok(())
}
/// Refresh access token
pub async fn refresh_session() -> Result<Session> {
let session = token::load_session()?;
let pds = session.pds.as_deref().unwrap_or("bsky.social");
let client = reqwest::Client::new();
let url = lexicons::url(pds, &com_atproto_server::REFRESH_SESSION);
let res = client
.post(&url)
.header("Authorization", format!("Bearer {}", session.refresh_jwt))
.send()
.await
.context("Failed to refresh session")?;
if !res.status().is_success() {
let status = res.status();
let body = res.text().await.unwrap_or_default();
anyhow::bail!("Refresh failed: {} - {}. Try logging in again.", status, body);
}
let new_session: CreateSessionResponse = res.json().await?;
let session = Session {
did: new_session.did,
handle: new_session.handle,
access_jwt: new_session.access_jwt,
refresh_jwt: new_session.refresh_jwt,
pds: Some(pds.to_string()),
};
token::save_session(&session)?;
Ok(session)
}
/// Refresh bot access token
pub async fn refresh_bot_session() -> Result<Session> {
let session = token::load_bot_session()?;
let pds = session.pds.as_deref().unwrap_or("bsky.social");
let client = reqwest::Client::new();
let url = lexicons::url(pds, &com_atproto_server::REFRESH_SESSION);
let res = client
.post(&url)
.header("Authorization", format!("Bearer {}", session.refresh_jwt))
.send()
.await
.context("Failed to refresh bot session")?;
if !res.status().is_success() {
let status = res.status();
let body = res.text().await.unwrap_or_default();
anyhow::bail!("Bot refresh failed: {} - {}. Try 'ailog login --bot' again.", status, body);
}
let new_session: CreateSessionResponse = res.json().await?;
let session = Session {
did: new_session.did,
handle: new_session.handle,
access_jwt: new_session.access_jwt,
refresh_jwt: new_session.refresh_jwt,
pds: Some(pds.to_string()),
};
token::save_bot_session(&session)?;
Ok(session)
}