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}