Skip to main content

reifydb_sub_server/
auth.rs

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