Status: Accepted Date: 2025-10-08 Authors: Sebastian Mendel
The LDAP self-service password changer application originally only supported authenticated password changes, requiring users to know their current password. When users forget their passwords, they must contact administrators for manual reset, creating support burden and user friction.
A self-service password reset feature reduces administrative overhead and improves user experience by allowing users to reset forgotten passwords independently through email verification.
- Users can request password reset via email address
- Secure token-based authentication for reset links
- Time-limited tokens to minimize attack surface
- Protection against user enumeration attacks
- Rate limiting to prevent abuse
- SMTP integration for email delivery
- LDAP integration to verify users and update passwords
- Configurable security parameters
Implement token-based password reset with email verification following these architectural principles:
Token Generation: Cryptographically secure random tokens
- 32 bytes (256 bits) from
crypto/rand - Base64 URL-safe encoding without padding
- No collision risk with sufficient entropy
Token Storage: In-memory thread-safe store
sync.RWMutexfor concurrent access protection- Metadata: token, username, email, timestamps, used flag
- Automatic cleanup of expired tokens via background goroutine
Token Lifecycle:
- 15-minute expiration (configurable via
RESET_TOKEN_EXPIRY_MINUTES) - Single-use enforcement with
Usedflag - Automatic periodic cleanup to prevent memory growth
Generic Response Pattern: All requests return identical success message
- "If an account exists, a reset email has been sent"
- No indication whether email exists in system
- Internal logging only for debugging
Silent Failure Handling:
- User not found → generic success
- LDAP errors → generic success
- Email delivery failures → generic success
- Rate limiting → generic success
Sliding Window Implementation:
- Configurable limits (default: 3 requests per 60 minutes)
- Per-email/username tracking with
sync.RWMutex - Automatic cleanup of expired entries
- Prevents brute force and enumeration attempts
Configuration Parameters:
RESET_RATE_LIMIT_REQUESTS: Max requests in windowRESET_RATE_LIMIT_WINDOW_MINUTES: Time window duration
SMTP Integration:
- Standard Go
net/smtplibrary - STARTTLS support for secure communication
- Plain auth with configurable credentials
- RFC 5322 compliant message formatting
Email Content:
- Clear reset link with embedded token
- 15-minute expiration notice
- Security reminder about ignoring unsolicited emails
- Plain text format for maximum compatibility
Configuration Parameters:
SMTP_HOST,SMTP_PORT: Server connection detailsSMTP_USERNAME,SMTP_PASSWORD: Authentication credentialsSMTP_FROM_ADDRESS: Sender email addressAPP_BASE_URL: Base URL for reset links
User Lookup:
- Find user by email address (
FindUserByMail) - Retrieve SAMAccountName for token association
- Validates user exists before token generation
Password Update:
- Uses readonly user credentials by default
- Optional dedicated reset service account (
LDAP_RESET_USER) for separation of concerns - Validates token before LDAP modification
- Enforces same password complexity rules as regular change
Package Structure:
internal/resettoken/ # Token generation and storage
internal/email/ # SMTP service for sending emails
internal/ratelimit/ # Sliding window rate limiter
internal/rpchandler/ # RPC handlers (request, reset)
internal/options/ # Configuration parsing
Interfaces for Testing:
EmailService: Email delivery abstractionRateLimiter: Rate limiting abstractionTokenStore: Token storage abstraction
User Flow:
- User visits
/forgot-password - Enters email address
- System sends email (or silently fails)
- User clicks link in email (
/reset-password?token=XXX) - User enters new password (validates complexity)
- System validates token and updates LDAP password
Templates:
forgot-password.html: Email input formreset-password.html: New password form- Atomic design pattern with reusable components
-
Reduced Administrative Burden: Users can reset passwords independently without admin intervention
-
Improved Security Posture:
- No credentials transmitted over insecure channels
- Time-limited tokens minimize attack surface
- Rate limiting prevents brute force attempts
- User enumeration protection prevents reconnaissance
-
Better User Experience:
- Self-service reduces wait time for password resets
- Email-based verification familiar to users
- Clear communication about security
-
Operational Flexibility:
- Feature toggle via
PASSWORD_RESET_ENABLEDflag - Configurable security parameters adapt to threat model
- Optional dedicated service account for security separation
- Feature toggle via
-
Code Quality:
- Clean package boundaries with clear responsibilities
- Testable design with interface abstractions
- Thread-safe concurrent operation
- Comprehensive test coverage
-
In-Memory Storage: Tokens stored in memory, not persistent database
- Trade-off: Simplicity vs persistence across restarts
- Acceptable: 15-minute lifetime, low volume
- Limitation: Tokens lost on server restart (users request new ones)
-
SMTP Dependency: Requires external SMTP server configuration
- Necessary: Email delivery fundamental to design
- Flexible: Supports any SMTP provider
-
Email Delivery Reliability: Silent failures if SMTP misconfigured
- Mitigation: Comprehensive logging for debugging
- Monitoring: Check logs for
password_reset_email_failedevents
-
Memory Footprint: Token storage consumes memory proportional to active reset requests
- Mitigation: Automatic cleanup of expired tokens
- Scale: 1000 concurrent tokens ≈ 100KB (negligible)
-
Single Point of Failure: Application restart clears all pending tokens
- Impact: Users must request new reset links
- Acceptable: 15-minute window minimizes disruption
-
No Admin Approval: Phase 1 implementation provides immediate reset
- Future:
RequiresApprovalflag designed for Phase 2 - Security: Rate limiting and email verification provide baseline protection
- Future:
Rejected:
- Weak security (answers often guessable or publicly available)
- Poor UX (users forget answers)
- Storage complexity (encrypted answers in database)
Rejected:
- Requires phone number collection and storage
- SMS delivery costs and reliability issues
- Regulatory compliance complexity (GDPR, telecoms)
- Overkill for internal LDAP system
Rejected:
- Original problem: high administrative burden
- Doesn't scale with organization size
- Creates bottleneck and user friction
Rejected:
- Adds database dependency and complexity
- Unnecessary for 15-minute token lifecycle
- In-memory sufficient for expected load
- Can revisit if scale requirements change
Rejected:
- Requires session management complexity
- Doesn't match LDAP authentication model
- Users expect password-based auth for enterprise systems
Phase 1 (Current):
- Token-based reset with email verification
- In-memory token storage
- Rate limiting and enumeration protection
- SMTP email delivery
- Configurable security parameters
Phase 2 (Future):
- Admin approval workflow (designed but not implemented)
- Enhanced monitoring and alerting
- Audit trail for compliance
- Multi-language email templates
SMTP_FROM_ADDRESS: Sender email addressAPP_BASE_URL: Application base URL for links
RESET_TOKEN_EXPIRY_MINUTES: Token validity (default: 15)RESET_RATE_LIMIT_REQUESTS: Max requests per window (default: 3)RESET_RATE_LIMIT_WINDOW_MINUTES: Rate limit window (default: 60)
SMTP_HOST: SMTP server hostname (default: smtp.gmail.com)SMTP_PORT: SMTP server port (default: 587)SMTP_USERNAME: SMTP authentication username (optional)SMTP_PASSWORD: SMTP authentication password (optional)
LDAP_RESET_USER: Dedicated service account for resetsLDAP_RESET_PASSWORD: Password for dedicated account- Falls back to
LDAP_READONLY_USERif not specified