Skip to main content

OTP API audit

Updated: 2026-05-14

Scope

This audit covers the current mobile auth OTP flows and the Golden Lotus Shop API endpoints they call:

  • Registration OTP: create pending account, resend registration OTP, verify registration OTP.
  • Forgot PIN / reset PIN by OTP: request reset PIN OTP, verify OTP, set the new 6 digit PIN.
  • Legacy forgot password: Vendure password-reset email flow.

Backend changes were pushed to origin/develop before this audit:

  • a0d5b3e Add mobile reset PIN OTP API
  • fd4a736 Notify customers when bookings are updated

Runtime status

Local Docker runtime was rebuilt and recreated for server and worker.

Checked endpoints:

  • GET http://127.0.0.1:38104/health -> 200
  • GET https://web-golden.azns.vn/health -> 200
  • GET http://127.0.0.1:38104/dashboard/ -> 200
  • GET https://web-golden.azns.vn/dashboard/ -> 200

Runtime GraphQL checks:

  • requestLotusRegisterOtp returned ok: true.
  • requestLotusResetPinOtp returned ok: true, providerConfigured: true.
  • resetLotusPinWithOtp exists in the schema and rejects an invalid OTP with the expected invalid/expired OTP error.
  • Vendure core requestPasswordReset returned Success for a non-existing audit email.

API inventory

Registration OTP

Backend:

  • registerCustomerAccount(input: RegisterCustomerInput!)
  • requestLotusRegisterOtp(input: LotusRequestRegisterOtpInput!)
  • verifyLotusRegisterOtp(input: VerifyLotusRegisterOtpInput!)

Mobile:

  • authService.startRegistration()
  • authService.requestRegisterOtp()
  • authService.verifyRegisterOtp()

Notes:

  • Initial registration uses Vendure registerCustomerAccount, which triggers the registration email handler.
  • Resend uses requestLotusRegisterOtp.
  • Verify uses verifyLotusRegisterOtp and creates a Vendure shop session when successful.

Forgot PIN / reset PIN OTP

Backend:

  • requestLotusResetPinOtp(input: LotusRequestResetPinOtpInput!)
  • resetLotusPinWithOtp(input: ResetLotusPinWithOtpInput!)

Mobile:

  • authService.requestResetPinOtp()
  • authService.resetPinWithOtp()
  • LoginScreen forgot PIN steps.

Notes:

  • Request accepts identifier and optional channel.
  • Complete reset accepts identifier, otpCode, newPin, optional channel, optional rememberMe.
  • Successful reset updates the native password/PIN and creates a Vendure shop session.

Legacy forgot password

Backend:

  • Vendure core requestPasswordReset(emailAddress: String!)

Mobile:

  • authService.forgotPassword()
  • ForgotPasswordScreen

Notes:

  • This is not the new OTP reset PIN flow.
  • It sends a Vendure password reset email/link.
  • It only supports email even though the screen currently validates email or phone.

Security and behavior

Current good points:

  • OTP codes are stored as HMAC hashes, not plain text.
  • OTP verification uses timingSafeEqual.
  • OTP purpose is scoped: REGISTER, LOGIN, and RESET_PIN.
  • OTP lookup is channel-aware.
  • Old active OTPs for the same purpose/email are consumed when a new OTP is created.
  • Requesting login/reset PIN OTP does not reveal whether the account exists.
  • Reset PIN checks the resolved user id before accepting an OTP.
  • New PIN is normalized to digits and must be exactly 6 digits.
  • OTP TTL, resend cooldown, and max attempts are configurable via env.

Observed limits:

  • Cooldown is email/purpose based, not IP/device based.
  • requestRegistrationOtp delegates to Vendure verification-token refresh; the response is generic, but the mobile UI assumes provider availability after initial registration.
  • Email provider detection checks EMAIL_DEV_MODE or EMAIL_TRANSPORT/EMAIL_PROVIDER, while Vendure email config defaults dev mode from APP_ENV. Keep these envs aligned in deployed environments.

Findings

  1. ForgotPasswordScreen accepts email or phone, but the backend call is Vendure requestPasswordReset(emailAddress).

    Impact: entering a phone can pass mobile validation but fail or behave incorrectly against the email-only API.

    Suggested fix: either restrict this screen to email only, or route phone/PIN recovery through the reset PIN OTP flow.

  2. forgotPassword is a legacy password reset link flow, not OTP.

    Impact: the screen copy says "code", but the API is Vendure password-reset email/link.

    Suggested fix: update copy or retire this route in favor of the in-login "Quen ma PIN" flow.

  3. Mobile registration start hardcodes providerConfigured: true and expiresInSeconds: 300 after registerCustomerAccount.

    Impact: if email provider is disabled or misconfigured, the UI can tell the customer that OTP was sent even when delivery is unavailable.

    Suggested fix: expose a backend registration-start wrapper that returns the same LotusMobileOtpResult, or check lotusMobileAuthConfig/provider state before showing success.

  4. Forgot PIN OTP screen shows a static resend timer and has no resend action on that step.

    Impact: customer cannot request a new reset PIN OTP from that screen after waiting.

    Suggested fix: wire a real countdown and call requestResetPinOtp again when the resend action is enabled.

  5. Public OTP request endpoints should add IP/device throttling before heavy public traffic.

    Impact: current cooldown protects per email/purpose, but not broad enumeration or spam attempts across identifiers.

    Suggested fix: add app-level throttle middleware or a small OTP rate-limit table keyed by IP/device/channel.

Source references

Backend:

  • docker/vendure_backend/apps/server/src/plugins/golden-lotus/services/lotus-email-otp.service.ts
  • docker/vendure_backend/apps/server/src/plugins/golden-lotus/api/shop/lotus-mobile-core.resolver.ts
  • docker/vendure_backend/apps/server/src/plugins/golden-lotus/api/shop-api-extensions.ts
  • docker/vendure_backend/apps/server/src/plugins/golden-lotus/email/lotus-register-otp-email.handler.ts
  • docker/vendure_backend/apps/server/src/plugins/golden-lotus/email/lotus-reset-pin-otp-email.handler.ts
  • docker/vendure_backend/apps/server/static/email/templates/lotus-register-otp/body.hbs
  • docker/vendure_backend/apps/server/static/email/templates/lotus-reset-pin-otp/body.hbs

Mobile:

  • api/services.ts
  • features/auth/screens/LoginScreen.tsx
  • features/auth/screens/RegisterScreen.tsx
  • features/auth/screens/ForgotPasswordScreen.tsx
  • features/auth/hooks.ts