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

Email spam security #641

Merged
merged 15 commits into from
Dec 18, 2024
Merged

Email spam security #641

merged 15 commits into from
Dec 18, 2024

Conversation

chiragchhatrala
Copy link
Collaborator

@chiragchhatrala chiragchhatrala commented Dec 10, 2024

Summary by CodeRabbit

Release Notes

  • New Features

    • Enhanced registration process with hCaptcha integration for improved security.
    • Introduced loading state in the integration modal for better user experience during save operations.
    • Added validation rules for email integrations based on user tiers.
  • Bug Fixes

    • Improved validation feedback for registration attempts, including handling of hCaptcha responses.
  • Documentation

    • Added tests for email integration functionalities, covering various user tiers and their limitations.
  • Chores

    • Updated user model to manage user metadata effectively.
    • Added migration for new meta field in the users table.

- Updated the `getValidationRules` method in various integration handlers (Discord, Email, Google Sheets, Slack, Webhook, Zapier) to accept an optional `Form` parameter, allowing for context-aware validation.
- Enhanced the `EmailIntegration` handler to enforce restrictions based on user plans, ensuring free users can only create one email integration per form and can only send to a single email address.
- Added a new test suite for `EmailIntegration` to validate the new restrictions and ensure proper functionality for both free and pro users.
- Introduced loading state management in the `IntegrationModal` component to improve user experience during save operations.

These changes improve the flexibility and user experience of form integrations, particularly for email handling.
Copy link
Contributor

coderabbitai bot commented Dec 10, 2024

Warning

Rate limit exceeded

@JhumanJ has exceeded the limit for the number of commits or files that can be reviewed per hour. Please wait 13 minutes and 40 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between b5f7e2f and c5b4900.

📒 Files selected for processing (2)
  • api/phpunit.xml (1 hunks)
  • api/tests/Feature/RegisterTest.php (4 hunks)

Walkthrough

The changes in this pull request involve enhancements to the user registration process and integration handling within the application. Key updates include the addition of rate limiting middleware in the RegisterController, improved validation logic with the introduction of hCaptcha, and the inclusion of a meta field for user tracking. Additionally, several integration handlers have been modified to accept a Form parameter for context-specific validation. The User model has been updated to manage user metadata, and a new migration has been added to accommodate these changes in the database schema.

Changes

File Change Summary
api/app/Http/Controllers/Auth/RegisterController.php Added rate limiting middleware, refactored validation rules, introduced h-captcha validation, and updated user creation to include a meta field.
api/app/Http/Requests/Integration/FormIntegrationsRequest.php Added a private property for Form, updated validation rules loading logic, and refined error handling.
api/app/Integrations/Handlers/AbstractIntegrationHandler.php Updated getValidationRules method to accept an optional Form parameter.
api/app/Integrations/Handlers/DiscordIntegration.php Modified getValidationRules to accept a Form parameter and updated shouldRun logic.
api/app/Integrations/Handlers/EmailIntegration.php Updated getValidationRules to accept a Form parameter, enhanced validation for free plan users.
api/app/Integrations/Handlers/GoogleSheetsIntegration.php Changed getValidationRules to accept a Form parameter and improved logging in handle method.
api/app/Integrations/Handlers/SlackIntegration.php Updated getValidationRules to accept a Form parameter and modified shouldRun logic.
api/app/Integrations/Handlers/WebhookIntegration.php Modified getValidationRules to accept a Form parameter and added a new import for Form.
api/app/Integrations/Handlers/ZapierIntegration.php Updated getValidationRules to accept a Form parameter.
api/app/Models/User.php Added meta field to $fillable, $hidden, and casts arrays in the User model.
api/database/migrations/2024_12_10_094605_add_meta_to_users_table.php Created a migration to add a meta column to the users table.
api/tests/Feature/Integrations/Email/EmailIntegrationTest.php Introduced tests for email integration functionalities, validating user capabilities based on their plan.
api/tests/Feature/RegisterTest.php Enhanced registration tests to include hCaptcha validation and updated assertions for user metadata.
api/tests/Feature/UserManagementTest.php Updated user registration tests to integrate hCaptcha validation and maintain existing assertions.
client/components/open/integrations/components/IntegrationModal.vue Added loading state to save functionality to enhance user experience during asynchronous operations.
client/components/pages/auth/components/RegisterForm.vue Integrated hCaptcha component into the registration form, including logic for handling responses.

Possibly related PRs

  • confirmation email integration fixes #434: The changes in the RegisterController include the addition of a new validation rule for the h-captcha-response field, which is relevant to the validation logic introduced in the SubmissionConfirmationIntegration class for the respondent_email. Both PRs enhance validation processes in their respective contexts.
  • Custom SMTP Settings #561: The modifications in the UserManagementTest.php file include refactoring tests related to user registration, which aligns with the changes made in the RegisterController that also focus on user registration processes.

🐇 In the land of code, where changes bloom,
A new hCaptcha guards registration's room.
With limits set, and rules refined,
We track the users, their data aligned.
So hop along, dear devs, take flight,
For with these updates, our future is bright! 🌟


Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 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. (Beta)
  • @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
Contributor

@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: 10

🧹 Outside diff range and nitpick comments (16)
api/app/Integrations/Handlers/WebhookIntegration.php (2)

9-9: Consider utilizing the Form parameter for context-specific validation

The Form parameter is currently unused. Consider leveraging it to implement form-specific validation rules, such as:

  • Different URL patterns for different form types
  • Specific domain restrictions based on form context
  • Custom validation rules based on form settings

Example implementation:

     public static function getValidationRules(?Form $form): array
     {
+        $baseRules = [
+            'webhook_url' => [
+                'required',
+                'url',
+            ]
+        ];
+
+        if ($form && $form->requires_strict_validation) {
+            $baseRules['webhook_url'][] = 'regex:/^https:\/\/api\.(trusted-domain)\.(com|org)\/webhook/';
+        }
+
+        return $baseRules;
     }

Consider implementing rate limiting for webhook endpoints

Based on the codebase analysis, while there's basic URL validation, the webhook endpoints could benefit from additional security measures. I notice that:

  1. The application already implements rate limiting in various places (e.g., API, authentication) but not specifically for webhook endpoints
  2. There's no existing domain allowlist/blocklist configuration for webhooks
  3. The webhook integration is part of a larger forms system with existing security considerations

Recommended implementation:

public static function getValidationRules(?Form $form): array
{
    return [
        'webhook_url' => [
            'required',
            'url',
            'not_regex:/^https?:\/\/(\d{1,3}\.){3}\d{1,3}/', // Prevent IP-based URLs
            function ($attribute, $value, $fail) {
                $domain = parse_url($value, PHP_URL_HOST);
                if (filter_var($domain, FILTER_VALIDATE_IP)) {
                    $fail('IP-based webhook URLs are not allowed.');
                }
            }
        ]
    ];
}

Additionally:

  • Add the throttle:10,1 middleware to webhook routes to prevent abuse
  • Consider implementing a webhook signing mechanism for secure payload delivery
🔗 Analysis chain

Line range hint 9-14: Consider enhancing webhook URL validation for better security

While the basic URL validation is present, given this PR's focus on spam security, consider implementing additional security measures:

  1. Domain allowlist/blocklist validation
  2. Rate limiting for webhook endpoints
  3. Webhook URL pattern restrictions

Here's a suggested implementation with enhanced security:

     public static function getValidationRules(?Form $form): array
     {
         return [
-            'webhook_url' => 'required|url'
+            'webhook_url' => [
+                'required',
+                'url',
+                'not_regex:/^https?:\/\/(\d{1,3}\.){3}\d{1,3}/', // Prevent IP-based URLs
+                function ($attribute, $value, $fail) {
+                    $blockedDomains = config('security.blocked_domains', []);
+                    $domain = parse_url($value, PHP_URL_HOST);
+                    if (in_array($domain, $blockedDomains)) {
+                        $fail('This webhook domain is not allowed.');
+                    }
+                }
+            ]
         ];
     }

Let's verify if there are any existing security measures or configurations for webhook URLs:

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check for existing webhook security configurations
rg -l "webhook.*security|blocked.*domains|allowed.*domains" config/
# Check for any rate limiting on webhook endpoints
rg -l "throttle.*webhook|rate.*limit.*webhook" app/Http/Middleware/

Length of output: 248


Script:

#!/bin/bash
# Let's first locate the config and middleware directories
fd -t d "config|middleware" -i

# Check for any existing webhook-related configurations
rg -i "webhook" --type php

# Check for any rate limiting or throttling implementations
rg -i "throttle|rate.?limit" --type php

# Look for any security-related configurations or middleware
rg -i "security|blocked.?domains|allowed.?domains" --type php

Length of output: 16425

api/tests/Feature/UserManagementTest.php (1)

68-68: Optimize HCaptcha token usage in error scenarios

The tests should verify that HCaptcha validation is not performed when the registration fails due to other validations (expired/invalid invite token). This prevents unnecessary API calls and potential costs in production.

Consider validating the invite token before performing HCaptcha verification:

+it('skips hCaptcha validation for expired invite token', function () {
+    // Setup expired token...
+    $response = $this->postJson('/register', [...]);
+    $response->assertStatus(400);
+    Http::assertNothingSent();
+});

Also applies to: 98-98, 115-115

api/app/Http/Requests/Integration/FormIntegrationsRequest.php (1)

22-22: Use $request->route('id') instead of global request() helper

For consistency and better testability, use the $request object provided in the constructor rather than the global request() helper.

Apply this diff to make the change:

- $this->form = Form::findOrFail(request()->route('id'));
+ $this->form = Form::findOrFail($request->route('id'));
api/app/Integrations/Handlers/EmailIntegration.php (2)

Line range hint 18-54: Potential null reference on $form in getValidationRules

The $form parameter is nullable, but the method uses $form without checking if it is null. This could lead to a fatal error if $form is null. Please ensure $form is not null before accessing its properties.

Consider enforcing a non-null $form parameter:

- public static function getValidationRules(?Form $form): array
+ public static function getValidationRules(Form $form): array

Alternatively, add a null check at the beginning of the method:

if (is_null($form)) {
    throw new \InvalidArgumentException('Form cannot be null.');
}

36-40: Enhance email address parsing in closure validation

The current logic may not accurately parse email addresses containing commas or newlines. Consider using a more reliable method to split and validate multiple email addresses.

Example using regular expressions:

$emails = preg_split('/[\s,]+/', trim($value));
if (count($emails) > 1) {
    $fail('You can only send to a single email address on the free plan. Please upgrade to the Pro plan to create a new integration.');
}
api/database/migrations/2024_12_10_094605_add_meta_to_users_table.php (1)

14-14: Consider indexing if querying meta field

If you plan to query the meta JSON column frequently, consider adding appropriate indexes to optimize performance.

Example:

$table->json('meta')->default('{}')->index();

Be aware that indexing JSON fields may have limitations depending on the database.

api/app/Integrations/Handlers/ZapierIntegration.php (1)

20-20: Consider removing nullable type if $form is required

If all integration handlers require a Form instance for validation, consider removing the nullable type to enforce this requirement.

- public static function getValidationRules(?Form $form): array
+ public static function getValidationRules(Form $form): array
api/tests/Feature/RegisterTest.php (1)

9-11: Consider using dynamic mock tokens for hCaptcha tests

While mocking hCaptcha responses is correct, using a hardcoded 'test-token' might make tests brittle. Consider generating dynamic tokens or extracting to a constant.

+ const MOCK_HCAPTCHA_TOKEN = 'test-token';
  Http::fake([
      ValidHCaptcha::H_CAPTCHA_VERIFY_URL => Http::response(['success' => true])
  ]);
  // ...
-     'h-captcha-response' => 'test-token', // Mock token for testing
+     'h-captcha-response' => MOCK_HCAPTCHA_TOKEN,

Also applies to: 20-20

client/components/open/integrations/components/IntegrationModal.vue (1)

Line range hint 115-119: Enhance error handling specificity

The catch block could be more specific about different types of errors to help identify potential security issues.

- try {
-   alert.error(error.data.message)
- } catch (e) {
-   alert.error("An error occurred while saving the integration")
- }
+ if (error.response?.status === 429) {
+   alert.error("Too many attempts. Please try again later.")
+ } else if (error.response?.data?.message) {
+   alert.error(error.response.data.message)
+ } else {
+   alert.error("An error occurred while saving the integration")
+ }
api/app/Integrations/Handlers/AbstractIntegrationHandler.php (1)

97-97: Consider adding base spam prevention validation rules

Since this PR focuses on email spam security, consider adding some base validation rules in the abstract class that all integrations must include. For example:

  • Rate limiting validation
  • Spam keyword detection
  • Input length restrictions
  • Character set validation

Would you like me to provide an example implementation of these security measures?

api/app/Integrations/Handlers/DiscordIntegration.php (2)

Line range hint 13-22: Add spam prevention validation rules

While restricting to pro users helps, additional validation rules should be added to prevent spam:

  1. Rate limiting validation
  2. Input sanitization for webhook messages
  3. Maximum length restrictions for submission data

Add these validation rules:

 public static function getValidationRules(?Form $form): array
 {
     return [
         'discord_webhook_url' => 'required|url|starts_with:https://discord.com/api/webhooks',
+        'message' => ['required', 'string', 'max:2000', 'not_regex:/(?:http|https|www\\.)[^\\s]+/i'],
         'include_submission_data' => 'boolean',
         'link_open_form' => 'boolean',
         'link_edit_form' => 'boolean',
         'views_submissions_count' => 'boolean',
         'link_edit_submission' => 'boolean'
     ];
 }

Line range hint 31-33: Add rate limiting to shouldRun method

The shouldRun method should include rate limiting checks to prevent spam attacks.

Consider implementing rate limiting:

 protected function shouldRun(): bool
 {
-    return !is_null($this->getWebhookUrl()) && $this->form->is_pro && parent::shouldRun();
+    return !is_null($this->getWebhookUrl()) 
+        && $this->form->is_pro 
+        && !$this->isRateLimited()
+        && parent::shouldRun();
 }
+
+private function isRateLimited(): bool
+{
+    $key = "discord_integration:{$this->form->id}";
+    return !RateLimiter::attempt($key, 60, function() {}, 60);
+}
api/app/Integrations/Handlers/SlackIntegration.php (1)

Line range hint 13-22: Add spam prevention validation rules and consider code reuse

The validation rules are similar to DiscordIntegration. Consider:

  1. Creating a shared trait for webhook security
  2. Adding spam prevention rules
  3. Implementing rate limiting

Create a new trait for shared security features:

trait WebhookSecurityTrait
{
    protected function getBaseValidationRules(): array
    {
        return [
            'message' => ['required', 'string', 'max:2000', 'not_regex:/(?:http|https|www\\.)[^\\s]+/i'],
            'include_submission_data' => 'boolean',
            'link_open_form' => 'boolean',
            'link_edit_form' => 'boolean',
            'views_submissions_count' => 'boolean',
            'link_edit_submission' => 'boolean'
        ];
    }

    protected function isRateLimited(): bool
    {
        $key = "webhook_integration:{$this->form->id}";
        return !RateLimiter::attempt($key, 60, function() {}, 60);
    }
}

Then update the validation rules:

 public static function getValidationRules(?Form $form): array
 {
-    return [
+    return array_merge(self::getBaseValidationRules(), [
         'slack_webhook_url' => 'required|url|starts_with:https://hooks.slack.com/',
-        'include_submission_data' => 'boolean',
-        'link_open_form' => 'boolean',
-        'link_edit_form' => 'boolean',
-        'views_submissions_count' => 'boolean',
-        'link_edit_submission' => 'boolean'
-    ];
+    ]);
 }
client/components/pages/auth/components/RegisterForm.vue (2)

182-184: Consider using camelCase for form field names.

The form field h-captcha-response uses kebab-case, which is unusual in JavaScript. Consider using camelCase (hCaptchaResponse) for consistency, unless this specific naming is required by the hCaptcha API.


Line range hint 1-324: Consider implementing a dedicated hCaptcha wrapper component.

The current implementation directly integrates hCaptcha into the registration form. Consider creating a reusable wrapper component that:

  1. Encapsulates the hCaptcha logic
  2. Provides a cleaner interface for getting/setting the response
  3. Handles self-hosted conditions internally
  4. Can be reused in other forms that need spam protection

This would improve maintainability and make it easier to add spam protection to other forms in the future.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between ea4cd85 and 01f7fa2.

📒 Files selected for processing (16)
  • api/app/Http/Controllers/Auth/RegisterController.php (4 hunks)
  • api/app/Http/Requests/Integration/FormIntegrationsRequest.php (3 hunks)
  • api/app/Integrations/Handlers/AbstractIntegrationHandler.php (1 hunks)
  • api/app/Integrations/Handlers/DiscordIntegration.php (1 hunks)
  • api/app/Integrations/Handlers/EmailIntegration.php (2 hunks)
  • api/app/Integrations/Handlers/GoogleSheetsIntegration.php (2 hunks)
  • api/app/Integrations/Handlers/SlackIntegration.php (1 hunks)
  • api/app/Integrations/Handlers/WebhookIntegration.php (1 hunks)
  • api/app/Integrations/Handlers/ZapierIntegration.php (2 hunks)
  • api/app/Models/User.php (3 hunks)
  • api/database/migrations/2024_12_10_094605_add_meta_to_users_table.php (1 hunks)
  • api/tests/Feature/Integrations/Email/EmailIntegrationTest.php (1 hunks)
  • api/tests/Feature/RegisterTest.php (3 hunks)
  • api/tests/Feature/UserManagementTest.php (6 hunks)
  • client/components/open/integrations/components/IntegrationModal.vue (4 hunks)
  • client/components/pages/auth/components/RegisterForm.vue (6 hunks)
🔇 Additional comments (23)
api/app/Integrations/Handlers/WebhookIntegration.php (1)

5-5: LGTM!

The Form import is correctly added to support the new parameter type.

api/tests/Feature/UserManagementTest.php (1)

Line range hint 1-156: Security Review: Additional Test Coverage Needed

While the addition of HCaptcha is a good step towards preventing spam registrations, the test coverage should be expanded to include:

  1. Edge cases in HCaptcha validation
  2. Integration with other security measures (rate limiting, email verification)
  3. Proper error handling and token optimization
  4. Protection against automated registration attempts

These improvements will ensure the spam protection measures are robust and effective.

Let's verify the current security measures:

api/app/Http/Requests/Integration/FormIntegrationsRequest.php (2)

5-5: Importing Form model

The Form model is correctly imported to access form data within the request.


83-85: Passing $this->form enhances context-aware validation

By passing the form instance to getValidationRules, validation can be tailored based on the specific form, improving flexibility and accuracy.

api/app/Integrations/Handlers/EmailIntegration.php (4)

5-6: Importing Form and FormIntegration models

The inclusion of the Form and FormIntegration models is necessary for accessing form data and integration capabilities.


12-12: Importing ValidationException for proper exception handling

The addition of ValidationException allows for more precise exception handling and user feedback during validation.


43-43: Verify the correctness of the route parameter 'integrationid'

Ensure that 'integrationid' is the correct route parameter. If it should be 'integration_id', this condition may not work as intended.

Check your route definitions to confirm the parameter name.


47-51: Appropriate use of ValidationException

Throwing ValidationException provides clear feedback to the user when validation constraints are not met.

api/database/migrations/2024_12_10_094605_add_meta_to_users_table.php (1)

14-14: Verify JSON column default value compatibility

Setting a default value for a JSON column may not be supported in some database versions (e.g., older MySQL versions). Ensure your database supports default JSON values.

Consider checking the minimum database version requirements and testing the migration on the target databases.

api/app/Integrations/Handlers/ZapierIntegration.php (1)

20-20: Ensure consistency in method signatures across integration handlers

The getValidationRules method now accepts a ?Form $form parameter. Even if it's not used in this class, this change maintains consistency with other integration handlers.

api/tests/Feature/RegisterTest.php (2)

25-28: LGTM: Good security practice tracking registration IPs

Properly testing the storage of registration IP addresses is crucial for security auditing and abuse prevention.


75-87: LGTM: Comprehensive hCaptcha validation for production

Good security practice ensuring hCaptcha is required in production environment.

client/components/open/integrations/components/IntegrationModal.vue (1)

103-104: LGTM: Good protection against duplicate submissions

The loading state check helps prevent duplicate form submissions, which is good for preventing spam and ensuring data integrity.

api/app/Integrations/Handlers/GoogleSheetsIntegration.php (2)

Line range hint 44-49: Review logging of sensitive information

While logging form identifiers is useful for debugging, ensure these logs are properly secured and not exposed to unauthorized users.

✅ Verification successful

Let me gather more context about the logging patterns to better understand how form IDs and slugs are handled in logs.


Let me try a different approach to check the logging patterns.


Form identifiers in logs are appropriately used and secured

The logging of form IDs and slugs appears to be implemented securely and appropriately:

  • Form IDs are only logged in debug/error contexts for operational tracking
  • Logs are used in controlled environments (integration handlers)
  • Sensitive data like email recipients and workspace details are logged separately
  • Error logging includes necessary context for troubleshooting integration issues
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check for other instances of form ID/slug logging
rg "form_(id|slug)" --type php

Length of output: 5991


Script:

#!/bin/bash
# Check for logging patterns around form data
rg "Log::(debug|info|warning|error).+form" --type php -A 2

Length of output: 60


Script:

#!/bin/bash
# Check for logging patterns in the integration handlers
rg "Log::" api/app/Integrations/Handlers/ -A 2

Length of output: 1365


26-28: Consider adding validation rules for spam prevention

The empty validation rules might need to be enhanced with rate limiting or other spam prevention measures, especially since this PR focuses on email security.

api/app/Integrations/Handlers/SlackIntegration.php (1)

Line range hint 31-33: Add rate limiting to shouldRun method

Similar to DiscordIntegration, implement rate limiting to prevent spam.

The same rate limiting implementation suggested for DiscordIntegration should be applied here.

api/app/Http/Controllers/Auth/RegisterController.php (1)

73-75: Verify security implications of skipping hCaptcha in self-hosted mode

While the hCaptcha integration helps prevent automated registrations, skipping this validation in self-hosted mode could create a security vulnerability if the instance is exposed to the internet.

Consider:

  1. Making hCaptcha configurable in self-hosted mode
  2. Implementing alternative spam prevention for self-hosted instances
✅ Verification successful

Rate limiting provides adequate security for self-hosted registrations

The registration endpoint is already protected by multiple layers of rate limiting:

  • 5 attempts per minute
  • 30 attempts per hour
  • Global API rate limit of 100 requests per minute

These rate limiting measures provide robust protection against automated registrations and brute force attacks, even without hCaptcha in self-hosted mode. The security concern is adequately addressed through these existing controls.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check if there are any existing security measures for self-hosted mode
rg -A 5 "self_hosted.*registration" 

Length of output: 37


Script:

#!/bin/bash
# Let's check for any registration-related security measures in the codebase
rg -A 5 "registration|register" config/

Length of output: 89


Script:

#!/bin/bash
# Check for other validation rules in the RegisterController
ast-grep --pattern 'function validator($$$) { $$$ }'  api/app/Http/Controllers/Auth/RegisterController.php

Length of output: 107


Script:

#!/bin/bash
# Look for any rate limiting or throttling mechanisms
rg -A 5 "throttle|RateLimit" api/app/Http/

Length of output: 2642


Script:

#!/bin/bash
# Check if there are any other security middleware applied to registration
rg -A 5 "middleware.*register" api/routes/

Length of output: 44

client/components/pages/auth/components/RegisterForm.vue (6)

148-148: LGTM!

The VueHcaptcha component is properly registered.


213-216: LGTM!

The hCaptcha reference is properly initialized based on the self-hosted condition.


55-69: LGTM! Verify hCaptcha integration in self-hosted environments.

The hCaptcha integration is well-implemented with proper error handling and conditional rendering.

Let's verify the self-hosted configuration handling:

✅ Verification successful

hCaptcha integration correctly handles self-hosted environments

The verification confirms that the self_hosted feature flag is properly integrated across the application:

  • The RegisterForm correctly uses isSelfHosted computed property derived from useFeatureFlag('self_hosted')
  • The conditional rendering v-if="!isSelfHosted" ensures hCaptcha is only shown in cloud environments
  • This aligns with the broader application's self-hosted vs cloud deployment strategy, as evidenced by similar patterns in other components
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for self-hosted feature flag configuration
rg -A 3 "self_hosted" --type js

Length of output: 3011


179-180: Verify the necessity of utm_data addition.

The addition of utm_data seems unrelated to the hCaptcha integration. Please verify if this change belongs in this PR.

✅ Verification successful

The utm_data addition is a valid part of user registration tracking

The codebase analysis shows that utm_data is a well-integrated feature for tracking user registration sources:

  • There's a dedicated migration 2024_09_18_091846_add_utm_data_to_users_table.php adding this field
  • The User model properly declares it in fillable and casts arrays
  • It's consistently used in both regular registration (RegisterController) and OAuth registration flows
  • The implementation in RegisterForm.vue matches the backend expectations

The change belongs in this PR as it's part of the registration form data structure, even though it's separate from the hCaptcha integration.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check usage of utm_data in the codebase
rg -A 3 "utm_data"

Length of output: 3734


188-190: LGTM! Verify secure handling of the site key.

The computed property correctly retrieves the hCaptcha site key. Ensure the key is properly secured in the runtime configuration.

✅ Verification successful

LGTM! The hCaptcha site key is properly handled.

The implementation is secure as:

  • The site key is properly sourced from environment variables via NUXT_PUBLIC_H_CAPTCHA_SITE_KEY
  • It's safely exposed through the runtime config system in client/runtimeConfig.js
  • The key is only used with the official @hcaptcha/vue3-hcaptcha component
  • There are no hardcoded keys in the codebase
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check for potential hardcoded site keys
rg -A 3 "hCaptchaSiteKey.*=.*['\"]" --type js

Length of output: 49


Script:

#!/bin/bash
# Search for hCaptcha related configuration in nuxt config files
fd -e js -e ts "nuxt.config" --exec rg -l "hCaptcha"

# Search for runtime config setup
rg -i "runtimeconfig.*hcaptcha" --type=vue --type=js --type=ts -A 3

Length of output: 148


Script:

#!/bin/bash
# Search for hCaptcha configuration in config files
rg -i "hcaptcha" --type-add 'vue:*.vue' --type=vue --type=js --type=ts -A 3

# Look for environment variable usage related to hCaptcha
rg -i "HCAPTCHA" --type=env --type=js --type=ts -A 2

Length of output: 5148


143-144: Verify the necessity of the workspaces import.

The fetchAllWorkspaces import appears unrelated to the hCaptcha integration. Please verify if this import is necessary for this component.

✅ Verification successful

The fetchAllWorkspaces import is necessary and correctly used

The import is used in the register method after successful user registration to fetch and set workspace data in the store. This is part of the post-registration flow where the application initializes the user's workspace data.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check usage of fetchAllWorkspaces in the component
ast-grep --pattern 'fetchAllWorkspaces($$$)'

Length of output: 119


Script:

#!/bin/bash
# Check if fetchAllWorkspaces is used in RegisterForm.vue
rg "fetchAllWorkspaces" "client/components/pages/auth/components/RegisterForm.vue"

# Get the content of RegisterForm.vue to understand the component's logic
cat "client/components/pages/auth/components/RegisterForm.vue"

Length of output: 8075

@@ -138,6 +147,7 @@
'password_confirmation' => 'secret',
'agree_terms' => true,
'invite_token' => $token,
'h-captcha-response' => 'test-token',
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Add comprehensive spam protection test cases

The current test suite focuses on invite token validation but lacks coverage for important spam prevention scenarios:

  • Rate limiting of registration attempts
  • Multiple failed HCaptcha attempts
  • Email verification workflow
  • IP-based restrictions

Would you like me to help create additional test cases for these scenarios? This would help ensure the email spam security measures are working as intended.

@@ -31,6 +36,7 @@
'password_confirmation' => 'secret',
'agree_terms' => true,
'invite_token' => $token,
'h-captcha-response' => 'test-token',
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Enhance HCaptcha validation test coverage

The test uses a static 'test-token' value without verifying if the HCaptcha validation was actually performed. Consider:

  • Asserting that the HCaptcha verification endpoint was called
  • Verifying the token value was passed correctly

Example enhancement:

+    Http::fake([
+        ValidHCaptcha::H_CAPTCHA_VERIFY_URL => Http::response(['success' => true])
+    ]);
     $response = $this->postJson('/register', [
         // ... other fields ...
         'h-captcha-response' => 'test-token',
     ]);
     $response->assertSuccessful();
+    Http::assertSent(function ($request) {
+        return $request->url() === ValidHCaptcha::H_CAPTCHA_VERIFY_URL
+            && $request->data()['response'] === 'test-token';
+    });

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines +11 to +13
Http::fake([
ValidHCaptcha::H_CAPTCHA_VERIFY_URL => Http::response(['success' => true])
]);
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Add test cases for HCaptcha verification failures

The current setup always mocks HCaptcha to return success. To ensure robust spam protection, we should also test:

  • Invalid/expired tokens
  • Rate-limited responses
  • Network failures

Example implementation:

 beforeEach(function () {
     $this->user = $this->actingAsProUser();
     $this->workspace = $this->createUserWorkspace($this->user);
-    Http::fake([
-        ValidHCaptcha::H_CAPTCHA_VERIFY_URL => Http::response(['success' => true])
-    ]);
 });

+it('prevents registration with invalid hCaptcha token', function () {
+    Http::fake([
+        ValidHCaptcha::H_CAPTCHA_VERIFY_URL => Http::response(['success' => false])
+    ]);
+    // Add test implementation
+});

Committable suggestion skipped: line range outside the PR's diff.

@@ -14,9 +15,11 @@ class FormIntegrationsRequest extends FormRequest
public array $integrationRules = [];

private ?string $integrationClassName = null;
private ?Form $form = null;
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Ensure $integrationClassName is initialized to prevent null reference errors

Since methods like attributes() and isOAuthRequired() rely on $this->integrationClassName, ensure it is initialized even when $request->integration_id is not provided to prevent potential null reference errors.

Consider initializing $integrationClassName or adding checks before its usage.

Comment on lines +31 to +32
$this->middleware('throttle:5,1')->only('register'); // 5 attempts per minute
$this->middleware('throttle:30,60')->only('register'); // 30 attempts per hour
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Consider implementing stricter rate limits for registration

While multiple rate limits are good, the current limits (5/min, 30/hour) might be too permissive for a registration endpoint. Consider reducing these limits to prevent potential abuse:

  • 3 attempts per hour is typically sufficient for legitimate users
  • Current limits could allow automated spam account creation
-        $this->middleware('throttle:5,1')->only('register'); // 5 attempts per minute
-        $this->middleware('throttle:30,60')->only('register'); // 30 attempts per hour
+        $this->middleware('throttle:3,60')->only('register'); // 3 attempts per hour
📝 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.

Suggested change
$this->middleware('throttle:5,1')->only('register'); // 5 attempts per minute
$this->middleware('throttle:30,60')->only('register'); // 30 attempts per hour
$this->middleware('throttle:3,60')->only('register'); // 3 attempts per hour

@@ -84,6 +93,7 @@
'password' => bcrypt($data['password']),
'hear_about_us' => $data['hear_about_us'],
'utm_data' => array_key_exists('utm_data', $data) ? $data['utm_data'] : null,
'meta' => ['registration_ip' => request()->ip()],
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Review IP address storage for privacy compliance

Storing IP addresses has privacy implications and might be subject to data protection regulations (GDPR, CCPA):

  1. Consider hashing the IP address
  2. Implement a retention policy
  3. Document the purpose of collection
-            'meta' => ['registration_ip' => request()->ip()],
+            'meta' => ['registration_ip' => hash('sha256', request()->ip())],

Committable suggestion skipped: line range outside the PR's diff.

@@ -33,6 +33,7 @@ class User extends Authenticatable implements JWTSubject
'password',
'hear_about_us',
'utm_data',
'meta'
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Enhance security for the meta attribute

While it's good that the meta field is hidden, being fillable without validation poses risks:

  1. Mass assignment could allow injection of unexpected data
  2. No schema validation for meta contents

Consider:

  1. Remove from fillable and use explicit setters
  2. Add validation for meta contents
  3. Implement strict typing or schema validation
-    protected $fillable = [
-        'name',
-        'email',
-        'password',
-        'hear_about_us',
-        'utm_data',
-        'meta'
-    ];
+    protected $fillable = [
+        'name',
+        'email',
+        'password',
+        'hear_about_us',
+        'utm_data',
+    ];

+    public function setMeta(array $meta): void
+    {
+        $allowedKeys = ['registration_ip'];
+        $filteredMeta = array_intersect_key($meta, array_flip($allowedKeys));
+        $this->attributes['meta'] = json_encode($filteredMeta);
+    }

Also applies to: 48-48, 61-61

Comment on lines +136 to +175
test('free user can update their single email integration', function () {
$user = $this->actingAsUser();
$workspace = $this->createUserWorkspace($user);
$form = $this->createForm($user, $workspace);

// Create initial integration
$response = $this->postJson(route('open.forms.integration.create', $form), [
'integration_id' => 'email',
'status' => true,
'settings' => [
'send_to' => 'test@example.com',
'sender_name' => 'Test Sender',
'subject' => 'Test Subject',
'email_content' => 'Test Content',
'include_submission_data' => true
]
]);

$response->assertSuccessful();
$integrationId = $response->json('form_integration.id');

// Update the integration
$response = $this->putJson(route('open.forms.integration.update', [$form, $integrationId]), [
'integration_id' => 'email',
'status' => true,
'settings' => [
'send_to' => 'updated@example.com',
'sender_name' => 'Updated Sender',
'subject' => 'Updated Subject',
'email_content' => 'Updated Content',
'include_submission_data' => true
]
]);

$response->assertSuccessful();

$integration = FormIntegration::find($integrationId);
expect($integration->data->send_to)->toBe('updated@example.com');
expect($integration->data->sender_name)->toBe('Updated Sender');
});
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add content validation test cases

The current tests should be extended to cover content security scenarios:

  1. HTML/Script injection in email content
  2. Maximum content size limits
  3. Content sanitization

Add these test cases:

+test('it sanitizes html in email content', function () {
+    $user = $this->actingAsUser();
+    $workspace = $this->createUserWorkspace($user);
+    $form = $this->createForm($user, $workspace);
+
+    $response = $this->postJson(route('open.forms.integration.create', $form), [
+        'integration_id' => 'email',
+        'status' => true,
+        'settings' => [
+            'send_to' => 'test@example.com',
+            'sender_name' => 'Test Sender',
+            'subject' => 'Test Subject',
+            'email_content' => '<script>alert("xss")</script>Test Content',
+            'include_submission_data' => true
+        ]
+    ]);
+
+    $response->assertStatus(422);
+});

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines +84 to +108
test('free user cannot add multiple emails', function () {
$user = $this->actingAsUser();
$workspace = $this->createUserWorkspace($user);
$form = $this->createForm($user, $workspace);

$response = $this->postJson(route('open.forms.integration.create', $form), [
'integration_id' => 'email',
'status' => true,
'settings' => [
'send_to' => "test@example.com\nanother@example.com",
'sender_name' => 'Test Sender',
'subject' => 'Test Subject',
'email_content' => 'Test Content',
'include_submission_data' => true
]
]);

$response->assertStatus(422)
->assertJsonValidationErrors(['settings.send_to'])
->assertJson([
'errors' => [
'settings.send_to' => ['You can only send to a single email address on the free plan. Please upgrade to the Pro plan to create a new integration.']
]
]);
});
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add security-focused test cases for email validation

While the current tests cover business rules well, consider adding security-focused test cases:

  1. Email header injection prevention
  2. Email address format validation
  3. Large payload handling

Add these test cases:

+test('it prevents email header injection', function () {
+    $user = $this->actingAsUser();
+    $workspace = $this->createUserWorkspace($user);
+    $form = $this->createForm($user, $workspace);
+
+    $response = $this->postJson(route('open.forms.integration.create', $form), [
+        'integration_id' => 'email',
+        'status' => true,
+        'settings' => [
+            'send_to' => "test@example.com\nBcc: malicious@attacker.com",
+            'sender_name' => "Innocent Name\nBcc: malicious@attacker.com",
+            'subject' => "Test\nBcc: malicious@attacker.com",
+            'email_content' => 'Test Content',
+            'include_submission_data' => true
+        ]
+    ]);
+
+    $response->assertStatus(422);
+});

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines 238 to 241
if (!this.isSelfHosted) {
this.form['h-captcha-response'] = document.getElementsByName('h-captcha-response')[0].value
this.hcaptcha.reset()
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Avoid direct DOM access for hCaptcha response.

Instead of accessing the DOM directly with document.getElementsByName, consider using the hCaptcha component's methods or events to get the response value. The Vue ref is already available as this.hcaptcha.

-this.form['h-captcha-response'] = document.getElementsByName('h-captcha-response')[0].value
+// Use the hCaptcha component's execute method to get the response
+const response = await this.hcaptcha.execute()
+this.form['h-captcha-response'] = response

Committable suggestion skipped: line range outside the PR's diff.

@@ -27,6 +28,10 @@ class RegisterController extends Controller
public function __construct()
{
$this->middleware('guest');
if (app()->environment() !== 'testing') {
Copy link
Owner

Choose a reason for hiding this comment

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

Any reason why this is in this if?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Testcases run too fast so no need this throttle for test cases

Copy link
Owner

@JhumanJ JhumanJ left a comment

Choose a reason for hiding this comment

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

Great work, small comments added thanks

api/app/Http/Controllers/Auth/RegisterController.php Outdated Show resolved Hide resolved
Copy link
Contributor

@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

🧹 Outside diff range and nitpick comments (3)
api/app/Integrations/Handlers/EmailIntegration.php (2)

35-40: Strengthen email validation for spam prevention.

The validation for multiple email addresses is good, but consider these enhancements:

  1. Add input sanitization for the email addresses
  2. Consider adding rate limiting for email sending
 $rules['send_to'][] = function ($attribute, $value, $fail) use ($form) {
+    $value = trim(strip_tags($value));
     if (count(explode("\n", trim($value))) > 1 || count(explode(',', $value)) > 1) {
         $fail('You can only send to a single email address on the free plan. Please upgrade to the Pro plan to create a new integration.');
     }
+    if (!filter_var($value, FILTER_VALIDATE_EMAIL)) {
+        $fail('Please enter a valid email address.');
+    }
 };

Line range hint 1-140: Good security implementation with layered defenses.

The implementation provides multiple layers of security:

  1. Input validation for free users
  2. Integration limits
  3. Risk-based restrictions
  4. Pro feature gating

Consider implementing these architectural improvements:

  1. Add rate limiting at the API level
  2. Implement email domain validation/blacklisting
  3. Add monitoring for abuse patterns
api/tests/Feature/RegisterTest.php (1)

Line range hint 32-42: Consider adding negative test cases for hCaptcha validation.

While the test covers duplicate email validation, consider adding test cases for:

  • Invalid hCaptcha token
  • Missing hCaptcha token
  • Failed hCaptcha verification
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 01f7fa2 and 9260b4b.

📒 Files selected for processing (5)
  • api/app/Http/Controllers/Auth/RegisterController.php (4 hunks)
  • api/app/Integrations/Handlers/EmailIntegration.php (2 hunks)
  • api/database/migrations/2024_12_10_094605_add_meta_to_users_table.php (1 hunks)
  • api/tests/Feature/RegisterTest.php (3 hunks)
  • client/components/pages/auth/components/RegisterForm.vue (6 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • api/database/migrations/2024_12_10_094605_add_meta_to_users_table.php
🔇 Additional comments (12)
api/app/Integrations/Handlers/EmailIntegration.php (3)

5-12: LGTM! Dependencies are properly imported.

The new imports for Form model and ValidationException are necessary for the added validation logic.


18-18: LGTM! Pro plan check is properly implemented.

The validation rules correctly bypass restrictions for pro users and self-hosted installations.

Also applies to: 31-33


16-16: Verify risk limit effectiveness.

The risk limit check provides an additional layer of security, but let's verify its effectiveness:

Also applies to: 77-89

✅ Verification successful

Risk limit implementation is effective and properly enforced

The verification shows a robust security implementation:

  • Risk assessment is cached for 15 minutes via remember() method
  • Workspace-level risk check evaluates all owners' risk status
  • Email sending is blocked when risky workspace reaches the 120 submissions limit
  • Suspicious activity is properly logged with workspace and form identifiers
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check for any bypass attempts or related security issues
rg -A 5 "RISKY_USERS_LIMIT|is_risky" --type php

# Check for any email-related security incidents in logs
rg -A 5 "!!!DANGER!!!" --type php

Length of output: 3650

api/tests/Feature/RegisterTest.php (3)

8-28: LGTM! Comprehensive test coverage for successful registration.

The test properly mocks hCaptcha verification and validates the registration flow including IP address storage.


Line range hint 44-73: LGTM! Comprehensive validation for disposable emails.

The test properly validates the rejection of disposable emails and includes proper error message verification.


75-87: LGTM! Good coverage of environment-specific validation.

The test properly validates that hCaptcha is required in production environment.

api/app/Http/Controllers/Auth/RegisterController.php (3)

64-81: LGTM! Comprehensive validation rules with proper hCaptcha integration.

The validation rules are well-structured and the conditional hCaptcha validation is properly implemented.


31-34: ⚠️ Potential issue

Consider implementing stricter rate limits for registration.

While multiple rate limits are good, the current limits (5/min, 30/hour) might be too permissive for a registration endpoint.

Apply this diff to implement stricter limits:

-        if (app()->environment() !== 'testing') {
-            $this->middleware('throttle:5,1')->only('register'); // 5 attempts per minute
-            $this->middleware('throttle:30,60')->only('register'); // 30 attempts per hour
+        if (app()->environment() !== 'testing') {
+            $this->middleware('throttle:3,60')->only('register'); // 3 attempts per hour
+        }

98-98: ⚠️ Potential issue

Review IP address storage for privacy compliance.

Storing IP addresses has privacy implications and might be subject to data protection regulations (GDPR, CCPA).

Apply this diff to implement IP hashing:

-            'meta' => ['registration_ip' => request()->ip()],
+            'meta' => ['registration_ip' => hash('sha256', request()->ip())],
client/components/pages/auth/components/RegisterForm.vue (3)

55-68: LGTM! Clean hCaptcha component integration.

The hCaptcha component is properly integrated with conditional rendering and error handling.


179-189: LGTM! Well-structured form data with proper computed properties.

The form data is well-organized and the computed property for the hCaptcha site key is properly implemented.


237-240: 🛠️ Refactor suggestion

Avoid direct DOM access for hCaptcha response.

Instead of accessing the DOM directly with document.getElementsByName, use the hCaptcha component's methods or events.

Apply this diff to use the component's methods:

      if (this.hCaptchaSiteKey) {
-        this.form['h-captcha-response'] = document.getElementsByName('h-captcha-response')[0].value
+        const response = await this.hcaptcha.execute()
+        this.form['h-captcha-response'] = response
        this.hcaptcha.reset()
      }

Comment on lines +42 to +52
// Free plan users can only have a single email integration per form (avoid spam)
if (!request()->route('integrationid')) {
$existingEmailIntegrations = FormIntegration::where('form_id', $form->id)
->where('integration_id', 'email')
->count();
if ($existingEmailIntegrations > 0) {
throw ValidationException::withMessages([
'settings.send_to' => ['Free users are limited to 1 email integration per form.']
]);
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Consider edge cases in integration validation.

The validation for existing integrations should:

  1. Consider soft-deleted integrations
  2. Use consistent error messages
 if (!request()->route('integrationid')) {
     $existingEmailIntegrations = FormIntegration::where('form_id', $form->id)
         ->where('integration_id', 'email')
+        ->whereNull('deleted_at')  // Exclude soft-deleted integrations
         ->count();
     if ($existingEmailIntegrations > 0) {
         throw ValidationException::withMessages([
-            'settings.send_to' => ['Free users are limited to 1 email integration per form.']
+            'settings.send_to' => ['You can only have a single email integration on the free plan. Please upgrade to the Pro plan to create a new integration.']
         ]);
     }
 }
📝 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.

Suggested change
// Free plan users can only have a single email integration per form (avoid spam)
if (!request()->route('integrationid')) {
$existingEmailIntegrations = FormIntegration::where('form_id', $form->id)
->where('integration_id', 'email')
->count();
if ($existingEmailIntegrations > 0) {
throw ValidationException::withMessages([
'settings.send_to' => ['Free users are limited to 1 email integration per form.']
]);
}
}
// Free plan users can only have a single email integration per form (avoid spam)
if (!request()->route('integrationid')) {
$existingEmailIntegrations = FormIntegration::where('form_id', $form->id)
->where('integration_id', 'email')
->whereNull('deleted_at') // Exclude soft-deleted integrations
->count();
if ($existingEmailIntegrations > 0) {
throw ValidationException::withMessages([
'settings.send_to' => ['You can only have a single email integration on the free plan. Please upgrade to the Pro plan to create a new integration.']
]);
}
}

Copy link
Owner

@JhumanJ JhumanJ left a comment

Choose a reason for hiding this comment

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

Please check changes + fix tests

@chiragchhatrala
Copy link
Collaborator Author

@JhumanJ Please add below in .env.testing file

H_CAPTCHA_SITE_KEY=TEST_KEY
H_CAPTCHA_SECRET_KEY=TEST_SECRET

- Removed environment check for throttling middleware in RegisterController, ensuring consistent rate limiting for the registration endpoint.
- Updated TestCase to disable throttle middleware during tests, allowing for more flexible testing scenarios without rate limiting interference.
Copy link
Contributor

@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)
api/tests/TestCase.php (1)

15-21: Consider adding dedicated test cases for throttling behavior.

While disabling throttling globally is correct, consider adding specific test cases that verify the throttling behavior in your registration endpoints. This ensures the spam protection works as intended.

Example test structure you might want to implement:

class RegistrationThrottlingTest extends TestCase
{
    protected function setUp(): void
    {
        parent::setUp();
        // Re-enable throttling for these specific tests
        $this->withMiddleware(ThrottleRequests::class);
    }

    public function test_registration_is_throttled_after_max_attempts()
    {
        // Test implementation
    }
}

Would you like me to help create these test cases or open a GitHub issue to track this task?

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between a372007 and b5f7e2f.

📒 Files selected for processing (3)
  • api/app/Http/Controllers/Auth/RegisterController.php (4 hunks)
  • api/tests/Feature/RegisterTest.php (4 hunks)
  • api/tests/TestCase.php (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • api/tests/Feature/RegisterTest.php
  • api/app/Http/Controllers/Auth/RegisterController.php
🔇 Additional comments (1)
api/tests/TestCase.php (1)

7-7: LGTM! Clean implementation of test setup.

The implementation correctly disables rate limiting for all tests, which is a good practice to prevent throttling from interfering with test execution.

Also applies to: 15-21

- Added hCaptcha site and secret keys to phpunit.xml for testing purposes.
- Updated RegisterTest to configure hCaptcha secret key dynamically, ensuring proper token validation in production environment.

These changes improve the testing setup for hCaptcha, facilitating more accurate simulation of production conditions.
@JhumanJ JhumanJ merged commit 7365479 into main Dec 18, 2024
5 checks passed
@JhumanJ JhumanJ deleted the 157a6-email-spam-security branch December 18, 2024 12:16
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.

2 participants