api: tfa management and login
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
This commit is contained in:
@ -600,8 +600,9 @@ fn check_auth(
|
||||
let ticket = user_auth_data.ticket.clone();
|
||||
let ticket_lifetime = tools::ticket::TICKET_LIFETIME;
|
||||
|
||||
let userid: Userid = Ticket::parse(&ticket)?
|
||||
.verify_with_time_frame(public_auth_key(), "PBS", None, -300..ticket_lifetime)?;
|
||||
let userid: Userid = Ticket::<super::ticket::ApiTicket>::parse(&ticket)?
|
||||
.verify_with_time_frame(public_auth_key(), "PBS", None, -300..ticket_lifetime)?
|
||||
.require_full()?;
|
||||
|
||||
let auth_id = Authid::from(userid.clone());
|
||||
if !user_info.is_active_auth_id(&auth_id) {
|
||||
|
77
src/server/ticket.rs
Normal file
77
src/server/ticket.rs
Normal file
@ -0,0 +1,77 @@
|
||||
use std::fmt;
|
||||
|
||||
use anyhow::{bail, Error};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::api2::types::Userid;
|
||||
use crate::config::tfa;
|
||||
|
||||
#[derive(Deserialize, Serialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct PartialTicket {
|
||||
#[serde(rename = "u")]
|
||||
userid: Userid,
|
||||
|
||||
#[serde(rename = "c")]
|
||||
challenge: tfa::TfaChallenge,
|
||||
}
|
||||
|
||||
/// A new ticket struct used in rest.rs's `check_auth` - mostly for better errors than failing to
|
||||
/// parse the userid ticket content.
|
||||
pub enum ApiTicket {
|
||||
Full(Userid),
|
||||
Partial(tfa::TfaChallenge),
|
||||
}
|
||||
|
||||
impl ApiTicket {
|
||||
/// Require the ticket to be a full ticket, otherwise error with a meaningful error message.
|
||||
pub fn require_full(self) -> Result<Userid, Error> {
|
||||
match self {
|
||||
ApiTicket::Full(userid) => Ok(userid),
|
||||
ApiTicket::Partial(_) => bail!("access denied - second login factor required"),
|
||||
}
|
||||
}
|
||||
|
||||
/// Expect the ticket to contain a tfa challenge, otherwise error with a meaningful error
|
||||
/// message.
|
||||
pub fn require_partial(self) -> Result<tfa::TfaChallenge, Error> {
|
||||
match self {
|
||||
ApiTicket::Full(_) => bail!("invalid tfa challenge"),
|
||||
ApiTicket::Partial(challenge) => Ok(challenge),
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new full ticket.
|
||||
pub fn full(userid: Userid) -> Self {
|
||||
ApiTicket::Full(userid)
|
||||
}
|
||||
|
||||
/// Create a new partial ticket.
|
||||
pub fn partial(challenge: tfa::TfaChallenge) -> Self {
|
||||
ApiTicket::Partial(challenge)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for ApiTicket {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match self {
|
||||
ApiTicket::Full(userid) => fmt::Display::fmt(userid, f),
|
||||
ApiTicket::Partial(partial) => {
|
||||
let data = serde_json::to_string(partial).map_err(|_| fmt::Error)?;
|
||||
write!(f, "!tfa!{}", data)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::str::FromStr for ApiTicket {
|
||||
type Err = Error;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Error> {
|
||||
if s.starts_with("!tfa!") {
|
||||
Ok(ApiTicket::Partial(serde_json::from_str(&s[5..])?))
|
||||
} else {
|
||||
Ok(ApiTicket::Full(s.parse()?))
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user