Skip to main content

Auth

What it is​

Signup, login, and account-recovery for the platform. Supports password-based auth, Google/Apple OAuth, and a gift-link-driven signup path that ties new accounts to a gift/event they arrived from.

Core concepts​

  • Login identifier is email-or-phone, not username — identifier is checked for an @ to decide which lookup to run (getUserByEmail vs. getUserByPhoneNumber after formatPhoneNumber normalization). Username isn't a login credential.
  • Two separate ban checks on every login attempt: once against the raw identifier (email/phone) before the user record is even loaded, and again against the resolved user's current email/phone/username/name after lookup. The second check exists specifically to catch someone who changed their contact info after being banned — a ban on the original identifier alone wouldn't survive that.
  • Phone verification can happen inline with signup — POST /auth/signup accepts an optional otp in the payload; if present, it's verified immediately and the account is created already phone-verified, skipping the separate verify-otp round trip. If omitted, the account is created unverified and POST /auth/verify-otp / POST /auth/resend-otp handle it afterward.

How it works​

Standard signup/login​

  1. POST /auth/signup — creates the account (password + phone/email), optionally verifying phone via an inline OTP as above.
  2. POST /auth/verify-otp / POST /auth/resend-otp — complete phone verification if it wasn't bundled into signup. Both require authMiddleware, so the account must already be created and the client already holding a token — verification is a post-signup step against an authenticated session, not a pre-signup gate.
  3. POST /auth/signin — identifier + password, returns a JWT (signToken(user.id, Role.User)).
  4. POST /auth/logout — invalidates the current session/token.

OAuth (Google / Apple)​

POST /auth/google and POST /auth/apple each verify the provider's token/credential server-side and either log in an existing account or create one, bypassing the password/OTP flow entirely — these are the accounts that never get a password field populated.

POST /auth/signup-with-eventid (signupWithGiftLink) — a distinct signup path used when someone arrives via a gift link (Secret Santa / My Birthday — see event types in the Events guide) rather than the general app. Takes the usual profile fields plus slug and sourceEventId, linking the new account back to the gift event that brought them in (eventTypeId is used instead when sourceEventId is 0, for signups not tied to a specific existing gift event).

Account recovery​

POST /auth/forgot-password/initiate → POST /auth/forgot-password/verify — a standard initiate/verify OTP pair (Zod-validated, unlike most of this module which is still on validatorjs), separate from the signup-time OTP flow.

Key business rules & edge cases​

  • Suspended accounts fail login with a distinct error (AccountStatus.SUSPENDED) before password is even checked in some paths — don't conflate "wrong password" and "account suspended" when building client-side error handling; they're different failure modes with different remediation (contact support vs. retry).
  • A successful login fires an async, non-blocking Customer.io identify call (creates-or-updates the CRM profile with login_method: 'password') — a failure here is logged but never fails the login itself.
  • Because phone verification can be satisfied either inline at signup or via the separate OTP endpoints, don't assume a freshly-created account is unverified — check isPhoneNumberVerified rather than inferring it from which signup path was used.

POST /v1/auth/signup, POST /v1/auth/signin, POST /v1/auth/google, POST /v1/auth/apple, POST /v1/auth/signup-with-eventid, POST /v1/auth/verify-otp, POST /v1/auth/resend-otp, POST /v1/auth/logout, POST /v1/auth/forgot-password/initiate, POST /v1/auth/forgot-password/verify. Not yet in the generated OpenAPI spec.

  • src/modules/auth/auth.route.ts, auth.controller.ts, auth.service.ts
  • src/modules/user/user.service.ts — createUser (shared by signup)
  • src/models/ban-list.model.ts — BanListModel.checkBanned
  • src/shared/utils/jwt.util.ts — signToken