Better Auth Harmony

repository·main·Indexed 18 days ago

https://github.com/gekorm/better-auth-harmony

A plugin suite for 'better-auth' providing email and phone number normalization, validation, and security. It includes the emailHarmony plugin to block over 55,000 temporary email domains and manage normalized email addresses, and the phoneHarmony plugin to ensure only normalized phone numbers are stored in the backend.

Tokens
6K
Snippets
16
Records
25
Agent score
57%

What's inside better-auth-harmony

  1. Setup the phoneHarmony plugin

    main

    The phoneHarmony plugin intercepts and modifies the user's phoneNumber, ensuring only normalized numbers are stored in the backend.

    Note: Unlike emailHarmony, this plugin modifies the phoneNumber field directly.

    Installation

    npm i better-auth-harmony

    Configuration

    To use phoneHarmony, you must also include the base phoneNumber() plugin from better-auth/plugins:

    import { betterAuth } from 'better-auth';
    import { phoneNumber } from 'better-auth/plugins';
    import { phoneHarmony } from 'better-auth-harmony';
    
    export const auth = betterAuth({
      // ... other config options
      plugins: [phoneNumber(), phoneHarmony()]
    });

    For details on configuring phoneNumber() validation, refer to the official better-auth documentation.

  2. Setup the emailHarmony plugin

    main

    The emailHarmony plugin provides email normalization (e.g., foo+temp@gmail.com -> foo@gmail.com), phone normalization, and validation to block over 55,000 temporary email domains.

    Installation

    npm i better-auth-harmony

    Configuration

    Add emailHarmony() to your betterAuth configuration:

    import { betterAuth } from 'better-auth';
    import { emailHarmony } from 'better-auth-harmony';
    
    export const auth = betterAuth({
      // ... other config options
      plugins: [emailHarmony()]
    });

    Database Migration

    Run the following command to add the required schema changes:

    npx @better-auth/cli migrate

    Or generate the schema:

    npx @better-auth/cli generate
  3. Configure TypeScript for better-auth-harmony projects

    main

    When working with this repository's configuration, you must maintain two distinct tsconfig files to separate development concerns from build concerns:

    1. tsconfig.json: Used for typechecking, ESLint, and IDE integrations. This file should extend @repo/tsconfig/eslint.json.
    2. tsconfig.build.json: Used specifically for building or bundling the project when necessary.
  4. Configure emailHarmony options

    main

    The emailHarmony() plugin accepts the following configuration options:

    • allowNormalizedSignin (default: false): If true, allows logging in with any version of the unnormalized email address (e.g., johndoe@googlemail.com can log in as john.doe@gmail.com). This performs one extra database query per login.
    • validator: A custom function to validate emails. Defaults to validator.js and Mailchecker.
    • normalizer: A custom function to normalize emails. Defaults to validator.js/normalizeEmail().
    • matchers: Customizes when to run input email validation and normalization. Note that normalization always runs on user creation and update regardless of this setting.
  5. Configure phoneHarmony options

    main

    The phoneHarmony() plugin accepts the following configuration options:

    • defaultCountry: Default country for numbers written in non-international form (without a + sign).
    • defaultCallingCode: Default calling code for numbers written in non-international form. Useful for non-geographic codes like +800.
    • extract (default: true): Defines the "strictness" of parsing. When true, it attempts to extract the phone number from any input string (e.g., "My phone number is (213) 373-4253").
    • acceptRawInputOnError (default: false): If the normalizer fails to parse the number, the original input is used (e.g., "+12" is saved as-is).
    • normalizer: Custom function to normalize phone numbers. Defaults to parsePhoneNumberWithError from libphonenumber-js/max. This can be used to infer country via the Request object (e.g., via IP geolocation).
    • matchers: Customize when to run input phoneNumber validation.
  6. Configure the emailHarmony plugin

    main

    The emailHarmony plugin provides email normalization and validation for better-auth. It automatically handles the creation and updating of a normalizedEmail field in your user database to allow for more flexible login and validation logic.

    Key Options

    • allowNormalizedSignin (boolean): If true, users can log in using any variation of their normalized email (e.g., john.doe@gmail.com can log in even if they signed up as johndoe@googlemail.com). This adds one extra database query per login attempt. Defaults to false.
    • validator (function): A function to validate the email. Defaults to validateEmail (which uses validator.isEmail and mailchecker).
    • normalizer (function): A function to normalize the email address. Defaults to validator.normalizeEmail.
    • matchers (object): Defines which routes the plugin intercepts.
      • signIn: Routes where the plugin should look up users by normalized email if allowNormalizedSignin is enabled. Defaults to allEmailSignIn.
      • validation: Routes where the plugin should validate the email format. Defaults to allEmail.
    • schema (object): Allows customizing the field name used for the normalized email in the database.

    Example Usage

    import emailHarmony from 'better-auth-harmony/email';
    import * as matchers from 'better-auth-harmony/email/matchers';
    
    export const auth = betterAuth({
      // ... other config options
      plugins: [
        emailHarmony({
          allowNormalizedSignin: true,
          matchers: {
            signIn: [matchers.emailOtpVerify, matchers.emailOtpForget, mathers.emailOtpReset]
          }
        })
      ]
    });
    import emailHarmony from 'better-auth-harmony/email';
    import * as matchers from 'better-auth-harmony/email/matchers';
    
    export const auth = betterAuth({
      // ... other config options
      plugins: [
        emailHarmony({
          allowNormalizedSignin: true,
          matchers: {
            signIn: [matchers.emailOtpVerify, matchers.emailOtpForget, matchers.emailOtpReset]
          }
        })
      ]
    });
  7. Configure ESLint using @repo/eslint-config

    main

    The @repo/eslint-config package provides a comprehensive ESLint flat configuration designed for TypeScript and JavaScript projects. It integrates several industry-standard plugins and rule sets to enforce best practices, security, and code style.

    Key Features

    • TypeScript Support: Uses typescript-eslint with strictTypeChecked and stylisticTypeChecked configurations.
    • Airbnb Base: Incorporates Airbnb's best practices, errors, ES6, imports, node, strict, style, and variables rules.
    • Security & Best Practices: Includes eslint-plugin-security, eslint-plugin-promise, and eslint-plugin-unicorn.
    • Import Management: Uses eslint-plugin-import and eslint-plugin-simple-import-sort for organized and valid imports.
    • Testing: Includes specialized configuration for vitest files (**/*.test.{js,jsx,cjs,mjs,ts,tsx,cts,mts}).
    • Multi-format Support: Provides linting for .md (Markdown), .json, and .yaml/.yml files.
    • Prettier Integration: Uses eslint-config-prettier to disable conflicting rules.

    Environment Variables

    • CHECK_REDOS: If set to 'true', the configuration extends plugin:eslint-plugin-redos/recommended to check for Regular Expression Denial of Service (ReDoS) vulnerabilities.
  8. Troubleshoot ESM issues with emailHarmony

    main

    Because validator.js lacks proper ESM support, you may encounter errors in certain environments.

    Error: Error [ERR_MODULE_NOT_FOUND]: Cannot find module

    • Next.js: Add better-auth-harmony to transpilePackages in your next.config.js.
    • Vite: Add better-auth-harmony to ssr.noExternal in your vite.config.ts.

    Error: Cannot use import statement outside a module

    Use one of the following workarounds:

    • Use Node.js 22 or higher.
    • For Node >= 20.10, use the --experimental-detect-module flag:
      • Via environment variable: NODE_OPTIONS=--experimental-detect-module
      • Via CLI: npx --node-options=--experimental-detect-module @better-auth/cli generate
      • Via package.json script:
        { "scripts": { "auth-generate": "NODE_OPTIONS=--experimental-detect-module cli generate" } }
    • If these fail, use yarn patch or npm patch-package to add "type": "module" to the validator package's package.json.
  9. Customize the normalizedEmail field name

    main

    By default, the plugin uses the field name normalizedEmail. You can customize this using the schema option in the emailHarmony configuration.

    emailHarmony({
      schema: {
        user: {
          fields: {
            normalizedEmail: 'custom_normalized_email_field'
          }
        }
      }
    })
    emailHarmony({
      schema: {
        user: {
          fields: {
            normalizedEmail: 'custom_normalized_email_field'
          }
        }
      }
    })
  10. Configure the Phone Harmony plugin

    main

    The phoneHarmony plugin normalizes phone numbers in request bodies to E.164 format before they reach your auth logic. You can configure how numbers are parsed, how errors are handled, and which routes the plugin should intercept.

    Configuration Options

    OptionTypeDefaultDescription
    defaultCountryCountryCodeundefinedThe default country code for parsing non-international numbers (those without a + sign).
    defaultCallingCodestringundefinedThe default calling code for non-international numbers. Useful for non-geographic numbering plans where defaultCountry is unusable.
    extractbooleantrueIf true, the plugin attempts to extract a phone number from a string (e.g., "My number is (213) 373-4253"). If false, the entire string must be the phone number.
    acceptRawInputOnErrorbooleanfalseIf true, the plugin falls back to the original input string if parsing fails. If false, it throws a BAD_REQUEST error (for ParseError) or the original error.
    normalizerNormalizePhoneNumberlibphonenumber-js defaultA custom function to normalize the phone number. It receives the phone string and the request object, allowing for logic like IP-based geolocation.
    matchersMatcher[][allPhone]An array of functions that determine which routes the plugin should process. Use ready-made matchers for specific flows like sign-in or OTP.
    import * as matchers from 'better-auth-harmony/phone/matchers';
    
    export const auth = betterAuth({
      // ... other config options
      plugins: [
        phoneNumber(),
        phoneHarmony({
          matchers: [matchers.signInPhone, matchers.phoneOtp]
        })
      ]
    });