1pub mod cert;
15pub mod column_policy_gate;
16pub mod locks;
17pub mod middleware;
18pub mod oauth;
19pub mod policies;
20pub mod privileges;
21pub mod scope_cache;
22pub mod scram;
23pub mod store;
24pub mod vault;
25
26pub use scope_cache::{AuthCache, AuthCacheStats, ScopeKey, DEFAULT_TTL as DEFAULT_SCOPE_TTL};
27
28pub use cert::{
29 CertAuthConfig, CertAuthError, CertAuthenticator, CertIdentity, CertIdentityMode,
30 ParsedClientCert,
31};
32pub use column_policy_gate::{
33 ColumnAccessRequest, ColumnDecision, ColumnDecisionEffect, ColumnPolicyGate,
34 ColumnPolicyOutcome, ColumnRef,
35};
36pub use oauth::{
37 DecodedJwt, Jwk, JwtClaims, JwtHeader, OAuthConfig, OAuthError, OAuthIdentity,
38 OAuthIdentityMode, OAuthValidator,
39};
40pub use privileges::{
41 check_grant, Action, AuthzContext, AuthzError, Grant, GrantPrincipal, GrantsView,
42 PermissionCache, Resource, UserAttributes,
43};
44pub use store::AuthStore;
45
46use std::fmt;
47
48#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
56pub enum Role {
57 Read,
58 Write,
59 Admin,
60}
61
62impl Role {
63 pub fn as_str(&self) -> &'static str {
64 match self {
65 Self::Read => "read",
66 Self::Write => "write",
67 Self::Admin => "admin",
68 }
69 }
70
71 pub fn from_str(s: &str) -> Option<Self> {
72 match s {
73 "read" => Some(Self::Read),
74 "write" => Some(Self::Write),
75 "admin" => Some(Self::Admin),
76 _ => None,
77 }
78 }
79
80 pub fn can_read(&self) -> bool {
81 true
82 }
83
84 pub fn can_write(&self) -> bool {
85 matches!(self, Self::Write | Self::Admin)
86 }
87
88 pub fn can_admin(&self) -> bool {
89 matches!(self, Self::Admin)
90 }
91}
92
93impl fmt::Display for Role {
94 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
95 f.write_str(self.as_str())
96 }
97}
98
99#[derive(Debug, Clone)]
111pub struct User {
112 pub username: String,
113 pub tenant_id: Option<String>,
117 pub password_hash: String,
118 pub scram_verifier: Option<scram::ScramVerifier>,
121 pub role: Role,
122 pub api_keys: Vec<ApiKey>,
123 pub created_at: u128,
124 pub updated_at: u128,
125 pub enabled: bool,
126 pub system_owned: bool,
127}
128
129#[derive(Debug, Clone, PartialEq, Eq, Hash)]
144pub struct UserId {
145 pub tenant: Option<String>,
146 pub username: String,
147}
148
149impl UserId {
150 pub fn platform(name: impl Into<String>) -> Self {
152 Self {
153 tenant: None,
154 username: name.into(),
155 }
156 }
157
158 pub fn scoped(tenant: impl Into<String>, name: impl Into<String>) -> Self {
160 Self {
161 tenant: Some(tenant.into()),
162 username: name.into(),
163 }
164 }
165
166 pub fn from_parts(tenant: Option<&str>, username: &str) -> Self {
169 Self {
170 tenant: tenant.map(|t| t.to_string()),
171 username: username.to_string(),
172 }
173 }
174}
175
176impl fmt::Display for UserId {
177 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
178 match &self.tenant {
179 Some(t) => write!(f, "{}/{}", t, self.username),
180 None => f.write_str(&self.username),
181 }
182 }
183}
184
185#[derive(Debug, Clone)]
191pub struct ApiKey {
192 pub key: String,
194 pub name: String,
196 pub role: Role,
198 pub created_at: u128,
199}
200
201#[derive(Debug, Clone)]
207pub struct Session {
208 pub token: String,
210 pub username: String,
211 pub tenant_id: Option<String>,
214 pub role: Role,
215 pub created_at: u128,
216 pub expires_at: u128,
218}
219
220#[derive(Debug, Clone)]
226pub struct AuthConfig {
227 pub enabled: bool,
229 pub session_ttl_secs: u64,
231 pub require_auth: bool,
233 pub auto_encrypt_storage: bool,
235 pub vault_enabled: bool,
240 pub cert: CertAuthConfig,
243 pub oauth: OAuthConfig,
246}
247
248impl Default for AuthConfig {
249 fn default() -> Self {
250 Self {
251 enabled: false,
252 session_ttl_secs: 3600,
253 require_auth: false,
254 auto_encrypt_storage: false,
255 vault_enabled: false,
256 cert: CertAuthConfig::default(),
257 oauth: OAuthConfig::default(),
258 }
259 }
260}
261
262#[derive(Debug, Clone)]
268pub enum AuthError {
269 UserExists(String),
270 UserNotFound(String),
271 InvalidCredentials,
272 KeyNotFound(String),
273 RoleExceeded { requested: Role, ceiling: Role },
274 SystemUserImmutable { username: String },
275 Disabled,
276 Forbidden(String),
277 Internal(String),
278}
279
280impl fmt::Display for AuthError {
281 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
282 match self {
283 Self::UserExists(u) => write!(f, "user already exists: {u}"),
284 Self::UserNotFound(u) => write!(f, "user not found: {u}"),
285 Self::InvalidCredentials => write!(f, "invalid credentials"),
286 Self::KeyNotFound(k) => write!(f, "api key not found: {k}"),
287 Self::RoleExceeded { requested, ceiling } => {
288 write!(
289 f,
290 "requested role '{requested}' exceeds ceiling '{ceiling}'"
291 )
292 }
293 Self::SystemUserImmutable { username } => {
294 write!(f, "system-owned user is immutable: {username}")
295 }
296 Self::Disabled => write!(f, "authentication is disabled"),
297 Self::Forbidden(msg) => write!(f, "forbidden: {msg}"),
298 Self::Internal(msg) => write!(f, "internal auth error: {msg}"),
299 }
300 }
301}
302
303impl std::error::Error for AuthError {}
304
305pub(crate) fn now_ms() -> u128 {
311 std::time::SystemTime::now()
312 .duration_since(std::time::UNIX_EPOCH)
313 .unwrap_or_default()
314 .as_millis()
315}
316
317#[cfg(test)]
322mod tests {
323 use super::*;
324
325 #[test]
326 fn test_role_ordering() {
327 assert!(Role::Read < Role::Write);
328 assert!(Role::Write < Role::Admin);
329 }
330
331 #[test]
332 fn test_role_roundtrip() {
333 for role in [Role::Read, Role::Write, Role::Admin] {
334 assert_eq!(Role::from_str(role.as_str()), Some(role));
335 }
336 assert_eq!(Role::from_str("unknown"), None);
337 }
338
339 #[test]
340 fn test_role_permissions() {
341 assert!(Role::Read.can_read());
342 assert!(!Role::Read.can_write());
343 assert!(!Role::Read.can_admin());
344
345 assert!(Role::Write.can_read());
346 assert!(Role::Write.can_write());
347 assert!(!Role::Write.can_admin());
348
349 assert!(Role::Admin.can_read());
350 assert!(Role::Admin.can_write());
351 assert!(Role::Admin.can_admin());
352 }
353
354 #[test]
355 fn test_auth_config_default() {
356 let cfg = AuthConfig::default();
357 assert!(!cfg.enabled);
358 assert_eq!(cfg.session_ttl_secs, 3600);
359 assert!(!cfg.require_auth);
360 assert!(!cfg.auto_encrypt_storage);
361 }
362
363 #[test]
364 fn test_auth_error_display() {
365 let err = AuthError::UserExists("alice".into());
366 assert!(err.to_string().contains("alice"));
367
368 let err = AuthError::InvalidCredentials;
369 assert!(err.to_string().contains("invalid"));
370 }
371}