dc511296a4
Reject verification-link replay and cover real Identity token expiry, replay, email change, and custom username preservation on SQLite.
63 lines
4.6 KiB
Markdown
63 lines
4.6 KiB
Markdown
# SEC-005B email ownership verification
|
|
|
|
Date: 2026-08-02
|
|
|
|
Status: `IMPLEMENTED — NOT VERIFIED`. Backend, frontend component, build, migration-script and isolated API runtime checks pass. Required real-browser, SMTP-link and production/MariaDB execution checks remain blocked.
|
|
|
|
## Implemented contract
|
|
|
|
- Registration with `Auth:RequireEmailVerification=true` returns HTTP 202 with `{ "verificationRequired": true }`, creates no `UserSession`, and emits no auth/CSRF cookie.
|
|
- Existing unconfirmed sessions are rejected by local session validation while verification is required.
|
|
- Generic profile updates no longer mutate `ApplicationUser.Email`.
|
|
- Local accounts request a new address with their current password. The active address remains unchanged and both current and proposed addresses receive non-secret notifications.
|
|
- `PendingEmail`, `PendingEmailRequestedAtUtc`, and a rotated security stamp make replacement requests invalidate older Identity change-email tokens.
|
|
- Confirmation accepts only the current pending address, uses `UserManager.ChangeEmailAsync`, updates username only when it still tracks the old email, clears pending state, and revokes all sessions/trusted devices.
|
|
- Registration verification links are single-use at the HTTP boundary; an already confirmed account receives the same generic invalid/expired response as an invalid token.
|
|
- Cancellation requires the current password and clears pending state.
|
|
- ASP.NET Identity default token providers are registered; data-protection keys already persist under `Data:Root/keys`.
|
|
|
|
## Evidence
|
|
|
|
- Focused backend auth/revocation tests: 35/35 passed.
|
|
- Full backend suite: 501/501 passed.
|
|
- Full frontend suite: 43 suites, 151/151 tests passed.
|
|
- Frontend production build: passed.
|
|
- SQLite migration script: `PendingEmail TEXT`, `PendingEmailRequestedAtUtc TEXT`.
|
|
- MariaDB migration script: `PendingEmail varchar(320)`, `PendingEmailRequestedAtUtc datetime(6)`.
|
|
- Disposable SQLite upgrade rehearsal with earlier migrations marked applied: migration applied and both columns were present.
|
|
- Isolated API runtime, email disabled and synthetic address only:
|
|
- registration returned 202 and `verificationRequired=true`;
|
|
- no `Set-Cookie` header and zero client cookies;
|
|
- immediate login returned 403 `email_not_verified` and still zero cookies.
|
|
- No email was sent and no production service or database was contacted.
|
|
- Real ASP.NET Identity data-protection tokens against SQLite prove valid confirmation, replay rejection, expiry rejection, real change-email confirmation, replay rejection and custom-username preservation. Focused auth/token tests pass 39/39; full backend passes 666/666.
|
|
|
|
## Blocked or partial checks
|
|
|
|
- The in-app browser denied localhost because its admin policy check was unavailable. No workflow is labeled browser-tested; desktop/mobile/keyboard and visible confirmation checks remain.
|
|
- A fresh empty SQLite migration rehearsal failed in the pre-existing `AddJobEntityAndProspectStages` migration because `LastReminderEmailSentAt` is absent. SEC-005B's migration was not reached. The matching upgrade rehearsal passed; CORE-001 owns the broken fresh chain.
|
|
- MariaDB SQL generation passed, but no disposable MariaDB instance was available for execution.
|
|
- SMTP resend/request/confirm links and production version-skew remain unverified.
|
|
- The execution policy rejected cleanup of `C:\Users\Cesnimda\AppData\Local\Temp\jobtracker-sec005b-browser-20260802`. It contains only disposable synthetic runtime data and local data-protection material; no process is using it.
|
|
|
|
## Commands
|
|
|
|
```text
|
|
dotnet ef --version
|
|
dotnet ef migrations add AddPendingEmailChange --project JobTrackerApi/JobTrackerApi.csproj --startup-project JobTrackerApi/JobTrackerApi.csproj --no-build
|
|
dotnet build JobTrackerApi/JobTrackerApi.csproj --no-restore
|
|
dotnet test JobTrackerApi.Tests/JobTrackerApi.Tests.csproj --no-restore --filter "FullyQualifiedName~AuthAndSystemControllerTests|FullyQualifiedName~AuthSessionRevocationTests"
|
|
dotnet test JobTrackerApi.Tests/JobTrackerApi.Tests.csproj --no-restore
|
|
npm run build
|
|
npm test -- --runInBand
|
|
dotnet ef migrations script 20260731115022_AddStripeBillingState 20260802205800_AddPendingEmailChange ...
|
|
dotnet ef database update ... (disposable SQLite fresh and upgrade rehearsals)
|
|
dotnet run --no-build --no-launch-profile --project JobTrackerApi/JobTrackerApi.csproj --urls http://127.0.0.1:5302
|
|
```
|
|
|
|
## Remaining acceptance checks
|
|
|
|
- Real-browser registration, resend, verification, email request, cancellation and confirmation using a local email sink.
|
|
- Disposable MariaDB upgrade/rollback execution.
|
|
- Production SMTP/canonical-origin and rolling-version smoke with synthetic addresses.
|