From 313bac8af43a4a4ee9e6fe616a6f640780551226 Mon Sep 17 00:00:00 2001 From: Dave Liu Date: Wed, 1 Jul 2026 09:24:04 +0900 Subject: [PATCH 1/2] Add logout command --- README.md | 6 ++++++ src/config.rs | 13 +++++++++++++ src/main.rs | 18 ++++++++++++++++-- 3 files changed, 35 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 9c34cff..988d6e6 100644 --- a/README.md +++ b/README.md @@ -104,6 +104,12 @@ blackcandy config The config file is written with user-only permissions on Unix systems. +To remove the stored credentials, log out: + +```sh +blackcandy logout +``` + For isolated test runs, point `BLACKCANDY_CONFIG` at a temporary file: ```sh diff --git a/src/config.rs b/src/config.rs index 4d19fe4..cd46c72 100644 --- a/src/config.rs +++ b/src/config.rs @@ -59,6 +59,19 @@ impl Config { } } +/// Remove the stored config file. Returns `true` if a file was deleted and +/// `false` if there was nothing to remove. +pub fn remove_config() -> Result { + let path = config_path()?; + if !path.exists() { + return Ok(false); + } + + fs::remove_file(&path) + .with_context(|| format!("failed to remove config at {}", path.display()))?; + Ok(true) +} + pub fn config_path() -> Result { if let Ok(path) = std::env::var("BLACKCANDY_CONFIG") { return Ok(PathBuf::from(path)); diff --git a/src/main.rs b/src/main.rs index 1841b4a..f5dd585 100644 --- a/src/main.rs +++ b/src/main.rs @@ -9,7 +9,7 @@ use std::io::{self, Write}; use crate::{ api::{ApiClient, SongQuery, validate_limit}, - config::{Config, config_path}, + config::{Config, config_path, remove_config}, }; #[derive(Debug, Parser)] @@ -25,6 +25,8 @@ struct Cli { enum Command { /// Log in to a Black Candy server and store an API token. Login(LoginArgs), + /// Log out by removing the stored credentials. + Logout, /// Show the configured server and current config path. Config, /// Show Black Candy server version information. @@ -194,6 +196,7 @@ async fn main() -> Result<()> { match cli.command { Command::Login(args) => login(args).await, + Command::Logout => logout(), Command::Config => show_config(), command => { let config = Config::load()?; @@ -237,6 +240,15 @@ async fn login(args: LoginArgs) -> Result<()> { Ok(()) } +fn logout() -> Result<()> { + if remove_config()? { + println!("Logged out. Removed config at {}.", config_path()?.display()); + } else { + println!("Not logged in; nothing to remove."); + } + Ok(()) +} + fn prompt_line(prompt: &str) -> Result { print!("{prompt}"); io::stdout().flush()?; @@ -299,7 +311,9 @@ async fn run_authenticated(command: Command, config: Config, client: ApiClient) Command::Queue(args) => run_queue(args, &client).await?, Command::Favorite(args) => run_favorite(args, &client).await?, Command::Playlist(args) => run_playlist(args, &client).await?, - Command::Login(_) | Command::Config => unreachable!("handled before authentication setup"), + Command::Login(_) | Command::Logout | Command::Config => { + unreachable!("handled before authentication setup") + } } Ok(()) From 40883c09c32d14ae35eb8aff8e5c3b1b3262ce84 Mon Sep 17 00:00:00 2001 From: Dave Liu Date: Fri, 10 Jul 2026 15:40:35 +0900 Subject: [PATCH 2/2] Revoke server session on logout --- README.md | 2 +- src/api.rs | 4 ++++ src/main.rs | 15 ++++++++++++--- 3 files changed, 17 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 988d6e6..0a6a6a2 100644 --- a/README.md +++ b/README.md @@ -104,7 +104,7 @@ blackcandy config The config file is written with user-only permissions on Unix systems. -To remove the stored credentials, log out: +To revoke the current server session and remove the stored credentials, log out: ```sh blackcandy logout diff --git a/src/api.rs b/src/api.rs index 34b2be9..5887adc 100644 --- a/src/api.rs +++ b/src/api.rs @@ -46,6 +46,10 @@ impl ApiClient { .await } + pub async fn logout(&self) -> Result<()> { + self.delete_empty("my/session").await + } + pub async fn system(&self) -> Result { self.get_without_auth("system", &[]).await } diff --git a/src/main.rs b/src/main.rs index f5dd585..e119298 100644 --- a/src/main.rs +++ b/src/main.rs @@ -196,7 +196,7 @@ async fn main() -> Result<()> { match cli.command { Command::Login(args) => login(args).await, - Command::Logout => logout(), + Command::Logout => logout().await, Command::Config => show_config(), command => { let config = Config::load()?; @@ -240,9 +240,18 @@ async fn login(args: LoginArgs) -> Result<()> { Ok(()) } -fn logout() -> Result<()> { +async fn logout() -> Result<()> { + let config = Config::load()?; + if config.api_token.is_some() { + let client = configured_client(&config)?; + client.logout().await?; + } + if remove_config()? { - println!("Logged out. Removed config at {}.", config_path()?.display()); + println!( + "Logged out. Removed config at {}.", + config_path()?.display() + ); } else { println!("Not logged in; nothing to remove."); }