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

  1. Click Security in the topbar (next to "Log out") → /me/security
  2. Click Set up 2FA
  3. Install an authenticator app (Google Authenticator, Authy, 1Password, Bitwarden, Microsoft Authenticator — any of them work)
  4. Scan the QR code or paste the manual setup key
  5. Enter the current 6-digit code to verify
  6. 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 →