Skip to main content

reifydb_sub_server/
auth.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright (c) 2025 ReifyDB
3
4use std::{error::Error as StdError, fmt};
5
6use reifydb_auth::service::AuthService;
7use reifydb_type::value::identity::IdentityId;
8
9/// Authentication error types.
10#[derive(Debug, Clone, PartialEq, Eq)]
11pub enum AuthError {
12	/// The authorization header is malformed or contains invalid UTF-8.
13	InvalidHeader,
14	/// No credentials were provided (no Authorization header or auth token).
15	MissingCredentials,
16	/// The provided token is invalid or cannot be verified.
17	InvalidToken,
18	/// The token has expired.
19	Expired,
20	/// The token is valid but the user lacks required permissions.
21	InsufficientPermissions,
22}
23
24impl fmt::Display for AuthError {
25	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
26		match self {
27			AuthError::InvalidHeader => write!(f, "Invalid authorization header"),
28			AuthError::MissingCredentials => write!(f, "Authentication required"),
29			AuthError::InvalidToken => write!(f, "Invalid authentication token"),
30			AuthError::Expired => write!(f, "Authentication token expired"),
31			AuthError::InsufficientPermissions => write!(f, "Insufficient permissions"),
32		}
33	}
34}
35
36impl StdError for AuthError {}
37
38/// Result type for authentication operations.
39pub type AuthResult<T> = Result<T, AuthError>;
40
41/// Extract identity from HTTP Authorization header value.
42///
43/// Supports the following authentication schemes:
44/// - `Bearer <token>` - Session token validated against AuthService
45/// - `Basic <base64>` - Basic authentication (username:password)
46pub fn extract_identity_from_auth_header(auth_service: &AuthService, auth_header: &str) -> AuthResult<IdentityId> {
47	if let Some(token) = auth_header.strip_prefix("Bearer ") {
48		validate_bearer_token(auth_service, token.trim())
49	} else if let Some(credentials) = auth_header.strip_prefix("Basic ") {
50		validate_basic_auth(auth_service, credentials.trim())
51	} else {
52		Err(AuthError::InvalidHeader)
53	}
54}
55
56/// Extract identity from WebSocket authentication message.
57///
58/// Called when a WebSocket client sends an Auth message with a token.
59pub fn extract_identity_from_ws_auth(auth_service: &AuthService, token: Option<&str>) -> AuthResult<IdentityId> {
60	match token {
61		Some(t) if !t.is_empty() => validate_bearer_token(auth_service, t),
62		_ => Ok(IdentityId::anonymous()),
63	}
64}
65
66/// Validate a bearer token and return the associated identity.
67fn validate_bearer_token(auth_service: &AuthService, token: &str) -> AuthResult<IdentityId> {
68	if token.is_empty() {
69		return Err(AuthError::InvalidToken);
70	}
71
72	match auth_service.validate_token(token) {
73		Some(session) => Ok(session.identity),
74		None => Err(AuthError::InvalidToken),
75	}
76}
77
78/// Validate basic authentication credentials (Base64-encoded username:password).
79fn validate_basic_auth(_auth_service: &AuthService, _credentials: &str) -> AuthResult<IdentityId> {
80	// TODO: Implement Basic auth (Base64 decode → username:password → auth_service.authenticate)
81	Err(AuthError::InvalidToken)
82}
83
84#[cfg(test)]
85pub mod tests {
86	use super::*;
87
88	#[test]
89	fn test_auth_error_display() {
90		assert_eq!(AuthError::InvalidHeader.to_string(), "Invalid authorization header");
91		assert_eq!(AuthError::MissingCredentials.to_string(), "Authentication required");
92		assert_eq!(AuthError::InvalidToken.to_string(), "Invalid authentication token");
93		assert_eq!(AuthError::Expired.to_string(), "Authentication token expired");
94	}
95
96	#[test]
97	fn test_anonymous_identity() {
98		let identity = IdentityId::anonymous();
99		assert!(identity.is_anonymous());
100	}
101
102	#[test]
103	fn test_root_identity() {
104		let identity = IdentityId::root();
105		assert!(identity.is_root());
106	}
107}