#![warn(missing_docs)]
#![allow(deprecated)]
pub mod sessions;
pub mod core;
pub mod current_user;
#[allow(deprecated)]
pub use current_user::CurrentUser;
pub mod auth_info;
pub use auth_info::AuthInfo;
pub mod guard;
pub use guard::{All, Any, Guard, Not, Public};
pub use reinhardt_auth_macros::guard;
pub mod auth_user;
pub use auth_user::AuthUser;
pub mod auth_extractors;
pub use auth_extractors::validate_auth_extractors;
pub(crate) const USER_ID_NAMESPACE: uuid::Uuid =
uuid::uuid!("c7a85537-073f-5092-8d10-774e109477c9");
pub use core::{
AllowAny, AnonymousUser, AuthBackend, AuthIdentity, BaseUser, CompositeAuthBackend, FullUser,
IsActiveUser, IsAdminUser, IsAuthenticated, IsAuthenticatedOrReadOnly, PasswordHasher,
Permission, PermissionContext, PermissionsMixin, SimpleUser, SuperuserCreator,
SuperuserCreatorRegistration, SuperuserInit, TypedSuperuserCreator, User,
auto_register_superuser_creator, get_superuser_creator, register_superuser_creator,
superuser_creator_for,
};
#[cfg(feature = "argon2-hasher")]
pub use core::Argon2Hasher;
pub use core::permission_operators;
pub mod repository;
pub use repository::{SimpleUserRepository, UserRepository};
pub mod advanced_permissions;
pub mod base_user_manager;
pub mod basic;
pub mod default_user;
pub mod default_user_manager;
pub mod group_management;
#[cfg(feature = "sessions")]
pub mod handlers;
pub mod ip_permission;
#[cfg(feature = "jwt")]
pub mod jwt;
pub mod mfa;
pub mod model_permissions;
#[cfg(feature = "oauth")]
pub mod oauth2;
pub mod object_permissions;
#[cfg(feature = "database")]
pub mod permission;
#[cfg(feature = "rate-limit")]
pub mod rate_limit_permission;
pub mod remote_user;
pub mod rest_authentication;
#[cfg(feature = "sessions")]
pub mod session;
#[cfg(feature = "social")]
pub mod social;
pub mod time_based_permission;
#[cfg(any(feature = "jwt", feature = "token"))]
pub mod token_blacklist;
#[cfg(any(feature = "jwt", feature = "token"))]
pub mod token_rotation;
#[cfg(any(feature = "jwt", feature = "token"))]
pub mod token_storage;
pub mod user_management;
pub use advanced_permissions::{ObjectPermission as AdvancedObjectPermission, RoleBasedPermission};
pub use base_user_manager::BaseUserManager;
pub use basic::BasicAuthentication as HttpBasicAuth;
#[cfg(feature = "argon2-hasher")]
pub use default_user::DefaultUser;
#[cfg(feature = "argon2-hasher")]
pub use default_user_manager::DefaultUserManager;
pub use group_management::{
CreateGroupData, Group, GroupManagementError, GroupManagementResult, GroupManager,
get_group_manager, register_group_manager,
};
#[cfg(feature = "sessions")]
pub use handlers::{LoginCredentials, LoginHandler, LogoutHandler, SESSION_COOKIE_NAME};
pub use ip_permission::{CidrRange, IpBlacklistPermission, IpWhitelistPermission};
#[cfg(feature = "jwt")]
pub use jwt::{Claims, JwtAuth, JwtError};
pub use mfa::MFAAuthentication as MfaManager;
pub use model_permissions::{
DjangoModelPermissions, DjangoModelPermissionsOrAnonReadOnly, ModelPermission,
};
#[cfg(feature = "oauth")]
pub use oauth2::{
AccessToken, AuthorizationCode, GrantType, InMemoryOAuth2Store, OAuth2Application,
OAuth2Authentication, OAuth2TokenStore,
};
pub use object_permissions::{ObjectPermission, ObjectPermissionChecker, ObjectPermissionManager};
#[cfg(feature = "database")]
pub use permission::AuthPermission;
pub use permission_operators::{AndPermission, NotPermission, OrPermission};
pub use reinhardt_core::exception::Error as BaseUserManagerError;
pub use serde_json::Value as JsonValue;
#[cfg(feature = "social")]
pub use social::{
AppleProvider, GenericOidcConfig, GenericOidcProvider, GitHubProvider, GoogleProvider, IdToken,
MicrosoftProvider, OAuthProvider, OAuthToken, PkceFlow, ProviderConfig, SocialAuthBackend,
SocialAuthError, StandardClaims, StateStore, TokenResponse, UserInfoMapper,
};
#[cfg(feature = "rate-limit")]
pub use rate_limit_permission::{RateLimitPermission, RateLimitPermissionBuilder};
pub use remote_user::RemoteUserAuthentication as RemoteUserAuth;
pub use rest_authentication::{
BasicAuthConfig, CompositeAuthentication, RemoteUserAuthentication, RestAuthentication,
SessionAuthConfig, SessionAuthentication, TokenAuthConfig, TokenAuthentication,
};
#[cfg(feature = "sessions")]
pub use session::{InMemorySessionStore, SESSION_KEY_USER_ID, Session, SessionId, SessionStore};
pub use time_based_permission::{DateRange, TimeBasedPermission, TimeWindow};
#[cfg(any(feature = "jwt", feature = "token"))]
pub use token_blacklist::{
BlacklistReason, BlacklistStats, BlacklistedToken, InMemoryRefreshTokenStore,
InMemoryTokenBlacklist, RefreshToken, RefreshTokenStore, TokenBlacklist, TokenRotationManager,
};
#[cfg(any(feature = "jwt", feature = "token"))]
pub use token_rotation::{AutoTokenRotationManager, TokenRotationConfig, TokenRotationRecord};
#[cfg(all(feature = "database", any(feature = "jwt", feature = "token")))]
pub use token_storage::DatabaseTokenStorage;
#[cfg(any(feature = "jwt", feature = "token"))]
pub use token_storage::{
InMemoryTokenStorage, StoredToken, TokenStorage, TokenStorageError, TokenStorageResult,
};
pub use user_management::{
CreateUserData, UpdateUserData, UserManagementError, UserManagementResult, UserManager,
};
#[non_exhaustive]
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum AuthenticationError {
InvalidCredentials,
UserNotFound,
SessionExpired,
InvalidToken,
TokenExpired,
NotAuthenticated,
DatabaseError(String),
Unknown(String),
}
impl std::fmt::Display for AuthenticationError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
AuthenticationError::InvalidCredentials => write!(f, "Invalid credentials"),
AuthenticationError::UserNotFound => write!(f, "User not found"),
AuthenticationError::SessionExpired => write!(f, "Session expired"),
AuthenticationError::InvalidToken => write!(f, "Invalid token"),
AuthenticationError::TokenExpired => write!(f, "Token expired"),
AuthenticationError::NotAuthenticated => write!(f, "User is not authenticated"),
AuthenticationError::DatabaseError(msg) => write!(f, "Database error: {}", msg),
AuthenticationError::Unknown(msg) => write!(f, "Authentication error: {}", msg),
}
}
}
impl std::error::Error for AuthenticationError {}
#[cfg(feature = "jwt")]
impl From<JwtError> for AuthenticationError {
fn from(err: JwtError) -> Self {
match err {
JwtError::TokenExpired => AuthenticationError::TokenExpired,
JwtError::InvalidSignature(_) | JwtError::InvalidToken(_) => {
AuthenticationError::InvalidToken
}
JwtError::EncodingError(msg) => AuthenticationError::Unknown(msg),
}
}
}
#[async_trait::async_trait]
pub trait AuthenticationBackend: Send + Sync {
async fn authenticate(
&self,
request: &reinhardt_http::Request,
) -> Result<Option<Box<dyn User>>, AuthenticationError>;
async fn get_user(&self, user_id: &str) -> Result<Option<Box<dyn User>>, AuthenticationError>;
}
#[cfg(test)]
mod tests {
use super::*;
use uuid::Uuid;
#[test]
#[cfg(feature = "jwt")]
fn test_auth_jwt_generate_unit() {
let jwt_auth = JwtAuth::new(b"test_secret_key");
let user_id = "user123".to_string();
let username = "testuser".to_string();
let token = jwt_auth
.generate_token(user_id, username, false, false)
.unwrap();
assert!(!token.is_empty());
}
#[tokio::test]
async fn test_permission_allow_any() {
use bytes::Bytes;
use hyper::Method;
use reinhardt_http::Request;
let permission = AllowAny;
let request = Request::builder()
.method(Method::GET)
.uri("/test")
.body(Bytes::new())
.build()
.unwrap();
let context = PermissionContext {
request: &request,
is_authenticated: false,
is_admin: false,
is_active: false,
user: None,
};
assert!(permission.has_permission(&context).await);
}
#[tokio::test]
async fn test_permission_is_authenticated_with_auth() {
use bytes::Bytes;
use hyper::Method;
use reinhardt_http::Request;
let permission = IsAuthenticated;
let request = Request::builder()
.method(Method::GET)
.uri("/test")
.body(Bytes::new())
.build()
.unwrap();
let context = PermissionContext {
request: &request,
is_authenticated: true,
is_admin: false,
is_active: true,
user: None,
};
assert!(permission.has_permission(&context).await);
}
#[tokio::test]
async fn test_permission_is_authenticated_without_auth() {
use bytes::Bytes;
use hyper::Method;
use reinhardt_http::Request;
let permission = IsAuthenticated;
let request = Request::builder()
.method(Method::GET)
.uri("/test")
.body(Bytes::new())
.build()
.unwrap();
let context = PermissionContext {
request: &request,
is_authenticated: false,
is_admin: false,
is_active: false,
user: None,
};
assert!(!permission.has_permission(&context).await);
}
#[tokio::test]
async fn test_permission_is_admin_user() {
use bytes::Bytes;
use hyper::Method;
use reinhardt_http::Request;
let permission = IsAdminUser;
let request = Request::builder()
.method(Method::GET)
.uri("/test")
.body(Bytes::new())
.build()
.unwrap();
let context = PermissionContext {
request: &request,
is_authenticated: true,
is_admin: true,
is_active: true,
user: None,
};
assert!(permission.has_permission(&context).await);
let context = PermissionContext {
request: &request,
is_authenticated: true,
is_admin: false,
is_active: true,
user: None,
};
assert!(!permission.has_permission(&context).await);
}
#[tokio::test]
async fn test_permission_is_active_user() {
use bytes::Bytes;
use hyper::Method;
use reinhardt_http::Request;
let permission = IsActiveUser;
let request = Request::builder()
.method(Method::GET)
.uri("/test")
.body(Bytes::new())
.build()
.unwrap();
let context = PermissionContext {
request: &request,
is_authenticated: true,
is_admin: false,
is_active: true,
user: None,
};
assert!(permission.has_permission(&context).await);
let context = PermissionContext {
request: &request,
is_authenticated: true,
is_admin: false,
is_active: false,
user: None,
};
assert!(!permission.has_permission(&context).await);
}
#[tokio::test]
async fn test_permission_is_authenticated_or_read_only_get() {
use bytes::Bytes;
use hyper::Method;
use reinhardt_http::Request;
let permission = IsAuthenticatedOrReadOnly;
let request = Request::builder()
.method(Method::GET)
.uri("/test")
.body(Bytes::new())
.build()
.unwrap();
let context = PermissionContext {
request: &request,
is_authenticated: false,
is_admin: false,
is_active: false,
user: None,
};
assert!(permission.has_permission(&context).await);
}
#[tokio::test]
async fn test_permission_is_authenticated_or_read_only_post() {
use bytes::Bytes;
use hyper::Method;
use reinhardt_http::Request;
let permission = IsAuthenticatedOrReadOnly;
let request = Request::builder()
.method(Method::POST)
.uri("/test")
.body(Bytes::new())
.build()
.unwrap();
let context = PermissionContext {
request: &request,
is_authenticated: false,
is_admin: false,
is_active: false,
user: None,
};
assert!(!permission.has_permission(&context).await);
let context = PermissionContext {
request: &request,
is_authenticated: true,
is_admin: false,
is_active: true,
user: None,
};
assert!(permission.has_permission(&context).await);
}
#[test]
fn test_simple_user_implementation() {
let user = SimpleUser {
id: Uuid::now_v7(),
username: "testuser".to_string(),
email: "test@example.com".to_string(),
is_active: true,
is_admin: false,
is_staff: false,
is_superuser: false,
};
assert!(!user.id().is_empty());
assert_eq!(user.username(), "testuser");
assert!(user.is_authenticated());
assert!(user.is_active());
assert!(!user.is_admin());
}
#[test]
fn test_anonymous_user() {
let user = AnonymousUser;
assert_eq!(user.id(), "");
assert_eq!(user.username(), "");
assert!(!user.is_authenticated());
assert!(!user.is_active());
assert!(!user.is_admin());
}
}