Last updated 2026-04-26
Login security
OtiumWork supports a layered authentication model. The number of factors required depends on the account's role.
What's required for whom
| Account | Factors |
|---|---|
| Employees, managers, customer admins | Password (always) + TOTP 2FA (optional but recommended) |
| OtiumWork platform owner (you) | Password + TOTP 2FA + email magic link (all 3 mandatory) |
The "owner" allowlist is set via the OPS_ACCESS_EMAILS env var. Only emails on that list trigger the 3rd-factor flow.
Setting up TOTP 2FA
- Click Security in the topbar (next to "Log out") →
/me/security - Click Set up 2FA
- Install an authenticator app (Google Authenticator, Authy, 1Password, Bitwarden, Microsoft Authenticator — any of them work)
- Scan the QR code or paste the manual setup key
- Enter the current 6-digit code to verify
- Save the 8 backup codes that appear next — you'll only see them once. Store them in your password manager or print and lock them in a drawer
After this, every sign-in asks for the 6-digit code after your password.
Backup codes
Each is single-use. If you lose your phone:
- Use one of the 8 codes you saved at enrollment
- After you sign in, regenerate fresh codes at /me/security
If you lose all 8 AND your phone, email info@otiumwork.com from your account email — we'll verify your identity manually and reset 2FA.
Owner-only: email magic link (3rd factor)
After you (the owner) pass password + TOTP, OtiumWork sends a one-time link to your account email. Click the link within 10 minutes to complete sign-in. The link burns on use; subsequent clicks fail.
Why three factors for owner? Your account has full access to OtiumWork itself — every customer's data, every Stripe key. Compromising password + phone alone shouldn't be enough; an attacker also needs your email session. With three independent attack surfaces, defense-in-depth is meaningful.
If your SMTP is broken and the magic link can't reach you, temporarily remove your email from OPS_ACCESS_EMAILS in /opt/timeopt/.env and restart the service — that disables the 3rd factor until SMTP is fixed.
Login rate limiting
5 failed password attempts per (email + IP) in 15 minutes triggers a 15-minute lock for that combination. Successful login clears the counter. Stops password-spraying / credential stuffing.
(In-memory bucket per gunicorn worker — multi-worker deployments effectively allow 5 × N attempts. For prod-grade rate limiting we can add Redis later.)
New-device email alerts
The first time we see a sign-in from a new IP+browser combo, you get a "new sign-in detected" email with: - Device label (e.g. "Chrome on Windows") - IP address - Direct revoke link
If it wasn't you, click the revoke link and change your password immediately.
Active sessions view + revoke
/me/sessions lists every device currently signed in to your account, with first-seen + last-seen timestamps. Revoke any individually, or "Sign out everywhere else" to revoke all but the current one.
A revoked session is invalidated server-side on the next request — even if the attacker still has the cookie, it stops working immediately.
Password policy
- Minimum 12 characters at signup
- Top breach-list passwords rejected
- Stored as PBKDF2 hash (Werkzeug default — slow + salted)
- No length cap, no character-class requirements (NIST 800-63B aligned: long unique > complex)
What's NOT in V1
- WebAuthn / FIDO2 hardware key support (planned — replaces TOTP for users with a YubiKey)
- Self-serve in-app password change (use Forgot Password to rotate)
- SSO via Microsoft Entra ID for enterprise customers (planned)
- IP allowlisting per account
- Have-I-Been-Pwned password check (k-anonymity API call)
- Audit log of every page view per session
Audit log entries
Every security-significant action is logged in audit_log:
- login — successful sign-in (with 2fa / 3fa method noted in details)
- login_failed — bad password
- login_lockout — too many failures
- login_2fa_failed — wrong TOTP / backup code
- 2fa_enabled / 2fa_disabled / 2fa_codes_regenerated
- session_revoked / session_revoked_all
Review at /admin/audit (per-tenant) or /ops (cross-tenant).
See something wrong or outdated in this article? Report it →