porchlight/docs/plans/2026-02-20-profile-validation-design.md
2026-02-20 15:01:55 +01:00

73 lines
2.5 KiB
Markdown

# Profile Form Validation Design
## Problem
The self-service (`/manage/profile`) and admin (`/admin/users/{id}/profile`) profile
forms have minimal validation: email only checks for `@`, phone number has no format
validation at all. Validation logic is duplicated inline in both route handlers.
## Decision
Use Pydantic types for field validation via a shared `ProfileUpdate` model.
- **Email**: `pydantic.EmailStr` (uses `email-validator` under the hood)
- **Phone**: `pydantic_extra_types.phone_numbers.PhoneNumberValidator` with
`number_format="E164"` — accepts various input formats, normalizes to strict
E.164 (`+46701234567`). Backed by Google's `libphonenumber`.
- **Picture URL**: custom Pydantic validator (http/https scheme, has netloc)
- **Field lengths**: `Field(max_length=...)` on each field
## New dependency
`pydantic-extra-types[phonenumbers]` — brings in the `phonenumbers` package.
## Shared validation module
`src/porchlight/validation.py`:
```python
from typing import Annotated
from pydantic import BaseModel, EmailStr, Field
from pydantic_extra_types.phone_numbers import PhoneNumberValidator
E164Phone = Annotated[
str, PhoneNumberValidator(number_format="E164")
]
class ProfileUpdate(BaseModel):
given_name: str = Field(default="", max_length=255)
family_name: str = Field(default="", max_length=255)
preferred_username: str = Field(default="", max_length=255)
email: EmailStr | None = None
phone_number: E164Phone | None = None
picture: str = Field(default="", max_length=2048) # + URL validator
locale: str = Field(default="", max_length=20)
```
Route handlers instantiate `ProfileUpdate` from form data, catch `ValidationError`,
and return the first user-friendly error as the existing `HTMLResponse` with
`role="alert"`.
## Route changes
Both `manage/routes.py` and `admin/routes.py` profile POST handlers will:
1. Parse form data through `ProfileUpdate`
2. Use validated/normalized values from the model
3. Replace all inline validation with a single try/except
## Template changes
Add `pattern` and `title` attributes on the phone input for browser-native hints
about E.164 format.
## Validation: server-side only
No client-side JS validation. The HTML `type="email"` and `type="tel"` attributes
already provide basic browser hints. Server-side validation via htmx response
handles all error display.
## Tests
- Unit tests for `ProfileUpdate` (valid/invalid emails, phones, URLs, lengths)
- Integration tests for manage profile POST (happy path + validation errors)
- Existing admin tests may be tightened