Skip to content

Add emailOTP plugin for passwordless DJ login #240

@jakebromberg

Description

@jakebromberg

Context

DJs at WXYC share a single computer in the control room. They log out after each set and the next DJ logs in. The current username+password flow is friction-heavy: DJs forget passwords, type them wrong, or need resets mid-shift. Email OTP (one-time passcode) lets a DJ type their email, get a 6-digit code on their phone, and sign in within seconds — no password to remember, no credentials stored on the shared machine.

Changes

Add the better-auth emailOTP plugin to Backend-Service and wire up OTP email sending via SES.

1. Add emailOTP plugin to auth definition

File: shared/authentication/src/auth.definition.ts

  • Import emailOTP from better-auth/plugins and sendOTPEmail from ./email
  • Add emailOTP() to the plugins array with configuration:
    • disableSignUp: true — matches existing policy; only admin-created accounts can sign in
    • allowedAttempts: 5 — slightly more lenient than default (3) since DJs may fumble codes
    • otpLength: 6, expiresIn: 300 (5 minutes)
    • storeOTP: 'hashed' in production, 'plain' in dev/test so the built-in GET /auth/email-otp/get-verification-otp endpoint works for E2E tests

2. Add OTP email template

File: shared/authentication/src/email.ts

  • New buildOTPEmailHtml() function — shares the same WXYC-branded outer shell (logo, pink/dark theme, footer) as buildEmailHtml but replaces the action button with a large monospace OTP code display
  • New sendOTPEmail() export — maps OTP type to subject/intro text:
    • "sign-in" → "Your WXYC login code"
    • "email-verification" → "Your WXYC verification code"
    • "forget-password" → "Your WXYC password reset code"
  • Footer: "This code expires in 5 minutes. If you didn't request this, you can safely ignore it."
  • Text fallback: "Your code is: 123456. It expires in 5 minutes."

3. Tests

New file: tests/unit/authentication/email-otp.test.ts

  • 9 tests across two describe blocks
  • buildOTPEmailHtml: OTP rendering, WXYC branding, monospace styling, custom/default footer
  • sendOTPEmail: parameterized test covering all 3 OTP types, plus missing SES_FROM_EMAIL error

Endpoints exposed

The emailOTP plugin automatically registers:

  • POST /auth/email-otp/send-verification-otp — sends an OTP to the given email
  • POST /auth/sign-in/email-otp — verifies OTP and creates a session
  • GET /auth/email-otp/get-verification-otp — returns the OTP in non-production (for E2E testing)

Deployment

No new env vars, no database migrations, no SES reconfiguration needed. The plugin uses the existing auth_verification table.

Files modified

File Change
shared/authentication/src/auth.definition.ts Add emailOTP plugin import + config
shared/authentication/src/email.ts Add buildOTPEmailHtml, sendOTPEmail
tests/unit/authentication/email-otp.test.ts New: OTP email tests

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions