mirror of
https://github.com/kanidm/kanidm.git
synced 2025-05-18 23:13:56 +02:00
190 lines
5.9 KiB
Rust
190 lines
5.9 KiB
Rust
use serde::{Deserialize, Serialize};
|
|
use std::cmp::Ordering;
|
|
use std::fmt;
|
|
use utoipa::ToSchema;
|
|
use uuid::Uuid;
|
|
|
|
use webauthn_rs_proto::PublicKeyCredential;
|
|
use webauthn_rs_proto::RequestChallengeResponse;
|
|
|
|
/// Authentication to Kanidm is a stepped process.
|
|
///
|
|
/// The session is fist initialised with the requested username.
|
|
///
|
|
/// In response the list of supported authentication mechanisms is provided.
|
|
///
|
|
/// The user chooses the authentication mechanism to proceed with.
|
|
///
|
|
/// The server responds with a challenge that the user provides a credential
|
|
/// to satisfy. This challenge and response process continues until a credential
|
|
/// fails to validate, an error occurs, or successful authentication is complete.
|
|
#[derive(Debug, Serialize, Deserialize, ToSchema)]
|
|
#[serde(rename_all = "lowercase")]
|
|
pub enum AuthStep {
|
|
/// Initialise a new authentication session
|
|
Init(String),
|
|
/// Initialise a new authentication session with extra flags
|
|
/// for requesting different types of session tokens or
|
|
/// immediate access to privileges.
|
|
Init2 {
|
|
username: String,
|
|
issue: AuthIssueSession,
|
|
#[serde(default)]
|
|
/// If true, the session will have r/w access.
|
|
privileged: bool,
|
|
},
|
|
/// Request the named authentication mechanism to proceed
|
|
Begin(AuthMech),
|
|
/// Provide a credential in response to a challenge
|
|
Cred(AuthCredential),
|
|
}
|
|
|
|
/// The response to an AuthStep request.
|
|
#[derive(Debug, Serialize, Deserialize, ToSchema)]
|
|
#[serde(rename_all = "lowercase")]
|
|
pub enum AuthState {
|
|
/// You need to select how you want to proceed.
|
|
Choose(Vec<AuthMech>),
|
|
/// Continue to auth, allowed mechanisms/challenges listed.
|
|
Continue(Vec<AuthAllowed>),
|
|
/// Something was bad, your session is terminated and no cookie.
|
|
Denied(String),
|
|
/// Everything is good, your bearer token has been issued and is within.
|
|
Success(String),
|
|
}
|
|
|
|
/// The credential challenge provided by a user.
|
|
#[derive(Serialize, Deserialize, ToSchema)]
|
|
#[serde(rename_all = "lowercase")]
|
|
pub enum AuthCredential {
|
|
Anonymous,
|
|
Password(String),
|
|
Totp(u32),
|
|
SecurityKey(Box<PublicKeyCredential>),
|
|
BackupCode(String),
|
|
// Should this just be discoverable?
|
|
Passkey(Box<PublicKeyCredential>),
|
|
}
|
|
|
|
impl fmt::Debug for AuthCredential {
|
|
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
|
|
match self {
|
|
AuthCredential::Anonymous => write!(fmt, "Anonymous"),
|
|
AuthCredential::Password(_) => write!(fmt, "Password(_)"),
|
|
AuthCredential::Totp(_) => write!(fmt, "TOTP(_)"),
|
|
AuthCredential::SecurityKey(_) => write!(fmt, "SecurityKey(_)"),
|
|
AuthCredential::BackupCode(_) => write!(fmt, "BackupCode(_)"),
|
|
AuthCredential::Passkey(_) => write!(fmt, "Passkey(_)"),
|
|
}
|
|
}
|
|
}
|
|
|
|
/// The mechanisms that may proceed in this authentication
|
|
#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialOrd, Ord, ToSchema)]
|
|
#[serde(rename_all = "lowercase")]
|
|
pub enum AuthMech {
|
|
Anonymous,
|
|
Password,
|
|
PasswordMfa,
|
|
Passkey,
|
|
}
|
|
|
|
impl PartialEq for AuthMech {
|
|
fn eq(&self, other: &Self) -> bool {
|
|
std::mem::discriminant(self) == std::mem::discriminant(other)
|
|
}
|
|
}
|
|
|
|
impl fmt::Display for AuthMech {
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
match self {
|
|
AuthMech::Anonymous => write!(f, "Anonymous (no credentials)"),
|
|
AuthMech::Password => write!(f, "Password"),
|
|
AuthMech::PasswordMfa => write!(f, "TOTP/Backup Code and Password"),
|
|
AuthMech::Passkey => write!(f, "Passkey"),
|
|
}
|
|
}
|
|
}
|
|
|
|
/// The type of session that should be issued to the client.
|
|
#[derive(Debug, Serialize, Deserialize, Copy, Clone, ToSchema)]
|
|
#[serde(rename_all = "lowercase")]
|
|
pub enum AuthIssueSession {
|
|
// Previously supported other types beside token.
|
|
Token,
|
|
}
|
|
|
|
/// A request for the next step of an authentication.
|
|
#[derive(Debug, Serialize, Deserialize, ToSchema)]
|
|
pub struct AuthRequest {
|
|
pub step: AuthStep,
|
|
}
|
|
|
|
/// A challenge containing the list of allowed authentication types
|
|
/// that can satisfy the next step. These may have inner types with
|
|
/// required context.
|
|
#[derive(Debug, Serialize, Deserialize, Clone, ToSchema)]
|
|
#[serde(rename_all = "lowercase")]
|
|
pub enum AuthAllowed {
|
|
Anonymous,
|
|
BackupCode,
|
|
Password,
|
|
Totp,
|
|
SecurityKey(RequestChallengeResponse),
|
|
Passkey(RequestChallengeResponse),
|
|
}
|
|
|
|
impl PartialEq for AuthAllowed {
|
|
fn eq(&self, other: &Self) -> bool {
|
|
std::mem::discriminant(self) == std::mem::discriminant(other)
|
|
}
|
|
}
|
|
|
|
impl From<&AuthAllowed> for u8 {
|
|
fn from(a: &AuthAllowed) -> u8 {
|
|
match a {
|
|
AuthAllowed::Anonymous => 0,
|
|
AuthAllowed::Password => 1,
|
|
AuthAllowed::BackupCode => 2,
|
|
AuthAllowed::Totp => 3,
|
|
AuthAllowed::Passkey(_) => 4,
|
|
AuthAllowed::SecurityKey(_) => 5,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Eq for AuthAllowed {}
|
|
|
|
impl Ord for AuthAllowed {
|
|
fn cmp(&self, other: &Self) -> Ordering {
|
|
let self_ord: u8 = self.into();
|
|
let other_ord: u8 = other.into();
|
|
self_ord.cmp(&other_ord)
|
|
}
|
|
}
|
|
|
|
impl PartialOrd for AuthAllowed {
|
|
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
|
Some(self.cmp(other))
|
|
}
|
|
}
|
|
|
|
impl fmt::Display for AuthAllowed {
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
match self {
|
|
AuthAllowed::Anonymous => write!(f, "Anonymous (no credentials)"),
|
|
AuthAllowed::Password => write!(f, "Password"),
|
|
AuthAllowed::BackupCode => write!(f, "Backup Code"),
|
|
AuthAllowed::Totp => write!(f, "TOTP"),
|
|
AuthAllowed::SecurityKey(_) => write!(f, "Security Token"),
|
|
AuthAllowed::Passkey(_) => write!(f, "Passkey"),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Serialize, Deserialize, ToSchema)]
|
|
pub struct AuthResponse {
|
|
pub sessionid: Uuid,
|
|
pub state: AuthState,
|
|
}
|