use crate::common::errors::DomainError; use crate::domain::entities::app_password::AppPassword; use crate::domain::entities::device_code::DeviceCode; use crate::domain::entities::session::Session; use crate::domain::entities::user::User; // ============================================================================ // 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.) /// /// Methods are async because implementations (e.g. Argon2) are CPU-intensive /// and must run on a blocking thread pool to avoid starving Tokio workers. pub trait PasswordHasherPort: Send + Sync + 'static { /// Hash a plain text password async fn hash_password(&self, password: &str) -> Result; /// Verify a plain text password against a hash async fn verify_password(&self, password: &str, hash: &str) -> Result; } /// 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; /// Validate a token and extract its claims fn validate_token(&self, token: &str) -> Result; /// 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 // ============================================================================ pub trait UserStoragePort: Send + Sync + 'static { /// Creates a new user async fn create_user(&self, user: User) -> Result; /// Gets a user by ID async fn get_user_by_id(&self, id: &str) -> Result; /// Gets a user by username async fn get_user_by_username(&self, username: &str) -> Result; /// Gets a user by email async fn get_user_by_email(&self, email: &str) -> Result; /// Updates an existing user async fn update_user(&self, user: User) -> Result; /// Updates only the storage usage of a user async fn update_storage_usage( &self, user_id: &str, usage_bytes: i64, ) -> Result<(), DomainError>; /// Lists users with pagination async fn list_users(&self, limit: i64, offset: i64) -> Result, DomainError>; /// Searches users by username or email (SQL ILIKE) with a limit. async fn search_users(&self, query: &str, limit: i64) -> Result, DomainError>; /// Lists users by role (e.g., "admin" or "user") async fn list_users_by_role(&self, role: &str) -> Result, DomainError>; /// Deletes a user by their ID async fn delete_user(&self, user_id: &str) -> Result<(), DomainError>; /// Changes a user's password 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; /// Activates or deactivates a user async fn set_user_active_status(&self, user_id: &str, active: bool) -> Result<(), DomainError>; /// Changes a user's role async fn change_role(&self, user_id: &str, role: &str) -> Result<(), DomainError>; /// Updates a user's storage quota async fn update_storage_quota( &self, user_id: &str, quota_bytes: i64, ) -> Result<(), DomainError>; /// Counts the total number of users async fn count_users(&self) -> Result; } // ============================================================================ // 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, } /// Claims extracted from the validated OIDC ID token #[derive(Debug, Clone)] pub struct OidcIdClaims { pub sub: String, pub email: Option, pub email_verified: Option, pub preferred_username: Option, pub name: Option, pub groups: Vec, } /// Port for OIDC operations — implemented in infrastructure layer 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. /// This is async because it may need to fetch the OIDC discovery document. async fn get_authorize_url( &self, state: &str, nonce: &str, pkce_challenge: &str, ) -> Result; /// Exchange an authorization code for tokens, providing PKCE code_verifier. async fn exchange_code( &self, code: &str, pkce_verifier: &str, ) -> Result; /// 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; /// Fetch user info from the UserInfo endpoint (fallback for missing ID token claims) async fn fetch_user_info(&self, access_token: &str) -> Result; /// Get the OIDC provider display name fn provider_name(&self) -> &str; } pub trait SessionStoragePort: Send + Sync + 'static { /// Creates a new session async fn create_session(&self, session: Session) -> Result; /// Gets a session by refresh token async fn get_session_by_refresh_token( &self, refresh_token: &str, ) -> Result; /// Revokes a specific session async fn revoke_session(&self, session_id: &str) -> Result<(), DomainError>; /// Revokes all sessions of a user async fn revoke_all_user_sessions(&self, user_id: &str) -> Result; } // ============================================================================ // Device Authorization Grant Port (RFC 8628) // ============================================================================ pub trait DeviceCodeStoragePort: Send + Sync + 'static { /// Persist a new device code flow async fn create_device_code(&self, device_code: DeviceCode) -> Result; /// Find a device code by its opaque device_code token (used by client polling) async fn get_by_device_code(&self, device_code: &str) -> Result; /// Find a pending device code by the short user_code (used on verification page) async fn get_pending_by_user_code(&self, user_code: &str) -> Result; /// Update a device code (status change, token storage, poll timestamp, etc.) async fn update_device_code(&self, device_code: DeviceCode) -> Result<(), DomainError>; /// Delete expired device codes (cleanup job) async fn delete_expired(&self) -> Result; /// List authorized device codes for a user (for UI management) async fn list_by_user(&self, user_id: &str) -> Result, DomainError>; /// Delete a specific device code by ID (revocation) async fn delete_by_id(&self, id: &str) -> Result<(), DomainError>; } // ============================================================================ // App Password Storage Port // ============================================================================ /// Storage port for application-specific passwords (HTTP Basic Auth for DAV clients). pub trait AppPasswordStoragePort: Send + Sync + 'static { /// Persist a new app password (hash already computed). async fn create(&self, app_password: AppPassword) -> Result; /// Get all active (non-expired) app passwords for a user. async fn list_by_user(&self, user_id: &str) -> Result, DomainError>; /// Get a specific app password by ID. async fn get_by_id(&self, id: &str) -> Result; /// Get all active app passwords for a user ID (for Basic auth verification). /// This includes the password hash for verification. async fn get_active_by_user_id(&self, user_id: &str) -> Result, DomainError>; /// Update the `last_used_at` timestamp after a successful authentication. async fn touch_last_used(&self, id: &str) -> Result<(), DomainError>; /// Get active app passwords for a user filtered by token prefix (first 8 chars). /// More efficient than `get_active_by_user_id` when the password prefix is known. async fn get_active_by_user_prefix( &self, user_id: &str, prefix: &str, ) -> Result, DomainError>; /// Deactivate (soft-delete) an app password, scoped to the owning user. async fn revoke(&self, id: &str, user_id: &str) -> Result<(), DomainError>; /// Delete an app password owned by a specific user. Returns true if found and deleted. async fn delete_by_user_and_id(&self, id: &str, user_id: &str) -> Result; /// Hard-delete expired/revoked app passwords (cleanup). async fn delete_expired(&self) -> Result; }