Skip to main content

reifydb_sub_server/
auth.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright (c) 2026 ReifyDB
3
4use std::{error::Error as StdError, fmt};
5
6use reifydb_auth::service::AuthService;
7use reifydb_value::value::identity::IdentityId;
8use tracing::error;
9
10#[derive(Debug, Clone, PartialEq, Eq)]
11pub enum AuthError {
12	InvalidHeader,
13
14	MissingCredentials,
15
16	InvalidToken,
17
18	Expired,
19
20	InsufficientPermissions,
21
22	Internal,
23}
24
25impl fmt::Display for AuthError {
26	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
27		match self {
28			AuthError::InvalidHeader => write!(f, "Invalid authorization header"),
29			AuthError::MissingCredentials => write!(f, "Authentication required"),
30			AuthError::InvalidToken => write!(f, "Invalid authentication token"),
31			AuthError::Expired => write!(f, "Authentication token expired"),
32			AuthError::InsufficientPermissions => write!(f, "Insufficient permissions"),
33			AuthError::Internal => write!(f, "Authentication could not be completed"),
34		}
35	}
36}
37
38impl StdError for AuthError {}
39
40pub type AuthResult<T> = Result<T, AuthError>;
41
42pub fn extract_identity_from_auth_header(auth_service: &AuthService, auth_header: &str) -> AuthResult<IdentityId> {
43	match auth_header.strip_prefix("Bearer ") {
44		Some(token) => validate_bearer_token(auth_service, token.trim()),
45		None => Err(AuthError::InvalidHeader),
46	}
47}
48
49pub fn extract_identity_from_ws_auth(auth_service: &AuthService, token: Option<&str>) -> AuthResult<IdentityId> {
50	match token {
51		Some(t) if !t.is_empty() => validate_bearer_token(auth_service, t),
52		_ => Ok(IdentityId::anonymous()),
53	}
54}
55
56fn validate_bearer_token(auth_service: &AuthService, token: &str) -> AuthResult<IdentityId> {
57	if token.is_empty() {
58		return Err(AuthError::InvalidToken);
59	}
60
61	match auth_service.validate_token(token) {
62		Ok(Some(session)) => Ok(session.identity),
63		Ok(None) => Err(AuthError::InvalidToken),
64		Err(e) => {
65			error!("token validation could not reach storage, rejecting the request: {e}");
66			Err(AuthError::Internal)
67		}
68	}
69}
70
71#[cfg(test)]
72pub mod tests {
73	use super::*;
74
75	#[test]
76	fn test_auth_error_display() {
77		assert_eq!(AuthError::InvalidHeader.to_string(), "Invalid authorization header");
78		assert_eq!(AuthError::MissingCredentials.to_string(), "Authentication required");
79		assert_eq!(AuthError::InvalidToken.to_string(), "Invalid authentication token");
80		assert_eq!(AuthError::Expired.to_string(), "Authentication token expired");
81	}
82
83	#[test]
84	fn test_anonymous_identity() {
85		let identity = IdentityId::anonymous();
86		assert!(identity.is_anonymous());
87	}
88
89	#[test]
90	fn test_root_identity() {
91		let identity = IdentityId::root();
92		assert!(identity.is_root());
93	}
94
95	#[test]
96	fn internal_does_not_read_as_a_client_mistake() {
97		// The gRPC status carries this text verbatim, so it must not send the caller after its credentials.
98		let message = AuthError::Internal.to_string();
99		assert_eq!(message, "Authentication could not be completed");
100		assert!(!message.contains("token"), "a storage failure must not point the client at its token");
101	}
102}