Skip to main content

nifi_rust_client/config/
auth.rs

1#![deny(missing_docs)]
2//! Flexible authentication providers for NiFi.
3//!
4//! The [`AuthProvider`] trait abstracts how a NiFi client obtains its JWT
5//! token, enabling username/password login, pre-shared tokens, environment
6//! variable lookups, and custom strategies (vault, mTLS exchange, etc.).
7
8use std::fmt;
9use std::sync::Arc;
10
11use crate::NifiClient;
12use crate::error::NifiError;
13
14// ── AuthProvider trait ───────────────────────────────────────────────────────
15
16/// Provides authentication for a [`NifiClient`].
17///
18/// Implementors produce a valid JWT token and install it on the client via
19/// [`NifiClient::login`] or [`NifiClient::set_token`]. The trait is
20/// object-safe so it can be stored as `Arc<dyn AuthProvider>` in the client
21/// and builder.
22#[async_trait::async_trait]
23pub trait AuthProvider: Send + Sync + fmt::Debug {
24    /// Authenticate the given client.
25    ///
26    /// Implementations should obtain a JWT token and store it on the client
27    /// (e.g. by calling [`NifiClient::login`] or [`NifiClient::set_token`]).
28    async fn authenticate(&self, client: &NifiClient) -> Result<(), NifiError>;
29}
30
31// Allow `Arc<dyn AuthProvider>` to be used as an `AuthProvider` directly.
32#[async_trait::async_trait]
33impl AuthProvider for Arc<dyn AuthProvider> {
34    async fn authenticate(&self, client: &NifiClient) -> Result<(), NifiError> {
35        (**self).authenticate(client).await
36    }
37}
38
39// ── PasswordAuth ─────────────────────────────────────────────────────────────
40
41/// Authenticates with a fixed username and password via [`NifiClient::login`].
42///
43/// Useful for tests or simple deployments where credentials are known at
44/// build time. The password is stored in a [`zeroize::Zeroizing`] wrapper
45/// so it is zeroed on drop.
46#[derive(Clone)]
47pub struct PasswordAuth {
48    username: String,
49    password: zeroize::Zeroizing<String>,
50}
51
52impl PasswordAuth {
53    /// Create a new `PasswordAuth` with the given username and password.
54    pub fn new(username: impl Into<String>, password: impl Into<String>) -> Self {
55        Self {
56            username: username.into(),
57            password: zeroize::Zeroizing::new(password.into()),
58        }
59    }
60}
61
62impl fmt::Debug for PasswordAuth {
63    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
64        f.debug_struct("PasswordAuth")
65            .field("username", &self.username)
66            .field("password", &"[REDACTED]")
67            .finish()
68    }
69}
70
71#[async_trait::async_trait]
72impl AuthProvider for PasswordAuth {
73    async fn authenticate(&self, client: &NifiClient) -> Result<(), NifiError> {
74        client.login(&self.username, &self.password).await
75    }
76}
77
78// ── EnvPasswordAuth ──────────────────────────────────────────────────────────
79
80/// Reads username and password from environment variables, then authenticates
81/// via [`NifiClient::login`].
82///
83/// By default reads `NIFI_USERNAME` and `NIFI_PASSWORD`. Use
84/// [`EnvPasswordAuth::with_vars`] to override the variable names.
85#[derive(Debug, Clone)]
86pub struct EnvPasswordAuth {
87    username_var: String,
88    password_var: String,
89}
90
91impl EnvPasswordAuth {
92    /// Create an `EnvPasswordAuth` using the default environment variable
93    /// names (`NIFI_USERNAME` and `NIFI_PASSWORD`).
94    pub fn new() -> Self {
95        Self {
96            username_var: "NIFI_USERNAME".to_string(),
97            password_var: "NIFI_PASSWORD".to_string(),
98        }
99    }
100
101    /// Create an `EnvPasswordAuth` using custom environment variable names.
102    pub fn with_vars(username_var: impl Into<String>, password_var: impl Into<String>) -> Self {
103        Self {
104            username_var: username_var.into(),
105            password_var: password_var.into(),
106        }
107    }
108
109    /// Returns the environment variable name used for the username.
110    pub fn username_var(&self) -> &str {
111        &self.username_var
112    }
113
114    /// Returns the environment variable name used for the password.
115    pub fn password_var(&self) -> &str {
116        &self.password_var
117    }
118}
119
120impl Default for EnvPasswordAuth {
121    fn default() -> Self {
122        Self::new()
123    }
124}
125
126#[async_trait::async_trait]
127impl AuthProvider for EnvPasswordAuth {
128    async fn authenticate(&self, client: &NifiClient) -> Result<(), NifiError> {
129        let username = std::env::var(&self.username_var).map_err(|_| NifiError::Auth {
130            message: format!("environment variable {} is not set", self.username_var),
131        })?;
132        let password =
133            zeroize::Zeroizing::new(std::env::var(&self.password_var).map_err(|_| {
134                NifiError::Auth {
135                    message: format!("environment variable {} is not set", self.password_var),
136                }
137            })?);
138        client.login(&username, &password).await
139    }
140}
141
142// ── StaticTokenAuth ──────────────────────────────────────────────────────────
143
144/// Authenticates by installing a pre-obtained JWT token directly.
145///
146/// Useful when a token has been acquired externally (e.g. from a vault or
147/// a previous session) and does not require a login round-trip. The token
148/// is stored in a [`zeroize::Zeroizing`] wrapper so it is zeroed on drop.
149#[derive(Clone)]
150pub struct StaticTokenAuth {
151    token: zeroize::Zeroizing<String>,
152}
153
154impl StaticTokenAuth {
155    /// Create a new `StaticTokenAuth` with the given JWT token.
156    pub fn new(token: impl Into<String>) -> Self {
157        Self {
158            token: zeroize::Zeroizing::new(token.into()),
159        }
160    }
161}
162
163impl fmt::Debug for StaticTokenAuth {
164    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
165        f.debug_struct("StaticTokenAuth")
166            .field("token", &"[REDACTED]")
167            .finish()
168    }
169}
170
171#[async_trait::async_trait]
172impl AuthProvider for StaticTokenAuth {
173    async fn authenticate(&self, client: &NifiClient) -> Result<(), NifiError> {
174        client.set_token((*self.token).clone()).await;
175        Ok(())
176    }
177}