Production-ready security improvements: 1. Password Security Enhancements: - Increased minimum password length from 4 to 8 characters - Added maximum length limit (100 chars) to prevent DoS - Migration now validates password exists and is not empty - Proper validation before creating admin user 2. Timing Attack Prevention: - Implemented constant-time comparison using crypto.timingSafeEqual - Added dummy hash computation for non-existent users - Prevents username enumeration via timing analysis 3. Comprehensive Input Validation: - Username: 3-50 chars, alphanumeric + . _ - only - Email: Format validation, 100 char limit - All validation centralized in user_management service - Proper error messages without leaking info 4. Code Quality Improvements: - Fixed parseInt() calls to use radix 10 and check NaN - Added try-catch for validation errors in API routes - Improved error handling throughout 5. Security Documentation: - Added comprehensive 'Security Considerations' section - Documented implemented protections - Listed recommended infrastructure-level protections - Documented known limitations (username enumeration, etc.) - Clear guidance on rate limiting, HTTPS, monitoring All changes maintain backward compatibility and pass TypeScript validation. Zero errors, production-ready security posture.
8.6 KiB
Multi-User Support for Trilium Notes
This document describes the multi-user functionality added to Trilium Notes.
Overview
Trilium now supports multiple users with role-based access control. Each user has their own credentials and can be assigned different roles (Admin, User, or Viewer).
Architecture
Database Schema
Multi-user support extends the existing user_data table (introduced in migration v229 for OAuth support).
Important Design Decisions:
-
Why
user_datatable? eliandoran asked about usinguser_infotable from MFA. We useuser_databecause it's the established table from OAuth migration (v229) with existing password hashing infrastructure. -
Why not Becca entities? Users are NOT implemented as Becca entities because:
- Becca entities are for synchronized content (notes, branches, attributes, etc.)
- User authentication data should never be synced across instances for security
- Each Trilium instance needs its own isolated user database
- Syncing user credentials would create massive security risks
-
Future sync support: When multi-user sync is implemented, it will need:
- Per-user sync credentials on each instance
- User-to-user mappings across instances
- Separate authentication from content synchronization
- This is documented as a future enhancement
user_data table fields:
tmpID: INTEGER primary keyusername: User's login nameemail: Optional email addressuserIDVerificationHash: Password hash (scrypt)salt: Password saltderivedKey: Key derivation saltuserIDEncryptedDataKey: Encrypted data key (currently unused)isSetup: 'true' or 'false' stringrole: 'admin', 'user', or 'viewer'isActive: 1 (active) or 0 (inactive)utcDateCreated: Creation timestamputcDateModified: Last modification timestamp
User Roles
- Admin: Full access to all notes and user management
- User: Can create, read, update, and delete their own notes
- Viewer: Read-only access to their notes
Migration (v234)
Migration Triggering: This migration runs automatically on next server start because the database version was updated to 234 in app_info.ts.
The migration automatically:
- Extends the
user_datatable with role and status fields - Adds
userIdcolumns to notes, branches, etapi_tokens, and recent_notes tables - Creates a default admin user from existing single-user credentials
- Associates all existing data with the admin user (tmpID=1)
- Maintains backward compatibility with single-user installations
Setup
For New Installations
On first login, set a password as usual. This creates the default admin user.
For Existing Installations
When you upgrade, the migration runs automatically:
- Your existing password becomes the admin user's password
- Username defaults to "admin"
- All your existing notes remain accessible
Creating Additional Users
After migration, you can create additional users via the REST API:
# Create a new user (requires admin privileges)
curl -X POST http://localhost:8080/api/users \
-H "Content-Type: application/json" \
-H "Cookie: connect.sid=YOUR_SESSION_COOKIE" \
-d '{
"username": "newuser",
"email": "user@example.com",
"password": "securepassword",
"role": "user"
}'
API Endpoints
All endpoints require authentication. Most require admin privileges.
List Users
GET /api/users
Query params: includeInactive=true (optional)
Requires: Admin
Get User
GET /api/users/:userId
Requires: Admin or own user
Create User
POST /api/users
Body: { username, email?, password, role? }
Requires: Admin
Update User
PUT /api/users/:userId
Body: { email?, password?, isActive?, role? }
Requires: Admin (or own user for email/password only)
Delete User
DELETE /api/users/:userId
Requires: Admin
Note: Soft delete (sets isActive=0)
Get Current User
GET /api/users/current
Requires: Authentication
Check Username Availability
GET /api/users/check-username?username=testuser
Requires: Authentication
Login
Single-User Mode
If only one user exists, login works as before (password-only).
Multi-User Mode
When multiple users exist:
- Username field appears on login page
- Enter username + password to authenticate
- Session stores user ID and role
Security
- Passwords are hashed using scrypt (N=16384, r=8, p=1)
- Each user has unique salt
- Sessions are maintained using express-session
- Users can only access their own notes (except admins)
Backward Compatibility
- Single-user installations continue to work without changes
- No username field shown if only one user exists
- Existing password continues to work after migration
- All existing notes remain accessible
Security Considerations
Implemented Protections
-
Password Security:
- scrypt hashing with N=16384, r=8, p=1 (matches Trilium's security)
- 32-byte random salt per user
- Minimum 8 character password requirement
- Maximum 100 character limit to prevent DoS
-
Timing Attack Prevention:
- Constant-time password comparison using
crypto.timingSafeEqual - Dummy hash computation for non-existent users to prevent user enumeration via timing
- Constant-time password comparison using
-
Input Validation:
- Username: 3-50 characters, alphanumeric +
._-only - Email: Format validation, 100 character limit
- All inputs sanitized before database operations
- Parameterized SQL queries (no SQL injection)
- Username: 3-50 characters, alphanumeric +
-
Authorization:
- Role-based access control (Admin/User/Viewer)
- Admin-only endpoints for user management
- Users can only modify their own data (except admins)
- Cannot delete last admin user
Recommended Additional Protections
Important: These should be implemented at the infrastructure level:
- Rate Limiting: Add rate limiting to
/loginand user API endpoints to prevent brute force attacks - HTTPS: Always use HTTPS in production to protect credentials in transit
- Reverse Proxy: Use nginx/Apache with request limiting and firewall rules
- Monitoring: Log failed login attempts and suspicious activity
- Password Policy: Consider enforcing complexity requirements (uppercase, numbers, symbols)
Known Limitations
-
Username Enumeration: The
/api/users/check-usernameendpoint reveals which usernames exist. Consider requiring authentication for this endpoint in production. -
No Account Lockout: Failed login attempts don't trigger account lockouts. Implement at reverse proxy level.
-
No Password Reset: Currently no password reset mechanism. Admins must manually update passwords via API.
Limitations
- No per-note sharing between users yet (planned for future)
- No user interface for user management (use API)
- Sync protocol not yet multi-user aware
- No user switching without logout
Future Enhancements
- UI for User Management: Add settings dialog for creating/managing users
- Note Sharing: Implement per-note sharing with other users
- Sync Support: Update sync protocol for multi-instance scenarios
- User Switching: Allow switching users without logout
- Groups: Add user groups for easier permission management
- Audit Log: Track user actions for security
Troubleshooting
Can't log in after migration
- Try username "admin" with your existing password
- Check server logs for migration errors
Want to reset admin password
- Stop Trilium
- Access document.db directly
- Update the user_data table manually
- Restart Trilium
Want to disable multi-user
Not currently supported. Once migrated, single-user mode won't work if additional users exist.
Technical Details
Files Modified
apps/server/src/migrations/0234__multi_user_support.ts- Migrationapps/server/src/services/user_management.ts- User management serviceapps/server/src/routes/api/users.ts- REST API endpointsapps/server/src/routes/login.ts- Multi-user login logicapps/server/src/services/auth.ts- Authentication middlewareapps/server/src/express.d.ts- Session type definitionsapps/server/src/assets/views/login.ejs- Login page UI
Testing
# Run tests
pnpm test
# Build
pnpm build
# Check TypeScript
pnpm --filter @triliumnext/server typecheck
Contributing
When extending multi-user support:
- Always test with both single-user and multi-user modes
- Maintain backward compatibility
- Update this documentation
- Add tests for new functionality
Support
For issues or questions:
- GitHub Issues: https://github.com/TriliumNext/Trilium/issues
- Discussions: https://github.com/orgs/TriliumNext/discussions