Password Reset System
This document describes the password reset and recovery system for Listsome. Since this is a single-user instance, the approach prioritizes simplicity while maintaining security.
Overview
The system provides two methods for password recovery:
- Direct CLI Reset - Immediate password change (requires SSH access)
- Token-Based Reset - Generate a one-time token for web-based reset
Architecture
Database Schema
CREATE TABLE password_reset_tokens (
id INTEGER PRIMARY KEY AUTOINCREMENT,
token_hash TEXT UNIQUE NOT NULL, -- Argon2 hash (never store plaintext)
account_id INTEGER NOT NULL,
created_at TIMESTAMP,
expires_at TIMESTAMP, -- 1 hour expiry
used_at TIMESTAMP, -- When consumed
ip_address TEXT, -- Optional audit
used BOOLEAN DEFAULT FALSE -- Single-use flag
);
Security Features:
- Tokens are hashed with Argon2id before storage
- 1-hour expiration window
- Single-use only (marked as
usedafter consumption) - Automatic cleanup of expired tokens (after 24 hours)
- Optional IP address logging for audit trail
Token Lifecycle
- Generation → Store hash, display plaintext once
- Validation → Compare hash, check expiry/usage
- Consumption → Mark as used, update timestamp
- Cleanup → Remove old tokens via trigger
CLI Tool Usage
Direct Password Reset
Use when you have SSH access to the server:
# Reset password immediately
$ listsome-cli reset-password
WARNING: This will reset the admin password immediately.
Type 'I am sure' to continue:
I am sure
Enter new password: ********
Confirm new password: ********
Password has been reset successfully.
Security: Requires exact confirmation phrase “I am sure”
Generate Reset Token
Use when you’ve forgotten the password but can access server logs:
# Generate a one-time token
$ listsome-cli generate-reset-token
This will generate a one-time password reset token.
Type 'generate' to create a token:
generate
========================================
RESET TOKEN (save this securely):
a1b2c3d4e5f6... (64 hex characters)
========================================
Use this token via:
1. Web interface: POST /reset-password with token
2. Or check server logs for the token
Token expires: 1 hour
Token is single-use
Token Format: 32 random bytes encoded as 64 hex characters
Cleanup Tokens
Remove expired and used tokens:
$ listsome-cli cleanup-tokens
Cleanup complete: removed 5 tokens
Setup (Initial Account Creation)
Create the first admin account if none exists:
$ listsome-cli setup
No admin account found. Let's create one.
Email: admin@example.com
Password: ********
Display Name: Admin User
Instance Domain (e.g., diy.example.com): diy.example.com
Setup complete! You can now log in.
Recovery Scenarios
Scenario 1: Forgot Password, Have SSH
Solution: Direct CLI reset
ssh user@server
listsome-cli reset-password
# Enter new password
Risk Level: Low - requires server access
Scenario 2: Forgot Password, No SSH
Solution: Generate token via logs
# On server (or in systemd logs)
journalctl -u listsome | grep "RESET TOKEN"
# Or check application logs
tail -f /var/log/listsome/app.log
Then use the token via web interface or cURL:
curl -X POST http://localhost:3000/reset-password \
-H "Content-Type: application/json" \
-d '{"token": "a1b2c3d4...", "new_password": "newpass123"}'
Risk Level: Medium - token exposure in logs
Scenario 3: Complete Lockout
Solution: Physical server access required
If you have:
- No SSH access
- No web access
- Forgot password
You must have physical access to the server to:
- Access console directly
- Mount the SQLite database
- Delete the account row to trigger re-setup
- Or use SQL to manually update password_hash
# Emergency: Delete account to re-trigger setup
sqlite3 data.db "DELETE FROM account WHERE id = 1;"
# Then run setup again
listsome-cli setup
Risk Level: High - requires physical security breach
Security Considerations
Threat Model
Assumed attackers:
- Someone with web access (can try tokens)
- Someone with file system read access (can see tokens in logs)
- Someone with database access (can see token hashes)
Mitigations:
- Tokens expire in 1 hour (reduces window of attack)
- Tokens are single-use (prevents replay)
- Tokens are hashed (attacker with DB access can’t use tokens)
- Direct reset requires confirmation phrase (prevents accidental execution)
Audit Trail
All CLI operations log to stderr:
[CLI] Starting password reset process
[CLI] Connecting to database: sqlite://data.db
[CLI] Password updated successfully for account_id=1
Recommendation: Redirect stderr to syslog:
listsome-cli reset-password 2> >(logger -t listsome-cli)
File Permissions
The CLI should be protected by file permissions:
# Make CLI only executable by owner
chmod 750 listsome-cli
chown root:admin listsome-cli
# Database should be readable only by application user
chmod 640 data.db
chown listsome:listsome data.db
Implementation Details
Token Generation
Uses cryptographically secure random bytes:
#![allow(unused)]
fn main() {
let token_bytes: Vec<u8> = (0..32).map(|_| rand::random::<u8>()).collect();
let token = hex::encode(&token_bytes); // 64 hex chars
}
Entropy: 256 bits (comparable to JWT signing keys)
Hash Algorithm
Uses Argon2id (same as password hashing):
- Memory-hard (resists GPU cracking)
- Salted (each token produces different hash)
- Tunable parameters
Database Cleanup
Automatic trigger runs on INSERT:
DELETE FROM password_reset_tokens
WHERE expires_at < datetime('now', '-24 hours')
OR (used = TRUE AND used_at < datetime('now', '-7 days'));
Future Enhancements
Possible improvements (not currently needed for single-user):
- Email Integration - Send tokens via email using
lettre - Rate Limiting - Limit token generation per IP
- Hardware Token - Require Yubikey for CLI operations
- Recovery Keys - Generate offline backup codes during setup
- Web UI - Add /forgot-password page for self-service
Testing
Run tests for the password reset system:
# Run all auth tests
cargo test auth
# Run specific token tests
cargo test test_create_reset_token
cargo test test_validate_and_consume_token
Troubleshooting
Token Not Working
Checklist:
- Token hasn’t expired (1 hour limit)
- Token hasn’t been used already
- Token is complete (64 characters)
- Database is accessible
CLI Can’t Connect to Database
# Check DATABASE_URL environment variable
echo $DATABASE_URL
# Default is sqlite://data.db
# Override: export DATABASE_URL="sqlite:///path/to/data.db"
Database Locked
If SQLite is locked by the running web server:
# Stop the web server temporarily
systemctl stop listsome
listsome-cli reset-password
systemctl start listsome