Sorting Office implements a secure, role-based authentication system with support for multiple admin users and different permission levels.
- Overview
- Roles and Permissions
- Configuration
- Password Management
- Session Management
- Security Considerations
- Error Handling
- Testing Authentication
- Troubleshooting
- Migration from Legacy System
- API Reference
- Related Documentation
The authentication system provides:
- Multiple Admin Support: Configure multiple admin users with different roles
- Role-Based Access Control: Two distinct permission levels (Read-Only and Edit)
- Secure Password Storage: bcrypt hashing for all passwords
- Session Management: HTTP-only cookies with expiration
- Backward Compatibility: Support for legacy single-admin configurations
Users with read-only permissions can:
- View all data (domains, users, aliases, backups, etc.)
- Access dashboard and statistics
- View configuration settings
- Access reports and system information
Routes accessible to read-only users:
GET /(dashboard)GET /domains(list)GET /domains/{id}(show)GET /users(list)GET /users/{id}(show)GET /aliases(list)GET /aliases/{id}(show)GET /backups/{id}(show)GET /statsGET /reportsGET /aboutGET /config
Users with edit permissions can perform all read-only operations plus:
- Create, update, and delete domains
- Create, update, and delete users
- Create, update, and delete aliases
- Create, update, and delete backups
- Toggle enabled/disabled status for all resources
- Access all edit forms and actions
Additional routes accessible to edit users:
POST /domains(create)PUT /domains/{id}(update)DELETE /domains/{id}(delete)GET /domains/new(create form)GET /domains/{id}/edit(edit form)POST /domains/{id}/toggle(toggle status)- Similar patterns for users, aliases, and backups
For backward compatibility, you can still use the old single-admin format:
[admin]
username = "admin"
password_hash = "$2b$12$EixZaYVK1fsbw1ZfbX3OXePaWxn96p36WQoeG6Lruj3vjPGga31lW"
role = "edit"For production use, configure multiple admins with different roles:
[[admins]]
username = "admin"
password_hash = "$2b$12$EixZaYVK1fsbw1ZfbX3OXePaWxn96p36WQoeG6Lruj3vjPGga31lW"
role = "edit"
[[admins]]
username = "viewer"
password_hash = "$2b$12$AnotherHashHere..."
role = "read-only"
[[admins]]
username = "operator"
password_hash = "$2b$12$YetAnotherHash..."
role = "edit"The authentication configuration is loaded from:
config/config.toml(if exists)- Environment variables (if config file not found)
- Default values (fallback)
You can also configure admins using environment variables:
# Single admin (legacy)
export ADMIN_USERNAME="admin"
export ADMIN_PASSWORD_HASH="$2b$12$EixZaYVK1fsbw1ZfbX3OXePaWxn96p36WQoeG6Lruj3vjPGga31lW"
export ADMIN_ROLE="edit"
# Multiple admins (JSON format)
export ADMINS='[
{
"username": "admin",
"password_hash": "$2b$12$EixZaYVK1fsbw1ZfbX3OXePaWxn96p36WQoeG6Lruj3vjPGga31lW",
"role": "edit"
},
{
"username": "viewer",
"password_hash": "$2b$12$AnotherHashHere...",
"role": "read-only"
}
]'To generate a bcrypt hash for a new password, you can use:
# Using Python
python3 -c "import bcrypt; print(bcrypt.hashpw('your_password'.encode('utf-8'), bcrypt.gensalt()).decode('utf-8'))"
# Using Node.js
node -e "const bcrypt = require('bcrypt'); bcrypt.hash('your_password', 12).then(hash => console.log(hash))"
# Using online bcrypt generators (for development only)
# https://bcrypt.online/Development Default:
- Username:
admin - Password:
admin123 - Hash:
$2b$12$EixZaYVK1fsbw1ZfbX3OXePaWxn96p36WQoeG6Lruj3vjPGga31lW
Authentication cookies are configured with:
- HttpOnly: Prevents XSS attacks
- SameSite=Lax: CSRF protection
- Path=/: Available across the entire site
- Expiration: 24 hours from login
- Format:
authenticated={expiry}:{role}
- Cookies are automatically cleared on logout
- Expired sessions redirect to login
- Invalid cookies are ignored
- Role information is embedded in the cookie
- Strong Passwords: Use strong, unique passwords for each admin
- Role Separation: Use read-only accounts for monitoring and edit accounts for administration
- Regular Rotation: Change passwords regularly
- HTTPS Only: Always use HTTPS in production
- Network Security: Restrict access to trusted networks
- bcrypt Hashing: Industry-standard password hashing with configurable cost
- Session Expiration: Automatic logout after 24 hours
- CSRF Protection: SameSite cookie attribute
- XSS Protection: HttpOnly cookies
- Input Validation: All inputs are validated and sanitized
- Invalid Credentials: Clear error message without revealing valid usernames
- Expired Sessions: Automatic redirect to login
- Insufficient Permissions: 403 Forbidden for unauthorized actions
- Unknown Routes: 403 for authenticated users, 404 for anonymous users
The system provides localized error messages for:
- Invalid username/password combinations
- Expired sessions
- Insufficient permissions
- System errors
The authentication system includes comprehensive tests:
# Run authentication tests
cargo test test_login_success
cargo test test_login_failure
cargo test test_role_based_access_control
cargo test test_is_authenticated_cookie
cargo test test_has_edit_permissions- Login Test: Try logging in with valid/invalid credentials
- Role Test: Verify read-only users can't access edit functions
- Session Test: Check that sessions expire correctly
- Logout Test: Verify cookies are cleared on logout
- Login Fails: Check password hash format and bcrypt cost
- Permission Denied: Verify user has correct role for the action
- Session Expires: Check system clock and cookie expiration
- Configuration Not Loaded: Verify config file syntax and location
Enable debug logging to troubleshoot authentication issues:
RUST_LOG=debug cargo runThis will show:
- Configuration loading details
- Authentication attempts
- Session management
- Permission checks
If you're upgrading from the old single-admin system:
- Backup Configuration: Save your current admin credentials
- Update Config: Convert to the new multiple-admin format
- Test Login: Verify all admins can log in with correct roles
- Update Documentation: Update any internal documentation
Old Format:
[admin]
username = "admin"
password_hash = "$2b$12$oldhash..."New Format:
[[admins]]
username = "admin"
password_hash = "$2b$12$oldhash..."
role = "edit"// Check if user is authenticated
pub fn is_authenticated(headers: &HeaderMap) -> bool
// Get user's role
pub fn get_user_role(headers: &HeaderMap) -> Option<AdminRole>
// Check if user has edit permissions
pub fn has_edit_permissions(headers: &HeaderMap) -> bool
// Verify admin credentials
pub fn verify_admin_credentials(&self, username: &str, password: &str) -> Option<AdminRole>// Require authentication for all routes
pub async fn require_auth(...) -> Result<Response, StatusCode>
// Require edit permissions for modifying routes
pub async fn require_edit_permissions(...) -> Result<Response, StatusCode>