reddb_server/auth/
middleware.rs1use super::{CertIdentity, OAuthIdentity, Role};
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq)]
14pub enum AuthSource {
15 Password,
17 ClientCert,
19 Oauth,
21}
22
23#[derive(Debug, Clone)]
25pub enum AuthResult {
26 Authenticated {
28 username: String,
29 role: Role,
30 source: AuthSource,
34 },
35 Anonymous,
37 Denied(String),
39}
40
41impl AuthResult {
42 pub fn password(username: impl Into<String>, role: Role) -> Self {
45 Self::Authenticated {
46 username: username.into(),
47 role,
48 source: AuthSource::Password,
49 }
50 }
51
52 pub fn from_cert(id: CertIdentity) -> Self {
54 Self::Authenticated {
55 username: id.username,
56 role: id.role,
57 source: AuthSource::ClientCert,
58 }
59 }
60
61 pub fn from_oauth(id: OAuthIdentity) -> Self {
63 Self::Authenticated {
64 username: id.username,
65 role: id.role,
66 source: AuthSource::Oauth,
67 }
68 }
69
70 pub fn summary(&self) -> String {
72 match self {
73 Self::Authenticated {
74 username,
75 role,
76 source,
77 } => {
78 let src = match source {
79 AuthSource::Password => "pwd",
80 AuthSource::ClientCert => "cert",
81 AuthSource::Oauth => "oauth",
82 };
83 format!("user={username} role={role} via={src}")
84 }
85 Self::Anonymous => "anonymous".to_string(),
86 Self::Denied(reason) => format!("denied: {reason}"),
87 }
88 }
89
90 pub fn is_authenticated(&self) -> bool {
92 matches!(self, Self::Authenticated { .. })
93 }
94}
95
96pub fn check_permission(
106 auth: &AuthResult,
107 requires_write: bool,
108 requires_admin: bool,
109) -> Result<(), String> {
110 match auth {
111 AuthResult::Authenticated { role, .. } => {
112 if requires_admin && !role.can_admin() {
113 return Err("admin role required".into());
114 }
115 if requires_write && !role.can_write() {
116 return Err("write permission required".into());
117 }
118 Ok(())
119 }
120 AuthResult::Anonymous => {
121 if requires_admin {
122 return Err("admin authentication required".into());
123 }
124 Ok(())
126 }
127 AuthResult::Denied(reason) => Err(reason.clone()),
128 }
129}
130
131#[cfg(test)]
136mod tests {
137 use super::*;
138
139 #[test]
140 fn test_admin_can_do_everything() {
141 let auth = AuthResult::Authenticated {
142 username: "root".into(),
143 role: Role::Admin,
144 source: AuthSource::Password,
145 };
146 assert!(check_permission(&auth, false, false).is_ok());
147 assert!(check_permission(&auth, true, false).is_ok());
148 assert!(check_permission(&auth, false, true).is_ok());
149 assert!(check_permission(&auth, true, true).is_ok());
150 }
151
152 #[test]
153 fn test_write_role_cannot_admin() {
154 let auth = AuthResult::Authenticated {
155 username: "writer".into(),
156 role: Role::Write,
157 source: AuthSource::Password,
158 };
159 assert!(check_permission(&auth, false, false).is_ok());
160 assert!(check_permission(&auth, true, false).is_ok());
161 assert!(check_permission(&auth, false, true).is_err());
162 }
163
164 #[test]
165 fn test_read_role_cannot_write() {
166 let auth = AuthResult::Authenticated {
167 username: "reader".into(),
168 role: Role::Read,
169 source: AuthSource::Password,
170 };
171 assert!(check_permission(&auth, false, false).is_ok());
172 assert!(check_permission(&auth, true, false).is_err());
173 assert!(check_permission(&auth, false, true).is_err());
174 }
175
176 #[test]
177 fn test_anonymous_access() {
178 let auth = AuthResult::Anonymous;
179 assert!(check_permission(&auth, false, false).is_ok());
181 assert!(check_permission(&auth, true, false).is_ok());
182 assert!(check_permission(&auth, false, true).is_err());
183 }
184
185 #[test]
186 fn test_denied_always_fails() {
187 let auth = AuthResult::Denied("bad token".into());
188 assert!(check_permission(&auth, false, false).is_err());
189 assert!(check_permission(&auth, true, true).is_err());
190 }
191
192 #[test]
193 fn test_auth_result_summary() {
194 let auth = AuthResult::Authenticated {
195 username: "alice".into(),
196 role: Role::Admin,
197 source: AuthSource::Password,
198 };
199 assert!(auth.summary().contains("alice"));
200 assert!(auth.is_authenticated());
201
202 let anon = AuthResult::Anonymous;
203 assert_eq!(anon.summary(), "anonymous");
204 assert!(!anon.is_authenticated());
205 }
206}