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(Debug, 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,
202 pub name: String,
204 pub role: Role,
206 pub created_at: u128,
207}
208
209#[derive(Debug, Clone)]
215pub struct Session {
216 pub token: String,
218 pub username: String,
219 pub tenant_id: Option<String>,
222 pub role: Role,
223 pub created_at: u128,
224 pub expires_at: u128,
226}
227
228#[derive(Debug, Clone)]
234pub struct AuthConfig {
235 pub enabled: bool,
237 pub session_ttl_secs: u64,
239 pub require_auth: bool,
241 pub auto_encrypt_storage: bool,
243 pub vault_enabled: bool,
248 pub cert: CertAuthConfig,
251 pub oauth: OAuthConfig,
254}
255
256impl Default for AuthConfig {
257 fn default() -> Self {
258 Self {
259 enabled: false,
260 session_ttl_secs: 3600,
261 require_auth: false,
262 auto_encrypt_storage: false,
263 vault_enabled: false,
264 cert: CertAuthConfig::default(),
265 oauth: OAuthConfig::default(),
266 }
267 }
268}
269
270#[derive(Debug, Clone)]
276pub enum AuthError {
277 UserExists(String),
278 UserNotFound(String),
279 InvalidCredentials,
280 KeyNotFound(String),
281 RoleExceeded { requested: Role, ceiling: Role },
282 Disabled,
283 Forbidden(String),
284 Internal(String),
285}
286
287impl fmt::Display for AuthError {
288 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
289 match self {
290 Self::UserExists(u) => write!(f, "user already exists: {u}"),
291 Self::UserNotFound(u) => write!(f, "user not found: {u}"),
292 Self::InvalidCredentials => write!(f, "invalid credentials"),
293 Self::KeyNotFound(k) => write!(f, "api key not found: {k}"),
294 Self::RoleExceeded { requested, ceiling } => {
295 write!(
296 f,
297 "requested role '{requested}' exceeds ceiling '{ceiling}'"
298 )
299 }
300 Self::Disabled => write!(f, "authentication is disabled"),
301 Self::Forbidden(msg) => write!(f, "forbidden: {msg}"),
302 Self::Internal(msg) => write!(f, "internal auth error: {msg}"),
303 }
304 }
305}
306
307impl std::error::Error for AuthError {}
308
309pub(crate) fn now_ms() -> u128 {
315 std::time::SystemTime::now()
316 .duration_since(std::time::UNIX_EPOCH)
317 .unwrap_or_default()
318 .as_millis()
319}
320
321#[cfg(test)]
326mod tests {
327 use super::*;
328
329 #[test]
330 fn test_role_ordering() {
331 assert!(Role::Read < Role::Write);
332 assert!(Role::Write < Role::Admin);
333 }
334
335 #[test]
336 fn test_role_roundtrip() {
337 for role in [Role::Read, Role::Write, Role::Admin] {
338 assert_eq!(Role::from_str(role.as_str()), Some(role));
339 }
340 assert_eq!(Role::from_str("unknown"), None);
341 }
342
343 #[test]
344 fn test_role_permissions() {
345 assert!(Role::Read.can_read());
346 assert!(!Role::Read.can_write());
347 assert!(!Role::Read.can_admin());
348
349 assert!(Role::Write.can_read());
350 assert!(Role::Write.can_write());
351 assert!(!Role::Write.can_admin());
352
353 assert!(Role::Admin.can_read());
354 assert!(Role::Admin.can_write());
355 assert!(Role::Admin.can_admin());
356 }
357
358 #[test]
359 fn test_auth_config_default() {
360 let cfg = AuthConfig::default();
361 assert!(!cfg.enabled);
362 assert_eq!(cfg.session_ttl_secs, 3600);
363 assert!(!cfg.require_auth);
364 assert!(!cfg.auto_encrypt_storage);
365 }
366
367 #[test]
368 fn test_auth_error_display() {
369 let err = AuthError::UserExists("alice".into());
370 assert!(err.to_string().contains("alice"));
371
372 let err = AuthError::InvalidCredentials;
373 assert!(err.to_string().contains("invalid"));
374 }
375}