Skip to content

Add password recovery feature#788

Merged
seapagan merged 16 commits intomainfrom
feature/password-recovery
Dec 28, 2025
Merged

Add password recovery feature#788
seapagan merged 16 commits intomainfrom
feature/password-recovery

Conversation

@seapagan
Copy link
Owner

@seapagan seapagan commented Dec 26, 2025

This pull request introduces a secure, token-based password recovery system to the authentication module. It enables users to request a password reset via email, supports both API and HTML form flows, and allows integration with custom frontends via a configurable URL. The update also protects against email enumeration attacks and includes robust validation for token handling.

Password Recovery System:

  • Added endpoints and logic for password reset requests (/forgot-password/) and password resets (/reset-password/), supporting both JSON API and HTML form submissions. Includes secure token generation, validation, and error handling.
  • Introduced a new email template (password_reset.html) for sending password reset links to users.
  • Added configuration option FRONTEND_URL to allow redirecting password reset flows to a custom frontend, with fallback to built-in forms if unset.

Security & Validation Enhancements:

  • Implemented helper function is_valid_jwt_format for fast syntactic JWT validation before decoding, improving error handling and security.
  • The password reset flow is protected against email enumeration and ensures only non-banned users can reset their passwords.

Documentation & Cleanup:

  • Updated the README.md to document the new password recovery system and its security features.
  • Removed the completed password recovery task from TODO.md.

…orresponding tests

Signed-off-by: Grant Ramsay <seapagan@gmail.com>
Signed-off-by: Grant Ramsay <seapagan@gmail.com>
@seapagan seapagan self-assigned this Dec 26, 2025
@seapagan seapagan added the enhancement New feature or request label Dec 26, 2025
@seapagan seapagan changed the title Feature/password recovery Add password recovery feature Dec 26, 2025
@codacy-production
Copy link

codacy-production bot commented Dec 26, 2025

Coverage summary from Codacy

See diff coverage on Codacy

Coverage variation Diff coverage
+0.00% (target: -1.00%) 100.00%
Coverage variation details
Coverable lines Covered lines Coverage
Common ancestor commit (cb3a967) 1547 1547 100.00%
Head commit (b05f8f2) 1670 (+123) 1670 (+123) 100.00% (+0.00%)

Coverage variation is the difference between the coverage for the head and common ancestor commits of the pull request branch: <coverage of head commit> - <coverage of common ancestor commit>

Diff coverage details
Coverable lines Covered lines Diff coverage
Pull request (#788) 129 129 100.00%

Diff coverage is the percentage of lines that are covered by tests out of the coverable lines that the pull request added or modified: <covered lines added or modified>/<coverable lines added or modified> * 100%

See your quality gate settings    Change summary preferences

Signed-off-by: Grant Ramsay <seapagan@gmail.com>
Signed-off-by: Grant Ramsay <seapagan@gmail.com>
…cations

Signed-off-by: Grant Ramsay <seapagan@gmail.com>
Signed-off-by: Grant Ramsay <seapagan@gmail.com>
Implements industry-standard password reset UX while maintaining full
API compatibility:

- Add GET /reset-password/ endpoint to validate token and display HTML
form
- Enhance POST /reset-password/ to handle both JSON (API) and form data
(HTML)
- Create password_reset_form.html template with embedded token
validation
- Create password_reset_success.html template for completion
confirmation
- Fix GET/POST routing conflict by manually parsing request based on
Content-Type
- Add proper type annotations for mypy strict mode compliance

The existing POST endpoint remains fully functional for frontend SPAs
and mobile
apps using JSON, while email links now work correctly for users without
a frontend.
Add comprehensive documentation for both GET and POST /reset-password/
endpoints:

- Document GET endpoint for HTML form display with token validation
- Update POST endpoint to clarify it accepts both JSON and form data
- Add details about HTML vs JSON responses
- Include error messages and usage notes for both endpoints
Introduces flexible password reset flow that works both standalone and
with
custom frontends:

- Add FRONTEND_URL setting to configuration (optional, defaults to None)
- Update GET /reset-password/ to redirect to frontend when FRONTEND_URL
is set
- Fallback to built-in HTML form when FRONTEND_URL is not configured
- Add comprehensive documentation for configuration and usage
- Update .env.example with FRONTEND_URL setting

Benefits:
- Standalone: Works out-of-the-box without configuration
- Flexible: Seamlessly integrates with React/Vue/Angular frontends
- No breaking changes: Existing functionality fully preserved
- Solves design clash issue between backend forms and custom frontends
URL-encode the reset token before embedding it in the redirect URL to
prevent
potential URL manipulation attacks.

Security Issue:
- The code parameter from user input was directly interpolated into
redirect URLs
- Special characters (&, ?, #, newlines, etc.) could alter URL structure
- Flagged by GitHub CodeQL security scanner

Fix:
- Import urllib.parse.quote for URL encoding
- Encode code parameter before embedding in redirect URL
- Ensures special characters are safely percent-encoded
- No functional changes, only security hardening

All tests pass, no breaking changes.
@seapagan seapagan force-pushed the feature/password-recovery branch from 592565a to 410f85f Compare December 27, 2025 22:44
@seapagan seapagan force-pushed the feature/password-recovery branch from 08991ce to 8e81a8f Compare December 27, 2025 22:53
Significantly improves test coverage for password reset functionality:

Integration Tests (9 new tests):
- GET /reset-password/ with valid, invalid, expired tokens
- GET /reset-password/ with FRONTEND_URL redirect
- POST /reset-password/ with form data (success, validation errors)
- POST /reset-password/ form data with invalid tokens
- Test coverage improved from 59% to 92% for auth.py

Unit Tests (5 new tests):
- URL encoding with special characters
- URL encoding with normal JWT tokens
- URL injection prevention
- Frontend URL construction validation
- Malicious token encoding safety

Coverage Improvement:
- app/resources/auth.py: 59% → 92% (37 missed → 7 missed)
- Comprehensive testing of all GET and POST form handling paths
- FRONTEND_URL redirect behavior fully tested

All tests pass with clean linting and type checking.
@seapagan seapagan force-pushed the feature/password-recovery branch 2 times, most recently from d337283 to 90ec5ce Compare December 27, 2025 23:07
Implements additional security layer for FRONTEND_URL redirects by
validating token format and size before redirecting.

Security Improvements:
- Validate token is not empty before redirect
- Reject tokens exceeding 1024 characters (JWT tokens typically 100-500
chars)
- Fall back to server-side form display if validation fails
- Prevents potential resource exhaustion from huge tokens
- Defense against malformed input in redirect flows

Changes:
- Add max_token_length validation check before redirect in GET endpoint
- Empty or oversized tokens skip redirect and show error in built-in
form
- Add 2 new integration tests for validation behavior

All tests pass (29 total), coverage remains at 95% for auth.py.

Addresses GitHub CodeQL recommendation for additional input validation
in URL redirect flows.
@seapagan seapagan force-pushed the feature/password-recovery branch from 90ec5ce to 02afa2a Compare December 27, 2025 23:09
Comprehensive documentation updates and TODO cleanup:

API Reference (docs/reference/api.md):
- Add "Token Validation" section documenting 1024 character limit
- Add "Security Features" section covering:
  - URL encoding for injection prevention
  - Length validation for resource exhaustion protection
  - Redirect safety with FRONTEND_URL
  - JWT verification and expiration details

README.md:
- Expand password recovery feature description
- Document built-in HTML forms for standalone use
- Mention FRONTEND_URL configuration for custom frontends
- Note dual support for JSON API and form-based submissions
- Highlight security features (URL encoding, token validation)

TODO.md:
- Remove completed password recovery endpoint task

These updates reflect the completed password recovery implementation
with security features, comprehensive testing, and documentation.
Signed-off-by: Grant Ramsay <seapagan@gmail.com>
This commit introduces a reusable helper function to validate JWT token
format before processing or redirecting with tokens. This provides an
additional validation layer that complements cryptographic verification.

Changes:
- Create app/managers/helpers.py with is_valid_jwt_format() function
  - Validates 3-part dot-separated structure
  - Checks base64url character set (A-Z a-z 0-9 - _)
  - Fast syntactic check without expensive cryptographic operations
- Update auth.py reset_password_form() to use helper before redirect
  - Prevents malformed tokens from being included in redirect URLs
  - Complements existing length validation (1024 char max)
- Add comprehensive unit tests for is_valid_jwt_format()
  - 8 test methods covering valid/invalid formats
  - Tests URL injection attempts and edge cases
  - Validates real-world JWT examples from jwt.io
- Add integration test for malformed JWT rejection in redirect flow
  - Verifies form display instead of redirect for invalid formats

Security benefits:
- Defense-in-depth: Multiple validation layers before token processing
- Better error messages for obviously malformed tokens
- Performance: Early rejection of invalid formats
- Attack surface reduction: Prevents malformed input in redirect URLs

All tests pass (33 integration + 8 unit tests), mypy and ruff checks
pass, coverage on auth.py at 95%, helpers.py at 100%.
@seapagan seapagan marked this pull request as ready for review December 28, 2025 10:02
Signed-off-by: Grant Ramsay <seapagan@gmail.com>
@seapagan seapagan merged commit 9fc77b4 into main Dec 28, 2025
17 checks passed
@seapagan seapagan deleted the feature/password-recovery branch December 28, 2025 10:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant