Skip to main content

Developer reference: email / password login

This document covers only the Govern365 login flow (work email + password → JWT in client). It does not cover signup, invites, OAuth, or post-login app features.

Repository layout

  • Web client: gaicc-app/Clients
  • API server: gaicc-app/Servers

UI surface

ItemLocation
Login pagegaicc-app/Clients/src/presentation/pages/Login.tsx
Routepath: "/login" in gaicc-app/Clients/src/App.tsx, wrapped in PublicOnlyRoute
Public gategaicc-app/Clients/src/presentation/components/PublicOnlyRoute.tsx — if auth.token is already set, user is redirected away from /login (onboarding, optional ?next=, or /dashboard)

UX pattern (two steps, same as register)

  1. Email step: Work email input, Continue → client validation.
  2. Password step: read-only email + Change, Password input, Show/Hide password, link Forgot password?Link to /forgot-password, Sign in submits login mutation.

Not implemented on this screen

  • Remember me — no checkbox or long-lived session flag in Login.tsx (grep across Clients shows no remember usage). Persistence is via redux-persist + localStorage for the auth slice (see below), not a separate "remember" control.

Visible cross-links

  • Forgot password?/forgot-password (ForgotPassword.tsx).
  • Create an accountnavigate("/register") (button, not tested in login-only scope).

Client data flow

ConcernLocation
Login mutationgaicc-app/Clients/src/application/queries/auth.queries.tsuseLogin
HTTPgaicc-app/Clients/src/infrastructure/repositories/auth.repository.tslogin()POST /api/auth/login
DTOgaicc-app/Clients/src/domain/types/auth.types.tsLoginDto
Auth stateuseLogin onSuccesssetAuth({ token, user, workspaces }) in auth.queries.ts
Reducergaicc-app/Clients/src/application/slices/authSlice.ts
Persistencegaicc-app/Clients/src/application/store.tsredux-persist key govern365-root, whitelist: ["auth"], localStorage adapter
Rehydration gategaicc-app/Clients/src/main.tsxPersistGate with loading={null} around <App />
Session refresh after rehydrategaicc-app/Clients/src/presentation/components/AuthBootstrap.tsx — when token exists, calls GET /api/auth/session and setAuth with fresh token / presigned URLs

HTTP client

  • gaicc-app/Clients/src/infrastructure/axiosInstance.ts — attaches Authorization: Bearer <token> when present.
  • /api/auth/login is listed as an "auth endpoint" so a 401 on login does not trigger the global interceptor that clears auth and redirects to /login (that path is for expired session on other APIs).

Success navigation (Login.tsx onSuccess)

Order of checks:

  1. ?next= — if searchParams.get("next") resolves to a safe internal path (safeAppInternalPath), navigate(next).
  2. OnboardingonboardingStep from response (default 3 if missing). If hubProvisioned and step < 1, treat step as 1. If step < 3, logic for onboarding is handled by RequireAuth / RequireOnboarding on protected routes; successful login still navigates to /dashboard when step >= 3 unless workspace rule applies.
  3. No workspaces — if step >= 3 and workspaces.length === 0, navigate to /create-workspace.
  4. Else /dashboard.

(Exact onboarding redirects for incomplete setup are also enforced when hitting protected routes; see RequireAuth.tsx.)


API

MethodPathAuth
POST/api/auth/loginNone

Controllergaicc-app/Servers/src/controllers/auth.controller.ts

  • Zod loginSchema: email (trim, lower, valid email), password (min length 1).
  • Success: res.json(result) (default 200).
  • Validation failure: 400 { message: "Validation failed", errors }.
  • Service UNAUTHORIZED: 401 { message }.

Servicegaicc-app/Servers/src/services/auth.service.ts login

  • Normalize email; load User by email; bcrypt.compare password.
  • Bad user or bad password → Invalid email or password (UNAUTHORIZED) — single message for unknown email vs wrong password.
  • If deletedAt set (and not healed) → Account is deactivated (UNAUTHORIZED).
  • Success → buildAuthResponse(user) (JWT, user payload, workspaces list) — same helper as register.

JWT

  • Signed in signJwt (auth.service.ts) with JWT_SECRET, payload userId, organizationId, role (JwtPayload in domain.layer/interfaces/auth.interfaces.ts).
  • Expiry: JWT_EXPIRES_IN env (default 7d in code).

MiddlewareauthenticateJWT in middleware/auth.middleware.ts for protected routes; login does not use it.


  • UI: gaicc-app/Clients/src/presentation/pages/ForgotPassword.tsx
  • API: POST /api/auth/forgot-passwordauth.service.ts forgotPassword — always returns If that email is registered, a reset link has been sent. to avoid email enumeration.

Session expiry

  • JWT expiry is controlled by JWT_EXPIRES_IN on the server. After expiry, API calls return 401; the axios response interceptor clears auth and sets window.location.href = "/login" for non-auth endpoints (see axiosInstance.ts).