This guide covers secure setup and management of user credentials for production deployments.
🔐 Security Principles
NEVER hardcode passwords in:
- Migration files
- Source code
- Environment variables (except encrypted secrets)
- Git repositories
ALWAYS:
- Prompt for passwords during setup
- Generate strong random passwords when user doesn't provide one
- Hash passwords with bcrypt (10+ rounds)
- Save credentials to local files with chmod 600
- Delete credential files after copying to password manager
- Use SSL/TLS for all database connections
Initial Setup (Root Tenant & Admin)
After deploying the database schema, initialize the root tenant and admin user:
# Connect to your managed database
export DATABASE_URL="postgresql://user:pass@host:port/db?sslmode=require"
# Run initialization script
bash devops/digitalocean/init_root_tenant.sh
Prompts:
- Root tenant name (default: "Root MSP")
- Subdomain (default: "root")
- Admin email
- Admin full name
- Admin password (or press Enter to auto-generate)
Output:
- Creates/updates root tenant with is_msp = true
- Creates/updates admin user with hashed password
- Saves credentials to devops/digitalocean/admin_credentials.txt (chmod 600)
- Displays login URL and credentials
Example:
Root tenant name [Root MSP]: My MSP Company
Root tenant subdomain [root]: mymsp
Admin email: admin@mymsp.com
Admin full name [System Administrator]: John Smith
Admin password (min 8 chars, or leave empty to auto-generate):
[INFO] Generated password: aB3$xY9zQm2#pL7n
[WARN] Save this password securely! It will not be shown again.
========================================
✅ Root Tenant & Admin User Created
========================================
Tenant: My MSP Company (subdomain: mymsp)
Admin: John Smith <admin@mymsp.com>
Password: aB3$xY9zQm2#pL7n
⚠️ SAVE THESE CREDENTIALS SECURELY!
========================================
[INFO] Credentials saved to devops/digitalocean/admin_credentials.txt
[INFO] You can now log in at: https://demo.everydayoffice.au/login
Creating Additional Tenants
To create a new tenant (customer) with an admin user:
bash devops/digitalocean/create_tenant.sh
Prompts:
- Tenant name (e.g., "Acme Corporation")
- Subdomain (e.g., "acme")
- MSP status (yes/no)
- Admin email
- Admin full name
- Admin password (or auto-generate)
Example:
Tenant name: Acme Corporation
Subdomain: acme
Is this an MSP tenant? (y/N): n
Admin email: admin@acme.com
Admin full name: Jane Doe
Admin password (or leave empty): [auto-generated]
========================================
✅ Tenant Created Successfully
========================================
Tenant: Acme Corporation
Subdomain: acme
MSP: false
Admin User:
Name: Jane Doe
Email: admin@acme.com
Password: pQ8$mN4xRz7#vL2k
Login URL: https://demo.everydayoffice.au/login
Use subdomain: acme
⚠️ SAVE THESE CREDENTIALS SECURELY!
========================================
Resetting User Passwords
If a user forgets their password or you need to reset it (e.g., security incident):
bash devops/digitalocean/reset_user_password.sh user@example.com
Prompts:
- New password (or press Enter to auto-generate)
Example:
Resetting password for: admin@acme.com
New password (or leave empty to auto-generate):
[INFO] Generated password: zX6$nM9qPl3#vB8k
[WARN] Save this password securely!
========================================
✅ Password Updated Successfully
========================================
User: admin@acme.com
New Password: zX6$nM9qPl3#vB8k
⚠️ SAVE THIS PASSWORD SECURELY!
========================================
Password Requirements
All passwords must meet these criteria:
- Minimum 8 characters (recommended: 16+)
- Auto-generated passwords are 16 characters with high entropy
- Hashed with bcrypt (cost factor 10)
- Never stored in plaintext
Auto-generated password format:
- 16 alphanumeric characters
- Mix of uppercase, lowercase, and numbers
- Secure random generation via openssl rand
Security Best Practices
1. Password Management
- ✅ Use a password manager (1Password, Bitwarden, LastPass)
- ✅ Store credentials immediately after generation
- ✅ Delete local credential files after copying:
rm -f devops/digitalocean/admin_credentials.txt
- ✅ Never share passwords via email or chat
- ✅ Use unique passwords for each tenant/user
2. Password Rotation
- Rotate admin passwords every 90 days
- Rotate after any security incident
- Rotate when an admin leaves the organization
- Use the reset_user_password.sh script for rotations
3. Multi-Factor Authentication (MFA)
- Enable MFA for all admin accounts (if implemented)
- Use TOTP apps (Google Authenticator, Authy)
- Store backup codes securely
4. Access Control
- Limit root/MSP tenant access to essential personnel
- Use staff role for day-to-day operations
- Review user access quarterly
- Disable inactive accounts immediately
5. Audit Trail
- Monitor failed login attempts
- Log password changes
- Review user activity regularly
- Set up alerts for suspicious activity
Credential File Security
All scripts save credentials to local files with restrictive permissions:
# Files created:
devops/digitalocean/db_credentials.txt # Database connection
devops/digitalocean/admin_credentials.txt # Root admin user
# Permissions:
-rw------- (600) - readable only by owner
After setup:
- Copy credentials to password manager
- Delete local files:
rm -f devops/digitalocean/*_credentials.txt
- Never commit credential files to git (already in .gitignore)
Troubleshooting
"bcrypt not found" error
Install bcrypt in your backend or globally:
# In backend directory
cd backend
npm install bcrypt
# Or globally
npm install -g bcrypt
"Could not connect to database"
- Check database firewall allows your IP
- Verify DATABASE_URL or .env variables
- Test connection:
psql "$DATABASE_URL" -c "SELECT version();"
"User already exists" when creating tenant
The script uses ON CONFLICT DO UPDATE, so it's safe to re-run. If you want to create a new user instead, change the email address.
Password too weak
The scripts enforce minimum 8 characters. For production:
- Use auto-generated passwords (16 chars)
- Or provide strong passwords with mix of:
- Uppercase letters
- Lowercase letters
- Numbers
- Symbols
Emergency Access
If you lose root admin credentials:
- Connect directly to database:
- Find the admin user:
SELECT id, email, full_name FROM users WHERE role = 'admin' AND tenant_id = (SELECT id FROM tenants WHERE subdomain = 'root');
- Use the reset script with the email:
bash devops/digitalocean/reset_user_password.sh admin@yourdomain.com
Integration with Backend
The backend expects bcrypt-hashed passwords in the users.password_hash column:
// Backend authentication (backend/routes/auth.js)
const bcrypt = require('bcrypt');
// Login
const match = await bcrypt.compare(plainPassword, user.password_hash);
// Change password
const hash = await bcrypt.hash(newPassword, 10);
await pool.query('UPDATE users SET password_hash = $1 WHERE id = $2', [hash, userId]);
All scripts use the same bcrypt configuration (cost factor 10) for consistency.
Last Updated: 4 November 2025
Status: ✅ Production-ready