feat: two-factor authentication (TOTP + recovery codes) - #121
Merged
Conversation
Port the Laravel/Symfony 2FA reference to the AdonisJS backend, which previously had no 2FA support. - TwoFactorService: RFC-6238 TOTP (HMAC-SHA1, 6 digits, 30s period) implemented with node:crypto (no new OTP dependency), plus base32 secret generation, otpauth:// enrollment URIs, single-use recovery codes, and AES-256-GCM secret encryption at rest. - TwoFactor Lucid model + escalated_two_factor migration (encrypted secret, hashed recovery codes, confirmed_at), mirroring the api_token store. - AdminTwoFactorController: setup, confirm, disable, regenerate recovery codes, and a post-login challenge/verify path (TOTP or recovery code). - Routes under admin settings plus an auth-only two-factor challenge group. - Unit spec covering RFC-6238 known-answer vectors, wrong-code rejection, and recovery-code single-use.
mpge
added a commit
that referenced
this pull request
Aug 1, 2026
Port the Laravel/Symfony 2FA reference to the AdonisJS backend, which previously had no 2FA support. - TwoFactorService: RFC-6238 TOTP (HMAC-SHA1, 6 digits, 30s period) implemented with node:crypto (no new OTP dependency), plus base32 secret generation, otpauth:// enrollment URIs, single-use recovery codes, and AES-256-GCM secret encryption at rest. - TwoFactor Lucid model + escalated_two_factor migration (encrypted secret, hashed recovery codes, confirmed_at), mirroring the api_token store. - AdminTwoFactorController: setup, confirm, disable, regenerate recovery codes, and a post-login challenge/verify path (TOTP or recovery code). - Routes under admin settings plus an auth-only two-factor challenge group. - Unit spec covering RFC-6238 known-answer vectors, wrong-code rejection, and recovery-code single-use. Co-authored-by: Matt Gros <mpge@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds two-factor authentication (TOTP + recovery codes) to the AdonisJS backend, which previously had no 2FA support (it was referenced only in a code comment). Ports the Laravel/Symfony reference contract.
What's included
TwoFactorService(src/services/two_factor_service.ts) — RFC-6238 TOTP (HMAC-SHA1, 6 digits, 30s period) implemented with Node's built-incryptomodule, no new OTP dependency. Provides:otpauth://enrollment URI (for QR display)verify()with a +/- 1 time-slice clock-drift window (constant-time compare)TwoFactorLucid model (src/models/two_factor.ts) +escalated_two_factormigration — encryptedsecret, hashedrecovery_codes,confirmed_at; unique per user, mirroring howapi_tokenis modeled. Uses theuserIdColumnhelper so it honors the host user key type (int/uuid/string).AdminTwoFactorController—index,setup,confirm,disable,regenerateRecoveryCodes, plus a post-loginchallenge/verifypath (accepts a TOTP code or a single-use recovery code).admin/settings/two-factor/*, and an auth-onlytwo-factor/challengegroup for the login challenge.Contract
Generate a TOTP secret → expose an
otpauth://URI for enrollment → confirm by verifying a first code → store secret (encrypted) + hashed recovery codes +confirmed_at→ verify a TOTP or recovery code at challenge time → disable + regenerate recovery codes.Tests
New unit spec
tests/unit/two_factor_service.spec.ts(16 tests), including:Full suite green with no regressions: 565 node:test tests + 21 Japa tests pass.
tsc --noEmit, ESLint, and Prettier (3.9.6) all clean on the changed files.