Skip to content

feat: add email-otp plugin#6

Open
ibxbit wants to merge 1 commit into
thecodearcher:masterfrom
ibxbit:feat/email-otp-plugin
Open

feat: add email-otp plugin#6
ibxbit wants to merge 1 commit into
thecodearcher:masterfrom
ibxbit:feat/email-otp-plugin

Conversation

@ibxbit

@ibxbit ibxbit commented Jun 5, 2026

Copy link
Copy Markdown

Adds a passwordless email-OTP plugin (plugins/email-otp) modeled on
better-auth's email-otp plugin.

Endpoints

  • POST /email-otp/send-otp — request OTP (type: sign-in |
    email-verification)
  • POST /email-otp/sign-in — sign in with OTP, auto-creates user
  • POST /email-otp/verify-email — mark existing user's email as
    verified

Programmatic API via emailotp.Use(auth).SendOTP / SignInWithOTP / VerifyOTP.

Defaults (match better-auth)

  • 6-digit numeric codes
  • 5-minute expiry
  • 3 allowed verification attempts
  • Rate-limited 3/min per endpoint
  • Optional WithHashStoredOTP for at-rest HMAC-SHA256 hashing
    (improvement over better-auth's plain default; opt-in for parity)

Security

  • Transactional attempt counting via core.WithTransaction — race-safe
    under concurrent verifies
  • Anti-enumeration: send-otp returns generic success for unknown
    emails / sign-up-disabled mode
  • Type-scoped identifiers (sign-in-otp-<email> vs
    email-verification-otp-<email>) so codes never cross flows — covered
    by tests

Tests

23 unit tests covering happy paths, expiry, wrong-code lockout, attempt
counting, single-use consumption, auto-signup, additional fields
propagation, type isolation, hashed storage, input validation, and
handler-level cookie/status assertions. go test -race -count=1 passes.
go vet and gofmt clean.

Tested end-to-end

Manually verified against a Neon Postgres instance: send-otp → sign-in
(auto-creates user + sets session cookie) → authenticated /api/profile
call. Example added at examples/email-otp/.

Deferred to follow-up PRs

  • Password reset via OTP
  • Change-email flow
  • sendVerificationOnSignUp hook
  • overrideDefaultEmailVerification
  • OTP "reuse" resend strategy

Notes for the reviewer

  1. plugins/email-otp/go.mod carries replace github.com/thecodearcher/limen => ../.. so it builds before the next
    core release. Remove and bump to limen v0.1.2 when tagging, matching
    the sweep in commit efdafac.
  2. The example also fixes a latent routing bug — auth.Handler() was
    being mounted at /api/auth/ without setting
    WithHTTPBasePath("/api/auth"), so requests 404'd. This PR fixes it for
    examples/email-otp/ only; the other examples likely have the same bug
    and could use a separate cleanup PR.
  3. CI lint runs golangci-lint run ./... from root, which won't recurse
    into the plugin module. If you want the new plugin linted in CI, the
    workflow's lint step needs the same per-module loop the test step has —
    happy to follow up.

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