Implementation Status (v1.1.1): ✅ Completed (Core Scope) Self-service user registration, user switching UI, and per-user vault isolation are now implemented in the app. Remaining items in this document should be treated as optional future enhancements.
Re-enable secure multi-user support in Keyper while maintaining the "Enhanced Security Mode" (no admin backdoors) architecture.
- ✅ Zero-knowledge architecture - Each user's vault is completely isolated
- ✅ No admin backdoors - No one can access another user's passphrase or vault
- ✅ Self-service passphrase reset - Users control their own emergency reset via bcrypt hash
- ✅ Database-level isolation - RLS policies already support multi-user (user_id field)
- ✅ Independent encryption - Each user has their own DEK and bcrypt hash
- ✅ Database schema supports multi-user (
user_idfield in all tables) - ✅ RLS policies allow multi-user access (policies use
truefor self-hosted mode) - ✅ Vault system supports per-user encryption (bcrypt + raw_dek per user_id)
- ✅ Emergency passphrase reset system exists (bcrypt hash in vault_config)
- ✅ Username switching works (Settings → Username field)
- ✅ User registration UI/flow (restored as self-service)
- ✅ User management interface (restored in Dashboard Settings)
- ✅ Admin controls remain intentionally removed (security-by-design)
- ✅ Self-service user registration flow
- ✅ User list/switching interface (non-admin)
- ✅ Optional: Invite code system for controlled access
Goal: Ensure database supports multi-user registration
- Verify
vault_configtable hasuser_idwith UNIQUE constraint - Verify
credentialstable hasuser_idfield - Verify
categoriestable hasuser_idfield - Confirm RLS policies allow multi-user access
Status: ✅ Schema already supports multi-user! No changes needed.
-- Optional table for invite-based registration
CREATE TABLE IF NOT EXISTS invite_codes (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
code TEXT NOT NULL UNIQUE,
created_by TEXT, -- Username who created it (optional)
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
expires_at TIMESTAMP WITH TIME ZONE,
max_uses INTEGER DEFAULT 1,
current_uses INTEGER DEFAULT 0,
is_active BOOLEAN DEFAULT true
);
CREATE INDEX idx_invite_codes_code ON invite_codes(code);
CREATE INDEX idx_invite_codes_active ON invite_codes(is_active) WHERE is_active = true;Decision: Skip for now, implement in Phase 3 if needed.
Goal: Create self-service registration without admin involvement
File: src/components/UserRegistration.tsx
Features:
- Username input (must be unique)
- Master passphrase creation (with strength indicator)
- Passphrase confirmation field
- Terms/security notice
- Check if username already exists in
vault_configtable
Validation:
- Username: 3-50 characters, alphanumeric + underscore/hyphen
- Passphrase: Minimum 8 characters (use existing PassphraseValidator)
- Check username uniqueness against
vault_config.user_id
File: src/components/PassphraseGate.tsx
Changes:
- Add "Create New User" button/link on login screen
- Show registration form when clicked
- After successful registration, automatically log in the new user
Flow:
┌─────────────────────────┐
│ PassphraseGate │
│ │
│ [Username Input] │
│ [Passphrase Input] │
│ │
│ [Unlock Vault] │
│ [Create New User] ←────┼─── NEW
└─────────────────────────┘
↓
┌─────────────────────────┐
│ UserRegistration │
│ │
│ [New Username] │
│ [New Passphrase] │
│ [Confirm Passphrase] │
│ │
│ [Register] [Cancel] │
└─────────────────────────┘
File: src/services/VaultManager.ts
New Method: registerNewUser(username: string, passphrase: string)
async registerNewUser(username: string, passphrase: string): Promise<void> {
// 1. Check if username already exists
const { data: existing } = await supabase
.from('vault_config')
.select('user_id')
.eq('user_id', username)
.single();
if (existing) {
throw new Error('Username already exists. Please choose a different username.');
}
// 2. Set the username in localStorage
saveCurrentUsername(username);
// 3. Create new vault for this user (uses existing createVault method)
await this.createVault(passphrase);
// 4. Create default categories for new user
await this.createDefaultCategories(username);
}
private async createDefaultCategories(username: string): Promise<void> {
const defaultCategories = [
{ name: 'Development', color: '#3b82f6', icon: 'code' },
{ name: 'Personal', color: '#10b981', icon: 'user' },
{ name: 'Work', color: '#f59e0b', icon: 'briefcase' },
// ... etc
];
for (const cat of defaultCategories) {
await supabase.from('categories').insert({
user_id: username,
...cat
});
}
}Goal: Allow users to see and switch between accounts (non-admin)
File: src/components/UserSwitcher.tsx
Features:
- List all users (query distinct
user_idfromvault_config) - Show current user with indicator
- Switch user button (locks current vault, changes username, shows login)
- "Add New User" button
UI Location:
- Option A: In Settings → User Management tab
- Option B: Dropdown in top navigation bar
- Option C: Both
Security Note: This only lists usernames, no access to vaults without passphrase.
File: src/components/Settings.tsx
New Tab: "User Management"
Features:
- Current user display
- List of all registered users
- Switch user functionality
- Create new user button
- Delete current user (with confirmation + passphrase verification)
Goal: Clearly communicate the security model to users
File: src/components/Settings.tsx
Add Section: "Multi-User Security Model"
### Multi-User Security Model
✅ **Independent Vaults**: Each user has their own encrypted vault
✅ **Zero-Knowledge**: No user can access another user's data
✅ **No Admin Backdoors**: Even system admins cannot recover passphrases
✅ **Self-Service Reset**: Users control their own emergency passphrase reset
⚠️ **Passphrase Responsibility**: Losing your passphrase means losing access to your vault
**How It Works**:
1. Each username gets its own vault_config entry with unique encryption keys
2. Your master passphrase never leaves your device
3. All credentials are encrypted client-side before storage
4. Switching users requires entering that user's passphraseFile: README.md
Update Section: "Multi-User Support"
- Explain self-service registration
- Clarify no admin access to vaults
- Document user switching process
- Link to emergency passphrase reset guide
- Admin can generate invite codes
- Registration requires valid invite code
- Codes can have expiration and usage limits
- Useful for controlled access scenarios
CREATE TABLE user_profiles (
user_id TEXT PRIMARY KEY,
display_name TEXT,
email TEXT, -- Optional, for notifications
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
last_login TIMESTAMP WITH TIME ZONE
);- Track user creation events
- Track login attempts (without exposing passphrases)
- Track vault operations for security monitoring
- Create
src/components/UserRegistration.tsx - Add username validation
- Add passphrase strength indicator
- Add username uniqueness check
- Add registration form UI
- Add
registerNewUser()method - Add
createDefaultCategories()helper - Add username existence check
- Test registration flow
- Add "Create New User" button
- Integrate UserRegistration component
- Handle registration success/failure
- Auto-login after registration
- Create
src/components/UserSwitcher.tsx - Query all users from vault_config
- Add user switching logic
- Add current user indicator
- Add "User Management" tab
- Integrate UserSwitcher
- Add user deletion functionality (not implemented by design in current scope)
- Update security documentation
- Test new user registration
- Test user switching
- Test vault isolation (User A cannot see User B's data)
- Test passphrase reset for multiple users
- Test SQLite multi-user support
- Test Supabase multi-user support
- Update README.md with multi-user instructions
- Update CHANGELOG.md
- Create user guide for multi-user setup
- Update security documentation
- New user can register with unique username
- Registration fails with duplicate username
- New user gets default categories
- User can switch between accounts
- Each user's vault is isolated
- Passphrase reset works per-user
- User A cannot access User B's credentials
- User A cannot see User B's categories
- Switching users locks previous vault
- No admin backdoor exists
- Emergency reset only affects current user
- Registration with special characters in username
- Very long usernames (50+ chars)
- Concurrent user creation
- User deletion with active credentials
- SQLite vs Supabase behavior differences
- ✅ Users can self-register without admin
- ✅ Users can switch between accounts
- ✅ Each user's vault is completely isolated
- ✅ No admin backdoors exist
- ✅ Emergency passphrase reset works per-user
- ⭐ Invite code system for controlled access
- ⭐ User profile metadata (display name, email)
- ⭐ Audit logging for security monitoring
- ⭐ User activity dashboard
- ❌ Admin user management (no privileged accounts)
- ❌ Admin passphrase recovery (zero-knowledge architecture)
- ❌ Cross-user data access (complete isolation)
- ❌ Centralized user authentication (self-hosted model)
- ✅ Self-service registration (no admin needed)
- ✅ Per-user encryption keys (complete isolation)
- ✅ Self-service passphrase reset (bcrypt hash method)
- ✅ Username-based vault separation (database-level)
- No Admin Backdoors: Registration doesn't require admin approval or create privileged access
- Zero-Knowledge: Each user's passphrase never leaves their device
- Database Isolation: RLS policies + user_id ensure data separation
- Self-Service Reset: Users control their own emergency reset via bcrypt hash
- Existing Architecture: Leverages current vault system, just adds registration UI
- Existing users: No changes needed, continue using current username
- New users: Can register via new registration flow
- Both types: Use same vault system, same security model
- ✅ Works with Supabase backend
- ✅ Works with SQLite backend
- ✅ Works in browser/PWA
- ✅ Works in Electron desktop app
- ✅ Backwards compatible with existing vaults
After implementation, Keyper will support:
- Self-Service Registration: Users can create accounts without admin
- User Switching: Easy switching between multiple user accounts
- Complete Isolation: Each user's vault is cryptographically isolated
- No Backdoors: Maintains "Enhanced Security Mode" architecture
- Emergency Reset: Each user controls their own passphrase reset
All while maintaining the EXCELLENT security rating! 🔐✨
Made with ❤️ by Pink Pixel - Dream it, Pixel it ✨