feat(daemon): wire TotpStore into AppState behind feature gate
This commit is contained in:
@@ -72,6 +72,15 @@ pub struct RecoveryCodeSummary {
|
|||||||
pub expires_unix: u64,
|
pub expires_unix: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, zvariant::Type)]
|
||||||
|
pub struct TotpEnrollment {
|
||||||
|
pub user: String,
|
||||||
|
/// Base32-encoded shared secret, no padding. ~32 chars for 160 bits.
|
||||||
|
pub secret_b32: String,
|
||||||
|
/// Full `otpauth://totp/...` URI suitable for QR encoding.
|
||||||
|
pub otpauth_uri: String,
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|||||||
@@ -257,6 +257,8 @@ central_path = "{}"
|
|||||||
policy_dir,
|
policy_dir,
|
||||||
pending_dir: tmp.path().join("pending"),
|
pending_dir: tmp.path().join("pending"),
|
||||||
recovery_dir: tmp.path().join("recovery"),
|
recovery_dir: tmp.path().join("recovery"),
|
||||||
|
#[cfg(feature = "totp")]
|
||||||
|
totp_dir: tmp.path().join("totp"),
|
||||||
userdb_path: tmp.path().join("users.db"),
|
userdb_path: tmp.path().join("users.db"),
|
||||||
pam_profile_path: tmp.path().join("authforge-pamconf"),
|
pam_profile_path: tmp.path().join("authforge-pamconf"),
|
||||||
pam_auth_update: shim,
|
pam_auth_update: shim,
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ use crate::storage::credentials::{CredEntry, CredentialsStore, CredsError, Creds
|
|||||||
use crate::storage::pending::{PendingError, PendingStore};
|
use crate::storage::pending::{PendingError, PendingStore};
|
||||||
use crate::storage::policy::PolicyStore;
|
use crate::storage::policy::PolicyStore;
|
||||||
use crate::storage::recovery::{RecoveryEntry, RecoveryStore, RecoveryStoreError};
|
use crate::storage::recovery::{RecoveryEntry, RecoveryStore, RecoveryStoreError};
|
||||||
|
#[cfg(feature = "totp")]
|
||||||
|
use crate::storage::totp::TotpStore;
|
||||||
use crate::storage::userdb::{UserDb, UserDbError};
|
use crate::storage::userdb::{UserDb, UserDbError};
|
||||||
use authforge_common::policy::{Policy, PolicyError};
|
use authforge_common::policy::{Policy, PolicyError};
|
||||||
use authforge_common::types::{
|
use authforge_common::types::{
|
||||||
@@ -32,12 +34,17 @@ pub enum StateError {
|
|||||||
Apply(#[from] ApplyError),
|
Apply(#[from] ApplyError),
|
||||||
#[error("authenticator: {0}")]
|
#[error("authenticator: {0}")]
|
||||||
Authn(String),
|
Authn(String),
|
||||||
|
#[allow(dead_code)] // wired through D-Bus in Task 5.
|
||||||
|
#[error("storage: {0}")]
|
||||||
|
Storage(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct StorageConfig {
|
pub struct StorageConfig {
|
||||||
pub policy_dir: PathBuf,
|
pub policy_dir: PathBuf,
|
||||||
pub pending_dir: PathBuf,
|
pub pending_dir: PathBuf,
|
||||||
pub recovery_dir: PathBuf,
|
pub recovery_dir: PathBuf,
|
||||||
|
#[cfg(feature = "totp")]
|
||||||
|
pub totp_dir: PathBuf,
|
||||||
pub userdb_path: PathBuf,
|
pub userdb_path: PathBuf,
|
||||||
/// Where pam-auth-update reads the AuthForge profile from. Defaults to
|
/// Where pam-auth-update reads the AuthForge profile from. Defaults to
|
||||||
/// `/usr/share/pam-configs/authforge`; tests / non-root runs override.
|
/// `/usr/share/pam-configs/authforge`; tests / non-root runs override.
|
||||||
@@ -58,6 +65,8 @@ impl StorageConfig {
|
|||||||
policy_dir: env("AUTHFORGE_POLICY_DIR", "/etc/authforge/policy.d"),
|
policy_dir: env("AUTHFORGE_POLICY_DIR", "/etc/authforge/policy.d"),
|
||||||
pending_dir: env("AUTHFORGE_PENDING_DIR", "/var/lib/authforge/pending"),
|
pending_dir: env("AUTHFORGE_PENDING_DIR", "/var/lib/authforge/pending"),
|
||||||
recovery_dir: env("AUTHFORGE_RECOVERY_DIR", "/var/lib/authforge/recovery"),
|
recovery_dir: env("AUTHFORGE_RECOVERY_DIR", "/var/lib/authforge/recovery"),
|
||||||
|
#[cfg(feature = "totp")]
|
||||||
|
totp_dir: env("AUTHFORGE_TOTP_DIR", "/etc/google-authenticator"),
|
||||||
userdb_path: env("AUTHFORGE_USERDB", "/var/lib/authforge/users.db"),
|
userdb_path: env("AUTHFORGE_USERDB", "/var/lib/authforge/users.db"),
|
||||||
pam_profile_path: env("AUTHFORGE_PAMCONF_PATH", "/usr/share/pam-configs/authforge"),
|
pam_profile_path: env("AUTHFORGE_PAMCONF_PATH", "/usr/share/pam-configs/authforge"),
|
||||||
pam_auth_update: env("AUTHFORGE_PAM_AUTH_UPDATE", "/usr/sbin/pam-auth-update"),
|
pam_auth_update: env("AUTHFORGE_PAM_AUTH_UPDATE", "/usr/sbin/pam-auth-update"),
|
||||||
@@ -70,6 +79,9 @@ pub struct AppState {
|
|||||||
pending: PendingStore,
|
pending: PendingStore,
|
||||||
#[allow(dead_code)] // wired through D-Bus in Task 6.
|
#[allow(dead_code)] // wired through D-Bus in Task 6.
|
||||||
recovery: RecoveryStore,
|
recovery: RecoveryStore,
|
||||||
|
#[cfg(feature = "totp")]
|
||||||
|
#[allow(dead_code)] // wired through D-Bus in Task 5.
|
||||||
|
totp: TotpStore,
|
||||||
userdb: Mutex<UserDb>,
|
userdb: Mutex<UserDb>,
|
||||||
authn: Arc<dyn Authenticator>,
|
authn: Arc<dyn Authenticator>,
|
||||||
applier: PolicyApplier,
|
applier: PolicyApplier,
|
||||||
@@ -80,12 +92,16 @@ impl AppState {
|
|||||||
let policy = PolicyStore::new(cfg.policy_dir);
|
let policy = PolicyStore::new(cfg.policy_dir);
|
||||||
let pending = PendingStore::new(cfg.pending_dir);
|
let pending = PendingStore::new(cfg.pending_dir);
|
||||||
let recovery = RecoveryStore::new(cfg.recovery_dir);
|
let recovery = RecoveryStore::new(cfg.recovery_dir);
|
||||||
|
#[cfg(feature = "totp")]
|
||||||
|
let totp = TotpStore::new(cfg.totp_dir);
|
||||||
let userdb = Mutex::new(UserDb::open(&cfg.userdb_path)?);
|
let userdb = Mutex::new(UserDb::open(&cfg.userdb_path)?);
|
||||||
let applier = PolicyApplier::new(cfg.pam_profile_path, cfg.pam_auth_update);
|
let applier = PolicyApplier::new(cfg.pam_profile_path, cfg.pam_auth_update);
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
policy,
|
policy,
|
||||||
pending,
|
pending,
|
||||||
recovery,
|
recovery,
|
||||||
|
#[cfg(feature = "totp")]
|
||||||
|
totp,
|
||||||
userdb,
|
userdb,
|
||||||
authn,
|
authn,
|
||||||
applier,
|
applier,
|
||||||
@@ -273,6 +289,37 @@ impl AppState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "totp")]
|
||||||
|
#[allow(dead_code)] // wired through D-Bus in Task 5.
|
||||||
|
impl AppState {
|
||||||
|
pub async fn enroll_totp(
|
||||||
|
&self,
|
||||||
|
user: &str,
|
||||||
|
) -> Result<authforge_common::types::TotpEnrollment, StateError> {
|
||||||
|
let inner = self
|
||||||
|
.totp
|
||||||
|
.enroll(user, "AuthForge")
|
||||||
|
.map_err(|e| StateError::Storage(e.to_string()))?;
|
||||||
|
Ok(authforge_common::types::TotpEnrollment {
|
||||||
|
user: inner.user,
|
||||||
|
secret_b32: inner.secret_b32,
|
||||||
|
otpauth_uri: inner.otpauth_uri,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn is_totp_enrolled(&self, user: &str) -> Result<bool, StateError> {
|
||||||
|
self.totp
|
||||||
|
.is_enrolled(user)
|
||||||
|
.map_err(|e| StateError::Storage(e.to_string()))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn revoke_totp(&self, user: &str) -> Result<bool, StateError> {
|
||||||
|
self.totp
|
||||||
|
.revoke(user)
|
||||||
|
.map_err(|e| StateError::Storage(e.to_string()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
@@ -293,6 +340,8 @@ mod tests {
|
|||||||
policy_dir: d.join("policy.d"),
|
policy_dir: d.join("policy.d"),
|
||||||
pending_dir: d.join("pending"),
|
pending_dir: d.join("pending"),
|
||||||
recovery_dir: d.join("recovery"),
|
recovery_dir: d.join("recovery"),
|
||||||
|
#[cfg(feature = "totp")]
|
||||||
|
totp_dir: d.join("totp"),
|
||||||
userdb_path: d.join("users.db"),
|
userdb_path: d.join("users.db"),
|
||||||
pam_profile_path: d.join("authforge-pamconf"),
|
pam_profile_path: d.join("authforge-pamconf"),
|
||||||
pam_auth_update: shim,
|
pam_auth_update: shim,
|
||||||
@@ -353,6 +402,8 @@ mod tests {
|
|||||||
policy_dir,
|
policy_dir,
|
||||||
pending_dir: d.path().join("pending"),
|
pending_dir: d.path().join("pending"),
|
||||||
recovery_dir: d.path().join("recovery"),
|
recovery_dir: d.path().join("recovery"),
|
||||||
|
#[cfg(feature = "totp")]
|
||||||
|
totp_dir: d.path().join("totp"),
|
||||||
userdb_path: d.path().join("users.db"),
|
userdb_path: d.path().join("users.db"),
|
||||||
pam_profile_path: d.path().join("authforge-pamconf"),
|
pam_profile_path: d.path().join("authforge-pamconf"),
|
||||||
pam_auth_update: shim,
|
pam_auth_update: shim,
|
||||||
|
|||||||
Reference in New Issue
Block a user