Skip to content

Sign-in and account (Admin)

Admin sign-in and password reset (forgot → OTP → new password) use the NestJS API. Registration remains a simulated SPA flow (no backend call). This page maps routes and expectations.


GoalsNon-goals
Give operators a single map of auth URLs and behaviorsGuarantee self-service registration without backend support
Call out in-memory OTP / multi-instance caveatsDocument Laravel legacy auth internals

  • / — public marketing landing (no JWT); loads GET /api/v1/public/landing-settings for document title, meta tags, and hero text (fallbacks + i18n). /login — sign-in (real API). /forgot-password, /verify-otp, /reset-passwordreal password reset (see Authentication and API paths below). /registersimulated only.
  • JWT and admin profile semantics: Authentication, Admin panel overview.

  1. User opens /login (or follows Sign in from /) → enters credentials → receives JWT → redirect to /dashboard.
  2. Forgot password → POST /api/v1/auth/admin/password-reset → enter OTP on /verify-otpPOST …/verify returns reset_token/reset-passwordPOST …/complete.

  • Use HTTPS in production; never expose admin tokens in screenshots or shared URLs.
  • Prefer org-owned provisioning for high-privilege admins; review register availability before exposing publicly.

IDScenarioExpected
AA1Valid loginLands on /dashboard with working session
AA2Bad passwordClear error; no silent success
AA3Forgot password + OTP + resetValid account: OTP path works; invalid email still shows generic success (anti-enumeration)

PathPurpose
/Marketing landing (public); Sign in/login
/loginSign in
/registerRegistration UI (see limitations below)
/forgot-passwordRequest password recovery
/verify-otpOTP entry (see limitations below)
/reset-passwordSet a new password (see limitations below)

Source: apps/admin-web/src/routes/app-router.tsx.

The login form calls the backend POST /auth/admin/login with email, password, and admin_type:

  • admin_employee — staff (default toggle in the UI).
  • admin — super / non-employee admin (second toggle).

On success, the app stores access and refresh tokens and profile fields in a persisted Zustand store (nipos-admin-auth), then navigates to /dashboard. It optionally calls GET /auth/admin/me to refresh the profile.

If you already have a valid access token (after hydration), visiting / or /login redirects to /dashboard.

Routes under /dashboard require a JWT. Unauthenticated users are sent back to /login, with the intended path preserved for redirect after login (see ProtectedRoute).

Flow uses admin-password-reset-api.ts with skipAuthHandler: true so OTP errors do not trigger JWT refresh.

  • Request: POST /api/v1/auth/admin/password-reset{ "email", "admin_type" } (admin_employee | admin). Response is always a generic success message.
  • Verify: POST /api/v1/auth/admin/password-reset/verify{ "email", "admin_type", "code" }{ "reset_token" }.
  • /verify-otp UI: one field for the full 6-digit code (non-digits are stripped on submit; paste and one-time-code autofill supported).
  • Complete: POST /api/v1/auth/admin/password-reset/complete{ "reset_token", "password" } (min 8 characters).

Session storage (sessionStorage) carries email, admin_type, and reset_token between steps. Operational caveats (in-memory OTP, logging): Authentication.

/register still uses timeouts and navigates to /dashboard without a backend call. Treat as non-production; prefer Master Admin / IT provisioning (see Admin panel overview).

The SPA attaches the access token to API requests and uses refresh handling shared with src/lib/api.ts. For token lifecycle and endpoints, see Authentication.