From 62b3c643134512dbf64e974b100098b7c1cad049 Mon Sep 17 00:00:00 2001 From: Laurence Tratt Date: Tue, 2 Jan 2024 08:59:21 +0000 Subject: [PATCH] Implement `pizauth revoke`. Currently this is local-only as OAuth2 provides no standard way of revoking remote tokens (though some providers do provide ad-hoc extensions to do so). --- pizauth.1 | 9 +++++++++ pizauth.conf.5 | 5 ++++- src/main.rs | 17 ++++++++++++++++- src/server/eventer.rs | 2 ++ src/server/mod.rs | 23 ++++++++++++++++++++++- src/user_sender.rs | 18 ++++++++++++++++++ 6 files changed, 71 insertions(+), 3 deletions(-) diff --git a/pizauth.1 b/pizauth.1 index d962a3a..5e4d512 100644 --- a/pizauth.1 +++ b/pizauth.1 @@ -84,6 +84,15 @@ See .Sy dump for information about the dump format, timestamp warnings, and encryption suggestions. +.It Sy revoke Ar account +Removes any token, and cancels any ongoing authentication, for +.Em account . +Note that OAuth2 provides no standard way of remotely revoking a token: +.Sy revoke +thus only affects the local +.Nm +instance. +Exits with 0 upon success. .It Sy server Oo Fl c Ar config-file Oc Oo Fl dv Oc Start the server. If not specified with diff --git a/pizauth.conf.5 b/pizauth.conf.5 index 759e17a..4346d82 100644 --- a/pizauth.conf.5 +++ b/pizauth.conf.5 @@ -84,7 +84,10 @@ if a previously valid access token is invalidated; .Em token_new if a new access token is obtained; .Em token_refreshed -if an access token is refreshed. +if an access token is refreshed; +.Em token_revoked +if the user has requested that any token, or ongoing authentication for, +an account should be removed or cancelled. Token events are queued and processed one-by-one in the order they were received: at most one instance of .Sy token_event_cmd diff --git a/src/main.rs b/src/main.rs index 0bd328b..9804ed3 100644 --- a/src/main.rs +++ b/src/main.rs @@ -54,7 +54,7 @@ fn fatal(msg: &str) -> ! { fn usage() -> ! { let pn = progname(); eprintln!( - "Usage:\n {pn:} dump\n {pn:} info [-j]\n {pn:} refresh [-u] \n {pn:} restore\n {pn:} reload\n {pn:} server [-c ] [-dv]\n {pn:} show [-u] \n {pn:} shutdown\n {pn:} status" + "Usage:\n {pn:} dump\n {pn:} info [-j]\n {pn:} refresh [-u] \n {pn:} restore\n {pn:} reload\n {pn:} revoke \n {pn:} server [-c ] [-dv]\n {pn:} show [-u] \n {pn:} shutdown\n {pn:} status" ); process::exit(1) } @@ -223,6 +223,21 @@ fn main() { process::exit(1); } } + "revoke" => { + let matches = opts.parse(&args[2..]).unwrap_or_else(|_| usage()); + if matches.opt_present("h") || matches.free.len() != 1 { + usage(); + } + stderrlog::new() + .module(module_path!()) + .verbosity(matches.opt_count("v")) + .init() + .unwrap(); + if let Err(e) = user_sender::revoke(&cache_path, &matches.free[0]) { + error!("{e:}"); + process::exit(1); + } + } "server" => { let matches = opts .optflagopt("c", "config", "Path to pizauth.conf.", "") diff --git a/src/server/eventer.rs b/src/server/eventer.rs index c66c6c9..49e6111 100644 --- a/src/server/eventer.rs +++ b/src/server/eventer.rs @@ -21,6 +21,7 @@ pub enum TokenEvent { Invalidated, New, Refresh, + Revoked, } impl Display for TokenEvent { @@ -29,6 +30,7 @@ impl Display for TokenEvent { TokenEvent::Invalidated => write!(f, "token_invalidated"), TokenEvent::New => write!(f, "token_new"), TokenEvent::Refresh => write!(f, "token_refreshed"), + TokenEvent::Revoked => write!(f, "token_revoked"), } } } diff --git a/src/server/mod.rs b/src/server/mod.rs index b973820..42fd75d 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -23,7 +23,7 @@ use pledge::pledge; use unveil::unveil; use crate::{config::Config, PIZAUTH_CACHE_SOCK_LEAF}; -use eventer::Eventer; +use eventer::{Eventer, TokenEvent}; use notifier::Notifier; use refresher::Refresher; use request_token::request_token; @@ -130,6 +130,27 @@ fn request(pstate: Arc, mut stream: UnixStream) -> Result<() } return Ok(()); } + "revoke" => { + let act_name = std::str::from_utf8(rest)?; + let mut ct_lk = pstate.ct_lock(); + match ct_lk.validate_act_name(act_name) { + Some(act_id) => { + ct_lk.tokenstate_replace(act_id, TokenState::Empty); + drop(ct_lk); + + pstate + .eventer + .token_event(act_name.to_owned(), TokenEvent::Revoked); + stream.write_all(format!("ok:").as_bytes())?; + return Ok(()); + } + None => { + drop(ct_lk); + stream.write_all(format!("error:No account '{act_name:}'").as_bytes())?; + return Ok(()); + } + }; + } "showtoken" => { let rest = std::str::from_utf8(rest)?; if let [with_url, act_name] = &rest.splitn(2, ' ').collect::>()[..] { diff --git a/src/user_sender.rs b/src/user_sender.rs index 4b0051f..51772ac 100644 --- a/src/user_sender.rs +++ b/src/user_sender.rs @@ -83,6 +83,24 @@ pub fn restore(cache_path: &Path) -> Result<(), Box> { } } +pub fn revoke(cache_path: &Path, account: &str) -> Result<(), Box> { + let sock_path = sock_path(cache_path); + let mut stream = UnixStream::connect(sock_path) + .map_err(|_| "pizauth authenticator not running or not responding")?; + stream + .write_all(format!("revoke:{account}").as_bytes()) + .map_err(|_| "Socket not writeable")?; + stream.shutdown(Shutdown::Write)?; + + let mut rtn = String::new(); + stream.read_to_string(&mut rtn)?; + match rtn.splitn(2, ':').collect::>()[..] { + ["ok", ""] => Ok(()), + ["error", cause] => Err(cause.into()), + _ => Err(format!("Malformed response '{rtn:}'").into()), + } +} + pub fn show_token(cache_path: &Path, account: &str, with_url: bool) -> Result<(), Box> { let sock_path = sock_path(cache_path); let with_url = if with_url { "withurl" } else { "withouturl" };