//! User authentication. use bitflags::bitflags; use serde::{Deserialize, Serialize}; use std::collections::HashMap; use uuid::Uuid; macro_rules! decl_session_args { (pub struct $sn:ident { $(pub $n:ident : $t:ty,)* }) => { #[derive(Debug, Clone)] pub struct $sn { $(pub $n: $t),* } impl $sn { pub fn parse(args: &[String]) -> Result> { Self::_from_session_args(SessionArgs::parse(args)?) } pub fn compose(&self) -> Vec { self._to_session_args().compose() } fn _from_session_args(sa: SessionArgs) -> Result> { Ok(Self { $( $n: sa.0.get(stringify!($n)) .ok_or_else(|| Box::::from(format!( "argument {} not found", stringify!($n), )))? .parse::<$t>()? ),* }) } fn _to_session_args(&self) -> SessionArgs { let mut map = HashMap::new(); $( map.insert(stringify!($n).into(), self.$n.to_string()); )* SessionArgs(map) } } }; } /// Maximum count of auth methods enabled for one user. pub const USER_MAX_AUTH_METHODS: usize = 32; /// Record of an auth method enabled for a user. #[derive(Debug, Clone, Serialize, Deserialize)] pub struct UserAuthMethodRecord { /// Name of the auth method. pub name: String, /// Flags of the auth method. pub flags: UserAuthMethodFlags, } bitflags! { #[derive(Debug, Clone, Copy, Serialize, Deserialize)] pub struct UserAuthMethodFlags: u32 { /// Indicates if the auth method is a "main" auth method. /// /// A "main" auth method is unique for a user. It provides a master key for secret storage. const MAIN = 1; } } /// Information about an authentication method. #[derive(Debug, Clone, Serialize, Deserialize)] pub struct AuthMethodInfo { /// Name of the auth method. pub name: String, /// True if the auth method supports providing the master key. pub provides_master_key: bool, /// Supported operation modes. pub modes: Vec, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ModeInfo { /// Name of the operation mode. pub name: String, } /// Command line of an authentication method provider. #[derive(Debug, Clone)] pub enum AuthCli { /// Queries information about the auth method. /// /// The implementor should output an [`AuthMethod`] in JSON to stdout stream, and then exit. QueryInformation, /// Begins an authentication session. Authenticate(AuthenticateArgs), /// Begins an update session. Update(UpdateArgs), } impl AuthCli { pub fn parse(args: &[String]) -> Result> { match args.first().map(String::as_str) { Some("QueryInformation") => Ok(Self::QueryInformation), Some("Authenticate") => Ok(Self::Authenticate(AuthenticateArgs::parse(&args[1..])?)), Some("Update") => Ok(Self::Update(UpdateArgs::parse(&args[1..])?)), Some(unexpected) => Err(Box::from(format!("unexpected command `{unexpected}`"))), None => Err(Box::from("no command specified")), } } pub fn compose(&self) -> Vec { match self { Self::QueryInformation => vec!["QueryInformation".into()], Self::Authenticate(args) => { let mut ret = vec!["Authenticate".into()]; ret.append(&mut args.compose()); ret } Self::Update(args) => { let mut ret = vec!["Update".into()]; ret.append(&mut args.compose()); ret } } } } decl_session_args! { pub struct AuthenticateArgs { pub user_uuid: Uuid, pub mode: String, } } decl_session_args! { pub struct UpdateArgs { pub user_uuid: Uuid, pub mode: String, } } #[derive(Debug, Clone)] struct SessionArgs(HashMap); impl SessionArgs { fn parse(args: &[String]) -> Result> { let mut map = HashMap::with_capacity(args.len()); for arg in args { let Some((key, val)) = arg.split_once('=') else { return Err(Box::from("invalid argument format")); }; map.insert(key.into(), val.into()); } Ok(Self(map)) } fn compose(&self) -> Vec { self.0.iter().map(|(k, v)| format!("{k}={v}")).collect() } }