Last updated 2026-10-11
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 ⚙ Settings at the bottom of the sidebar (or Settings 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.
Passkeys: sign in with Face ID
A passkey lets you sign in with your face or fingerprint (Face ID, Touch ID, Windows Hello, or an Android phone's screen lock) instead of typing a password and a code. It counts as both, and it can't be phished or mistyped.
- Add one: Settings → Security → Passkeys, type your current password (and a 2FA code if 2FA is on), then Add a passkey, on the device you'll use. On a Windows PC you can instead choose iPhone in the prompt and scan the QR code, which saves the passkey on your phone. We email you every time a passkey is added; if you didn't add it, remove it and reset your password.
- Sign in: on the login page tap Sign in with Face ID or a passkey. You stay signed in on that device (30 days, renewed each time you use Otium).
- Owner account: after the passkey you still confirm the emailed link with the code on the waiting screen, as after a password and code. On a phone, open the email anywhere; the waiting Otium screen signs itself in.
- Remove one from the same list (a lost phone, for example). Your password and code keep working.
- Removed automatically when you reset your password, when an admin sets a new password for you, and when you Sign out everywhere else. Add them again afterwards.
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 (or a passkey), the waiting screen shows a 6-character code and OtiumWork sends a one-time link to your account email. The email and the page the link opens both show which device, IP address and time is waiting: check they're yours. Type the code from the waiting screen on that page within 10 minutes to complete sign-in. The link burns on use; subsequent clicks fail, and 5 wrong codes burn it too (sign in again for a fresh one).
The link works from any device: on a phone the email usually opens in Safari or Outlook, and the Otium screen that is waiting (for example the Home Screen app) signs itself in within a few seconds of you confirming. Only the screen that showed that code signs itself in, so a stranger who has your password and a code can't ride on your click: they'd need the code from their own screen, which you never see. If you started over, an earlier email still works with the code that screen showed at the time. The browser that only opened the link stays signed in until it closes (no 30-day cookie).
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
- login_3fa_failed — wrong code on the owner's emailed-link page
- 2fa_enabled / 2fa_disabled / 2fa_codes_regenerated
- session_revoked / session_revoked_all
- passkey_added / passkey_removed / passkey_add_failed (wrong password or code when adding)
- passkeys_revoked — all of a person's passkeys removed (password reset, admin password set, sign out everywhere)
Review at /admin/audit (per-tenant) or /ops (cross-tenant).
See something wrong or outdated in this article? Report it →