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}
127
128#[derive(Debug, Clone, PartialEq, Eq, Hash)]
143pub struct UserId {
144 pub tenant: Option<String>,
145 pub username: String,
146}
147
148impl UserId {
149 pub fn platform(name: impl Into<String>) -> Self {
151 Self {
152 tenant: None,
153 username: name.into(),
154 }
155 }
156
157 pub fn scoped(tenant: impl Into<String>, name: impl Into<String>) -> Self {
159 Self {
160 tenant: Some(tenant.into()),
161 username: name.into(),
162 }
163 }
164
165 pub fn from_parts(tenant: Option<&str>, username: &str) -> Self {
168 Self {
169 tenant: tenant.map(|t| t.to_string()),
170 username: username.to_string(),
171 }
172 }
173}
174
175impl fmt::Display for UserId {
176 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
177 match &self.tenant {
178 Some(t) => write!(f, "{}/{}", t, self.username),
179 None => f.write_str(&self.username),
180 }
181 }
182}
183
184#[derive(Debug, Clone)]
190pub struct ApiKey {
191 pub key: String,
193 pub name: String,
195 pub role: Role,
197 pub created_at: u128,
198}
199
200#[derive(Debug, Clone)]
206pub struct Session {
207 pub token: String,
209 pub username: String,
210 pub tenant_id: Option<String>,
213 pub role: Role,
214 pub created_at: u128,
215 pub expires_at: u128,
217}
218
219#[derive(Debug, Clone)]
225pub struct AuthConfig {
226 pub enabled: bool,
228 pub session_ttl_secs: u64,
230 pub require_auth: bool,
232 pub auto_encrypt_storage: bool,
234 pub vault_enabled: bool,
239 pub cert: CertAuthConfig,
242 pub oauth: OAuthConfig,
245}
246
247impl Default for AuthConfig {
248 fn default() -> Self {
249 Self {
250 enabled: false,
251 session_ttl_secs: 3600,
252 require_auth: false,
253 auto_encrypt_storage: false,
254 vault_enabled: false,
255 cert: CertAuthConfig::default(),
256 oauth: OAuthConfig::default(),
257 }
258 }
259}
260
261#[derive(Debug, Clone)]
267pub enum AuthError {
268 UserExists(String),
269 UserNotFound(String),
270 InvalidCredentials,
271 KeyNotFound(String),
272 RoleExceeded { requested: Role, ceiling: Role },
273 Disabled,
274 Forbidden(String),
275 Internal(String),
276}
277
278impl fmt::Display for AuthError {
279 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
280 match self {
281 Self::UserExists(u) => write!(f, "user already exists: {u}"),
282 Self::UserNotFound(u) => write!(f, "user not found: {u}"),
283 Self::InvalidCredentials => write!(f, "invalid credentials"),
284 Self::KeyNotFound(k) => write!(f, "api key not found: {k}"),
285 Self::RoleExceeded { requested, ceiling } => {
286 write!(
287 f,
288 "requested role '{requested}' exceeds ceiling '{ceiling}'"
289 )
290 }
291 Self::Disabled => write!(f, "authentication is disabled"),
292 Self::Forbidden(msg) => write!(f, "forbidden: {msg}"),
293 Self::Internal(msg) => write!(f, "internal auth error: {msg}"),
294 }
295 }
296}
297
298impl std::error::Error for AuthError {}
299
300pub(crate) fn now_ms() -> u128 {
306 std::time::SystemTime::now()
307 .duration_since(std::time::UNIX_EPOCH)
308 .unwrap_or_default()
309 .as_millis()
310}
311
312#[cfg(test)]
317mod tests {
318 use super::*;
319
320 #[test]
321 fn test_role_ordering() {
322 assert!(Role::Read < Role::Write);
323 assert!(Role::Write < Role::Admin);
324 }
325
326 #[test]
327 fn test_role_roundtrip() {
328 for role in [Role::Read, Role::Write, Role::Admin] {
329 assert_eq!(Role::from_str(role.as_str()), Some(role));
330 }
331 assert_eq!(Role::from_str("unknown"), None);
332 }
333
334 #[test]
335 fn test_role_permissions() {
336 assert!(Role::Read.can_read());
337 assert!(!Role::Read.can_write());
338 assert!(!Role::Read.can_admin());
339
340 assert!(Role::Write.can_read());
341 assert!(Role::Write.can_write());
342 assert!(!Role::Write.can_admin());
343
344 assert!(Role::Admin.can_read());
345 assert!(Role::Admin.can_write());
346 assert!(Role::Admin.can_admin());
347 }
348
349 #[test]
350 fn test_auth_config_default() {
351 let cfg = AuthConfig::default();
352 assert!(!cfg.enabled);
353 assert_eq!(cfg.session_ttl_secs, 3600);
354 assert!(!cfg.require_auth);
355 assert!(!cfg.auto_encrypt_storage);
356 }
357
358 #[test]
359 fn test_auth_error_display() {
360 let err = AuthError::UserExists("alice".into());
361 assert!(err.to_string().contains("alice"));
362
363 let err = AuthError::InvalidCredentials;
364 assert!(err.to_string().contains("invalid"));
365 }
366}