Summary
Consider migrating the authorization layer from the custom Check trait pattern to Axum's native FromRequestParts extractor pattern for improved idiomatic alignment and code reuse.
Current Architecture
The codebase uses a custom authorization system in web/src/protect/mod.rs:
Check trait - Defines reusable authorization rules
Predicate struct - Pairs a Check with arguments
authorize() function - Runs predicates via from_fn_with_state middleware
Example checks: UserInOrganization, UserIsAdmin, UserCanAccessCoachingSession
Proposed Architecture
Migrate to Axum's FromRequestParts extractor pattern, which:
- Is Axum's core abstraction - Extractors are the primary way Axum handles request processing
- Enables dual-use - Same type works as middleware (via
from_extractor) AND directly in handlers
- Provides type-safe context - Extractors can carry validated data to handlers
- Reduces redundancy - No need to re-extract path parameters in handlers after middleware validates them
Example Migration
Before (Check trait):
// In protect/users/actions.rs
pub(crate) async fn index(
State(app_state): State<AppState>,
AuthenticatedUser(user): AuthenticatedUser,
Path(user_id): Path<Id>,
request: Request,
next: Next,
) -> impl IntoResponse {
let checks = vec![Predicate::new(UserCanAccessUserData, vec![user_id])];
authorize(&app_state, user, request, next, checks).await
}
// In controller - must re-extract user_id
pub async fn index(
Path(user_id): Path<Id>,
// ...
)
After (Extractor):
// New extractor in extractors/user_data_access.rs
pub struct UserDataAccess {
pub target_user_id: Id,
pub authenticated_user: domain::users::Model,
}
impl<S> FromRequestParts<S> for UserDataAccess { /* ... */ }
// In controller - authorization AND extraction in one step
pub async fn index(
UserDataAccess { target_user_id, .. }: UserDataAccess,
// ...
)
// Or as middleware
.route_layer(from_extractor::<UserDataAccess>())
Benefits
| Aspect |
Current (Check) |
Proposed (Extractor) |
| Axum alignment |
Custom abstraction |
Native Axum pattern |
| Code reuse |
Middleware only |
Middleware + handlers |
| Type safety |
Manual arg passing |
Compile-time checked |
| Documentation |
Custom docs needed |
Axum docs apply |
Implementation Considerations
-
State access - Extractors need AppState via Extension layer rather than State. This requires adding .layer(Extension(app_state.clone())) to the router.
-
Gradual migration - Can migrate one authorization check at a time; both patterns can coexist.
-
Testing - Extractors are easier to unit test in isolation.
References
Scope
This is a refactoring effort that could be done incrementally. Priority candidates for migration:
Summary
Consider migrating the authorization layer from the custom
Checktrait pattern to Axum's nativeFromRequestPartsextractor pattern for improved idiomatic alignment and code reuse.Current Architecture
The codebase uses a custom authorization system in
web/src/protect/mod.rs:Checktrait - Defines reusable authorization rulesPredicatestruct - Pairs aCheckwith argumentsauthorize()function - Runs predicates viafrom_fn_with_statemiddlewareExample checks:
UserInOrganization,UserIsAdmin,UserCanAccessCoachingSessionProposed Architecture
Migrate to Axum's
FromRequestPartsextractor pattern, which:from_extractor) AND directly in handlersExample Migration
Before (Check trait):
After (Extractor):
Benefits
Implementation Considerations
State access - Extractors need
AppStateviaExtensionlayer rather thanState. This requires adding.layer(Extension(app_state.clone()))to the router.Gradual migration - Can migrate one authorization check at a time; both patterns can coexist.
Testing - Extractors are easier to unit test in isolation.
References
Scope
This is a refactoring effort that could be done incrementally. Priority candidates for migration:
UserCanAccessCoachingSession→CoachingSessionAccessextractorUserInOrganization→OrganizationMemberAccessextractorUserIsAdmin→AdminAccessextractorUserCanAccessUserData→UserDataAccessextractor