162 lines
4.8 KiB
Rust
162 lines
4.8 KiB
Rust
//! 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, Box<dyn std::error::Error>> {
|
|
Self::_from_session_args(SessionArgs::parse(args)?)
|
|
}
|
|
|
|
pub fn compose(&self) -> Vec<String> {
|
|
self._to_session_args().compose()
|
|
}
|
|
|
|
fn _from_session_args(sa: SessionArgs) -> Result<Self, Box<dyn std::error::Error>> {
|
|
Ok(Self {
|
|
$(
|
|
$n: sa.0.get(stringify!($n))
|
|
.ok_or_else(|| Box::<dyn std::error::Error>::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<ModeInfo>,
|
|
}
|
|
|
|
#[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<Self, Box<dyn std::error::Error>> {
|
|
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<String> {
|
|
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<String, String>);
|
|
impl SessionArgs {
|
|
fn parse(args: &[String]) -> Result<Self, Box<dyn std::error::Error>> {
|
|
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<String> {
|
|
self.0.iter().map(|(k, v)| format!("{k}={v}")).collect()
|
|
}
|
|
}
|