08 — Form Specifications (Auth Module)
- 1. Login Form —
POST /auth/login(login.dto.ts) - 2. Register Form —
POST /auth/register(register.dto.ts) - 3. Forgot Password —
POST /auth/forgot-password(forgot-password.dto.ts) - 4. Reset Password —
POST /auth/reset-password(reset-password.dto.ts) - 5. 2FA (enable/verify/disable) —
totp-setup.dto.ts - 6. API Key Create —
POST /auth/api-keys(create-api-key.dto.ts) - 7. Verify Email —
verify-email.dto.ts - Form-level rules (all)
- Client-side error priority
Every form field-by-field for the Auth module. Validation mirror class-validator decorators exactly from
src/modules/auth/dto/*.ts; messages follow theVALIDATION_ERROR(400) envelope with per-fielddetails. Passwords havehed server side (Argon2id). Client validates inline then submits; server 400 shadows client.
1. Login Form — POST /auth/login (login.dto.ts)
| # | Field | Label | Hint / Placeholder | Keyboard | Autofill | Validation (server) | Client UX |
|---|---|---|---|---|---|---|---|
| 1 | email | john.doe@springfield.edu | emailAddress | username | @IsEmail() required | trim+lower; error: "Enter a valid email"; TextInputAction.next | |
| 2 | password | Password | type your password | text(default) | current-password | @IsString() + @MinLength(6) required | obscure+reveal; error: Password must be at least 6 characters; onSubmitted submits |
Cta: primary full 48 h, loading replaces label. Server 401 → field-level inline "Invalid
email or password." (both fields). Double-submit blocked (00-shared/08 §6).
2. Register Form — POST /auth/register (register.dto.ts)
Form metadata: firstName(req), lastName(req), email(req), password(req, min8),
tenantId(req), phone(opt).
| # | Field | Type | Autofill | Decorators | Notes/UX |
|---|---|---|---|---|---|
| 3 | firstName | text | given-name | @IsString() req | first on form |
| 4 | lastName | text | family-name | @IsString() req | — |
| 5 | email | username | @IsEmail() req | normalized lowercase by server (user.schema.ts:28) | |
| 6 | phone | tel (optional) | tel | @IsOptional() @IsString() | country-code prefix; no server format check |
| 7 | password | password | new-password | @IsString() @MinLength(8) | min 8; show strength meter (client-only, not server policy) |
| 8 | passwordConfirm | password (client-only) | — | — | must equal password; no server field — local only |
| 9 | tenantId | text | — | @IsString() req | school slug/tenant id; placeholder "e.g. springfield-school" |
Submit → loading → server:
- 201/409: 409
DUPLICATE_RESOURCE→ banner "An account with this email already exists." - success: navigate
/home+ "verify your email" banner (server mints tokens inregister(); navigating to/homeuses them).
Password minimums: register 8, login 6 (MinLength mismatch between
register.dto.ts:19 and login.dto.ts:12 — flag: login accepting 6-char passwords implies
legacy/other-created accounts; display rule is min 8 for new).
3. Forgot Password — POST /auth/forgot-password (forgot-password.dto.ts)
| # | Field | Type | Keyboard | Autofill | Validation |
|---|---|---|---|---|---|
| 10 | email | — | username | @IsEmail() required |
Behavior: submit disabled until valid email; on success a neutral confirmation screen
(identical copy whether the account exists — server returns same message for both, auth.service.ts:256-283). Rate auth 3/min (auth.controller.ts:92-95). On 429 → disable resubmit + countdown.
4. Reset Password — POST /auth/reset-password (reset-password.dto.ts)
| # | Field | Type | Autofill | Validation |
|---|---|---|---|---|
| 11 | token | hidden (from deep-link ?token=) | — | @IsString() required |
| 12 | password | password | new-password | @IsString() @MinLength(8) |
| 13 | passwordConfirm | password | new-password | = password (client) |
Errors: token invalid → banner 400 "Invalid or expired reset token.", button "Request a new
link" (→ forgot). Token expired → banner "Reset token has expired" (server: auth.service.ts:293-296).
Success → "Password reset successfully." (clears sessions) → re-login.
5. 2FA (enable/verify/disable) — totp-setup.dto.ts
One field token (TOTP code): 6 digits (default TOTP_DIGITS=6; env.ts:78), numeric,
8 digits max (min/max from env.ts:78), keyboard number, Regex validation client
^\d{6,8}$, exactly 6 unless org config differs.
6. API Key Create — POST /auth/api-keys (create-api-key.dto.ts)
| Field | Type | Required | Validation | UX |
|---|---|---|---|---|
name | text | ✓ | @IsString() | max length 48 (client), autofocus |
scopes | chips | no | @IsArray() @IsString({each:true}) | free-text input chips; hinted "e.g. report.generate"; no enum validation server-side — free string list |
Submit → returns {id, prefix, key} → swaps form for AppSecretReveal. Done → list refresh.
If 429 → disable button, countdown.
7. Verify Email — verify-email.dto.ts
token @IsString() — presented via deep link or manual paste box "Enter your code".
Form-level rules (all)
- Double-submit: disabled while pending; stop-media-first.
- Optimistic: no optimistic writes for any auth mutation (tokens/credentials are never guessed) — always server-confirm.
- Undo: none applicable to credential/secret actions; revoke has a confirm (14).
- Abandonment: pre-auth config is single-state; clearing email preserves injected token.
- Keyboard:
.nextsequence, last.done;Entersubmits. - Password managers:
autofillHintsset on every auth field (09 §10). - Error copy: from
messageof envelope only for business 4xx; codes for the rest.
Client-side error priority
- 400 VALIDATION → field.
- 401 UNAUTHENTICATED → form message.
- 409 DUPLICATE_RESOURCE → inline.
- 422 BUSINESS_RULE_VIOLATION → banner.
- 429 RATE_LIMITED → countdown.
- 5xx → AppErrorState.