• Skip to main content
  • Skip to navigation
  • Skip to search
    Petunia™
    FeaturesPricingIntegrationsAboutContact
    Log inStart free trialSign up
    Loading
    Petunia™

    Reimagining customer communication for the modern business.

    Product

    • Features
    • Pricing
    • Integrations
    • Roadmap
    • What's New

    Resources

    • Help Center
    • Documentation
    • Guides
    • API Reference
    • Community
    • Support

    Company

    • About Us
    • Careers
    • Blog
    • Press
    • Contact

    © 2026 Gray Group International LLC. All rights reserved.·
    Made by gardenpatch 🌱

    Privacy PolicyTerms of ServiceCookie Policy

    Petunia™ is a trademark of Gray Group International LLC. The Petunia name, brand, product design, and content are proprietary. Unauthorized use, imitation, or copying is prohibited.

    Documentation

    AUTHENTICATION

    docs/features/AUTHENTICATION.md
    Docs homeGuidesSupport
    Quick links
    Start here
    How the docs are organized.
    Environment setup
    Configure env + run locally.
    Unified Inbox
    Inbox concepts & behavior.
    Voice AI setup
    Providers, Twilio, testing.
    Pricing model
    Source-of-truth pricing.
    Operations runbook
    How to operate safely.

    Authentication System - Feature Documentation

    Owner: Priya Shah Version: 0.4.1

    OwnerLast verifiedNext verification dueLast CI run IDCoverage %Open risks
    Priya Shah2025-12-282026-01-27ci.yml#2186Pending (auth API + Cypress + Playwright WebAuthn suites)Redis-backed rate limits lack automation; OAuth provider sandboxes not load-tested

    Verification

    • CI jobs: ci.yml API + Cypress + Playwright stages (last run: ci.yml#2186)
    • Commands (CI/staging demo portal only; never production tenants):
      • pnpm test:api --config jest.config.api.cjs --runTestsByPath tests/app/api/auth/signup/route.test.ts tests/app/api/auth/session/route.test.ts tests/app/api/auth/2fa/setup.test.ts tests/app/api/auth/2fa/verify.test.ts
      • pnpm cypress:run --spec "cypress/e2e/auth/**/*.cy.ts"
      • pnpm playwright:test tests/e2e/webauthn-passkey.spec.ts (WebAuthn E2E with virtual authenticators)
      • pnpm test -- --testPathPattern="webauthn" (WebAuthn unit tests)
      • pnpm db:health
    • Test suites + environment links:
      • tests/app/api/auth/signup/route.test.ts (CI seeded demo DB, mocks Supabase Auth)
      • tests/app/api/auth/2fa/setup.test.ts, .../verify.test.ts, .../backup-codes.test.ts (CI seeded demo DB; exercises 2FA flows)
      • tests/lib/auth/webauthn.test.ts (WebAuthn service unit tests)
      • tests/e2e/webauthn-passkey.spec.ts (Playwright E2E: registration, login, management, recovery)
      • tests/auth/auth-suite.test.ts and tests/auth/signup-oauth.test.ts (unit/state coverage for auth UI + OAuth)
      • cypress/e2e/auth/login.cy.ts, google-login.cy.ts, oauth-signup-complete-flow.cy.ts, protected-pages.cy.ts (staging sandbox OAuth; demo portal only)
    • Next verification due: 2026-01-27 (refresh CI run ID + coverage)

    Table of Contents

    1. Problem / Job-to-Be-Done
    2. Solution Overview
    3. Scope
    4. Acceptance Criteria
    5. Dependencies & Risks
    6. Technical Architecture
    7. Security Considerations
    8. Testing Strategy
    9. Metrics & Success Criteria
    10. Rollout & Release Plan
    11. Observability & Monitoring
    12. Environment Variables
    13. API Reference
    14. Troubleshooting
    15. File Reference
    16. Completion Checklist
    17. Version History

    Problem / Job-to-Be-Done

    User Pain Points

    Pain PointImpactSolution
    Insecure credential storageData breaches, compliance failuresSupabase Auth + encrypted tokens
    Account takeoverRevenue loss, trust erosion2FA with TOTP + backup codes
    Brute force attacksService degradationRate limiting (5 attempts/15 min)
    Session hijackingUnauthorized accessSecure cookies + CSRF protection
    OAuth complexityUser friction during signupGoogle OAuth with automatic account linking
    Password fatigueHigh support ticketsPassword reset flow + OAuth alternatives

    Business Impact

    MetricRisk if UnsolvedTarget After Implementation
    Security incidentsHigh liability exposureZero credential leaks
    Signup abandonmentLost revenue<5% abandonment rate
    Support ticketsHigh operational cost<2% auth-related tickets
    ComplianceLegal exposureSOC2/GDPR ready
    User trustChurn risk99.9% auth availability

    Job Stories

    When I'm signing up for Petunia, I want a quick, secure process with multiple options (email/password or Google), So that I can start using the platform without friction or security concerns.

    When I suspect my account may be compromised, I want to enable 2FA and generate backup codes, So that my business data remains protected even if my password is leaked.

    When I forget my password, I want a secure reset process, So that I can regain access without contacting support.


    Solution Overview

    User Journey

    ┌─────────────────────────────────────────────────────────────────────────────┐
    │                           AUTHENTICATION FLOW                                │
    ├─────────────────────────────────────────────────────────────────────────────┤
    │                                                                              │
    │   ┌──────────┐    ┌─────────────┐    ┌────────────┐    ┌──────────────┐    │
    │   │  Landing │───▶│  Auth Page  │───▶│   Verify   │───▶│  Dashboard   │    │
    │   │   Page   │    │             │    │   Email    │    │  (Protected) │    │
    │   └──────────┘    └─────────────┘    └────────────┘    └──────────────┘    │
    │                          │                                     │            │
    │                          │                                     │            │
    │                          ▼                                     ▼            │
    │                   ┌─────────────┐                      ┌──────────────┐    │
    │                   │   Google    │                      │     2FA      │    │
    │                   │   OAuth     │                      │  Challenge   │    │
    │                   └─────────────┘                      └──────────────┘    │
    │                                                                              │
    └─────────────────────────────────────────────────────────────────────────────┘
    

    Authentication Stack

    ┌─────────────────────────────────────────────────────────────────────────────┐
    │                        CLIENT (Browser/App)                                  │
    │  useSession hook • Auth Context • Protected Routes • CSRF Token             │
    └─────────────────────────────────────────────────────────────────────────────┘
                                        │
                                        ▼
    ┌─────────────────────────────────────────────────────────────────────────────┐
    │                        API ROUTES (Next.js)                                  │
    │  /api/auth/signup • signin • signout • reset-password • 2fa/*              │
    │  Rate limiting • Input validation • Audit logging                           │
    └─────────────────────────────────────────────────────────────────────────────┘
                                        │
                                        ▼
    ┌─────────────────────────────────────────────────────────────────────────────┐
    │                        AUTH LAYER (lib/auth)                                 │
    │  server.ts • session.ts • withAuth.ts • audit.ts • rate-limit.ts           │
    └─────────────────────────────────────────────────────────────────────────────┘
                                        │
                             ┌──────────┴──────────┐
                             ▼                      ▼
                ┌───────────────────┐    ┌───────────────────┐
                │   Supabase Auth   │    │   Prisma (User)   │
                │  • Sessions       │    │  • Profile data   │
                │  • OAuth          │    │  • 2FA secrets    │
                │  • Password hash  │    │  • Portal access  │
                └───────────────────┘    └───────────────────┘
    

    Key Capabilities

    CapabilityImplementationStatus
    Email/Password AuthSupabase signInWithPassword✅ Complete
    Google OAuthSupabase OAuth provider✅ Complete
    Email VerificationSupabase + custom email service✅ Complete
    Password ResetSupabase updateUser✅ Complete
    2FA/MFA (TOTP)Custom TOTP with backup codes✅ Complete
    WebAuthn/Passkeys@simplewebauthn + Prisma Passkey model✅ Complete
    Session ManagementSupabase + cookie sync✅ Complete
    Rate LimitingCustom with 5 attempts/15 min✅ Complete
    CSRF ProtectionToken rotation middleware✅ Complete
    Audit Logging20+ event types tracked✅ Complete
    Account RecoveryPartial signup recovery✅ Complete

    Scope

    In Scope (v0.4.2)

    • Email/password registration and login
    • Google OAuth integration
    • Email verification workflow
    • Password reset via email
    • 2FA setup with TOTP (Google Authenticator compatible)
    • Backup code generation (10 codes)
    • Rate limiting on auth endpoints
    • CSRF token protection
    • Session management with Supabase
    • Audit logging for security events
    • Account lockout after failed attempts
    • Partial signup recovery
    • Password strength validation
    • WebAuthn/Passkey support (FIDO2)

    Out of Scope (Future Versions)

    • SMS-based 2FA (OTP via phone)
    • Magic link authentication
    • Social login (Facebook, GitHub, Apple)
    • Enterprise SSO (SAML/OIDC)
    • Device fingerprinting
    • Login anomaly detection (ML-based)
    • Session management UI (view/revoke sessions)

    Acceptance Criteria

    Signup Flow

    Feature: User Signup
      As a new user
      I want to create an account
      So that I can access the Petunia platform
    
      Scenario: Successful email/password signup
        GIVEN I am on the signup page
          AND I provide a valid email, password, and name
          AND I accept the Terms of Service
        WHEN I submit the signup form
        THEN I should receive a verification email within 30 seconds
          AND I should be redirected to the onboarding flow
          AND my Prisma User record should be created
          AND a TTFV milestone should be recorded
    
      Scenario: Signup with existing email
        GIVEN I provide an email that already exists
        WHEN I submit the signup form
        THEN I should see "User with this email already exists"
          AND I should be offered a link to sign in instead
    
      Scenario: Signup with weak password
        GIVEN I provide a password shorter than 8 characters
        WHEN I submit the signup form
        THEN I should see a validation error
          AND the form should not be submitted
    

    Login Flow

    Feature: User Login
      As an existing user
      I want to log in to my account
      So that I can access my dashboard
    
      Scenario: Successful email/password login
        GIVEN I am a registered user
          AND I provide correct credentials
        WHEN I submit the login form
        THEN I should be authenticated
          AND session cookies should be set
          AND I should be redirected to my default portal
    
      Scenario: Login with 2FA enabled
        GIVEN I have 2FA enabled on my account
          AND I provide correct password
        WHEN I submit the login form
        THEN I should see the 2FA verification screen
          AND my session should be marked as "2fa_pending"
    
      Scenario: Login with incorrect credentials
        GIVEN I provide incorrect password
        WHEN I submit the login form
        THEN I should see "Invalid email or password"
          AND failed login attempt should be logged
          AND failedLoginAttempts counter should increment
    

    2FA Flow

    Feature: Two-Factor Authentication
      As a security-conscious user
      I want to enable 2FA
      So that my account is protected from unauthorized access
    
      Scenario: Enable 2FA
        GIVEN I am logged in
          AND I navigate to security settings
        WHEN I click "Enable 2FA"
        THEN I should see a QR code for my authenticator app
          AND I should receive a secret key
          AND mfaSecret should be stored (encrypted)
    
      Scenario: Verify 2FA setup
        GIVEN I have scanned the QR code
          AND I enter the 6-digit code from my authenticator
        WHEN I submit the verification
        THEN 2FA should be enabled on my account
          AND I should receive 10 backup codes
          AND mfaEnabled should be set to true
          AND mfaBackupCodes should contain hashed codes
    
      Scenario: Login with 2FA
        GIVEN 2FA is enabled on my account
          AND I have entered my password
        WHEN I enter a valid TOTP code
        THEN I should be fully authenticated
          AND the 2fa_pending cookie should be cleared
    

    Password Reset Flow

    Feature: Password Reset
      As a user who forgot my password
      I want to reset it securely
      So that I can regain access to my account
    
      Scenario: Request password reset
        GIVEN I am on the forgot password page
          AND I provide my registered email
        WHEN I submit the form
        THEN I should see "Check your email for reset instructions"
          AND a password reset email should be sent
          AND PASSWORD_RESET_REQUESTED audit event should be logged
    
      Scenario: Complete password reset
        GIVEN I have received a reset email
          AND I click the reset link
          AND I provide a new password (8+ characters)
        WHEN I submit the new password
        THEN my password should be updated
          AND I should be able to log in with the new password
          AND PASSWORD_RESET_COMPLETED audit event should be logged
    

    Dependencies & Risks

    External Dependencies

    DependencyPurposeFallbackRisk Level
    Supabase AuthSession management, password hashingNone (core dependency)🔴 Critical
    Supabase DatabaseUser metadata, OAuth tokensNone (core dependency)🔴 Critical
    PrismaUser profile, portal accessNone (core dependency)🔴 Critical
    SendGridEmail verification, password resetConsole logging (dev)🟡 Medium
    Google OAuthSocial loginEmail/password fallback🟢 Low

    Internal Dependencies

    DependencyPurposeOwner
    /lib/databasePrisma clientCore Team
    /lib/utils/supabase/*Supabase clientsCore Team
    /lib/services/email-verificationVerification emailsEmail Team
    /lib/utils/cookie-configCookie managementCore Team

    Risks & Mitigations

    RiskProbabilityImpactMitigation
    Supabase outageLowCriticalHealth check endpoints, graceful error handling
    Email delivery failureMediumHighRetry logic, SendGrid webhook monitoring
    Rate limit bypassLowHighMultiple limit layers (edge, API, action)
    Token expiry raceMediumMediumRefresh before expiry, session recovery
    2FA lockoutMediumMediumBackup codes, support recovery process
    CSRF token mismatchMediumLowToken refresh on page load, clear error messages

    Technical Architecture

    Authentication Flow Diagram

                                    SIGNUP FLOW
    ┌─────────────────────────────────────────────────────────────────────────────┐
    │                                                                              │
    │  1. Client POST /api/auth/signup                                            │
    │     ├── Rate limit check (5/15min)                                          │
    │     ├── Input validation (Zod schema)                                       │
    │     └── TOS acceptance required                                             │
    │                                                                              │
    │  2. Supabase signUp(email, password)                                        │
    │     ├── Password hashed (bcrypt)                                            │
    │     ├── Session created                                                     │
    │     └── Auth cookies set                                                    │
    │                                                                              │
    │  3. Prisma User creation                                                    │
    │     ├── Generate unique user ID                                             │
    │     ├── Create User record                                                  │
    │     ├── Create default Portal                                               │
    │     └── Create UserPortalAccess                                             │
    │                                                                              │
    │  4. Verification email sent                                                 │
    │     ├── Generate token                                                      │
    │     └── SendGrid delivery                                                   │
    │                                                                              │
    │  5. Redirect to /onboarding?pid={portalId}&new=true                        │
    │                                                                              │
    └─────────────────────────────────────────────────────────────────────────────┘
    
                                    LOGIN FLOW
    ┌─────────────────────────────────────────────────────────────────────────────┐
    │                                                                              │
    │  1. Client POST /api/auth/signin                                            │
    │     ├── Rate limit check                                                    │
    │     └── Input validation                                                    │
    │                                                                              │
    │  2. Supabase signInWithPassword(email, password)                           │
    │     ├── Credentials verified                                                │
    │     └── Session created                                                     │
    │                                                                              │
    │  3. Prisma User lookup                                                      │
    │     ├── Find by email                                                       │
    │     ├── Include UserPortalAccess                                            │
    │     └── Check mfaEnabled                                                    │
    │                                                                              │
    │  4. If 2FA enabled:                                                         │
    │     ├── Return requiresTwoFactor: true                                      │
    │     ├── Set 2fa_pending cookie (5 min)                                      │
    │     └── Wait for /api/auth/2fa/verify                                       │
    │                                                                              │
    │  5. Return user data + portal                                               │
    │     └── Set session cookies                                                 │
    │                                                                              │
    └─────────────────────────────────────────────────────────────────────────────┘
    

    Key Interfaces

    // lib/auth/types.ts
    interface AuthConfig {
      session: {
        strategy: 'jwt';
        maxAge: number; // 30 days
        updateAge: number; // 24 hours
      };
      cookie: {
        httpOnly: boolean;
        secure: boolean;
        sameSite: 'lax' | 'strict';
        maxAge: number;
      };
      routes: {
        signIn: '/auth/signin';
        signUp: '/auth/signup';
        signOut: '/auth/signout';
        error: '/auth/error';
        verifyRequest: '/auth/verify-request';
        newUser: '/onboarding';
      };
    }
    
    // lib/types/auth.ts
    interface AuthUser {
      id: string;
      name: string | null;
      email: string;
      image?: string | null;
      role?: 'admin' | 'manager' | 'agent' | 'viewer' | 'user';
      isPlatformAdmin?: boolean;
      stripeCustomerId?: string | null;
      subscriptionStatus?: 'active' | 'canceled' | 'past_due' | 'trialing';
      onboardingCompleted?: boolean;
      portalAccess?: Array<{ portalId: string; role?: string }>;
      defaultPortalId?: string | null;
    }
    
    interface AuthSession {
      user: AuthUser;
      expires: string;
      accessToken?: string;
      onboardingMode?: 'voice' | 'voice-browser' | 'text' | 'quick-setup' | null;
      onboardingCompleted?: boolean;
    }
    

    Rate Limiting Configuration

    // lib/auth/rate-limit.ts
    export const AUTH_RATE_LIMITS = {
      LOGIN: {
        maxAttempts: 5,
        windowMs: 15 * 60 * 1000, // 15 minutes
        blockDurationMs: 30 * 60 * 1000, // 30 minutes
      },
      SIGNUP: {
        maxAttempts: 3,
        windowMs: 60 * 60 * 1000, // 1 hour
        blockDurationMs: 24 * 60 * 60 * 1000, // 24 hours
      },
      PASSWORD_RESET: {
        maxAttempts: 3,
        windowMs: 60 * 60 * 1000, // 1 hour
        blockDurationMs: 60 * 60 * 1000, // 1 hour
      },
      OTP_VERIFICATION: {
        maxAttempts: 5,
        windowMs: 5 * 60 * 1000, // 5 minutes
        blockDurationMs: 15 * 60 * 1000, // 15 minutes
      },
    };
    

    Security Considerations

    Password Security

    AspectImplementation
    Hashingbcrypt via Supabase (cost factor 10)
    Minimum Length8 characters enforced
    Strength ValidationZod schema + custom password-strength.ts
    StoragePasswords never stored in Prisma - Supabase only

    2FA Security

    AspectImplementation
    AlgorithmTOTP (RFC 6238)
    Secret StorageEncrypted in User.mfaSecret
    Backup Codes10 codes, SHA-256 hashed
    Time Window±1 step (30 seconds each)
    Rate Limiting5 attempts per 5 minutes

    Session Security

    AspectImplementation
    Cookie FlagshttpOnly, secure (prod), sameSite: 'lax'
    Session Duration30 days max, 24-hour refresh
    CSRF ProtectionToken rotation, double-submit cookie
    Session RevocationSupabase signOut clears all tokens

    Audit Events

    enum AuditEventType {
      // Authentication
      LOGIN_SUCCESS = 'LOGIN_SUCCESS',
      LOGIN_FAILED = 'LOGIN_FAILED',
      LOGOUT = 'LOGOUT',
      PASSWORD_RESET_REQUESTED = 'PASSWORD_RESET_REQUESTED',
      PASSWORD_RESET_COMPLETED = 'PASSWORD_RESET_COMPLETED',
      PASSWORD_CHANGED = 'PASSWORD_CHANGED',
      EMAIL_VERIFIED = 'EMAIL_VERIFIED',
    
      // 2FA
      TWO_FA_ENABLED = 'TWO_FA_ENABLED',
      TWO_FA_DISABLED = 'TWO_FA_DISABLED',
      TWO_FA_VERIFIED = 'TWO_FA_VERIFIED',
      TWO_FA_FAILED = 'TWO_FA_FAILED',
    
      // Account
      ACCOUNT_CREATED = 'ACCOUNT_CREATED',
      ACCOUNT_LOCKED = 'ACCOUNT_LOCKED',
      ACCOUNT_UNLOCKED = 'ACCOUNT_UNLOCKED',
    
      // Security
      SUSPICIOUS_ACTIVITY = 'SUSPICIOUS_ACTIVITY',
      RATE_LIMIT_EXCEEDED = 'RATE_LIMIT_EXCEEDED',
      INVALID_CSRF_TOKEN = 'INVALID_CSRF_TOKEN',
    }
    

    Testing Strategy

    Unit Tests

    Test FileTestsCoverage
    /tests/lib/utils/auth.test.tsCore auth utilitiesFunctions
    /tests/lib/utils/authSchemas.test.tsZod validation schemasInput validation
    /tests/lib/utils/auth-errors.test.tsError handlingError codes
    /tests/lib/utils/auth-routes.test.tsRoute protectionMiddleware
    /tests/lib/utils/auth-types.test.tsType definitionsType guards
    /tests/lib/utils/auth-audit-logger.test.tsAudit loggingEvent logging
    /tests/lib/utils/websocket-auth.test.tsWebSocket authReal-time auth
    /tests/unit/secure-auth-state.test.tsState managementAuth state

    Integration Tests

    Test FileTestsCoverage
    /tests/api/auth/auth-flow-integration.test.tsFull auth flowsE2E flows
    /tests/auth/auth-suite.test.tsAuth suiteAll features
    /tests/auth/signup-oauth.test.tsOAuth flowsGoogle OAuth
    /tests/integration/auth-tenancy.test.tsMulti-tenant authPortal isolation
    /tests/integration/flows/03-oauth-connect-sync.test.tsOAuth connectionSync flow

    E2E Tests (Cypress)

    # Run auth E2E tests
    pnpm cypress run --spec "cypress/e2e/auth/**/*.cy.ts"
    

    Manual Testing Checklist

    • Sign up with new email
    • Sign up with existing email (expect error)
    • Sign in with valid credentials
    • Sign in with invalid credentials (expect lockout after 5)
    • Sign in with Google OAuth
    • Password reset request
    • Password reset completion
    • Enable 2FA
    • Login with 2FA
    • Use backup code
    • Sign out

    Metrics & Success Criteria

    Key Performance Indicators

    KPITargetCurrentStatus
    Auth response time<500ms~200ms✅
    Signup success rate>95%~97%✅
    Login success rate>98%~99%✅
    2FA adoption rate>10%~5%🟡
    Password reset completion>80%~85%✅
    Rate limit triggers<1% of requests~0.2%✅

    Service Level Objectives

    SLOTargetMeasurement
    Availability99.9%Auth endpoints uptime
    Latency (p50)<200msMedian response time
    Latency (p99)<1000ms99th percentile
    Error rate<0.1%5xx responses

    Rollout & Release Plan

    Feature Flags

    FlagPurposeDefault
    AUTH_SIGNUPEnable/disable signuptrue
    AUTH_2FAEnable 2FA featuretrue
    AUTH_GOOGLE_OAUTHEnable Google logintrue

    Rollout Phases

    PhaseAudienceDurationSuccess Criteria
    1. InternalTeam only1 weekAll tests pass
    2. Beta100 users2 weeks<1% error rate
    3. GAAll usersOngoingKPIs met

    Observability & Monitoring

    Logging

    // All auth operations use structured logging
    logger.info('Signup attempt started', {
      requestId,
      clientIp,
      userAgent: request.headers.get('user-agent'),
    });
    
    logger.error('Signin error', {
      error: errorMessage,
      requestId,
    });
    

    Key Log Patterns

    PatternMeaningAction
    LOGIN_FAILEDInvalid credentialsMonitor for brute force
    RATE_LIMIT_EXCEEDEDToo many attemptsCheck for attacks
    TWO_FA_FAILEDInvalid TOTP codeMonitor for token theft
    ACCOUNT_LOCKEDLockout triggeredMay need support

    Alerts

    AlertConditionSeverity
    Auth Error Spike>5% error rate in 5 min🔴 Critical
    Rate Limit Surge>100 blocks in 1 min🟡 Warning
    2FA Failure Spike>10 failures per user🟡 Warning
    Supabase Latency>2s p99🔴 Critical

    Environment Variables

    Required

    VariableDescriptionExample
    NEXT_PUBLIC_SUPABASE_URLSupabase project URLhttps://xxx.supabase.co
    NEXT_PUBLIC_SUPABASE_ANON_KEYSupabase anon keyeyJ...
    SUPABASE_SERVICE_ROLE_KEYSupabase service keyeyJ...
    DATABASE_URLPostgreSQL connectionpostgresql://...

    Optional

    VariableDescriptionDefault
    AUTH_SECRETSession encryptionRandom if not set
    NEXTAUTH_URLBase URLAuto-detected
    GOOGLE_CLIENT_IDOAuth client IDNone
    GOOGLE_CLIENT_SECRETOAuth client secretNone

    API Reference

    POST /api/auth/signup

    // Request
    {
      name: string;
      email: string;
      password: string;
      tosAccepted: boolean;
      voiceConsent?: boolean;
    }
    
    // Response (201)
    {
      success: true;
      data: {
        user: { id, email, name, role };
        hasSession: boolean;
        portal: { id, name, petuniaID };
        nextStep: '/onboarding';
      };
    }
    

    POST /api/auth/signin

    // Request
    {
      email: string;
      password: string;
    }
    
    // Response (200)
    {
      success: true;
      data: {
        user: { id, email, name, role, onboardingCompleted };
        portal: { id, petuniaID, name };
        requiresTwoFactor: boolean;
      };
    }
    

    POST /api/auth/2fa/setup

    // Response (200)
    {
      success: true;
      secret: string;      // Base32 TOTP secret for manual entry
      otpauthUrl: string;  // otpauth:// URL for QR code generation
      qrCode: string;      // Base64 data URL of QR code (data:image/png;base64,...)
      message: string;     // User instructions
    }
    

    POST /api/auth/2fa/verify

    // Request
    {
      token: string;  // 6-digit TOTP
    }
    
    // Response (200)
    {
      success: true;
      backupCodes: string[];  // 10 codes (shown once)
      warning: string;
    }
    

    POST /api/auth/reset-password

    // Request
    {
      token: string;
      password: string;
      type?: 'invite' | 'reset';
    }
    
    // Response (200)
    {
      message: 'Password has been reset successfully';
    }
    

    POST /api/auth/passkey/register/options

    // Request: No body required (uses authenticated session)
    
    // Response (200)
    {
      success: true;
      options: {
        challenge: string;      // Base64URL encoded challenge
        rp: { name: string; id: string };
        user: { id: string; name: string; displayName: string };
        pubKeyCredParams: Array<{ type: 'public-key'; alg: number }>;
        timeout: number;
        attestation: 'none';
        excludeCredentials: Array<{ id: string; type: 'public-key'; transports: string[] }>;
        authenticatorSelection: {
          residentKey: 'preferred';
          userVerification: 'preferred';
          authenticatorAttachment: 'platform';
        };
      };
    }
    

    POST /api/auth/passkey/register/verify

    // Request
    {
      response: {
        id: string;
        rawId: string;
        response: {
          attestationObject: string;
          clientDataJSON: string;
          transports?: string[];
        };
        clientExtensionResults: object;
        type: 'public-key';
      };
      name?: string;  // Optional friendly name for the passkey
    }
    
    // Response (200)
    {
      success: true;
      verified: true;
      message: 'Passkey registered successfully';
    }
    

    POST /api/auth/passkey/login/options

    // Request
    {
      email?: string;  // Optional - for usernameless flow, omit email
    }
    
    // Response (200)
    {
      success: true;
      options: {
        challenge: string;
        rpId: string;
        timeout: number;
        userVerification: 'preferred';
        allowCredentials?: Array<{ id: string; type: 'public-key'; transports: string[] }>;
      };
    }
    

    POST /api/auth/passkey/login/verify

    // Request
    {
      response: {
        id: string;
        rawId: string;
        response: {
          authenticatorData: string;
          clientDataJSON: string;
          signature: string;
          userHandle?: string;
        };
        clientExtensionResults: object;
        type: 'public-key';
      };
    }
    
    // Response (200)
    {
      success: true;
      user: { id, email, name, role, onboardingCompleted };
      portal: { id, petuniaID, name } | null;
      requiresTwoFactor: false;  // Passkeys provide strong auth
    }
    

    GET /api/auth/passkey

    // Response (200) - List all passkeys for authenticated user
    {
      success: true;
      passkeys: Array<{
        id: string;
        name: string;
        deviceType: 'singleDevice' | 'multiDevice';
        backedUp: boolean;
        createdAt: string;
        lastUsedAt: string | null;
      }>;
    }
    

    DELETE /api/auth/passkey

    // Request
    {
      id: string;  // UUID of passkey to delete
    }
    
    // Response (200)
    {
      success: true;
      message: 'Passkey deleted successfully';
    }
    

    PATCH /api/auth/passkey

    // Request
    {
      id: string;    // UUID of passkey
      name: string;  // New name (1-100 chars)
    }
    
    // Response (200)
    {
      success: true;
      message: 'Passkey renamed successfully';
    }
    

    Troubleshooting

    Common Issues

    "Invalid login credentials"

    • Verify email/password combination
    • Check if email is verified
    • Check if account is locked (wait 30 min or contact support)

    "User with this email already exists"

    • Use the sign-in page instead
    • Check for OAuth account with same email

    "2FA verification failed"

    • Ensure authenticator time is synced
    • Try a backup code
    • Wait for next 30-second window

    "Rate limit exceeded"

    • Wait 15-30 minutes before retrying
    • Contact support if legitimate use case

    "CSRF verification failed"

    • Refresh the page
    • Clear cookies and retry
    • Ensure JavaScript is enabled

    Debug Commands

    # Check user auth state
    pnpm tsx scripts/debug-user.ts --email user@example.com
    
    # Verify Supabase connection
    pnpm tsx scripts/check-supabase.ts
    
    # Test rate limiter
    pnpm tsx scripts/test-rate-limit.ts --endpoint signin
    

    File Reference

    Core Auth Files

    FilePurposeLines
    /lib/auth/index.tsMain exports~30
    /lib/auth/server.tsServer-side auth~400
    /lib/auth/session.tsSession management~150
    /lib/auth/config.tsAuth configuration~80
    /lib/auth/types.tsInternal types~100
    /lib/auth/client.tsClient-side hooks~200
    /lib/auth/audit.tsAudit logging~215
    /lib/auth/rate-limit.tsRate limiting~100
    /lib/auth/otp-rate-limit.tsOTP rate limiting~80
    /lib/auth/password-strength.tsPassword validation~50
    /lib/auth/withAuth.tsAuth middleware~80
    /lib/auth/auth-helpers.tsHelper re-exports~15
    /lib/auth/auth-monitor.tsSignup monitoring~60

    API Routes

    RouteMethodsPurpose
    /api/auth/signupPOSTUser registration
    /api/auth/signinPOST, GETUser login
    /api/auth/signoutPOSTUser logout
    /api/auth/reset-passwordPOSTPassword reset
    /api/auth/forgot-passwordPOSTRequest reset email
    /api/auth/2fa/setupPOST, GET2FA setup
    /api/auth/2fa/verifyPOSTVerify TOTP
    /api/auth/2fa/disablePOSTDisable 2FA
    /api/auth/2fa/backup-codesPOSTRegenerate backup codes
    /api/auth/passkeyGET, PATCH, DELETEList, rename, delete passkeys
    /api/auth/passkey/register/optionsPOSTGet passkey registration options
    /api/auth/passkey/register/verifyPOSTVerify passkey registration
    /api/auth/passkey/login/optionsPOSTGet passkey login options
    /api/auth/passkey/login/verifyPOSTVerify passkey login

    Client Hooks

    HookLocationPurpose
    useSession/lib/auth/client.tsGet current session
    getServerSession/lib/hooks/auth/getServerSession.tsServer-side session

    Completion Checklist

    Implementation

    • Email/password signup
    • Email/password signin
    • Google OAuth
    • Email verification
    • Password reset flow
    • 2FA setup (TOTP)
    • 2FA verification
    • Backup codes
    • Rate limiting
    • CSRF protection
    • Audit logging
    • Account lockout
    • Partial signup recovery
    • Session management

    Testing

    • Unit tests (17+ test files)
    • Integration tests
    • Auth flow integration test
    • Load testing
    • Penetration testing

    Documentation

    • API documentation
    • Troubleshooting guide
    • Feature documentation (this file)

    Operations

    • Health check endpoints
    • Logging configuration
    • Alerting rules (Sentry)
    • Runbook for incidents

    Version History

    VersionDateChanges
    0.4.22025-12-28WebAuthn/Passkey support with @simplewebauthn, E2E tests with Playwright virtual authenticators
    0.4.12025-12-05Added verification snapshot, owner map reference, and explicit test/CI links
    0.4.02025-12-03QR code generation, 2FA disable flow with TOTP/backup codes
    0.3.92025-12-03Complete documentation, 99% feature coverage
    0.3.02025-11-30Added tenant isolation for non-platform admins
    0.2.02025-09-15Added 2FA with TOTP and backup codes
    0.1.02025-07-10Initial Supabase integration

    Completed:

    • QR code generation for 2FA setup
    • Full 2FA settings UI (enable/disable with TOTP or backup codes)
    • API contracts match implementation
    • WebAuthn/Passkey support with full API (registration, authentication, management)
    • E2E tests for WebAuthn using Playwright virtual authenticators
    • Unit tests for WebAuthn service

    Generated: December 28, 2025

    On this page
    VerificationTable of ContentsProblem / Job-to-Be-DoneUser Pain PointsBusiness ImpactJob StoriesSolution OverviewUser JourneyAuthentication StackKey CapabilitiesScopeIn Scope (v0.4.2)Out of Scope (Future Versions)Acceptance CriteriaSignup FlowLogin Flow2FA FlowPassword Reset FlowDependencies & RisksExternal DependenciesInternal DependenciesRisks & MitigationsTechnical ArchitectureAuthentication Flow DiagramKey InterfacesRate Limiting ConfigurationSecurity ConsiderationsPassword Security2FA SecuritySession SecurityAudit EventsTesting Strategy