1pub mod action_catalog;
15pub mod browser_token;
16pub mod cert;
17pub mod column_policy_gate;
18pub mod enforcement_mode;
19pub mod locks;
20pub mod managed_config;
21pub mod managed_policy;
22pub mod middleware;
23pub mod migrate_policy_mode;
24pub mod oauth;
25pub mod policies;
26pub mod policy_linter;
27pub mod privileges;
28pub mod registry;
29pub mod scope_cache;
30pub mod scram;
31pub mod self_lock_guard;
32pub mod store;
33pub mod vault;
34
35pub use scope_cache::{AuthCache, AuthCacheStats, ScopeKey, DEFAULT_TTL as DEFAULT_SCOPE_TTL};
36
37pub use cert::{
38 CertAuthConfig, CertAuthError, CertAuthenticator, CertIdentity, CertIdentityMode,
39 ParsedClientCert,
40};
41pub use column_policy_gate::{
42 ColumnAccessRequest, ColumnDecision, ColumnDecisionEffect, ColumnPolicyGate,
43 ColumnPolicyOutcome, ColumnRef,
44};
45pub use oauth::{
46 DecodedJwt, Jwk, JwtClaims, JwtHeader, OAuthConfig, OAuthError, OAuthIdentity,
47 OAuthIdentityMode, OAuthValidator,
48};
49pub use privileges::{
50 check_grant, Action, AuthzContext, AuthzError, Grant, GrantPrincipal, GrantsView,
51 PermissionCache, Resource, UserAttributes,
52};
53pub use store::AuthStore;
54
55use std::fmt;
56
57#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
65pub enum Role {
66 Read,
67 Write,
68 Admin,
69}
70
71impl Role {
72 pub fn as_str(&self) -> &'static str {
73 match self {
74 Self::Read => "read",
75 Self::Write => "write",
76 Self::Admin => "admin",
77 }
78 }
79
80 pub fn from_str(s: &str) -> Option<Self> {
81 match s {
82 "read" => Some(Self::Read),
83 "write" => Some(Self::Write),
84 "admin" => Some(Self::Admin),
85 _ => None,
86 }
87 }
88
89 pub fn can_read(&self) -> bool {
90 true
91 }
92
93 pub fn can_write(&self) -> bool {
94 matches!(self, Self::Write | Self::Admin)
95 }
96
97 pub fn can_admin(&self) -> bool {
98 matches!(self, Self::Admin)
99 }
100}
101
102impl fmt::Display for Role {
103 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
104 f.write_str(self.as_str())
105 }
106}
107
108#[derive(Clone)]
120pub struct User {
121 pub username: String,
122 pub tenant_id: Option<String>,
126 pub password_hash: String,
127 pub scram_verifier: Option<scram::ScramVerifier>,
130 pub role: Role,
131 pub api_keys: Vec<ApiKey>,
132 pub created_at: u128,
133 pub updated_at: u128,
134 pub enabled: bool,
135}
136
137#[derive(Debug, Clone, PartialEq, Eq, Hash)]
152pub struct UserId {
153 pub tenant: Option<String>,
154 pub username: String,
155}
156
157impl UserId {
158 pub fn platform(name: impl Into<String>) -> Self {
160 Self {
161 tenant: None,
162 username: name.into(),
163 }
164 }
165
166 pub fn scoped(tenant: impl Into<String>, name: impl Into<String>) -> Self {
168 Self {
169 tenant: Some(tenant.into()),
170 username: name.into(),
171 }
172 }
173
174 pub fn from_parts(tenant: Option<&str>, username: &str) -> Self {
177 Self {
178 tenant: tenant.map(|t| t.to_string()),
179 username: username.to_string(),
180 }
181 }
182}
183
184impl fmt::Display for UserId {
185 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
186 match &self.tenant {
187 Some(t) => write!(f, "{}/{}", t, self.username),
188 None => f.write_str(&self.username),
189 }
190 }
191}
192
193#[derive(Debug, Clone)]
199pub struct ApiKey {
200 pub key: String,
207 pub name: String,
209 pub role: Role,
211 pub created_at: u128,
212 pub expires_at: Option<u128>,
214}
215
216#[derive(Clone)]
222pub struct Session {
223 pub token: String,
225 pub username: String,
226 pub tenant_id: Option<String>,
229 pub role: Role,
230 pub created_at: u128,
231 pub expires_at: u128,
233}
234
235#[derive(Debug, Clone)]
241pub struct AuthConfig {
242 pub enabled: bool,
244 pub session_ttl_secs: u64,
246 pub require_auth: bool,
248 pub auto_encrypt_storage: bool,
250 pub vault_enabled: bool,
255 pub cert: CertAuthConfig,
258 pub oauth: OAuthConfig,
261}
262
263impl Default for AuthConfig {
264 fn default() -> Self {
265 Self {
266 enabled: false,
267 session_ttl_secs: 3600,
268 require_auth: false,
269 auto_encrypt_storage: false,
270 vault_enabled: false,
271 cert: CertAuthConfig::default(),
272 oauth: OAuthConfig::default(),
273 }
274 }
275}
276
277#[derive(Debug, Clone)]
283pub enum AuthError {
284 UserExists(String),
285 UserNotFound(String),
286 InvalidCredentials,
287 KeyNotFound(String),
288 RoleExceeded { requested: Role, ceiling: Role },
289 Disabled,
290 Forbidden(String),
291 Internal(String),
292}
293
294impl fmt::Display for AuthError {
295 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
296 match self {
297 Self::UserExists(u) => write!(f, "user already exists: {u}"),
298 Self::UserNotFound(u) => write!(f, "user not found: {u}"),
299 Self::InvalidCredentials => write!(f, "invalid credentials"),
300 Self::KeyNotFound(k) => write!(f, "api key not found: {k}"),
301 Self::RoleExceeded { requested, ceiling } => {
302 write!(
303 f,
304 "requested role '{requested}' exceeds ceiling '{ceiling}'"
305 )
306 }
307 Self::Disabled => write!(f, "authentication is disabled"),
308 Self::Forbidden(msg) => write!(f, "forbidden: {msg}"),
309 Self::Internal(msg) => write!(f, "internal auth error: {msg}"),
310 }
311 }
312}
313
314impl std::error::Error for AuthError {}
315
316pub(crate) fn now_ms() -> u128 {
322 std::time::SystemTime::now()
323 .duration_since(std::time::UNIX_EPOCH)
324 .unwrap_or_default()
325 .as_millis()
326}
327
328#[cfg(test)]
333mod tests {
334 use super::*;
335
336 #[test]
337 fn test_role_ordering() {
338 assert!(Role::Read < Role::Write);
339 assert!(Role::Write < Role::Admin);
340 }
341
342 #[test]
343 fn test_role_roundtrip() {
344 for role in [Role::Read, Role::Write, Role::Admin] {
345 assert_eq!(Role::from_str(role.as_str()), Some(role));
346 }
347 assert_eq!(Role::from_str("unknown"), None);
348 }
349
350 #[test]
351 fn test_role_permissions() {
352 assert!(Role::Read.can_read());
353 assert!(!Role::Read.can_write());
354 assert!(!Role::Read.can_admin());
355
356 assert!(Role::Write.can_read());
357 assert!(Role::Write.can_write());
358 assert!(!Role::Write.can_admin());
359
360 assert!(Role::Admin.can_read());
361 assert!(Role::Admin.can_write());
362 assert!(Role::Admin.can_admin());
363 }
364
365 #[test]
366 fn test_auth_config_default() {
367 let cfg = AuthConfig::default();
368 assert!(!cfg.enabled);
369 assert_eq!(cfg.session_ttl_secs, 3600);
370 assert!(!cfg.require_auth);
371 assert!(!cfg.auto_encrypt_storage);
372 }
373
374 #[test]
375 fn test_auth_error_display() {
376 let err = AuthError::UserExists("alice".into());
377 assert!(err.to_string().contains("alice"));
378
379 let err = AuthError::InvalidCredentials;
380 assert!(err.to_string().contains("invalid"));
381 }
382}
383
384impl std::fmt::Debug for User {
388 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
389 f.debug_struct("User")
390 .field("username", &self.username)
391 .field("tenant_id", &self.tenant_id)
392 .field("password_hash", &"<redacted>")
393 .field(
394 "scram_verifier",
395 &self.scram_verifier.as_ref().map(|_| "<redacted>"),
396 )
397 .field("role", &self.role)
398 .field("api_keys", &self.api_keys.len())
399 .field("created_at", &self.created_at)
400 .field("updated_at", &self.updated_at)
401 .field("enabled", &self.enabled)
402 .finish()
403 }
404}
405
406impl std::fmt::Debug for Session {
407 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
408 f.debug_struct("Session")
409 .field("token", &"<redacted>")
410 .field("username", &self.username)
411 .field("tenant_id", &self.tenant_id)
412 .field("role", &self.role)
413 .field("created_at", &self.created_at)
414 .field("expires_at", &self.expires_at)
415 .finish()
416 }
417}