Skip to content

Add comprehensive email validation for blocked users#8

Open
ShashankFC wants to merge 1 commit into
blocked-email-validation-prefrom
blocked-email-validation-post
Open

Add comprehensive email validation for blocked users#8
ShashankFC wants to merge 1 commit into
blocked-email-validation-prefrom
blocked-email-validation-post

Conversation

@ShashankFC

Copy link
Copy Markdown

Test 3

Summary by CodeRabbit

Release Notes

New Features

  • Added email blocking mechanism for account creation that prevents specific email addresses from being used
  • Enhanced email validation to track and reject previously failed emails
  • Improved account creation error responses with detailed validation information

Database

  • New database table added to persist blocked email configurations and tracking data

✏️ Tip: You can customize this high-level summary in your review settings.


Replicated from ai-code-review-evaluation/discourse-coderabbit#3

… many times each email address is blocked, and last time it was blocked. Move email validation out of User model and into EmailValidator. Signup form remembers which email addresses have failed and shows validation error on email field.
@ShashankFC ShashankFC requested a review from Copilot January 30, 2026 10:23

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR adds a comprehensive email blocking system to prevent account creation with specific email addresses. The implementation includes a new BlockedEmail model for tracking and managing blocked emails, with support for different action types (block or do nothing) and usage statistics.

Changes:

  • Created BlockedEmail model with database migration for tracking blocked emails and their match statistics
  • Refactored email validation from User model into a dedicated EmailValidator that integrates with the blocked email system
  • Enhanced account creation error responses to include detailed validation information and track rejected emails on the client side

Reviewed changes

Copilot reviewed 10 out of 10 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
app/models/blocked_email.rb New model implementing blocked email logic with action types and match tracking
db/migrate/20130724201552_create_blocked_emails.rb Database migration creating the blocked_emails table with email, action_type, and tracking fields
lib/validators/email_validator.rb New custom validator consolidating email domain whitelist/blacklist and blocked email checks
app/models/user.rb Removed inline email validation logic, replaced with declarative validator usage
app/controllers/users_controller.rb Enhanced error response to include structured errors and user values
app/assets/javascripts/discourse/controllers/create_account_controller.js Added client-side tracking of rejected emails to provide immediate feedback
config/locales/server.en.yml Added new error message for blocked emails
spec/models/blocked_email_spec.rb Comprehensive test coverage for BlockedEmail model behavior
spec/fabricators/blocked_email_fabricator.rb Test factory for creating BlockedEmail instances
spec/components/validators/email_validator_spec.rb Test coverage for EmailValidator integration with blocked emails

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.


describe "new record" do
it "sets a default action_type" do
BlockedEmail.create(email: email).action_type.should == BlockedEmail.actions[:block]

Copilot AI Jan 30, 2026

Copy link

Choose a reason for hiding this comment

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

The deprecated RSpec syntax .should is used. Consider updating to the modern expect syntax: expect(BlockedEmail.create(email: email).action_type).to eq(BlockedEmail.actions[:block])

Copilot uses AI. Check for mistakes.
it "last_match_at is null" do
# If we manually load the table with some emails, we can see whether those emails
# have ever been blocked by looking at last_match_at.
BlockedEmail.create(email: email).last_match_at.should be_nil

Copilot AI Jan 30, 2026

Copy link

Choose a reason for hiding this comment

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

The deprecated RSpec syntax .should is used. Consider updating to the modern expect syntax: expect(BlockedEmail.create(email: email).last_match_at).to be_nil

Suggested change
BlockedEmail.create(email: email).last_match_at.should be_nil
expect(BlockedEmail.create(email: email).last_match_at).to be_nil

Copilot uses AI. Check for mistakes.
subject { BlockedEmail.should_block?(email) }

it "returns false if a record with the email doesn't exist" do
subject.should be_false

Copilot AI Jan 30, 2026

Copy link

Choose a reason for hiding this comment

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

The deprecated RSpec syntax .should is used. Consider updating to the modern expect syntax: expect(subject).to be false

Copilot uses AI. Check for mistakes.
it "updates statistics" do
Timecop.freeze(Time.zone.now) do
expect { subject }.to change { blocked_email.reload.match_count }.by(1)
blocked_email.last_match_at.should be_within_one_second_of(Time.zone.now)

Copilot AI Jan 30, 2026

Copy link

Choose a reason for hiding this comment

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

The deprecated RSpec syntax .should is used. Consider updating to the modern expect syntax: expect(blocked_email.last_match_at).to be_within_one_second_of(Time.zone.now)

Copilot uses AI. Check for mistakes.
Comment on lines +13 to +19
record.errors[:email].should_not be_present
end

it "adds an error when email matches a blocked email" do
BlockedEmail.stubs(:should_block?).with(record.email).returns(true)
validate
record.errors[:email].should be_present

Copilot AI Jan 30, 2026

Copy link

Choose a reason for hiding this comment

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

The deprecated RSpec syntax .should_not is used. Consider updating to the modern expect syntax: expect(record.errors[:email]).not_to be_present

Suggested change
record.errors[:email].should_not be_present
end
it "adds an error when email matches a blocked email" do
BlockedEmail.stubs(:should_block?).with(record.email).returns(true)
validate
record.errors[:email].should be_present
expect(record.errors[:email]).not_to be_present
end
it "adds an error when email matches a blocked email" do
BlockedEmail.stubs(:should_block?).with(record.email).returns(true)
validate
expect(record.errors[:email]).to be_present

Copilot uses AI. Check for mistakes.
Comment on lines +13 to +19
record.errors[:email].should_not be_present
end

it "adds an error when email matches a blocked email" do
BlockedEmail.stubs(:should_block?).with(record.email).returns(true)
validate
record.errors[:email].should be_present

Copilot AI Jan 30, 2026

Copy link

Choose a reason for hiding this comment

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

The deprecated RSpec syntax .should is used. Consider updating to the modern expect syntax: expect(record.errors[:email]).to be_present

Suggested change
record.errors[:email].should_not be_present
end
it "adds an error when email matches a blocked email" do
BlockedEmail.stubs(:should_block?).with(record.email).returns(true)
validate
record.errors[:email].should be_present
expect(record.errors[:email]).not_to be_present
end
it "adds an error when email matches a blocked email" do
BlockedEmail.stubs(:should_block?).with(record.email).returns(true)
validate
expect(record.errors[:email]).to be_present

Copilot uses AI. Check for mistakes.
record.errors.add(attribute, I18n.t(:'user.email.not_allowed'))
end
end
if record.errors[attribute].blank? and BlockedEmail.should_block?(value)

Copilot AI Jan 30, 2026

Copy link

Choose a reason for hiding this comment

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

Ruby style prefers && over and for boolean operators in conditional expressions. Using and can lead to unexpected precedence issues.

Suggested change
if record.errors[attribute].blank? and BlockedEmail.should_block?(value)
if record.errors[attribute].blank? && BlockedEmail.should_block?(value)

Copilot uses AI. Check for mistakes.
Comment on lines +11 to +20
def self.should_block?(email)
record = BlockedEmail.where(email: email).first
if record
record.match_count += 1
record.last_match_at = Time.zone.now
record.save
end
record && record.action_type == actions[:block]
end

Copilot AI Jan 30, 2026

Copy link

Choose a reason for hiding this comment

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

The should_block? method performs a write operation (incrementing match_count and updating last_match_at) on every check. This creates a database write for every email validation, even during read-only operations like form validation. Consider separating the blocking check from the statistics update, or using a background job to update statistics asynchronously.

Suggested change
def self.should_block?(email)
record = BlockedEmail.where(email: email).first
if record
record.match_count += 1
record.last_match_at = Time.zone.now
record.save
end
record && record.action_type == actions[:block]
end
def self.find_by_email(email)
BlockedEmail.where(email: email).first
end
def self.should_block?(email)
record = find_by_email(email)
record && record.action_type == actions[:block]
end
def self.record_match!(email)
record = find_by_email(email)
return unless record
record.match_count += 1
record.last_match_at = Time.zone.now
record.save
end

Copilot uses AI. Check for mistakes.
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.

3 participants