Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Platform apis #13

Open
wants to merge 13 commits into
base: master
Choose a base branch
from
Open

Platform apis #13

wants to merge 13 commits into from

Conversation

AnthonyRonning
Copy link
Contributor

@AnthonyRonning AnthonyRonning commented Feb 24, 2025

Summary by CodeRabbit

  • New Features

    • Introduced a robust email verification process for enhanced registration and secure login.
    • Improved authentication workflows for a more responsive and secure login experience.
    • Enabled refined management of organizations and projects for smoother operations.
    • Enhanced validation to allow underscores in alphanumeric strings.
  • Refactor

    • Streamlined key internal processes and updated error handling to boost overall system reliability.
    • Updated identifier handling across organization and project management to use UUIDs for consistency.
  • Chores

    • Cleaned up outdated migration processes and refined code structure to improve maintainability.
    • Removed unused imports and attributes to enhance code clarity.

Copy link

coderabbitai bot commented Feb 24, 2025

Walkthrough

This pull request introduces a platform email verification feature. It adds database migrations to create a new platform_email_verifications table with associated indexes and a trigger, along with corresponding cleanup in the down migration. The database layer in src/db.rs has been extended with several new CRUD and verification methods, accompanied by new error variants. New modules and models are added in the models directory to handle email verification records, while web routes are updated to use UUIDs, rename verification-related methods, and adjust logging and attribute handling.

Changes

File(s) Change Summary
migrations/.../platform_email_verification/{up.sql, down.sql} up.sql: Creates platform_email_verifications table with columns (id, platform_user_id, verification_code, is_verified, created_at, updated_at, expires_at), indexes, and a trigger; down.sql: Drops the trigger, indexes, and the table.
src/db.rs Added new methods for creating, retrieving, updating, deleting, and verifying platform email verifications; updated DBError enum with PlatformEmailVerificationError and PlatformEmailVerificationNotFound variants.
src/main.rs Modified authenticate_platform_user by adding warning logs and changing password verification logic; uncommented and re-enabled platform login and organization routes.
src/migrations.rs Added a comment: // TODO remove migration code now that this ran successfully.
src/models/{mod.rs, platform_email_verification.rs, schema.rs} Introduced a new module for platform_email_verification: added model definitions, CRUD methods, custom error types, and a new table schema for platform_email_verifications.
src/web/{mod.rs, platform/login_routes.rs, platform/mod.rs, platform/org_routes.rs} Renamed verification model from NewEmailVerification to NewPlatformEmailVerification; updated route handlers to use new DB methods and UUID types instead of i32; removed obsolete attributes and updated error log messages for clarity.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant WebServer
    participant DB
    participant VerificationModel

    Client->>WebServer: Registration or verification request
    WebServer->>DB: Invoke create_platform_email_verification()
    DB->>VerificationModel: Insert new verification record
    VerificationModel-->>DB: Record inserted
    DB-->>WebServer: Return verification record
    WebServer-->>Client: Send response (verification details)
Loading

Poem

I'm a rabbit hopping through the code,
Leaving trails in migrations and DB mode.
Email verifications now dance in line,
Models and routes coded just fine.
With a twitch of my nose and a joyful leap,
CodeRabbit celebrates this change so deep!
🐇✨


📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6842e7e and 6070cb0.

⛔ Files ignored due to path filters (2)
  • pcrDev.json is excluded by !pcrDev.json
  • pcrProd.json is excluded by !pcrProd.json
📒 Files selected for processing (2)
  • src/main.rs (6 hunks)
  • src/web/platform/validation.rs (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/web/platform/validation.rs
  • src/main.rs
⏰ Context from checks skipped due to timeout of 100000ms (1)
  • GitHub Check: Development Reproducible Build

🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Inline review comments failed to post. This is likely due to GitHub's limits when posting large numbers of comments. If you are seeing this consistently, please check "Code review limits" under "Moderation" settings.

Actionable comments posted: 3

🧹 Nitpick comments (4)
src/web/platform/login_routes.rs (1)

196-204: Consider enhancing error handling with more specific error types.

The error handling could be more specific to help with debugging and user feedback. Consider mapping database errors to specific API errors instead of always returning InternalServerError.

     let verification = match data.db.create_platform_email_verification(new_verification) {
         Ok(v) => v,
         Err(e) => {
             error!("Error creating platform email verification: {:?}", e);
-            return Err(ApiError::InternalServerError);
+            return match e {
+                DBError::DuplicateKey => Err(ApiError::EmailAlreadyExists),
+                DBError::PlatformEmailVerificationError(_) => Err(ApiError::BadRequest),
+                _ => {
+                    error!("Unexpected database error: {:?}", e);
+                    Err(ApiError::InternalServerError)
+                }
+            };
         }
     };
src/main.rs (2)

1081-1084: Avoid logging potentially sensitive user data.
You are logging the exact user email in the warning. This can reveal PII in logs. Consider anonymizing or removing the email from the log message to protect user privacy.

Possible fix:

- warn!("Could not find platform user by email: {email}");
+ warn!("Could not find platform user by email.");

1093-1110: Use secure memory handling for decrypted password hash.
Currently, the decrypted password hash is kept in memory, which might pose a security risk if the process or logs are compromised. Consider zeroizing or clearing the buffer after verification to minimize exposure.

Example approach for secure memory cleanup (pseudocode):

    let decrypted_password_hash = String::from_utf8(decrypted_password_bytes)
        .map_err(|e| Error::EncryptionError(format!("Failed to decode UTF-8: {}", e)))?;

    let result = task::spawn_blocking(move || verify_password(password, &decrypted_password_hash)).await?;

    // Attempt to clear the memory holding the password
    zeroize::Zeroizing::new(decrypted_password_hash);
src/db.rs (1)

324-359: New trait methods for platform email verifications appear coherent.
The CRUD and verification methods enable comprehensive handling of email verification state. Consider these points:

  • Ensure these verification records are indexed properly in the database for best performance.
  • Validate that the single “by_platform_user_id” record assumption aligns with business requirements. If multiple records per user are possible, we may need a different approach (e.g., retrieving a list).

Example suggestions:

  1. Index: Confirm there's an index on platform_user_id and verification_code to speed up lookups.
  2. Multiplicity: If users can trigger multiple verifications concurrently, consider returning all. Otherwise, a single record approach is fine.
🛑 Comments failed to post (3)
src/web/platform/login_routes.rs (1)

4-7: ⚠️ Potential issue

Fix formatting issues.

The CI pipeline indicates formatting issues. Run cargo fmt to fix the code style.

src/web/platform/org_routes.rs (2)

459-474: 🛠️ Refactor suggestion

Redundant pattern matching warning at line 460.
Instead of if let Some(_) = ..., use .is_some() to align with the Rust CI suggestion.

-if let Some(_) = data
+if data
     .db
     .get_org_project_by_name_and_org(&create_request.name, org.id)
     .map_err(|e| {
         error!("Failed to check for existing project: {:?}", e);
         ApiError::InternalServerError
     })?
-{
+.is_some() {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

    // Check if a project with the same name already exists in this organization
    if data
        .db
        .get_org_project_by_name_and_org(&create_request.name, org.id)
        .map_err(|e| {
            error!("Failed to check for existing project: {:?}", e);
            ApiError::InternalServerError
        })?
        .is_some() {
        // Project with this name already exists
        error!(
            "Project with name '{}' already exists in this organization",
            &create_request.name
        );
        return Err(ApiError::BadRequest);
    }
🧰 Tools
🪛 GitHub Actions: Rust CI

[error] 460-460: redundant pattern matching, consider using is_some()


575-596: 🛠️ Refactor suggestion

Redundant pattern matching warning at line 580.
Similar to the prior fix; replace if let Some(_) = ... with .is_some().

-if let Some(_) = data
+if data
     .db
     .get_org_project_by_name_and_org(&name, org.id)
     .map_err(|e| {
         error!("Failed to check for existing project: {:?}", e);
         ApiError::InternalServerError
     })?
-{
+.is_some() {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

    // Update the project
    let mut updated_project = project;
    if let Some(name) = update_request.name {
        // If name is changing, check if it conflicts with an existing project
        if name != updated_project.name {
            if data
                .db
                .get_org_project_by_name_and_org(&name, org.id)
                .map_err(|e| {
                    error!("Failed to check for existing project: {:?}", e);
                    ApiError::InternalServerError
                })?
                .is_some() {
                // Project with this name already exists
                error!(
                    "Project with name '{}' already exists in this organization",
                    &name
                );
                return Err(ApiError::BadRequest);
            }
        }
        updated_project.name = name;
🧰 Tools
🪛 GitHub Actions: Rust CI

[error] 580-580: redundant pattern matching, consider using is_some()

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Inline review comments failed to post. This is likely due to GitHub's limits when posting large numbers of comments. If you are seeing this consistently, please check "Code review limits" under "Moderation" settings.

Actionable comments posted: 2

🧹 Nitpick comments (6)
src/main.rs (2)

1081-1084: Be cautious about logging user emails in warnings.
While logging is helpful for debugging, logging user email addresses may leak PII. Consider masking or removing it to protect user privacy, especially in production.


1978-1982: New platform routes re-enabled.
Uncommenting these lines successfully restores the platform login and org routes with validate_platform_jwt. Be sure to add integration tests to confirm functionality.

src/db.rs (2)

85-88: Added DBError variants for platform email verification.
Expanding the DBError enum with verification-specific variants helps improve error granularity. Confirm that all newly introduced error branches are tested and handled across the codebase.


324-359: Trait extension for platform email verification methods.
Adding these CRUD and “verify” methods for emails clarifies the workflow for platform-level verification. Verify that transaction boundaries and concurrency edge cases (e.g., multiple calls to the same code) are safely handled.

src/models/platform_email_verification.rs (2)

18-28: Data model for PlatformEmailVerification.
Storing timestamps as UTC and including an expires_at field is a good design. The attribute metadata aligns with Diesel's macros. Ensure indexes are present to optimize frequent lookups (especially by platform_user_id or verification_code).


104-133: Constructor and insert method for new verifications.
Auto-generating the verification_code via Uuid::new_v4() is straightforward. The code is cohesive and easy to maintain. Consider logging or metering how often verifications are requested to detect spam or repeated attempts.

🛑 Comments failed to post (2)
src/web/platform/org_routes.rs (2)

939-965: 🛠️ Refactor suggestion

Extract common validation logic into a helper function.

There's significant code duplication in project-related routes for validating org/project relationships and user permissions. Consider extracting this into a helper function.

Here's a suggested implementation:

/// Helper function to validate org, project, and user permissions
async fn validate_project_access(
    db: &impl DBConnection,
    org_id: Uuid,
    project_id: Option<Uuid>,
    platform_user_id: Uuid,
    require_admin: bool,
) -> Result<(Org, Option<OrgProject>), ApiError> {
    // Get org by UUID
    let org = db.get_org_by_uuid(org_id)
        .map_err(|_| ApiError::NotFound)?;

    // Get project if ID provided
    let project = if let Some(project_id) = project_id {
        let project = db.get_org_project_by_uuid(project_id)
            .map_err(|_| ApiError::NotFound)?;
        
        // Ensure project belongs to org
        if project.org_id != org.id {
            error!("Project does not belong to organization");
            return Err(ApiError::NotFound);
        }
        Some(project)
    } else {
        None
    };

    // Verify user permissions
    let membership = db
        .get_org_membership_by_platform_user_and_org(platform_user_id, org.id)
        .map_err(|_| ApiError::Unauthorized)?;

    if require_admin {
        let role: OrgRole = membership.role.into();
        if !matches!(role, OrgRole::Owner | OrgRole::Admin) {
            return Err(ApiError::Unauthorized);
        }
    }

    Ok((org, project))
}

Usage example:

async fn create_secret(
    State(data): State<Arc<AppState>>,
    Extension(platform_user): Extension<PlatformUser>,
    Path((org_id, project_id)): Path<(Uuid, Uuid)>,
    Extension(create_request): Extension<CreateSecretRequest>,
    Extension(session_id): Extension<Uuid>,
) -> Result<Json<EncryptedResponse<SecretResponse>>, ApiError> {
    debug!("Creating project secret");

    // Validate request
    if let Err(errors) = create_request.validate() {
        error!("Validation error: {:?}", errors);
        return Err(ApiError::BadRequest);
    }

    // Validate access with admin requirement
    let (org, project) = validate_project_access(
        &*data.db,
        org_id,
        Some(project_id),
        platform_user.uuid,
        true
    ).await?;

    // Rest of the function...
}

This refactor will:

  1. Reduce code duplication
  2. Centralize access control logic
  3. Make the code more maintainable
  4. Make it easier to modify the validation logic in one place

Also applies to: 1011-1033, 1063-1090, 1125-1147, 1185-1212, 1245-1267, 1295-1322, 1352-1374, 1399-1426


460-474: ⚠️ Potential issue

Fix redundant pattern matching.

Replace if let Some(_) with is_some() to fix the pipeline failures and make the code more idiomatic.

Apply this diff:

-    if let Some(_) = data
+    if data
         .db
         .get_org_project_by_name_and_org(&create_request.name, org.id)
         .map_err(|e| {
             error!("Failed to check for existing project: {:?}", e);
             ApiError::InternalServerError
-        })?
+        })?.is_some()
     {
         error!(
             "Project with name '{}' already exists in this organization",
             &create_request.name
         );
         return Err(ApiError::BadRequest);
     }

-    if let Some(_) = data
+    if data
         .db
         .get_org_project_by_name_and_org(&name, org.id)
         .map_err(|e| {
             error!("Failed to check for existing project: {:?}", e);
             ApiError::InternalServerError
-        })?
+        })?.is_some()
     {
         error!(
             "Project with name '{}' already exists in this organization",
             &name
         );
         return Err(ApiError::BadRequest);
     }

Also applies to: 580-595

🧰 Tools
🪛 GitHub Actions: Rust CI

[error] 460-460: redundant pattern matching, consider using is_some()

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (6)
src/web/platform/org_routes.rs (6)

319-425: Enhance error handling in organization routes.

Consider adding more specific error messages in the error logs to help with debugging. For example:

-            error!("Failed to create organization with owner: {:?}", e);
+            error!("Failed to create organization '{}' with owner {}: {:?}", create_request.name, platform_user.uuid, e);

459-475: Consider using a database constraint for project name uniqueness.

The current implementation checks for project name uniqueness using a separate query. Consider adding a unique constraint in the database for (org_id, name) to enforce this at the database level.


476-488: Enhance error logging in project creation.

Add more context to error messages to help with debugging.

-        error!("Failed to create project: {:?}", e);
+        error!("Failed to create project '{}' in organization {}: {:?}", new_project.name, org.id, e);

924-1001: Consider additional validation for secret key names.

While validate_alphanumeric_only is used, consider adding more specific validation rules for secret key names:

  1. Prevent common sensitive words in key names (e.g., "password", "token", "key").
  2. Add a regex pattern to enforce a specific format.

Example implementation:

fn validate_secret_key_name(key_name: &str) -> Result<(), validator::ValidationError> {
    let sensitive_words = ["password", "token", "key", "secret"];
    if sensitive_words.iter().any(|word| key_name.to_lowercase().contains(word)) {
        return Err(validator::ValidationError::new("sensitive_key_name"));
    }
    
    // Only allow letters, numbers, and underscores, must start with a letter
    let key_pattern = regex::Regex::new(r"^[a-zA-Z][a-zA-Z0-9_]*$").unwrap();
    if !key_pattern.is_match(key_name) {
        return Err(validator::ValidationError::new("invalid_key_name_format"));
    }
    
    Ok(())
}

1176-1235: Add rate limiting for settings updates.

Consider implementing rate limiting for settings updates to prevent abuse.

You could use a rate limiting middleware or implement it directly in the handler:

use tokio::time::{Duration, Instant};
use std::collections::HashMap;
use std::sync::Mutex;

struct RateLimiter {
    last_update: HashMap<(Uuid, String), Instant>,
    min_interval: Duration,
}

impl RateLimiter {
    fn new(min_interval: Duration) -> Self {
        Self {
            last_update: HashMap::new(),
            min_interval,
        }
    }

    fn can_update(&mut self, project_id: Uuid, category: &str) -> bool {
        let now = Instant::now();
        let key = (project_id, category.to_string());
        
        if let Some(last) = self.last_update.get(&key) {
            if now.duration_since(*last) < self.min_interval {
                return false;
            }
        }
        
        self.last_update.insert(key, now);
        true
    }
}

153-160: Enhance email verification URL validation.

Add more specific validation for the email verification URL format and security.

fn validate_verification_url(url: &str) -> Result<(), validator::ValidationError> {
    // Parse URL
    let parsed_url = url::Url::parse(url).map_err(|_| {
        validator::ValidationError::new("invalid_url")
    })?;
    
    // Ensure HTTPS
    if parsed_url.scheme() != "https" {
        return Err(validator::ValidationError::new("non_https_url"));
    }
    
    // Validate URL pattern
    if !url.contains("{token}") {
        return Err(validator::ValidationError::new("missing_token_placeholder"));
    }
    
    Ok(())
}
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between bcacfe6 and ce42947.

📒 Files selected for processing (1)
  • src/web/platform/org_routes.rs (38 hunks)
🧰 Additional context used
🪛 GitHub Actions: Rust CI
src/web/platform/org_routes.rs

[error] 577-577: Formatting check failed. Please run 'cargo fmt' to fix code style issues in this file.

⏰ Context from checks skipped due to timeout of 100000ms (1)
  • GitHub Check: Development Reproducible Build
🔇 Additional comments (1)
src/web/platform/org_routes.rs (1)

107-146: LGTM! Response structs successfully migrated to UUIDs.

The changes to use Uuid type in response structs are consistent and align well with the broader transition from integer IDs to UUIDs.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
src/web/platform/org_routes.rs (1)

1018-1027: Enhance error messages for better debugging.

While the error handling is thorough, consider making the error messages more descriptive by including the UUIDs in the messages. For example:

-        error!("Project not found");
+        error!("Project with UUID {} not found", project_id);
-        error!("Project does not belong to organization");
+        error!("Project {} does not belong to organization {}", project_id, org_id);
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ce42947 and 4126438.

📒 Files selected for processing (1)
  • src/web/platform/org_routes.rs (38 hunks)
⏰ Context from checks skipped due to timeout of 100000ms (1)
  • GitHub Check: Development Reproducible Build
🔇 Additional comments (2)
src/web/platform/org_routes.rs (2)

108-126: Clean transition to UUIDs in response structs!

The response structs have been updated to use UUIDs instead of integer IDs, which is a good practice for globally unique identifiers.


427-498: Good addition of duplicate project name validation!

The create_project handler has been enhanced with:

  1. Proper UUID parameter handling
  2. New validation to prevent duplicate project names within an organization
  3. Clear error messages for better debugging

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (3)
src/web/platform/org_routes.rs (3)

149-156: Enhance email provider validation.

Consider these improvements to make the validation more robust and maintainable:

  1. Use case-insensitive comparison for better user experience
  2. Define a constant for the provider name to avoid magic strings

Apply this diff to implement the suggested improvements:

+const RESEND_PROVIDER: &str = "resend";

 fn validate_email_provider(provider: &str) -> Result<(), validator::ValidationError> {
-    if provider != "resend" {
+    if provider.to_lowercase() != RESEND_PROVIDER {
         let mut error = validator::ValidationError::new("invalid_email_provider");
-        error.message = Some("Only 'resend' is supported as an email provider".into());
+        error.message = Some(format!("Only '{}' is supported as an email provider", RESEND_PROVIDER).into());
         return Err(error);
     }
     Ok(())
 }

1264-1273: Extract OAuth validation to a separate function.

The OAuth settings validation logic could be moved to a separate function to improve readability and maintainability.

Apply this diff to extract the validation:

+fn validate_oauth_settings_consistency(
+    update_request: &UpdateOAuthSettingsRequest,
+) -> Result<(), ApiError> {
+    if update_request.google_oauth_enabled && update_request.google_oauth_settings.is_none() {
+        error!("Google OAuth settings must be provided when Google OAuth is enabled");
+        return Err(ApiError::BadRequest);
+    }
+
+    if update_request.github_oauth_enabled && update_request.github_oauth_settings.is_none() {
+        error!("GitHub OAuth settings must be provided when GitHub OAuth is enabled");
+        return Err(ApiError::BadRequest);
+    }
+
+    Ok(())
+}

 async fn update_oauth_settings(
     State(data): State<Arc<AppState>>,
     Extension(platform_user): Extension<PlatformUser>,
     Path((org_id, project_id)): Path<(Uuid, Uuid)>,
     Extension(update_request): Extension<UpdateOAuthSettingsRequest>,
     Extension(session_id): Extension<Uuid>,
 ) -> Result<Json<EncryptedResponse<OAuthSettings>>, ApiError> {
     debug!("Updating project OAuth settings");

     // Validate request using validator
     if let Err(errors) = update_request.validate() {
         error!("Validation error: {:?}", errors);
         return Err(ApiError::BadRequest);
     }

-    // Additional validation for the relationship between enabled flags and settings
-    if update_request.google_oauth_enabled && update_request.google_oauth_settings.is_none() {
-        error!("Google OAuth settings must be provided when Google OAuth is enabled");
-        return Err(ApiError::BadRequest);
-    }
-
-    if update_request.github_oauth_enabled && update_request.github_oauth_settings.is_none() {
-        error!("GitHub OAuth settings must be provided when GitHub OAuth is enabled");
-        return Err(ApiError::BadRequest);
-    }
+    validate_oauth_settings_consistency(&update_request)?;

934-939: Extract project ownership validation to a helper function.

The project ownership validation is duplicated across multiple handlers. Consider extracting it to a helper function to reduce code duplication.

Apply this diff to create a helper function:

+async fn validate_project_ownership(
+    data: &AppState,
+    org_id: Uuid,
+    project_id: Uuid,
+) -> Result<(), ApiError> {
+    let org = data
+        .db
+        .get_org_by_uuid(org_id)
+        .map_err(|_| ApiError::NotFound)?;
+
+    let project = data.db.get_org_project_by_uuid(project_id).map_err(|_| {
+        error!("Project not found");
+        ApiError::NotFound
+    })?;
+
+    if project.org_id != org.id {
+        error!("Project does not belong to organization");
+        return Err(ApiError::NotFound);
+    }
+
+    Ok(())
+}

Then use it in the handlers:

-    // Ensure project belongs to org
-    if project.org_id != org.id {
-        error!("Project does not belong to organization");
-        return Err(ApiError::NotFound);
-    }
+    validate_project_ownership(&data, org_id, project_id).await?;

Also applies to: 1059-1064, 1171-1176, 1286-1291

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4126438 and 0281e0e.

📒 Files selected for processing (1)
  • src/web/platform/org_routes.rs (35 hunks)
⏰ Context from checks skipped due to timeout of 100000ms (2)
  • GitHub Check: Test Suite
  • GitHub Check: Development Reproducible Build
🔇 Additional comments (1)
src/web/platform/org_routes.rs (1)

106-137: LGTM! Response structs updated to use UUIDs.

The response structs have been correctly updated to use Uuid instead of i32 for identifiers, aligning with the transition to UUID-based identification.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (2)
src/models/org_memberships.rs (2)

157-166: Consider the performance implications of loading all records instead of using a count query.

The previous implementation may have used a direct count query, while the updated code loads all owner memberships and then counts them. This approach could be less efficient for organizations with many owners, as it retrieves complete records instead of just the count.

Consider using diesel::dsl::count or similar if performance for organizations with many owners is a concern:

- // First, get all owner memberships with FOR UPDATE lock
- let owner_memberships = org_memberships::table
    .filter(org_memberships::org_id.eq(membership.org_id))
    .filter(org_memberships::role.eq(OrgRole::Owner.as_str()))
    .for_update() // This locks the rows
    .load::<OrgMembership>(conn)?;

- // Then count them
- let owner_count = owner_memberships.len() as i64;
+ // Get count of owner memberships with FOR UPDATE lock
+ let owner_count = org_memberships::table
+     .filter(org_memberships::org_id.eq(membership.org_id))
+     .filter(org_memberships::role.eq(OrgRole::Owner.as_str()))
+     .for_update() // This locks the rows
+     .count()
+     .get_result::<i64>(conn)?;

196-205: Consider using consistent type comparison instead of conversion.

The conversion from usize to i64 might be unnecessary. Since we're comparing with a constant value (1), we could adjust the comparison value to match the type of len() which is usize.

// First, get all owner memberships with FOR UPDATE lock
let owner_memberships = org_memberships::table
    .filter(org_memberships::org_id.eq(membership.org_id))
    .filter(org_memberships::role.eq(OrgRole::Owner.as_str()))
    .for_update() // This locks the rows
    .load::<OrgMembership>(conn)?;

// Then count them
- let owner_count = owner_memberships.len() as i64;
+ let owner_count = owner_memberships.len();

- if owner_count <= 1 {
+ if owner_count <= 1_usize {

Alternatively, the same issue exists in the update_role_with_owner_check method and should be addressed there as well.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0281e0e and 43d596b.

⛔ Files ignored due to path filters (2)
  • pcrDev.json is excluded by !pcrDev.json
  • pcrProd.json is excluded by !pcrProd.json
📒 Files selected for processing (1)
  • src/models/org_memberships.rs (2 hunks)
⏰ Context from checks skipped due to timeout of 100000ms (1)
  • GitHub Check: Development Reproducible Build
🔇 Additional comments (1)
src/models/org_memberships.rs (1)

157-166: Document why this approach was chosen over a direct count query.

The change from what was likely a direct count query to loading all records and then counting them isn't accompanied by any comments explaining the rationale. Given that this pull request focuses on platform email verification features, it would be helpful to understand how this specific change relates to that objective.

Consider adding a comment to explain why this approach was chosen over a potentially more efficient count query:

+ // We load all owner memberships rather than using count() because...

Also applies to: 196-205

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
src/web/platform/org_routes.rs (1)

1346-1416: Implementation of user profile endpoint with organization data.

The get_platform_user function provides a comprehensive user profile with email verification status and organizations.

Note that this implementation makes multiple database calls (verification check, memberships, and one per organization), which could potentially be optimized if performance becomes a concern with a large number of organizations.

Consider optimizing the database queries to reduce the number of round trips, particularly for users with many organizations. This could be done by joining the relevant tables or implementing a more efficient query pattern.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 43d596b and 493d51a.

📒 Files selected for processing (3)
  • src/jwt.rs (0 hunks)
  • src/web/platform/login_routes.rs (4 hunks)
  • src/web/platform/org_routes.rs (37 hunks)
💤 Files with no reviewable changes (1)
  • src/jwt.rs
⏰ Context from checks skipped due to timeout of 100000ms (1)
  • GitHub Check: Development Reproducible Build
🔇 Additional comments (18)
src/web/platform/login_routes.rs (5)

4-7: Import path updated to use platform-specific modules.

The import has been updated to use platform-specific modules, which aligns with the transition to platform-focused email verification.


74-94: New data structures for platform organization and project management.

Good addition of PlatformOrg and PlatformProject structs that use UUIDs and include comprehensive fields for platform entities. These structures support the broader transition from integer IDs to UUIDs and provide a consistent API surface.


219-221: Platform-specific email verification implementation.

The code now uses NewPlatformEmailVerification instead of the previous generic implementation, which is consistent with the platform APIs focus of this PR.


224-225: Enhanced error logging specificity.

Error logging has been improved to specifically mention "platform email verification," which enhances debugging clarity.


277-278: More specific token validation context.

The token validation now uses "platform_refresh" as the context, making the validation more specific and aligned with the platform-focused approach.

src/web/platform/org_routes.rs (13)

108-110: Transition from i32 to UUID for organization ID.

The organization response structure now uses UUID instead of integer ID, which is consistent with the broader transition to UUIDs throughout the codebase.


114-119: Project response structure updated to use UUID.

The project response structure has been updated to use UUID as the primary identifier, which aligns with the transition to UUIDs throughout the API.


139-147: New platform user response structure with email verification status.

This new structure provides a comprehensive representation of a platform user, including email verification status, which is valuable for client applications.


149-153: New response structure for user profile with organizations.

The MeResponse structure encapsulates both user information and their organizations, providing a convenient way to fetch this related data in a single request.


158-159: Email provider validation has been added.

A new validation constraint has been added to ensure only compatible email providers are used.


165-172: Restrictive email provider validation logic.

The validation function only allows "resend" as a valid provider. While this is likely intentional if "resend" is the only supported provider, it creates a hard constraint that might require code changes if additional providers need to be supported in the future.

Is there a strategic reason to only support "resend" as an email provider, or would it be better to design for future extensibility?


262-263: Comment clarification for project settings routes.

The comment has been updated to clarify that only specialized endpoints are kept, which helps future developers understand the intent behind the API design.


316-320: New endpoint to retrieve platform user information.

Great addition of a /platform/me endpoint that provides a comprehensive view of the user's profile and associated organizations, which is a common requirement for platform applications.


403-408: Updated org retrieval to use UUID.

The code now retrieves an organization by UUID instead of ID, which is consistent with the transition to UUIDs throughout the API.


447-452: Organization lookup now uses UUID.

The code has been updated to use UUID-based lookup for organizations, which aligns with the broader transition to UUIDs.


464-480: Added project name uniqueness check within an organization.

This is a good addition that prevents duplicate project names within the same organization, enhancing data integrity.


1354-1364: Email verification status check with explicit error handling.

Good implementation of email verification status check with proper error handling. The code differentiates between the case where verification doesn't exist (returns false) and other database errors (returns an internal server error).


582-582: Code formatting.

The indentation and formatting in this section appear correct now, consistent with Rust formatting standards.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
src/web/platform/org_routes.rs (1)

1350-1420: New endpoint provides comprehensive user profile data.

The implementation correctly retrieves the user's profile information, email verification status, and organization memberships in a single endpoint, which is a good API design pattern.

Consider optimizing the database access pattern:

  1. The code makes separate database calls for each organization in a loop (lines 1388-1394)
  2. For users with many organizations, this could become a performance bottleneck
-    // Create the list of organizations with roles
-    let mut organizations = Vec::new();
-    for membership in memberships {
-        let org_id = membership.org_id;
-
-        // Fetch the organization details
-        let org = match data.db.get_org_by_id(org_id) {
-            Ok(org) => org,
-            Err(e) => {
-                error!("Error fetching organization {}: {:?}", org_id, e);
-                continue; // Skip this organization but continue processing others
-            }
-        };
-
-        // Add to our list of organizations
-        organizations.push(OrgResponse {
-            id: org.uuid,
-            name: org.name,
-        });
-    }
+    // Extract all org IDs from memberships
+    let org_ids: Vec<i32> = memberships.iter().map(|m| m.org_id).collect();
+    
+    // Fetch all organizations in bulk if possible
+    let orgs = match data.db.get_orgs_by_ids(&org_ids) {
+        Ok(orgs) => orgs,
+        Err(e) => {
+            error!("Error fetching organizations: {:?}", e);
+            return Err(ApiError::InternalServerError);
+        }
+    };
+    
+    // Create response objects
+    let organizations: Vec<OrgResponse> = orgs
+        .into_iter()
+        .map(|org| OrgResponse {
+            id: org.uuid,
+            name: org.name,
+        })
+        .collect();

Note: This suggestion assumes you can implement a get_orgs_by_ids method in your database layer that fetches multiple organizations in a single query.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 493d51a and 846f2a1.

⛔ Files ignored due to path filters (2)
  • pcrDev.json is excluded by !pcrDev.json
  • pcrProd.json is excluded by !pcrProd.json
📒 Files selected for processing (3)
  • src/jwt.rs (0 hunks)
  • src/web/platform/login_routes.rs (4 hunks)
  • src/web/platform/org_routes.rs (36 hunks)
💤 Files with no reviewable changes (1)
  • src/jwt.rs
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/web/platform/login_routes.rs
⏰ Context from checks skipped due to timeout of 100000ms (1)
  • GitHub Check: Development Reproducible Build
🔇 Additional comments (4)
src/web/platform/org_routes.rs (4)

166-173: Good addition of email provider validation.

This validator ensures only 'resend' is accepted as an email provider, which maintains system compatibility with supported services.


263-263: Clear documentation of route changes.

The comment effectively communicates that only specialized endpoints are being maintained for project settings.


1362-1368: Explicit handling of email verification not found.

The code appropriately handles the specific PlatformEmailVerificationNotFound error case by setting email_verified to false, which is a logical default.


1404-1411: Comprehensive user response model.

The PlatformUserResponse includes all necessary user information including the important email_verified flag that will be needed by client applications.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (4)
src/web/platform/org_routes.rs (4)

166-173: Consider a more maintainable approach for email provider validation.

Currently, the validation function only allows "resend" as an email provider with a hardcoded string comparison. If support for additional providers is planned in the future, consider using an enum or a configuration-based approach for better maintainability.

-fn validate_email_provider(provider: &str) -> Result<(), validator::ValidationError> {
-    if provider != "resend" {
-        let mut error = validator::ValidationError::new("invalid_email_provider");
-        error.message = Some("Only 'resend' is supported as an email provider".into());
-        return Err(error);
-    }
-    Ok(())
-}
+fn validate_email_provider(provider: &str) -> Result<(), validator::ValidationError> {
+    const SUPPORTED_PROVIDERS: [&str; 1] = ["resend"];
+    
+    if !SUPPORTED_PROVIDERS.contains(&provider) {
+        let mut error = validator::ValidationError::new("invalid_email_provider");
+        error.message = Some(format!("Only {:?} are supported as email providers", SUPPORTED_PROVIDERS).into());
+        return Err(error);
+    }
+    Ok(())
+}

1406-1416: Consider error handling refinement for email verification lookup.

The error handling distinguishes between "not found" and other errors, which is good. However, consider adding more specific error logging for non-404 errors to aid in troubleshooting.

     match data
         .db
         .get_platform_email_verification_by_platform_user_id(platform_user.uuid)
     {
         Ok(verification) => verification.is_verified,
         Err(crate::db::DBError::PlatformEmailVerificationNotFound) => false,
         Err(e) => {
-            error!("Error fetching platform email verification: {:?}", e);
+            error!("Error fetching platform email verification for user {}: {:?}", platform_user.uuid, e);
             return Err(ApiError::InternalServerError);
         }
     };

1437-1442: Add more context to organization lookup error log.

When logging errors during organization lookup, include both organization ID and user ID to make troubleshooting easier.

            Err(e) => {
-                error!("Error fetching organization {}: {:?}", org_id, e);
+                error!("Error fetching organization {} for user {}: {:?}", org_id, platform_user.uuid, e);
                continue; // Skip this organization but continue processing others
            }

1398-1468: Consider performance optimization for the user profile endpoint.

The get_platform_user function makes multiple sequential database queries - one to check email verification status and another to get memberships. Consider refactoring the database layer to combine these operations if this endpoint becomes a performance bottleneck.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 846f2a1 and 08f6aff.

⛔ Files ignored due to path filters (2)
  • pcrDev.json is excluded by !pcrDev.json
  • pcrProd.json is excluded by !pcrProd.json
📒 Files selected for processing (1)
  • src/web/platform/org_routes.rs (37 hunks)
⏰ Context from checks skipped due to timeout of 100000ms (1)
  • GitHub Check: Development Reproducible Build
🔇 Additional comments (3)
src/web/platform/org_routes.rs (3)

242-245: LGTM! Added new endpoint is consistent with existing patterns.

The new get_project endpoint follows the same pattern as other endpoints and is correctly secured with authentication and authorization.


321-325: LGTM! Well-designed user profile endpoint.

This new endpoint for retrieving the current user's profile is a valuable addition and follows good REST patterns.


589-589: Fix the formatting issue to make CI pass.

As indicated by the previous pipeline failure, this line still has a formatting issue. Run cargo fmt to fix the code style.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant