reifydb_sub_server/auth.rs
1// Copyright (c) reifydb.com 2025
2// This file is licensed under the AGPL-3.0-or-later
3
4//! Authentication and identity extraction for HTTP and WebSocket connections.
5//!
6//! This module provides functions to extract user identity from request headers,
7//! tokens, and WebSocket authentication messages.
8//!
9//! # Security Note
10//!
11//! The current implementation provides a framework for authentication but requires
12//! proper implementation of token validation before production use. The `validate_*`
13//! functions are stubs that should be connected to actual authentication services.
14
15use reifydb_core::interface::Identity;
16
17/// Authentication error types.
18#[derive(Debug, Clone, PartialEq, Eq)]
19pub enum AuthError {
20 /// The authorization header is malformed or contains invalid UTF-8.
21 InvalidHeader,
22 /// No credentials were provided (no Authorization header or API key).
23 MissingCredentials,
24 /// The provided token is invalid or cannot be verified.
25 InvalidToken,
26 /// The token has expired.
27 Expired,
28 /// The token is valid but the user lacks required permissions.
29 InsufficientPermissions,
30}
31
32impl std::fmt::Display for AuthError {
33 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
34 match self {
35 AuthError::InvalidHeader => write!(f, "Invalid authorization header"),
36 AuthError::MissingCredentials => write!(f, "Authentication required"),
37 AuthError::InvalidToken => write!(f, "Invalid authentication token"),
38 AuthError::Expired => write!(f, "Authentication token expired"),
39 AuthError::InsufficientPermissions => write!(f, "Insufficient permissions"),
40 }
41 }
42}
43
44impl std::error::Error for AuthError {}
45
46/// Result type for authentication operations.
47pub type AuthResult<T> = std::result::Result<T, AuthError>;
48
49/// Extract identity from HTTP Authorization header value.
50///
51/// Supports the following authentication schemes:
52/// - `Bearer <token>` - JWT or opaque bearer token
53/// - `Basic <base64>` - Basic authentication (username:password)
54///
55/// # Arguments
56///
57/// * `auth_header` - The value of the Authorization header
58///
59/// # Returns
60///
61/// * `Ok(Identity)` - The authenticated user identity
62/// * `Err(AuthError)` - Authentication failed
63///
64/// # Example
65///
66/// ```ignore
67/// let identity = extract_identity_from_auth_header("Bearer eyJ...")?;
68/// ```
69pub fn extract_identity_from_auth_header(auth_header: &str) -> AuthResult<Identity> {
70 if let Some(token) = auth_header.strip_prefix("Bearer ") {
71 validate_bearer_token(token.trim())
72 } else if let Some(credentials) = auth_header.strip_prefix("Basic ") {
73 validate_basic_auth(credentials.trim())
74 } else {
75 Err(AuthError::InvalidHeader)
76 }
77}
78
79/// Extract identity from an API key.
80///
81/// # Arguments
82///
83/// * `api_key` - The API key value
84///
85/// # Returns
86///
87/// * `Ok(Identity)` - The identity associated with the API key
88/// * `Err(AuthError)` - API key validation failed
89pub fn extract_identity_from_api_key(api_key: &str) -> AuthResult<Identity> {
90 validate_api_key(api_key)
91}
92
93/// Extract identity from WebSocket authentication message.
94///
95/// Called when a WebSocket client sends an Auth message with a token.
96///
97/// # Arguments
98///
99/// * `token` - Optional token from the Auth message
100///
101/// # Returns
102///
103/// * `Ok(Identity)` - The authenticated user identity
104/// * `Err(AuthError::MissingCredentials)` - No token provided
105/// * `Err(AuthError)` - Token validation failed
106pub fn extract_identity_from_ws_auth(token: Option<&str>) -> AuthResult<Identity> {
107 match token {
108 Some(t) if !t.is_empty() => validate_bearer_token(t),
109 _ => Err(AuthError::MissingCredentials),
110 }
111}
112
113/// Create an anonymous identity for unauthenticated requests.
114///
115/// Use this when authentication is optional and the request has no credentials.
116pub fn anonymous_identity() -> Identity {
117 Identity::Anonymous {}
118}
119
120/// Create a root system identity.
121///
122/// **Warning**: This should only be used for internal operations, never for
123/// external request handling.
124pub fn root_identity() -> Identity {
125 Identity::root()
126}
127
128// ============================================================================
129// Token Validation Functions
130//
131// These functions are stubs that should be implemented with actual authentication
132// logic before production use. They currently return errors to prevent accidental
133// use of unauthenticated requests.
134// ============================================================================
135
136/// Validate a bearer token and return the associated identity.
137///
138/// # TODO: Implementation
139///
140/// This function should:
141/// 1. Validate the token signature (if JWT)
142/// 2. Check token expiration
143/// 3. Look up the user/identity from the token claims
144/// 4. Return the Identity
145fn validate_bearer_token(token: &str) -> AuthResult<Identity> {
146 // TODO: Implement actual JWT or opaque token validation
147 //
148 // Example JWT implementation:
149 // 1. Decode and verify the JWT signature
150 // 2. Check `exp` claim for expiration
151 // 3. Extract `sub` claim for user ID
152 // 4. Look up user in database or cache
153 // 5. Return Identity::User { id, name }
154 //
155 // For now, accept any non-empty token and return a system identity
156 if token.is_empty() {
157 Err(AuthError::InvalidToken)
158 } else {
159 Ok(Identity::System {
160 id: 1,
161 name: "authenticated".to_string(),
162 })
163 }
164}
165
166/// Validate basic authentication credentials.
167///
168/// # TODO: Implementation
169///
170/// This function should:
171/// 1. Base64 decode the credentials
172/// 2. Split into username:password
173/// 3. Verify credentials against user store
174/// 4. Return the Identity
175fn validate_basic_auth(credentials: &str) -> AuthResult<Identity> {
176 // TODO: Implement basic auth validation
177 //
178 // 1. Base64 decode credentials
179 // 2. Split on ':' to get username and password
180 // 3. Verify against user database
181 // 4. Return Identity::User { id, name }
182 let _ = credentials;
183 Err(AuthError::InvalidToken)
184}
185
186/// Validate an API key and return the associated identity.
187///
188/// # TODO: Implementation
189///
190/// This function should:
191/// 1. Look up the API key in the database
192/// 2. Check if the key is active and not expired
193/// 3. Return the associated Identity
194fn validate_api_key(api_key: &str) -> AuthResult<Identity> {
195 // TODO: Implement API key validation
196 //
197 // 1. Hash the API key
198 // 2. Look up in database
199 // 3. Verify key is active
200 // 4. Return the associated Identity
201 //
202 // For now, accept any non-empty API key and return a system identity
203 if api_key.is_empty() {
204 Err(AuthError::InvalidToken)
205 } else {
206 Ok(Identity::System {
207 id: 1,
208 name: "authenticated".to_string(),
209 })
210 }
211}
212
213#[cfg(test)]
214mod tests {
215 use super::*;
216
217 #[test]
218 fn test_auth_error_display() {
219 assert_eq!(AuthError::InvalidHeader.to_string(), "Invalid authorization header");
220 assert_eq!(AuthError::MissingCredentials.to_string(), "Authentication required");
221 assert_eq!(AuthError::InvalidToken.to_string(), "Invalid authentication token");
222 assert_eq!(AuthError::Expired.to_string(), "Authentication token expired");
223 }
224
225 #[test]
226 fn test_extract_from_bearer_header() {
227 // Should accept any non-empty token
228 let result = extract_identity_from_auth_header("Bearer test_token");
229 assert!(result.is_ok());
230 }
231
232 #[test]
233 fn test_extract_from_invalid_scheme() {
234 let result = extract_identity_from_auth_header("Unknown test_token");
235 assert!(matches!(result, Err(AuthError::InvalidHeader)));
236 }
237
238 #[test]
239 fn test_extract_from_ws_auth_none() {
240 let result = extract_identity_from_ws_auth(None);
241 assert!(matches!(result, Err(AuthError::MissingCredentials)));
242 }
243
244 #[test]
245 fn test_extract_from_ws_auth_empty() {
246 let result = extract_identity_from_ws_auth(Some(""));
247 assert!(matches!(result, Err(AuthError::MissingCredentials)));
248 }
249
250 #[test]
251 fn test_anonymous_identity() {
252 let identity = anonymous_identity();
253 assert!(matches!(identity, Identity::Anonymous {}));
254 }
255
256 #[test]
257 fn test_root_identity() {
258 let identity = root_identity();
259 match identity {
260 Identity::System {
261 id,
262 name,
263 } => {
264 assert_eq!(id, 0);
265 assert_eq!(name, "root");
266 }
267 _ => panic!("Expected System identity"),
268 }
269 }
270}