Skip to content

feat: add TOTP 2FA with recovery codes and upload gating - #146

Open
NTBBloodbath wants to merge 2 commits into
masterfrom
push-sxtxlywpqtzu
Open

NTBBloodbath wants to merge 2 commits into
masterfrom
push-sxtxlywpqtzu

Conversation

@NTBBloodbath

Copy link
Copy Markdown
Member
  • opt-in enrollment in settings via QR code (PNG, EC level Q) + code confirm
  • 10 single-use hashed recovery codes, shown once on enrollment
  • login gate: enrolled users pass a /login/totp challenge after OAuth
  • API upload gate (Support 2FA for package uploads (TOTP) #101): enrolled users must exchange a TOTP code for a 10-minute TFA token (LuaRocks verify_tfa protocol, X-TFA-Token header) on both /api/1/:key/upload and /api/releases
  • rate-limit TOTP verification attempts (10/hr)
  • verify_totp accepts the previous 30s window to avoid boundary flakes

closes #101

- opt-in enrollment in settings via QR code (PNG, EC level Q) + code confirm
- 10 single-use hashed recovery codes, shown once on enrollment
- login gate: enrolled users pass a `/login/totp` challenge after OAuth
- API upload gate (#101): enrolled users must exchange a TOTP code for a
  10-minute TFA token (LuaRocks `verify_tfa` protocol, `X-TFA-Token`
  header) on both `/api/1/:key/upload` and `/api/releases`
- rate-limit TOTP verification attempts (10/hr)
- verify_totp accepts the previous 30s window to avoid boundary flakes

closes #101
@NTBBloodbath NTBBloodbath added Scope / Backend Back-end related issue or pull request Scope / Frontend Front-end related issue or pull request Kind / Feature New functionality Kind / Testing Issue or pull request related to testing dependencies Pull requests that update a dependency file elixir Pull requests that update elixir code labels Sep 20, 2026
@NTBBloodbath
NTBBloodbath marked this pull request as ready for review September 20, 2026 20:51
@NTBBloodbath
NTBBloodbath marked this pull request as draft September 20, 2026 21:04
… codes

The 2FA feature assumed the database was trusted, which left it exposed
if the database were ever exfiltrated. TOTP secrets were stored in
plaintext, so a leaked dump would let an attacker mint valid codes
indefinitely, and recovery codes were hashed with sha256 over only
40 bits of entropy, making them brute-forceable offline in hours.

This encrypts users.totp_secret with Cloak AES-GCM, sourcing the key
from the LUANOX_FIELD_ENCRYPTION_KEY environment variable so it never
lives alongside the data it protects, and switches recovery codes to
argon2id with 96 bits of entropy. Since argon2 is salted and
non-deterministic, recovery codes are now verified by iterating the
user's hashes rather than a direct code_hash lookup. The unused
bcrypt_elixir dependency is also dropped.

Requires migration 20260920220434 (totp_secret varchar -> bytea) and the
LUANOX_FIELD_ENCRYPTION_KEY variable in production once deployed :)
@NTBBloodbath
NTBBloodbath marked this pull request as ready for review September 20, 2026 22:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

dependencies Pull requests that update a dependency file elixir Pull requests that update elixir code Kind / Feature New functionality Kind / Testing Issue or pull request related to testing Scope / Backend Back-end related issue or pull request Scope / Frontend Front-end related issue or pull request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Support 2FA for package uploads (TOTP)

1 participant