Problem
RegisterRequest.email (app/models/auth.py) is a plain str:
class RegisterRequest(LoginRequest):
full_name: str = Field(..., min_length=1)
role: Role = Role.engineer
with no EmailStr type or format validator. AuthStore.register uses it directly as the primary identity and the sessions/audit/GDPR join key.
Consequences:
- Junk identities are first-class accounts:
"a", "not-an-email", or "x"*500 all register; the email becomes the unique key for sessions, token families, audit events, and GDPR lookups, so malformed emails pollute every auth-adjacent table.
- GDPR erase/export are keyed on the email:
erase_user_data and export_user_data filter audit rows by email == user.email — a garbage email that happens to substring-match or equal another user's malformed input can cross-match, and duplicate malformed emails are rejected only by the users.email unique index (an opaque 500 via IntegrityError instead of a clean 400).
- The API promises registration semantics it does not validate: OpenAPI shows
email: string with no format, so clients cannot rely on server-side email validation existing.
Root cause
The auth models were written before (or without) Pydantic's EmailStr/format validation, and the register endpoint never added a manual check.
Why this is architecturally hard
- The fix (an
EmailStr field or field_validator) is small, but it changes the 422 validation behavior for existing clients that may have sent non-email strings; the change must be documented in the OpenAPI snapshot.
- Email normalization (lowercase? trim?) is a related decision:
get_by_email is exact-match, so User@X.com and user@x.com are different accounts today; choosing normalization affects dedupe and session lookups.
- A test must assert registration rejects malformed emails and that the error is a structured 422, not an
IntegrityError 500 on duplicates — the repo has no such test.
Proposed design
Use EmailStr (or an explicit validator) for RegisterRequest.email/LoginRequest.email, decide and implement normalization, and add tests for malformed, duplicate, and case-variant emails.
Acceptance criteria
Service
Tests
Out of scope
Role assignment (tracked separately) and password policy.
Getting started
pytest tests/test_auth*.py tests/test_be_205_228_236_238.py -q
make typecheck
Good first files to read: app/models/auth.py, app/api/v1/endpoints/auth.py, app/services/auth_store.py.
Problem
RegisterRequest.email(app/models/auth.py) is a plainstr:with no
EmailStrtype or format validator.AuthStore.registeruses it directly as the primary identity and the sessions/audit/GDPR join key.Consequences:
"a","not-an-email", or"x"*500all register; the email becomes the unique key for sessions, token families, audit events, and GDPR lookups, so malformed emails pollute every auth-adjacent table.erase_user_dataandexport_user_datafilter audit rows byemail == user.email— a garbage email that happens to substring-match or equal another user's malformed input can cross-match, and duplicate malformed emails are rejected only by theusers.emailunique index (an opaque 500 viaIntegrityErrorinstead of a clean 400).email: stringwith no format, so clients cannot rely on server-side email validation existing.Root cause
The auth models were written before (or without) Pydantic's
EmailStr/format validation, and the register endpoint never added a manual check.Why this is architecturally hard
EmailStrfield orfield_validator) is small, but it changes the 422 validation behavior for existing clients that may have sent non-email strings; the change must be documented in the OpenAPI snapshot.get_by_emailis exact-match, soUser@X.comanduser@x.comare different accounts today; choosing normalization affects dedupe and session lookups.IntegrityError500 on duplicates — the repo has no such test.Proposed design
Use
EmailStr(or an explicit validator) forRegisterRequest.email/LoginRequest.email, decide and implement normalization, and add tests for malformed, duplicate, and case-variant emails.Acceptance criteria
Service
IntegrityError500.Tests
Out of scope
Role assignment (tracked separately) and password policy.
Getting started
pytest tests/test_auth*.py tests/test_be_205_228_236_238.py -q make typecheckGood first files to read:
app/models/auth.py,app/api/v1/endpoints/auth.py,app/services/auth_store.py.