8.7 KiB
Messages Handlers Authentication Refactor
Overview
This PRD outlines the plan to refactor all message handlers in libs/handlers/src/messages/
to accept the complete AuthenticatedUser
object instead of just the user_id
parameter.
Problem Statement
Currently, message handlers accept only a user ID (Uuid
) as the user parameter. This approach has several limitations:
- It lacks rich user context information such as organization memberships and roles
- It requires additional database lookups to fetch user data within handlers
- It doesn't align with the REST endpoints which already use the
AuthenticatedUser
type from middleware
By refactoring these handlers to accept the complete AuthenticatedUser
object, we will:
- Improve code efficiency by reducing redundant database queries
- Enhance security by making permission checks more comprehensive
- Increase consistency across the codebase
- Improve test reliability with standardized test user fixtures
Goals
- Update all message handlers to use
AuthenticatedUser
instead of user ID - Ensure tests continue to pass with the new parameter format
- Optimize handler code to use available user context information
- Maintain backward compatibility with existing functionality
Non-Goals
- Changing the business logic of the message handlers
- Modifying database schemas
- Adding new features to message handlers
- Changing the API contract between handlers and consumers
Technical Design
Overview
The refactoring will involve updating function signatures across all message handlers to accept &AuthenticatedUser
instead of &Uuid
, and then modifying the internal logic to use user.id
instead of user_id
where appropriate. We'll also leverage additional user information to optimize certain operations.
Components to Modify
Message Handlers
helpers/update_message_handler.rs
helpers/delete_message_handler.rs
- Any other message handlers or helper functions that use user ID
Example Function Signature Changes
// Before
pub async fn update_message_handler(
message_id: &Uuid,
update: &MessageUpdate,
user_id: &Uuid
) -> Result<Message> {
// ...
}
// After
pub async fn update_message_handler(
message_id: &Uuid,
update: &MessageUpdate,
user: &AuthenticatedUser
) -> Result<Message> {
// ...
}
Key Implementation Details
For each handler, we'll:
- Update the function signature to accept
&AuthenticatedUser
instead of&Uuid
- Replace all instances of
user_id
withuser.id
in function body - Leverage user attributes and organization info where applicable
- Update database queries to filter by
user.id
instead ofuser_id
- Enhance permission checks using organization and team roles
Example Implementation (for update_message_handler.rs)
// Before
pub async fn update_message_handler(
message_id: &Uuid,
update: &MessageUpdate,
user_id: &Uuid
) -> Result<Message> {
let mut conn = get_pg_pool().get().await?;
// Get the message to verify ownership
let message = messages::table
.filter(messages::id.eq(message_id))
.filter(messages::created_by.eq(user_id))
.filter(messages::deleted_at.is_null())
.first::<Message>(&mut conn)
.await
.map_err(|e| match e {
diesel::result::Error::NotFound =>
anyhow!("Message not found or you don't have permission to update it"),
_ => anyhow!("Database error: {}", e),
})?;
// Update the message
let updated_message = diesel::update(messages::table)
.filter(messages::id.eq(message_id))
.set(update.to_db_update())
.get_result::<Message>(&mut conn)
.await
.map_err(|e| anyhow!("Failed to update message: {}", e))?;
Ok(updated_message)
}
// After
pub async fn update_message_handler(
message_id: &Uuid,
update: &MessageUpdate,
user: &AuthenticatedUser
) -> Result<Message> {
let mut conn = get_pg_pool().get().await?;
// Get the message first to check ownership and chat relationships
let message = messages::table
.filter(messages::id.eq(message_id))
.filter(messages::deleted_at.is_null())
.first::<Message>(&mut conn)
.await
.map_err(|e| match e {
diesel::result::Error::NotFound =>
anyhow!("Message not found or has been deleted"),
_ => anyhow!("Database error: {}", e),
})?;
// Enhanced permission check
if message.created_by != user.id {
// Get the chat to check if user has admin privileges in the chat's organization
let chat = chats::table
.filter(chats::id.eq(message.chat_id))
.first::<Chat>(&mut conn)
.await
.map_err(|e| anyhow!("Failed to get chat: {}", e))?;
// Check if user has admin privileges in this organization
let is_org_admin = user.organizations.iter()
.any(|org| org.id == chat.organization_id &&
(org.role == UserOrganizationRole::Admin ||
org.role == UserOrganizationRole::Owner));
if !is_org_admin {
return Err(anyhow!("You don't have permission to update this message"));
}
}
// Update the message
let updated_message = diesel::update(messages::table)
.filter(messages::id.eq(message_id))
.set(update.to_db_update())
.get_result::<Message>(&mut conn)
.await
.map_err(|e| anyhow!("Failed to update message: {}", e))?;
Ok(updated_message)
}
REST Endpoint Changes
REST endpoints will need minimal changes:
// Before
pub async fn update_message_route(
Path(message_id): Path<Uuid>,
Extension(user): Extension<AuthenticatedUser>,
Json(update): Json<MessageUpdateRequest>
) -> Result<ApiResponse<MessageResponse>, ApiError> {
let message_update = MessageUpdate::from(update);
match update_message_handler(&message_id, &message_update, &user.id).await {
// ...
}
}
// After
pub async fn update_message_route(
Path(message_id): Path<Uuid>,
Extension(user): Extension<AuthenticatedUser>,
Json(update): Json<MessageUpdateRequest>
) -> Result<ApiResponse<MessageResponse>, ApiError> {
let message_update = MessageUpdate::from(update);
match update_message_handler(&message_id, &message_update, &user).await {
// ...
}
}
Files to Modify
Handler Files
/libs/handlers/src/messages/helpers/update_message_handler.rs
/libs/handlers/src/messages/helpers/delete_message_handler.rs
- Any other message-related handlers that use user ID
REST Endpoints
/src/routes/rest/routes/messages/update_message.rs
/src/routes/rest/routes/messages/delete_message.rs
- Any other REST endpoints that use message handlers
Implementation Plan
Phase 1: Handler Refactoring
- ⏳ Update signature and implementation of message handlers
- ⏳ Enhance permission checks using user organization information
- 🔜 Run tests to validate the changes
Phase 2: REST Endpoint Integration
- 🔜 Update REST endpoints to pass the full user object
- 🔜 Run integration tests to ensure everything works together
- 🔜 Fix any issues that emerge during testing
Testing Strategy
Unit Tests
- Each refactored handler will need updated tests
- Tests will use the new test utilities to create mock users
- Tests should verify handling of different user roles and permissions
Integration Tests
- End-to-end tests to validate the complete flow
- Tests for permission checks with various user types
Test Cases
- User updating their own message
- User attempting to update a message they don't own
- Admin user updating messages in their organization
- Permission checks for messages in shared chats
Rollback Plan
If issues arise during implementation:
- Revert affected handlers to original implementation
- Document specific issues for resolution in next attempt
Success Criteria
- All message handlers successfully accept
AuthenticatedUser
instead of just user ID - All tests pass with the new implementation
- REST endpoints work correctly with refactored handlers
- No regression in functionality or performance
- Enhanced permission checks using organization information
Dependencies
- Completion of the test utilities for creating mock
AuthenticatedUser
objects middleware::AuthenticatedUser
struct fromlibs/middleware/src/types.rs
- Existing message handlers implementation
- Completion of the chats handlers refactoring (since messages are related to chats)
Timeline
Expected completion time: 1-2 days
This PRD depends on the completion of the test utilities PRD and chats handlers refactoring, and should be implemented after those are completed.