Files
account/lib/semios_account/src/auth.rs
T
sisungo bfcc98b50e initial commit
Signed-off-by: sisungo <[email protected]>
2026-07-18 20:30:39 +08:00

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()
}
}