Skip to content

implement captcha on auth pages#384

Merged
freedit-dev merged 1 commit intofreedit-org:mainfrom
YGGverse:auth-page-captcha
Dec 23, 2025
Merged

implement captcha on auth pages#384
freedit-dev merged 1 commit intofreedit-org:mainfrom
YGGverse:auth-page-captcha

Conversation

@oooo-ps
Copy link

@oooo-ps oooo-ps commented Dec 23, 2025

#381

Summary by CodeRabbit

  • New Features
    • Implemented CAPTCHA verification across authentication flows to strengthen account security
    • Users must complete CAPTCHA when signing in
    • CAPTCHA validation required during account registration
    • Password reset now includes CAPTCHA verification
    • Protects accounts against unauthorized access and automated attacks

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

…d sign in link to the password reset page; implement shared `CaptchaSession` struct; reorder some struct members ASC
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 23, 2025

Walkthrough

This pull request integrates CAPTCHA validation into the user authentication and account recovery flows. A new CaptchaSession helper struct generates and stores CAPTCHA data server-side, which is then embedded into page and form structures for signin, signup, and reset endpoints. CAPTCHA validation occurs before password verification in each flow.

Changes

Cohort / File(s) Summary
User Controller & CAPTCHA Integration
src/controller/user.rs
Added captcha\_id and captcha\_image fields to PageReset, PageSignin, and PageSignup structs. Updated FormReset, FormSignin, and FormSignup structs to include captcha\_id and captcha\_value fields. Introduced new CaptchaSession struct with create() factory method that generates captcha, persists it, and returns serialized image data. Modified signin, signup, and reset handlers to instantiate CaptchaSession on GET requests and validate captcha before proceeding with password/authentication checks. Added read\_only mode gating to signup flow.
Template Updates
templates/reset.html, templates/signin.html
Added Content-Security-Policy meta tags. Introduced CAPTCHA UI elements: image display (captcha\_image), text input for user\_captcha response, and hidden captcha\_id field. Retained existing form actions and submission methods.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant Handler
    participant CaptchaSession
    participant Storage
    participant AuthCheck

    User->>Handler: GET /signin (or /reset, /signup)
    Handler->>CaptchaSession: create(site_config)
    CaptchaSession->>Storage: store captcha string
    CaptchaSession-->>Handler: {id, image}
    Handler-->>User: PageSignin {captcha_id, captcha_image, ...}
    
    User->>Handler: POST form {captcha_id, captcha_value, ...}
    Handler->>Storage: retrieve captcha for captcha_id
    alt Captcha Valid
        Handler->>AuthCheck: proceed with password/auth checks
        AuthCheck-->>Handler: auth result
        Handler-->>User: success or auth error
    else Captcha Invalid
        Handler-->>User: captcha validation error
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The pull request title 'implement captcha on auth pages' accurately describes the main change: adding CAPTCHA integration to authentication-related pages (signin, reset, signup). The title is concise, clear, and reflects the primary objective of the changeset.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

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

🧹 Nitpick comments (1)
src/controller/user.rs (1)

739-745: Consider case-insensitive CAPTCHA validation.

The current validation (line 743) is case-sensitive, which may frustrate users who type the CAPTCHA in the wrong case. Consider normalizing both sides to lowercase for better UX:

-    if captcha_char != input.captcha_value {
+    if captcha_char.to_lowercase() != input.captcha_value.to_lowercase() {
         return Err(AppError::CaptchaError);
     }

Apply this same change to signin_post (line 932) and signup_post (line 1035).

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 936a20b and 8cbbcdd.

📒 Files selected for processing (3)
  • src/controller/user.rs
  • templates/reset.html
  • templates/signin.html
🧰 Additional context used
🧬 Code graph analysis (1)
src/controller/user.rs (3)
src/controller/admin.rs (1)
  • default (363-384)
src/controller/fmt.rs (1)
  • clean_html (68-83)
src/controller/db_utils.rs (2)
  • is_valid_name (374-389)
  • generate_nanoid_ttl (412-416)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Cargo test, clippy and doc (windows-latest, x86_64-pc-windows-gnu)
  • GitHub Check: Cargo test, clippy and doc (windows-latest, x86_64-pc-windows-msvc)
🔇 Additional comments (11)
templates/signin.html (2)

3-7: LGTM! CSP properly configured for CAPTCHA.

The Content Security Policy correctly allows data: URIs for images, which is necessary for displaying base64-encoded CAPTCHA images while maintaining a secure CSP.


31-41: LGTM! CAPTCHA UI elements properly integrated.

The CAPTCHA implementation includes all necessary elements: image display, user input field, and hidden ID field for validation.

src/controller/user.rs (6)

690-720: LGTM! CAPTCHA properly integrated into reset flow.

The reset page correctly generates and embeds CAPTCHA data before rendering, following the same pattern as other auth pages.


897-924: LGTM! Signin flow properly implements CAPTCHA.

The signin page correctly creates a CAPTCHA session and includes a read-only check to prevent signins when the site is in read-only mode.


928-934: LGTM! CAPTCHA validation prevents brute-force attacks.

Good use of take() to ensure one-time CAPTCHA usage, and validation occurs before the expensive password check. The same case-sensitivity improvement mentioned earlier applies here as well.


987-1005: LGTM! Signup flow properly implements CAPTCHA.

The signup page follows the same pattern as signin and reset, creating a CAPTCHA session before rendering.


1031-1037: LGTM! Signup validation properly implements CAPTCHA.

The signup flow correctly validates CAPTCHA before proceeding with account creation. The same case-insensitivity improvement mentioned earlier applies here as well.


26-29: Web search unavailable — unable to verify captcha crate best practices.

The original comment requests verification of the captcha library configuration against official best practices, but the web search tool is currently unavailable. While the code shows reasonable filter usage (Noise, Wave, Cow) with sensible parameters and a functional implementation, verification against the library's official recommendations cannot be completed.

templates/reset.html (3)

3-7: LGTM! CSP properly configured.

The Content Security Policy correctly allows data: URIs for CAPTCHA images while maintaining security.


65-83: LGTM! CAPTCHA UI elements properly integrated.

The CAPTCHA implementation follows the same pattern as the signin page, with proper form field structure and hidden ID field.


96-103: LGTM! Nice UX improvement.

Adding a sign-in link on the password reset page helps users who may have navigated there by mistake.

Comment on lines +1205 to +1239
/// Captcha session members
struct CaptchaSession {
id: String,
image: String,
}

impl CaptchaSession {
/// Generate new captcha
fn create(site_config: &SiteConfig) -> Result<Self, AppError> {
let captcha_difficulty = match site_config.captcha_difficulty.as_str() {
"Easy" => Difficulty::Easy,
"Medium" => Difficulty::Medium,
"Hard" => Difficulty::Hard,
_ => return Err(AppError::NotFound),
};

let captcha = match site_config.captcha_name.as_str() {
"Amelia" => captcha::by_name(captcha_difficulty, CaptchaName::Amelia),
"Lucy" => captcha::by_name(captcha_difficulty, CaptchaName::Lucy),
"Mila" => captcha::by_name(captcha_difficulty, CaptchaName::Mila),
"Digits" => captcha_digits(),
_ => return Err(AppError::NotFound),
};

let id = generate_nanoid_ttl(60);

DB.open_partition("captcha", Default::default())?
.insert(&id, &*captcha.chars_as_string())?;

Ok(Self {
id,
image: captcha.as_base64().unwrap(),
})
}
}
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 | 🟠 Major

Consider increasing the CAPTCHA TTL.

The 60-second TTL (line 1229) may be too short for users who need to read the form, solve the CAPTCHA, or switch between password managers. Consider increasing to 300 seconds (5 minutes) or making it configurable via SiteConfig.

🔎 Suggested improvement
-        let id = generate_nanoid_ttl(60);
+        let id = generate_nanoid_ttl(300);  // 5 minutes

Or better yet, add a captcha_ttl field to SiteConfig for configurability.

📝 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
/// Captcha session members
struct CaptchaSession {
id: String,
image: String,
}
impl CaptchaSession {
/// Generate new captcha
fn create(site_config: &SiteConfig) -> Result<Self, AppError> {
let captcha_difficulty = match site_config.captcha_difficulty.as_str() {
"Easy" => Difficulty::Easy,
"Medium" => Difficulty::Medium,
"Hard" => Difficulty::Hard,
_ => return Err(AppError::NotFound),
};
let captcha = match site_config.captcha_name.as_str() {
"Amelia" => captcha::by_name(captcha_difficulty, CaptchaName::Amelia),
"Lucy" => captcha::by_name(captcha_difficulty, CaptchaName::Lucy),
"Mila" => captcha::by_name(captcha_difficulty, CaptchaName::Mila),
"Digits" => captcha_digits(),
_ => return Err(AppError::NotFound),
};
let id = generate_nanoid_ttl(60);
DB.open_partition("captcha", Default::default())?
.insert(&id, &*captcha.chars_as_string())?;
Ok(Self {
id,
image: captcha.as_base64().unwrap(),
})
}
}
/// Captcha session members
struct CaptchaSession {
id: String,
image: String,
}
impl CaptchaSession {
/// Generate new captcha
fn create(site_config: &SiteConfig) -> Result<Self, AppError> {
let captcha_difficulty = match site_config.captcha_difficulty.as_str() {
"Easy" => Difficulty::Easy,
"Medium" => Difficulty::Medium,
"Hard" => Difficulty::Hard,
_ => return Err(AppError::NotFound),
};
let captcha = match site_config.captcha_name.as_str() {
"Amelia" => captcha::by_name(captcha_difficulty, CaptchaName::Amelia),
"Lucy" => captcha::by_name(captcha_difficulty, CaptchaName::Lucy),
"Mila" => captcha::by_name(captcha_difficulty, CaptchaName::Mila),
"Digits" => captcha_digits(),
_ => return Err(AppError::NotFound),
};
let id = generate_nanoid_ttl(300); // 5 minutes
DB.open_partition("captcha", Default::default())?
.insert(&id, &*captcha.chars_as_string())?;
Ok(Self {
id,
image: captcha.as_base64().unwrap(),
})
}
}

@oooo-ps
Copy link
Author

oooo-ps commented Dec 23, 2025

Just for info: I'm using this branch on my instance and can confirm it's working, at least on login step.
Not sure only about password reset, on localhost it was working. So everything looks resistant to brute force.

  • I have an additional idea to make this feature optional, or alternatively implement increasing failed login delays.

@freedit-dev
Copy link
Member

Maybe better to increase delay

sleep(Duration::from_secs(1)).await;

@freedit-dev freedit-dev merged commit 1a48ec9 into freedit-org:main Dec 23, 2025
7 checks passed
This was referenced Dec 28, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants