summaryrefslogtreecommitdiff
path: root/src/domain
diff options
context:
space:
mode:
Diffstat (limited to 'src/domain')
-rw-r--r--src/domain/conversions.rs40
-rw-r--r--src/domain/dto.rs2
-rw-r--r--src/domain/mappers.rs40
-rw-r--r--src/domain/mod.rs2
-rw-r--r--src/domain/models.rs8
-rw-r--r--src/domain/queries.rs19
-rw-r--r--src/domain/repositories.rs9
-rw-r--r--src/domain/services.rs70
-rw-r--r--src/domain/specifications.rs40
-rw-r--r--src/domain/unit_of_work.rs10
10 files changed, 169 insertions, 71 deletions
diff --git a/src/domain/conversions.rs b/src/domain/conversions.rs
index 53e6062..13a1b9b 100644
--- a/src/domain/conversions.rs
+++ b/src/domain/conversions.rs
@@ -1,4 +1,4 @@
-use crate::database::{DbAccessToken, DbAuthCode, DbAuditLog, DbOAuthClient};
+use crate::database::{DbAccessToken, DbAuditLog, DbAuthCode, DbOAuthClient};
use crate::domain::models::*;
use anyhow::Result;
@@ -18,9 +18,21 @@ pub trait ToDb<T> {
impl FromDb<DbOAuthClient> for OAuthClient {
fn from_db(db_client: DbOAuthClient) -> Result<Self> {
let redirect_uris: Vec<String> = serde_json::from_str(&db_client.redirect_uris)?;
- let scopes: Vec<String> = db_client.scopes.split_whitespace().map(|s| s.to_string()).collect();
- let grant_types: Vec<String> = db_client.grant_types.split_whitespace().map(|s| s.to_string()).collect();
- let response_types: Vec<String> = db_client.response_types.split_whitespace().map(|s| s.to_string()).collect();
+ let scopes: Vec<String> = db_client
+ .scopes
+ .split_whitespace()
+ .map(|s| s.to_string())
+ .collect();
+ let grant_types: Vec<String> = db_client
+ .grant_types
+ .split_whitespace()
+ .map(|s| s.to_string())
+ .collect();
+ let response_types: Vec<String> = db_client
+ .response_types
+ .split_whitespace()
+ .map(|s| s.to_string())
+ .collect();
Ok(OAuthClient {
client_id: db_client.client_id,
@@ -57,8 +69,13 @@ impl ToDb<DbOAuthClient> for OAuthClient {
// Authorization Code conversions
impl FromDb<DbAuthCode> for AuthorizationCode {
fn from_db(db_code: DbAuthCode) -> Result<Self> {
- let scopes = db_code.scope
- .map(|s| s.split_whitespace().map(|scope| scope.to_string()).collect())
+ let scopes = db_code
+ .scope
+ .map(|s| {
+ s.split_whitespace()
+ .map(|scope| scope.to_string())
+ .collect()
+ })
.unwrap_or_default();
Ok(AuthorizationCode {
@@ -103,8 +120,13 @@ impl ToDb<DbAuthCode> for AuthorizationCode {
// Access Token conversions
impl FromDb<DbAccessToken> for AccessToken {
fn from_db(db_token: DbAccessToken) -> Result<Self> {
- let scopes = db_token.scope
- .map(|s| s.split_whitespace().map(|scope| scope.to_string()).collect())
+ let scopes = db_token
+ .scope
+ .map(|s| {
+ s.split_whitespace()
+ .map(|scope| scope.to_string())
+ .collect()
+ })
.unwrap_or_default();
Ok(AccessToken {
@@ -171,4 +193,4 @@ impl ToDb<DbAuditLog> for AuditEvent {
success: self.success,
})
}
-} \ No newline at end of file
+}
diff --git a/src/domain/dto.rs b/src/domain/dto.rs
index 336db61..1c342bc 100644
--- a/src/domain/dto.rs
+++ b/src/domain/dto.rs
@@ -131,4 +131,4 @@ impl From<crate::domain::OAuthError> for ErrorResponseDto {
error_uri: error.uri,
}
}
-} \ No newline at end of file
+}
diff --git a/src/domain/mappers.rs b/src/domain/mappers.rs
index 6efe276..405b08b 100644
--- a/src/domain/mappers.rs
+++ b/src/domain/mappers.rs
@@ -1,4 +1,4 @@
-use crate::database::{DbAccessToken, DbAuthCode, DbAuditLog, DbOAuthClient};
+use crate::database::{DbAccessToken, DbAuditLog, DbAuthCode, DbOAuthClient};
use crate::domain::models::*;
use anyhow::Result;
@@ -14,9 +14,21 @@ pub struct OAuthClientMapper;
impl DataMapper<OAuthClient, DbOAuthClient> for OAuthClientMapper {
fn to_domain(&self, db_client: DbOAuthClient) -> Result<OAuthClient> {
let redirect_uris: Vec<String> = serde_json::from_str(&db_client.redirect_uris)?;
- let scopes: Vec<String> = db_client.scopes.split_whitespace().map(|s| s.to_string()).collect();
- let grant_types: Vec<String> = db_client.grant_types.split_whitespace().map(|s| s.to_string()).collect();
- let response_types: Vec<String> = db_client.response_types.split_whitespace().map(|s| s.to_string()).collect();
+ let scopes: Vec<String> = db_client
+ .scopes
+ .split_whitespace()
+ .map(|s| s.to_string())
+ .collect();
+ let grant_types: Vec<String> = db_client
+ .grant_types
+ .split_whitespace()
+ .map(|s| s.to_string())
+ .collect();
+ let response_types: Vec<String> = db_client
+ .response_types
+ .split_whitespace()
+ .map(|s| s.to_string())
+ .collect();
Ok(OAuthClient {
client_id: db_client.client_id,
@@ -53,8 +65,13 @@ pub struct AuthCodeMapper;
impl DataMapper<AuthorizationCode, DbAuthCode> for AuthCodeMapper {
fn to_domain(&self, db_code: DbAuthCode) -> Result<AuthorizationCode> {
- let scopes = db_code.scope
- .map(|s| s.split_whitespace().map(|scope| scope.to_string()).collect())
+ let scopes = db_code
+ .scope
+ .map(|s| {
+ s.split_whitespace()
+ .map(|scope| scope.to_string())
+ .collect()
+ })
.unwrap_or_default();
Ok(AuthorizationCode {
@@ -99,8 +116,13 @@ pub struct AccessTokenMapper;
impl DataMapper<AccessToken, DbAccessToken> for AccessTokenMapper {
fn to_domain(&self, db_token: DbAccessToken) -> Result<AccessToken> {
- let scopes = db_token.scope
- .map(|s| s.split_whitespace().map(|scope| scope.to_string()).collect())
+ let scopes = db_token
+ .scope
+ .map(|s| {
+ s.split_whitespace()
+ .map(|scope| scope.to_string())
+ .collect()
+ })
.unwrap_or_default();
Ok(AccessToken {
@@ -206,4 +228,4 @@ impl Default for MapperRegistry {
fn default() -> Self {
Self::new()
}
-} \ No newline at end of file
+}
diff --git a/src/domain/mod.rs b/src/domain/mod.rs
index 9a8bfca..7ba3b00 100644
--- a/src/domain/mod.rs
+++ b/src/domain/mod.rs
@@ -16,4 +16,4 @@ pub use queries::*;
pub use repositories::*;
pub use services::*;
pub use specifications::*;
-pub use unit_of_work::*; \ No newline at end of file
+pub use unit_of_work::*;
diff --git a/src/domain/models.rs b/src/domain/models.rs
index 85b554f..26e6df3 100644
--- a/src/domain/models.rs
+++ b/src/domain/models.rs
@@ -108,9 +108,9 @@ pub struct AuthorizationRequest {
#[derive(Debug, Clone, PartialEq)]
pub struct TokenRequest {
pub grant_type: String,
- pub code: Option<String>, // For authorization_code grant
- pub refresh_token: Option<String>, // For refresh_token grant
- pub redirect_uri: Option<String>, // For authorization_code grant
+ pub code: Option<String>, // For authorization_code grant
+ pub refresh_token: Option<String>, // For refresh_token grant
+ pub redirect_uri: Option<String>, // For authorization_code grant
pub client_id: String,
pub client_secret: Option<String>,
// PKCE
@@ -220,4 +220,4 @@ impl Scope {
description: Some("Access to user email address".to_string()),
}
}
-} \ No newline at end of file
+}
diff --git a/src/domain/queries.rs b/src/domain/queries.rs
index d4eb19e..88fb480 100644
--- a/src/domain/queries.rs
+++ b/src/domain/queries.rs
@@ -277,31 +277,24 @@ pub struct CommonQueries;
impl CommonQueries {
/// Get recent failed login attempts (security monitoring)
pub fn recent_failed_logins() -> FailedAuthQuery {
- FailedAuthQuery::new()
- .last_24_hours()
- .min_attempts(3)
+ FailedAuthQuery::new().last_24_hours().min_attempts(3)
}
/// Get audit trail for a specific client
pub fn client_audit_trail(client_id: &str) -> AuditEventsQuery {
- AuditEventsQuery::new()
- .for_client(client_id)
- .limit(1000)
+ AuditEventsQuery::new().for_client(client_id).limit(1000)
}
/// Get token usage statistics for the last 30 days
pub fn monthly_token_usage() -> TokenUsageQuery {
let now = Utc::now();
let thirty_days_ago = now - chrono::Duration::days(30);
-
- TokenUsageQuery::new(TokenUsageGroupBy::Day)
- .date_range(thirty_days_ago, now)
+
+ TokenUsageQuery::new(TokenUsageGroupBy::Day).date_range(thirty_days_ago, now)
}
/// Get all active clients with OpenID scope
pub fn openid_clients() -> OAuthClientsQuery {
- OAuthClientsQuery::new()
- .active_only()
- .with_scope("openid")
+ OAuthClientsQuery::new().active_only().with_scope("openid")
}
-} \ No newline at end of file
+}
diff --git a/src/domain/repositories.rs b/src/domain/repositories.rs
index 1aa4f33..3622373 100644
--- a/src/domain/repositories.rs
+++ b/src/domain/repositories.rs
@@ -36,6 +36,11 @@ pub trait DomainAuditRepository: Send + Sync {
/// Domain-focused repository for rate limiting
pub trait DomainRateRepository: Send + Sync {
fn check_rate_limit(&self, limit: &RateLimit) -> Result<u32>; // Returns current count
- fn increment_rate_limit(&self, identifier: &str, endpoint: &str, window_minutes: u32) -> Result<u32>;
+ fn increment_rate_limit(
+ &self,
+ identifier: &str,
+ endpoint: &str,
+ window_minutes: u32,
+ ) -> Result<u32>;
fn cleanup_old_rate_limits(&self) -> Result<()>;
-} \ No newline at end of file
+}
diff --git a/src/domain/services.rs b/src/domain/services.rs
index 2c23cdc..0e22ddb 100644
--- a/src/domain/services.rs
+++ b/src/domain/services.rs
@@ -3,10 +3,22 @@ use anyhow::Result;
/// Domain service for OAuth2 authorization flow
pub trait AuthorizationService: Send + Sync {
- fn authorize(&self, request: &AuthorizationRequest, user: &User) -> Result<AuthorizationResult, OAuthError>;
+ fn authorize(
+ &self,
+ request: &AuthorizationRequest,
+ user: &User,
+ ) -> Result<AuthorizationResult, OAuthError>;
fn validate_client(&self, client_id: &str) -> Result<OAuthClient, OAuthError>;
- fn validate_redirect_uri(&self, client: &OAuthClient, redirect_uri: &str) -> Result<(), OAuthError>;
- fn validate_scopes(&self, client: &OAuthClient, requested_scopes: &[String]) -> Result<Vec<String>, OAuthError>;
+ fn validate_redirect_uri(
+ &self,
+ client: &OAuthClient,
+ redirect_uri: &str,
+ ) -> Result<(), OAuthError>;
+ fn validate_scopes(
+ &self,
+ client: &OAuthClient,
+ requested_scopes: &[String],
+ ) -> Result<Vec<String>, OAuthError>;
}
/// Domain service for OAuth2 token operations
@@ -23,28 +35,59 @@ pub trait ClientService: Send + Sync {
fn get_client(&self, client_id: &str) -> Result<Option<OAuthClient>>;
fn update_client(&self, client: &OAuthClient) -> Result<()>;
fn delete_client(&self, client_id: &str) -> Result<()>;
- fn authenticate_client(&self, client_id: &str, client_secret: &str) -> Result<OAuthClient, OAuthError>;
+ fn authenticate_client(
+ &self,
+ client_id: &str,
+ client_secret: &str,
+ ) -> Result<OAuthClient, OAuthError>;
}
/// Domain service for user management
pub trait UserService: Send + Sync {
fn get_user(&self, user_id: &str) -> Result<Option<User>>;
fn authenticate_user(&self, username: &str, password: &str) -> Result<User, OAuthError>;
- fn is_user_authorized(&self, user: &User, client: &OAuthClient, scopes: &[String]) -> Result<bool>;
+ fn is_user_authorized(
+ &self,
+ user: &User,
+ client: &OAuthClient,
+ scopes: &[String],
+ ) -> Result<bool>;
}
/// Domain service for audit logging
pub trait AuditService: Send + Sync {
- fn log_authorization_attempt(&self, request: &AuthorizationRequest, user: Option<&User>, success: bool, ip_address: Option<&str>) -> Result<()>;
- fn log_token_request(&self, request: &TokenRequest, success: bool, ip_address: Option<&str>) -> Result<()>;
- fn log_token_introspection(&self, token_hash: &str, client_id: &str, success: bool) -> Result<()>;
+ fn log_authorization_attempt(
+ &self,
+ request: &AuthorizationRequest,
+ user: Option<&User>,
+ success: bool,
+ ip_address: Option<&str>,
+ ) -> Result<()>;
+ fn log_token_request(
+ &self,
+ request: &TokenRequest,
+ success: bool,
+ ip_address: Option<&str>,
+ ) -> Result<()>;
+ fn log_token_introspection(
+ &self,
+ token_hash: &str,
+ client_id: &str,
+ success: bool,
+ ) -> Result<()>;
fn log_token_revocation(&self, token_hash: &str, client_id: &str, success: bool) -> Result<()>;
}
/// Domain service for rate limiting
pub trait RateLimitService: Send + Sync {
fn check_rate_limit(&self, identifier: &str, endpoint: &str) -> Result<(), OAuthError>;
- fn is_rate_limited(&self, identifier: &str, endpoint: &str, max_requests: u32, window_minutes: u32) -> Result<bool>;
+ fn is_rate_limited(
+ &self,
+ identifier: &str,
+ endpoint: &str,
+ max_requests: u32,
+ window_minutes: u32,
+ ) -> Result<bool>;
}
/// Domain service for PKCE operations
@@ -57,7 +100,12 @@ pub trait PkceService: Send + Sync {
/// Domain service for JWT operations
pub trait JwtService: Send + Sync {
fn generate_access_token(&self, claims: &TokenClaims) -> Result<String>;
- fn generate_refresh_token(&self, client_id: &str, user_id: &str, scopes: &[String]) -> Result<String>;
+ fn generate_refresh_token(
+ &self,
+ client_id: &str,
+ user_id: &str,
+ scopes: &[String],
+ ) -> Result<String>;
fn validate_token(&self, token: &str) -> Result<TokenClaims>;
fn get_jwks(&self) -> Result<String>; // JSON Web Key Set
-} \ No newline at end of file
+}
diff --git a/src/domain/specifications.rs b/src/domain/specifications.rs
index 3237d1b..76aafcc 100644
--- a/src/domain/specifications.rs
+++ b/src/domain/specifications.rs
@@ -22,7 +22,7 @@ impl<T> Specification<T> for AndSpecification<T> {
fn is_satisfied_by(&self, candidate: &T) -> bool {
self.left.is_satisfied_by(candidate) && self.right.is_satisfied_by(candidate)
}
-
+
fn reason_for_failure(&self, candidate: &T) -> Option<String> {
if !self.left.is_satisfied_by(candidate) {
self.left.reason_for_failure(candidate)
@@ -40,7 +40,7 @@ impl Specification<OAuthClient> for ActiveClientSpecification {
fn is_satisfied_by(&self, client: &OAuthClient) -> bool {
client.is_active
}
-
+
fn reason_for_failure(&self, _client: &OAuthClient) -> Option<String> {
Some("Client is not active".to_string())
}
@@ -60,7 +60,7 @@ impl Specification<OAuthClient> for ValidRedirectUriSpecification {
fn is_satisfied_by(&self, client: &OAuthClient) -> bool {
client.redirect_uris.contains(&self.redirect_uri)
}
-
+
fn reason_for_failure(&self, _client: &OAuthClient) -> Option<String> {
Some(format!("Invalid redirect_uri: {}", self.redirect_uri))
}
@@ -78,15 +78,19 @@ impl SupportedScopesSpecification {
impl Specification<OAuthClient> for SupportedScopesSpecification {
fn is_satisfied_by(&self, client: &OAuthClient) -> bool {
- self.requested_scopes.iter().all(|scope| client.scopes.contains(scope))
+ self.requested_scopes
+ .iter()
+ .all(|scope| client.scopes.contains(scope))
}
-
+
fn reason_for_failure(&self, client: &OAuthClient) -> Option<String> {
- let unsupported: Vec<_> = self.requested_scopes.iter()
+ let unsupported: Vec<_> = self
+ .requested_scopes
+ .iter()
.filter(|scope| !client.scopes.contains(scope))
.cloned()
.collect();
-
+
if !unsupported.is_empty() {
Some(format!("Unsupported scopes: {}", unsupported.join(", ")))
} else {
@@ -101,7 +105,7 @@ impl Specification<AuthorizationCode> for UnusedAuthCodeSpecification {
fn is_satisfied_by(&self, code: &AuthorizationCode) -> bool {
!code.is_used
}
-
+
fn reason_for_failure(&self, _code: &AuthorizationCode) -> Option<String> {
Some("Authorization code has already been used".to_string())
}
@@ -112,7 +116,7 @@ impl Specification<AuthorizationCode> for ValidAuthCodeSpecification {
fn is_satisfied_by(&self, code: &AuthorizationCode) -> bool {
chrono::Utc::now() < code.expires_at
}
-
+
fn reason_for_failure(&self, _code: &AuthorizationCode) -> Option<String> {
Some("Authorization code has expired".to_string())
}
@@ -132,7 +136,7 @@ impl Specification<AuthorizationCode> for MatchingClientSpecification {
fn is_satisfied_by(&self, code: &AuthorizationCode) -> bool {
code.client_id == self.client_id
}
-
+
fn reason_for_failure(&self, _code: &AuthorizationCode) -> Option<String> {
Some("Client ID mismatch".to_string())
}
@@ -153,14 +157,18 @@ impl Specification<AuthorizationCode> for ValidPkceSpecification {
fn is_satisfied_by(&self, code: &AuthorizationCode) -> bool {
if let Some(challenge) = &code.code_challenge {
let method = code.code_challenge_method.as_deref().unwrap_or("plain");
- crate::oauth::pkce::verify_code_challenge(&self.code_verifier, challenge,
- &crate::oauth::pkce::CodeChallengeMethod::from_str(method).unwrap_or(crate::oauth::pkce::CodeChallengeMethod::Plain)
- ).is_ok()
+ crate::oauth::pkce::verify_code_challenge(
+ &self.code_verifier,
+ challenge,
+ &crate::oauth::pkce::CodeChallengeMethod::from_str(method)
+ .unwrap_or(crate::oauth::pkce::CodeChallengeMethod::Plain),
+ )
+ .is_ok()
} else {
true // No PKCE required
}
}
-
+
fn reason_for_failure(&self, _code: &AuthorizationCode) -> Option<String> {
Some("PKCE verification failed".to_string())
}
@@ -172,7 +180,7 @@ impl Specification<AccessToken> for ValidTokenSpecification {
fn is_satisfied_by(&self, token: &AccessToken) -> bool {
!token.is_revoked && chrono::Utc::now() < token.expires_at
}
-
+
fn reason_for_failure(&self, token: &AccessToken) -> Option<String> {
if token.is_revoked {
Some("Token has been revoked".to_string())
@@ -191,4 +199,4 @@ pub trait SpecificationExt<T>: Specification<T> + Sized + 'static {
}
}
-impl<T, S: Specification<T> + 'static> SpecificationExt<T> for S {} \ No newline at end of file
+impl<T, S: Specification<T> + 'static> SpecificationExt<T> for S {}
diff --git a/src/domain/unit_of_work.rs b/src/domain/unit_of_work.rs
index db8294a..7d6a0e3 100644
--- a/src/domain/unit_of_work.rs
+++ b/src/domain/unit_of_work.rs
@@ -11,10 +11,10 @@ pub trait UnitOfWork: Send + Sync {
pub trait Transaction: Send + Sync {
/// Commit all changes in this transaction
fn commit(self: Box<Self>) -> Result<()>;
-
+
/// Rollback all changes in this transaction
fn rollback(self: Box<Self>) -> Result<()>;
-
+
/// Get repositories within this transaction context
fn client_repository(&self) -> Arc<dyn crate::domain::DomainClientRepository>;
fn auth_code_repository(&self) -> Arc<dyn crate::domain::DomainAuthCodeRepository>;
@@ -31,7 +31,7 @@ impl OAuthUnitOfWork {
pub fn new(uow: Arc<dyn UnitOfWork>) -> Self {
Self { uow }
}
-
+
/// Execute OAuth2 authorization code exchange atomically
pub fn exchange_authorization_code<F>(&self, operation: F) -> Result<()>
where
@@ -46,7 +46,7 @@ impl OAuthUnitOfWork {
}
}
}
-
+
/// Execute token refresh atomically
pub fn refresh_tokens<F>(&self, operation: F) -> Result<()>
where
@@ -61,4 +61,4 @@ impl OAuthUnitOfWork {
}
}
}
-} \ No newline at end of file
+}