When an admin manually unlocks an account, both the account-level
lockout and all IP-based rate limits for the login endpoint are now
cleared. This ensures legitimate users can immediately attempt to
login after being unlocked, without being blocked by stale rate
limit cache entries.
Changes:
- Added clearAllRateLimitsForEndpoint() function to database utils
- Modified unlock endpoint to clear login rate limits after unlocking
- Updated success message to reflect rate limit clearing
- Enhanced logging to track rate limit clearing operations
Fixes issue where users would see "Too many login attempts" message
even with correct credentials after admin unlock, due to persistent
IP rate limit cache from previous failed attempts.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Fixed inconsistency between IP-based rate limiting and per-account lockout.
Previously, users would hit the IP rate limit at 5 attempts (15 min lockout)
but the account wouldn't be marked as locked until 10 attempts (30 min).
This caused confusion in the admin UI where locked accounts wouldn't show
the unlock button until 10 attempts were reached.
Changes:
- Reduced account lockout threshold from 10 to 5 failed attempts
- Reduced account lockout duration from 30 to 15 minutes
- Updated error message to reflect 15 minute lockout period
- Added detailed logging when account gets locked
- Updated README documentation to reflect correct limits
Both protection layers now work in harmony:
- IP-based rate limiting: 5 attempts = 15 min lockout
- Per-account lockout: 5 attempts = 15 min lock
This ensures the admin UI accurately shows account lock status and provides
the unlock option as soon as users hit the lockout threshold.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Critical fix for password reset flow. The frontend form was still configured
for 6-digit numeric codes while the backend was updated to use 8-character
alphanumeric codes.
Changes:
- Updated maxlength from 6 to 8 characters
- Changed pattern from [0-9]{6} to [0-9A-Za-z]{8}
- Updated placeholder from "000000" to "ABC123XY"
- Changed label from "6-Digit Code" to "Reset Code"
- Updated help text to say "8-character code"
- Added uppercase styling for better UX
- Changed button validation from code.length !== 6 to !== 8
This ensures users can enter the full reset code sent via email.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Simplified email configuration to always use process.env directly instead of
Nuxt runtime config. This ensures Docker environment variables are properly
read at runtime rather than being baked in at build time.
Changes:
- Removed Nuxt runtime config dependency from getEmailConfig()
- Always read EMAIL_* environment variables directly from process.env
- Added comprehensive debug logging to diagnose configuration issues
- Updated nuxt.config.ts with better documentation of runtime config behavior
This ensures environment variables set in docker-compose.yml are properly
used by the application at runtime.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Fixed an issue where SMTP configuration would fall back to defaults despite
environment variables being set in docker-compose.yml. The email utility now
properly accesses runtime configuration by accepting the H3 event context.
Changes:
- Created getEmailConfig() helper with dual-strategy config access
- Pass event context from API handlers to email functions
- Added fallback to direct process.env access for reliability
- Added debug logging to diagnose configuration issues in production
This ensures Office365 and other SMTP providers work correctly when configured
via environment variables.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Changes:
- Add items-start to status column flex container
- Ensures status badges (Active, Locked, Failed attempts) align to the left
- Consistent with Role badge alignment above
- Improves visual consistency in the table layout
Before: Status badges were centered/stretched in the column
After: Status badges are left-aligned like other badges
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Changes:
- docker-compose.yml: Change from external volume to managed volume
- Volume name: nlcc-data → data
- Docker Compose auto-prefixes: nlcc-itinerary_data
- Remove external: true flag
- Volume created automatically on first up
- README.md: Update all volume references
- Installation: Remove manual volume creation step
- Volume name: nlcc-data → nlcc-itinerary_data
- Add note about automatic creation
- Update all backup/restore/inspect commands
Benefits:
- No manual volume creation required
- Docker Compose manages lifecycle automatically
- Project-scoped volume naming (nlcc-itinerary_data)
- Cleaner deployment process (one less step)
- Volume automatically created on docker-compose up
- Consistent with Docker Compose best practices
The volume will be created as "nlcc-itinerary_data" where:
- nlcc-itinerary = project directory name
- data = volume name defined in docker-compose.yml
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Changes:
- Replace bind mount (./data) with external named volume (nlcc-data)
- Volume must be created before first run: docker volume create nlcc-data
- Improves portability and follows Docker best practices
- Better separation between code and data
Benefits:
- Data persists across container rebuilds and updates
- Easier backup and restore operations
- Platform-agnostic (works same on Linux/Windows/macOS)
- Managed by Docker's volume system
- No permission issues with bind mounts
README Updates:
- Added volume creation step to installation instructions
- Documented volume management commands (create, inspect, backup, restore)
- Added backup/restore examples using alpine container
- Clarified data persistence behavior
Note: Existing deployments using ./data bind mount will need to:
1. Backup existing data: cp -r ./data ./data-backup
2. Create volume: docker volume create nlcc-data
3. Restart container: docker-compose up -d
4. Copy data to volume if needed
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Changed image reference from local tag to GitLab container registry at
glcr.rydertech.us/ryder/nlcc-itinerary:latest
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Removed build configuration from docker-compose.yml. The image should be built
separately using 'docker build -t nlcc-itinerary:latest .' and the compose file
now just references the pre-built image with runtime configuration.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Changed behavior so that editing a sermon no longer redirects to homepage.
Instead, it shows a success message and keeps the form filled for further edits.
Creating a new sermon still redirects to homepage as before.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Implemented a configurable retention policy system for sermons with automatic cleanup:
- Added settings table to store retention policy configuration
- Created API endpoints for getting/setting retention policy
- Added Database Settings section to admin page with retention options (forever, 1-10 years)
- Implemented manual cleanup endpoint for on-demand deletion
- Added automated daily cleanup task via Nitro plugin
- Sermons are deleted based on their date field according to the retention policy
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>