use std::io::{self, Write};
use anyhow::Result;
use anytype::prelude::*;
use serde_json::json;
use crate::cli::{AppContext, AuthArgs, AuthCommands, emit_json};
pub async fn handle(ctx: &AppContext, args: AuthArgs) -> Result<()> {
match args.command {
AuthCommands::Login { force } => login(ctx, force).await,
AuthCommands::Logout => logout(ctx),
AuthCommands::Status => status(ctx).await,
AuthCommands::SetHttp => set_http(ctx),
AuthCommands::SetGrpc {
config,
account_key,
token,
} => set_grpc(ctx, config, account_key, token),
}
}
async fn login(ctx: &AppContext, force: bool) -> Result<()> {
ctx.client
.authenticate_interactive(
|challenge_id| {
println!("Challenge ID: {challenge_id}");
print!("Enter 4-digit code displayed by Anytype: ");
io::stdout().flush().map_err(|err| AnytypeError::Auth {
message: err.to_string(),
})?;
let mut code = String::new();
io::stdin()
.read_line(&mut code)
.map_err(|err| AnytypeError::Auth {
message: err.to_string(),
})?;
Ok(code.trim().to_string())
},
force,
)
.await?;
emit_json(&json!({ "authenticated": true }))
}
fn logout(ctx: &AppContext) -> Result<()> {
ctx.client.logout()?;
emit_json(&json!({ "authenticated": false }))
}
async fn status(ctx: &AppContext) -> Result<()> {
let status = ctx.client.auth_status()?;
let http_ping = if status.http.is_authenticated() {
match ctx.client.ping_http().await {
Ok(()) => "Ping check ok".to_string(),
Err(e) => format!("Ping failed: {e}"),
}
} else {
"(credentials required)".to_string()
};
let grpc_ping = if status.grpc.is_authenticated() {
match ctx.client.ping_grpc().await {
Ok(()) => "Ping check ok".to_string(),
Err(e) => format!("Ping failed: {e}"),
}
} else {
"(credentials required)".to_string()
};
emit_json(&json!({
"status": status,
"ping": {
"http": http_ping,
"grpc": grpc_ping,
}
}))
}
fn set_http(ctx: &AppContext) -> Result<()> {
print!("Enter HTTP API token: ");
io::stdout().flush()?;
let mut token = String::new();
io::stdin().read_line(&mut token)?;
let token = token.trim();
if token.is_empty() {
anyhow::bail!("HTTP API token is empty");
}
let creds = HttpCredentials::new(token.to_string());
ctx.client.get_key_store().update_http_credentials(&creds)?;
emit_json(&serde_json::json!({ "http_credentials": "updated" }))
}
#[derive(serde::Deserialize)]
struct HeadlessConfig {
#[serde(default, rename = "accountId")]
account_id: Option<String>,
#[serde(default, rename = "accountKey")]
account_key: Option<String>,
#[serde(default, rename = "sessionToken")]
session_token: Option<String>,
}
fn set_grpc(
ctx: &AppContext,
config: Option<std::path::PathBuf>,
account_key: bool,
token: bool,
) -> Result<()> {
let options = [token, config.is_some(), account_key]
.into_iter()
.filter(|enabled| *enabled)
.count();
if options > 1 {
anyhow::bail!("--token, --config, and --account-key are mutually exclusive");
}
let creds = if token {
print!("Enter gRPC session token: ");
io::stdout().flush()?;
let mut token = String::new();
io::stdin().read_line(&mut token)?;
let token = token.trim();
if token.is_empty() {
anyhow::bail!("gRPC session token is empty");
}
GrpcCredentials::from_token(token.to_string())
} else if account_key {
print!("Enter gRPC account key: ");
io::stdout().flush()?;
let mut account_key = String::new();
io::stdin().read_line(&mut account_key)?;
let account_key = account_key.trim();
if account_key.is_empty() {
anyhow::bail!("gRPC account key is empty");
}
GrpcCredentials::from_account_key(account_key.to_string())
} else {
let path = config.ok_or_else(|| {
anyhow::anyhow!("--config PATH, --account-key, or --token is required")
})?;
let contents = std::fs::read_to_string(&path)?;
let cfg: HeadlessConfig = serde_json::from_str(&contents)?;
let mut creds = GrpcCredentials::default();
if let Some(account_id) = cfg.account_id {
creds = creds.with_account_id(account_id);
}
if let Some(account_key) = cfg.account_key {
creds = creds.with_account_key(account_key);
}
if let Some(session_token) = cfg.session_token {
creds = creds.with_session_token(session_token);
}
creds
};
ctx.client.get_key_store().update_grpc_credentials(&creds)?;
emit_json(&serde_json::json!({ "grpc_credentials": "updated" }))
}