Files
Oxicloud/src/application/ports/auth_ports.rs
T

170 lines
6.7 KiB
Rust
Raw Normal View History

2025-03-20 09:22:31 +01:00
use async_trait::async_trait;
use crate::domain::entities::user::User;
use crate::domain::entities::session::Session;
use crate::common::errors::DomainError;
// ============================================================================
// Cryptography Ports - Extracted from Domain to maintain Clean Architecture
// ============================================================================
/// Port for password hashing operations.
///
/// This trait abstracts cryptographic password operations, allowing the domain
/// layer to remain independent of specific hashing implementations (argon2, bcrypt, etc.)
pub trait PasswordHasherPort: Send + Sync + 'static {
/// Hash a plain text password
fn hash_password(&self, password: &str) -> Result<String, DomainError>;
/// Verify a plain text password against a hash
fn verify_password(&self, password: &str, hash: &str) -> Result<bool, DomainError>;
}
/// Claims contained in a JWT token
#[derive(Debug, Clone)]
pub struct TokenClaims {
/// Subject identifier (user ID)
pub sub: String,
/// Expiration timestamp (seconds since Unix epoch)
pub exp: i64,
/// Issued at timestamp (seconds since Unix epoch)
pub iat: i64,
/// JWT unique ID
pub jti: String,
/// Username
pub username: String,
/// User email
pub email: String,
/// User role
pub role: String,
}
/// Port for JWT token operations.
///
/// This trait abstracts token generation and validation, allowing the domain
/// layer to remain independent of specific JWT implementations.
pub trait TokenServicePort: Send + Sync + 'static {
/// Generate an access token for a user
fn generate_access_token(&self, user: &User) -> Result<String, DomainError>;
/// Validate a token and extract its claims
fn validate_token(&self, token: &str) -> Result<TokenClaims, DomainError>;
/// Generate a refresh token
fn generate_refresh_token(&self) -> String;
/// Get refresh token expiry in seconds
fn refresh_token_expiry_secs(&self) -> i64;
/// Get refresh token expiry in days
fn refresh_token_expiry_days(&self) -> i64;
}
// ============================================================================
// Storage Ports
// ============================================================================
2025-03-20 09:22:31 +01:00
#[async_trait]
pub trait UserStoragePort: Send + Sync + 'static {
/// Crea un nuevo usuario
async fn create_user(&self, user: User) -> Result<User, DomainError>;
/// Obtiene un usuario por ID
async fn get_user_by_id(&self, id: &str) -> Result<User, DomainError>;
/// Obtiene un usuario por nombre de usuario
async fn get_user_by_username(&self, username: &str) -> Result<User, DomainError>;
/// Obtiene un usuario por correo electrónico
async fn get_user_by_email(&self, email: &str) -> Result<User, DomainError>;
/// Actualiza un usuario existente
async fn update_user(&self, user: User) -> Result<User, DomainError>;
/// Actualiza solo el uso de almacenamiento de un usuario
async fn update_storage_usage(&self, user_id: &str, usage_bytes: i64) -> Result<(), DomainError>;
/// Lista usuarios con paginación
async fn list_users(&self, limit: i64, offset: i64) -> Result<Vec<User>, DomainError>;
2025-04-12 18:58:21 +02:00
/// Lista usuarios por rol (por ejemplo, "admin" o "user")
async fn list_users_by_role(&self, role: &str) -> Result<Vec<User>, DomainError>;
/// Elimina un usuario por su ID
async fn delete_user(&self, user_id: &str) -> Result<(), DomainError>;
2025-03-20 09:22:31 +01:00
/// Cambia la contraseña de un usuario
async fn change_password(&self, user_id: &str, password_hash: &str) -> Result<(), DomainError>;
/// Finds a user by OIDC provider + subject pair
async fn get_user_by_oidc_subject(&self, provider: &str, subject: &str) -> Result<User, DomainError>;
/// Activa o desactiva un usuario
async fn set_user_active_status(&self, user_id: &str, active: bool) -> Result<(), DomainError>;
/// Cambia el rol de un usuario
async fn change_role(&self, user_id: &str, role: &str) -> Result<(), DomainError>;
/// Actualiza la cuota de almacenamiento de un usuario
async fn update_storage_quota(&self, user_id: &str, quota_bytes: i64) -> Result<(), DomainError>;
/// Cuenta el número total de usuarios
async fn count_users(&self) -> Result<i64, DomainError>;
}
// ============================================================================
// OIDC Port
// ============================================================================
/// Represents the token set returned by the OIDC provider after code exchange
#[derive(Debug, Clone)]
pub struct OidcTokenSet {
pub access_token: String,
pub id_token: String,
pub refresh_token: Option<String>,
}
/// Claims extracted from the validated OIDC ID token
#[derive(Debug, Clone)]
pub struct OidcIdClaims {
pub sub: String,
pub email: Option<String>,
pub preferred_username: Option<String>,
pub name: Option<String>,
pub groups: Vec<String>,
}
/// Port for OIDC operations — implemented in infrastructure layer
#[async_trait]
pub trait OidcServicePort: Send + Sync + 'static {
/// Get the authorization URL for redirecting the user to the IdP.
/// Includes PKCE code_challenge (S256) and nonce for ID token binding.
fn get_authorize_url(&self, state: &str, nonce: &str, pkce_challenge: &str) -> Result<String, DomainError>;
/// Exchange an authorization code for tokens, providing PKCE code_verifier.
async fn exchange_code(&self, code: &str, pkce_verifier: &str) -> Result<OidcTokenSet, DomainError>;
/// Validate an ID token and extract claims.
/// If `expected_nonce` is provided, verifies the `nonce` claim matches.
async fn validate_id_token(&self, id_token: &str, expected_nonce: Option<&str>) -> Result<OidcIdClaims, DomainError>;
/// Fetch user info from the UserInfo endpoint (fallback for missing ID token claims)
async fn fetch_user_info(&self, access_token: &str) -> Result<OidcIdClaims, DomainError>;
/// Get the OIDC provider display name
fn provider_name(&self) -> &str;
2025-03-20 09:22:31 +01:00
}
#[async_trait]
pub trait SessionStoragePort: Send + Sync + 'static {
/// Crea una nueva sesión
async fn create_session(&self, session: Session) -> Result<Session, DomainError>;
/// Obtiene una sesión por token de actualización
async fn get_session_by_refresh_token(&self, refresh_token: &str) -> Result<Session, DomainError>;
/// Revoca una sesión específica
async fn revoke_session(&self, session_id: &str) -> Result<(), DomainError>;
/// Revoca todas las sesiones de un usuario
async fn revoke_all_user_sessions(&self, user_id: &str) -> Result<u64, DomainError>;
}