services/api/src/modules/auth/auth.validation.js

Overview

This file centralizes the input validation rules for the authentication module. It normalizes request bodies and rejects invalid values by throwing ApiError with 400 status codes and specific error metadata.

It supports the auth flows that need strict, deterministic input checks before the controller delegates to the service layer. The file covers registration, sign-in, Google credential handling, account deletion, account restoration, and phone verification.

[!IMPORTANT] This file does not expose HTTP endpoints. It only validates data that arrives from auth.controller.js before service execution. fileciteturn0file8turn0file9

How it works

The module follows a simple pattern: it extracts values from body, normalizes them, validates them against fixed rules, and returns a cleaned object. If any rule fails, it throws ApiError immediately.

The validation functions are designed to be consumed by the auth controller. In the documented auth flow, the controller calls validateRegisterInput and validateSignInInput before invoking the service layer. fileciteturn0file8turn0file9

flowchart TD
    Body[Request body] --> Register[validateRegisterInput]
    Body --> GoogleAuth[validateGoogleAuthInput]
    Body --> GoogleReauth[validateGoogleAccountDeletionReauthInput]
    Body --> PasswordCreate[validatePasswordCreateInput]
    Body --> SignIn[validateSignInInput]
    Body --> Delete[validateDeleteAccountInput]
    Body --> RestoreResend[validateAccountRestorationResendInput]
    Body --> Restore[validateRestoreAccountInput]
    Body --> PhoneStart[validatePhoneVerificationStartInput]
    Body --> PhoneCheck[validatePhoneVerificationCheckInput]

    Register --> CleanRegister[Normalized registration payload]
    GoogleAuth --> CleanGoogle[Credential only]
    GoogleReauth --> CleanGoogleReauth[Credential only]
    PasswordCreate --> CleanPasswordCreate[Token and new password]
    SignIn --> CleanSignIn[Email and password]
    Delete --> CleanDelete[Deletion confirmation payload]
    RestoreResend --> CleanResend[Email only]
    Restore --> CleanRestore[Token and ALTCHA payload]
    PhoneStart --> CleanPhoneStart[Phone only]
    PhoneCheck --> CleanPhoneCheck[Phone and verification code]

    Register -.-> Error[ApiError 400]
    GoogleAuth -.-> Error
    GoogleReauth -.-> Error
    PasswordCreate -.-> Error
    SignIn -.-> Error
    Delete -.-> Error
    RestoreResend -.-> Error
    Restore -.-> Error
    PhoneStart -.-> Error
    PhoneCheck -.-> Error

The main design is deterministic. Each validator has a fixed contract, and each failure returns a predictable error message or code.

[!NOTE] validateRegisterInput builds name from firstName and lastName when the body does not provide name.

Key components

External dependency

{
    "title": "ApiError",
    "content": "Exception class used to reject invalid auth input with HTTP 400 and structured error metadata.",
    "type": "class",
    "filePath": "services/api/src/utils/apiError.js",
    "badges": [
        "public"
    ]
}

ApiError is the error carrier used by every validator in this file. The validators use it to report bad requests and, in some cases, structured fields and codes.

Internal helper

{
    "title": "normalizeUsername",
    "content": "Normalizes usernames by trimming whitespace, removing leading @ characters, and converting the value to lowercase.",
    "type": "function",
    "filePath": "services/api/src/modules/auth/auth.validation.js",
    "badges": [
        "public"
    ]
}

Although it is not exported, this helper is central to username handling. It ensures the same username normalization logic is used every time registration validates a username.

Exported validators

{
    "title": "validateRegisterInput",
    "content": "Validates and normalizes registration data, including identity fields, password rules, role restrictions, and ALTCHA proof.",
    "type": "function",
    "filePath": "services/api/src/modules/auth/auth.validation.js",
    "badges": [
        "public"
    ]
}
{
    "title": "validateGoogleAuthInput",
    "content": "Validates that the Google credential is present and returns it in a normalized object.",
    "type": "function",
    "filePath": "services/api/src/modules/auth/auth.validation.js",
    "badges": [
        "public"
    ]
}
{
    "title": "validateGoogleAccountDeletionReauthInput",
    "content": "Validates the Google credential used to issue account-deletion reauthentication.",
    "type": "function",
    "filePath": "services/api/src/modules/auth/auth.validation.js",
    "badges": [
        "public"
    ]
}
{
    "title": "validatePasswordCreateInput",
    "content": "Validates the Google reauth token and the new password used to create a password for a Google-only user.",
    "type": "function",
    "filePath": "services/api/src/modules/auth/auth.validation.js",
    "badges": [
        "public"
    ]
}
{
    "title": "validateSignInInput",
    "content": "Validates email and password for sign-in requests and returns the normalized credentials.",
    "type": "function",
    "filePath": "services/api/src/modules/auth/auth.validation.js",
    "badges": [
        "public"
    ]
}
{
    "title": "validateDeleteAccountInput",
    "content": "Validates account deletion confirmation, ALTCHA proof, and optional deletion grace preferences.",
    "type": "function",
    "filePath": "services/api/src/modules/auth/auth.validation.js",
    "badges": [
        "public"
    ]
}
{
    "title": "validateAccountRestorationResendInput",
    "content": "Validates the email used to resend an account restoration link.",
    "type": "function",
    "filePath": "services/api/src/modules/auth/auth.validation.js",
    "badges": [
        "public"
    ]
}
{
    "title": "validateRestoreAccountInput",
    "content": "Validates the restoration token and ALTCHA proof used to restore an account.",
    "type": "function",
    "filePath": "services/api/src/modules/auth/auth.validation.js",
    "badges": [
        "public"
    ]
}
{
    "title": "validatePhoneVerificationStartInput",
    "content": "Validates the phone number used to start a phone verification flow.",
    "type": "function",
    "filePath": "services/api/src/modules/auth/auth.validation.js",
    "badges": [
        "public"
    ]
}
{
    "title": "validatePhoneVerificationCheckInput",
    "content": "Validates the phone number and verification code used to complete phone verification.",
    "type": "function",
    "filePath": "services/api/src/modules/auth/auth.validation.js",
    "badges": [
        "public"
    ]
}

Data & fields

Registration payload

[
    {
        "name": "firstName",
        "type": "string",
        "required": true,
        "description": "Trimmed first name. It must exist and contain at least 2 characters."
    },
    {
        "name": "lastName",
        "type": "string",
        "required": true,
        "description": "Trimmed last name. It must exist and contain at least 2 characters."
    },
    {
        "name": "name",
        "type": "string",
        "description": "Full name. If missing, the validator builds it from firstName and lastName."
    },
    {
        "name": "email",
        "type": "string",
        "required": true,
        "description": "Lowercased email validated against the module email pattern."
    },
    {
        "name": "phone",
        "type": "string",
        "required": true,
        "description": "Phone number normalized to E.164 before validation."
    },
    {
        "name": "username",
        "type": "string",
        "description": "Optional username normalized to lowercase without leading @ characters."
    },
    {
        "name": "password",
        "type": "string",
        "required": true,
        "description": "Password that must satisfy the password strength regex."
    },
    {
        "name": "passwordConfirm",
        "type": "string",
        "required": true,
        "description": "Confirmation password that must match password exactly."
    },
    {
        "name": "accountRole",
        "type": "string",
        "description": "Account type. Only creator and seeker are accepted. Defaults to creator."
    },
    {
        "name": "altchaPayload",
        "type": "string",
        "required": true,
        "description": "ALTCHA proof required to pass the anti-robot check."
    },
    {
        "name": "role",
        "type": "string",
        "description": "Disallowed in public registration. Its presence causes an error."
    },
    {
        "name": "systemRole",
        "type": "string",
        "description": "Disallowed in public registration. Its presence causes an error."
    }
]

Google authentication payload

[
    {
        "name": "credential",
        "type": "string",
        "required": true,
        "description": "Google credential token used by the auth flow."
    }
]

Google reauthentication payload for account deletion

[
    {
        "name": "credential",
        "type": "string",
        "required": true,
        "description": "Google credential token reused to issue the deletion reauth token."
    }
]

Password creation payload

[
    {
        "name": "googleReauthToken",
        "type": "string",
        "required": true,
        "description": "Token proving the user reauthenticated with Google."
    },
    {
        "name": "newPassword",
        "type": "string",
        "required": true,
        "description": "New password that must contain at least 8 characters."
    }
]

Sign-in payload

[
    {
        "name": "email",
        "type": "string",
        "required": true,
        "description": "Email lowercased before validation."
    },
    {
        "name": "password",
        "type": "string",
        "required": true,
        "description": "Password required for sign-in."
    }
]

Account deletion payload

[
    {
        "name": "currentPassword",
        "type": "string",
        "description": "Password used when the account uses password authentication."
    },
    {
        "name": "googleReauthToken",
        "type": "string",
        "description": "Token used when the account deletion flow depends on Google reauthentication."
    },
    {
        "name": "confirmation",
        "type": "string",
        "required": true,
        "description": "Must equal ELIMINAR exactly."
    },
    {
        "name": "altchaPayload",
        "type": "string",
        "required": true,
        "description": "ALTCHA proof required for the deletion request."
    },
    {
        "name": "deletionGracePreferences",
        "type": "object",
        "description": "Optional allowlist of account deletion grace preferences."
    }
]
[
    {
        "name": "keepPublishedListingsVisible",
        "type": "boolean",
        "description": "Controls whether published listings stay visible during the grace period."
    },
    {
        "name": "allowLeadCapture",
        "type": "boolean",
        "description": "Controls whether lead capture remains available during the grace period."
    },
    {
        "name": "allowComments",
        "type": "boolean",
        "description": "Controls whether comments remain available during the grace period."
    }
]

The validator only merges these three fields. Any other key is ignored silently.

Account restoration payloads

[
    {
        "name": "token",
        "type": "string",
        "required": true,
        "description": "Restoration token required to restore the account."
    },
    {
        "name": "altchaPayload",
        "type": "string",
        "required": true,
        "description": "ALTCHA proof required for restoration."
    }
]
[
    {
        "name": "email",
        "type": "string",
        "required": true,
        "description": "Email used to resend a restoration link."
    }
]

Phone verification payloads

[
    {
        "name": "phone",
        "type": "string",
        "required": true,
        "description": "Phone number normalized and checked as E.164."
    }
]
[
    {
        "name": "phone",
        "type": "string",
        "required": true,
        "description": "Phone number normalized and checked as E.164."
    },
    {
        "name": "code",
        "type": "string",
        "required": true,
        "description": "Verification code required to complete phone verification."
    }
]

Behaviors

  • normalizeUsername — trims input, removes leading @ characters, lowercases the value, and returns a normalized string.
  • validateRegisterInput — validates the public registration payload, enforces name, email, phone, username, password, account role, and ALTCHA rules, and returns a normalized object.
  • validateGoogleAuthInput — ensures the Google credential exists and returns { credential }.
  • validateGoogleAccountDeletionReauthInput — validates the same Google credential shape used to issue account-deletion reauth tokens.
  • validatePasswordCreateInput — requires a Google reauth token and a new password of at least 8 characters.
  • validateSignInInput — checks email format and requires a non-empty password.
  • normalizeDeletionGracePreferences — merges only the allowed deletion grace preference booleans and ignores any other keys.
  • validateDeleteAccountInput — checks the confirmation keyword, ALTCHA payload, and optional deletion grace preferences, then returns a normalized object.
  • validateAccountRestorationResendInput — validates the email used to resend account restoration links.
  • validateRestoreAccountInput — requires both the restoration token and ALTCHA payload.
  • validatePhoneVerificationStartInput — validates a phone number in E.164 format.
  • validatePhoneVerificationCheckInput — validates the phone number and the verification code.

[!NOTE] validateGoogleAuthInput and validateGoogleAccountDeletionReauthInput have the same field contract. The file separates them by intent, not by data shape. fileciteturn0file8turn0file9

How the validators are used in the auth flow

The auth controller imports these validators and runs them before service calls. The repository context shows the controller using validateRegisterInput and validateSignInInput as the first gate in the registration and sign-in path. fileciteturn0file8turn0file9

sequenceDiagram
    participant Client
    participant Controller as auth.controller.js
    participant Validation as auth.validation.js
    participant Service as auth.service.js

    Client->>Controller: Auth request body
    Controller->>Validation: validate*Input
    Validation-->>Controller: Clean data or ApiError
    Controller->>Service: Domain operation
    Service-->>Controller: Result
    Controller-->>Client: HTTP response

The pattern is consistent across the module. The controller receives req.body, the validator normalizes and checks it, and the service receives only validated data.

Gotchas & edge cases

[!WARNING] validateRegisterInput rejects public registration if role or systemRole is present. The error includes ADMIN_ROLE_NOT_ALLOWED and points to the offending field.

[!WARNING] validateRegisterInput requires altchaPayload. If it is empty, registration fails with a 400 error.

[!CAUTION] username is optional, but when present it must match the allowed pattern and length limits. The validator also strips leading @ characters before checking it.

[!CAUTION] normalizeDeletionGracePreferences ignores every key except the three allowed boolean preferences. Extra client-provided keys do not survive normalization.

[!WARNING] validateDeleteAccountInput requires the confirmation string to be exactly ELIMINAR. Any other value is rejected.

[!WARNING] validateRestoreAccountInput requires both token and altchaPayload. Missing either value produces a 400 error.

[!WARNING] The phone validators require E.164-compatible input after normalization. Invalid phone formatting fails before any downstream operation can continue.

This file is tightly connected to the rest of the auth module.

  • services/api/src/modules/auth/auth.controller.js consumes these validators before calling service methods. fileciteturn0file4turn0file5
  • services/api/src/utils/apiError.js provides the exception type used for all validation failures.
  • services/api/src/utils/phone.js supplies normalizePhoneE164 and isValidE164Phone, which underpin all phone-related validation.
  • services/api/src/modules/auth/auth.routes.js defines the HTTP surface that reaches the controller and, indirectly, this validation layer. fileciteturn0file8turn0file9
  • services/api/src/config/altcha.js provides the ALTCHA challenge and consumption flow used by registration and other sensitive operations. fileciteturn0file4turn0file5

[!IMPORTANT] This file is a pure validation boundary. It does not persist data, create sessions, verify tokens, or call external APIs directly.