Skip to content

feat(auth): TOTP second factor for the local provider - #779

Open
Hashir-Ashraf-Awan wants to merge 1 commit into
libredb:mainfrom
Hashir-Ashraf-Awan:feat/totp-second-factor
Open

feat(auth): TOTP second factor for the local provider#779
Hashir-Ashraf-Awan wants to merge 1 commit into
libredb:mainfrom
Hashir-Ashraf-Awan:feat/totp-second-factor

Conversation

@Hashir-Ashraf-Awan

Copy link
Copy Markdown

Adds optional TOTP (RFC 6238) on top of the local email/password provider, opt-in per account through ADMIN_TOTP_SECRET and USER_TOTP_SECRET. SSO already covered MFA by delegating to the identity provider (docs/OIDC.md); this closes the gap for deployments that authenticate locally.

Verification is in-tree and dependency-free: HOTP is a truncated HMAC and base32 is a 32-character alphabet, so an OTP library would add supply-chain surface to the one code path that exists to raise the cost of a compromise. HMAC-SHA-1 is RFC 6238's default and the only algorithm authenticator apps interoperate on for a bare otpauth URI.

Secrets are env vars rather than enrolled state: the chart and the image both run on a read-only filesystem, and a second factor that silently degraded when the data dir was unwritable would be worse than none. A value that is not base32 is an AuthConfigError, so a typo stops login with a 503 naming the variable instead of quietly dropping the factor or rejecting every correct code.

Notable properties:

  • The password alone never creates a session, and an accepted code cannot be replayed inside its 90-second window (RFC 6238 5.2). Both are asserted in tests/security/mfa-second-factor.test.ts, now control 1.6 in docs/SECURITY.md.
  • Replying "code required" is not the enumeration oracle control 1.5 removes: it is reachable only with a correct password, and without MFA that same request would have returned a session.
  • Being asked for a code costs no rate-limit budget; a wrong code spends both buckets. login_client allows five failures per five minutes, so charging the prompt would cap legitimate users at five logins per window.
  • NEXT_PUBLIC_AUTH_PROVIDER=oidc changes what the login page renders, not what POST /api/auth/login accepts. An OIDC deployment that still sets ADMIN_PASSWORD keeps a route that never reaches the issuer; these variables are honoured in that mode too, which is how it is closed. Documented rather than assumed away.
  • The chart carries the secret in its Secret and references it from the pod, so it never lands in the Deployment spec the way extraEnv would. Both refs stay optional even in strict mode, so an unasked-for factor cannot block startup.

Chart version bumped to 0.1.63 per the packaged-file rule (#167); 0.1.62 is already released. Coverage is 100% on all four touched source files.

Description

Type of Change

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Documentation update
  • Code refactoring
  • Performance improvement
  • Test addition or update

Related Issue

Closes #

Changes Made

Testing

  • I have tested this locally
  • I have added/updated tests
  • All existing tests pass

Test Environment

  • LibreDB Studio Version:
  • Browser:
  • OS:
  • Node.js/Bun Version:
  • Database Type:

Screenshots (if applicable)

Checklist

  • My code follows the project's code style guidelines
  • I have performed a self-review of my code
  • I have commented my code, particularly in hard-to-understand areas
  • I have updated the documentation accordingly
  • My changes generate no new warnings
  • I have added tests that prove my fix is effective or that my feature works
  • New and existing unit tests pass locally with my changes
  • The required CI test job passes the 100% line-coverage gate (bun run test:coverage and bun run coverage:check)
  • If I changed src/lib/db/providers/, I updated the matching docs/providers/ documentation and tests/integration/db/ tests in the same PR (provider triad)
  • Any dependent changes have been merged and published

Additional Notes

Adds optional TOTP (RFC 6238) on top of the local email/password provider,
opt-in per account through ADMIN_TOTP_SECRET and USER_TOTP_SECRET. SSO already
covered MFA by delegating to the identity provider (docs/OIDC.md); this closes
the gap for deployments that authenticate locally.

Verification is in-tree and dependency-free: HOTP is a truncated HMAC and base32
is a 32-character alphabet, so an OTP library would add supply-chain surface to
the one code path that exists to raise the cost of a compromise. HMAC-SHA-1 is
RFC 6238's default and the only algorithm authenticator apps interoperate on for
a bare otpauth URI.

Secrets are env vars rather than enrolled state: the chart and the image both run
on a read-only filesystem, and a second factor that silently degraded when the
data dir was unwritable would be worse than none. A value that is not base32 is
an AuthConfigError, so a typo stops login with a 503 naming the variable instead
of quietly dropping the factor or rejecting every correct code.

Notable properties:

- The password alone never creates a session, and an accepted code cannot be
  replayed inside its 90-second window (RFC 6238 5.2). Both are asserted in
  tests/security/mfa-second-factor.test.ts, now control 1.6 in docs/SECURITY.md.
- Replying "code required" is not the enumeration oracle control 1.5 removes: it
  is reachable only with a correct password, and without MFA that same request
  would have returned a session.
- Being asked for a code costs no rate-limit budget; a wrong code spends both
  buckets. login_client allows five failures per five minutes, so charging the
  prompt would cap legitimate users at five logins per window.
- NEXT_PUBLIC_AUTH_PROVIDER=oidc changes what the login page renders, not what
  POST /api/auth/login accepts. An OIDC deployment that still sets ADMIN_PASSWORD
  keeps a route that never reaches the issuer; these variables are honoured in
  that mode too, which is how it is closed. Documented rather than assumed away.
- The chart carries the secret in its Secret and references it from the pod, so
  it never lands in the Deployment spec the way extraEnv would. Both refs stay
  optional even in strict mode, so an unasked-for factor cannot block startup.

Chart version bumped to 0.1.63 per the packaged-file rule (libredb#167); 0.1.62 is
already released. Coverage is 100% on all four touched source files.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@gitguardian

gitguardian Bot commented Sep 10, 2026

Copy link
Copy Markdown

⚠️ GitGuardian has uncovered 2 secrets following the scan of your pull request.

Please consider investigating the findings and remediating the incidents. Failure to do so may lead to compromising the associated services or software components.

Since your pull request originates from a forked repository, GitGuardian is not able to associate the secrets uncovered with secret incidents on your GitGuardian dashboard.
Skipping this check run and merging your pull request will create secret incidents on your GitGuardian dashboard.

🔎 Detected hardcoded secrets in your pull request
GitGuardian id GitGuardian status Secret Commit Filename
- - Generic High Entropy Secret 4444029 tests/unit/lib/totp.test.ts View secret
36754989 Triggered Generic Password 4444029 tests/api/auth/login.test.ts View secret
🛠 Guidelines to remediate hardcoded secrets
  1. Understand the implications of revoking this secret by investigating where it is used in your code.
  2. Replace and store your secrets safely. Learn here the best practices.
  3. Revoke and rotate these secrets.
  4. If possible, rewrite git history. Rewriting git history is not a trivial act. You might completely break other contributing developers' workflow and you risk accidentally deleting legitimate data.

To avoid such incidents in the future consider


🦉 GitGuardian detects secrets in your source code to help developers and security teams secure the modern development process. You are seeing this because you or someone else with access to this repository has authorized GitGuardian to scan your pull request.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant