feat(portal): add the set-password page the setup email links to #459
Loading…
Reference in a new issue
No description provided.
Delete branch "feat/MAPPS-396-portal-set-password"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
mokosh-server has mailed
{CLIENT_ORIGIN}/portal/set-password?token={token}on every portal-access grant since PMS-136 (src/modules/contacts/service.rs), but the SPA declared no such route, so every customer who clicked the link landed on the catch-all 404 and no contact could ever set a portal password. This adds the page behind that URL.PortalSetPasswordsits in the portal block of theRouteenum, outsideAuthGuard: the emailed single-use token is the only credential the visitor holds, so the page reads it from?token=viacurrent_query_paramand POSTs{token, password}toPOST /api/v1/portal/auth/setup-passwordwith no bearer. The token is{contact_id}.{64 alphanumerics}(ContactService::insert_setup_token), so the raw query value needs no decoding. The form takes a password plus confirmation and enforces the 8-character floor client-side, mirroring thelength(min = 8)validator onPortalSetupPasswordRequest.The endpoint answers 204 with an empty body, which
post_typedcannot decode, sohooks::fetch::apigainspost_typed_no_content: an unauthed POST that only inspects the status and keeps the typedApiErrorso the three documented outcomes stay distinguishable. 204 shows the success panel, 410 reads "This link was already used. Your password is set, so sign in to the portal instead.", and 400 reads "This link is expired or invalid. Ask your account team for a new one." (preferring the server's own message when it carries one, since 400 also covers a rejected password).Success lands on the portal entry point rather than a sign-in form: the
/portal/loginpage is MAPPS-395's deliverable and does not exist yet, so linking there today would send the customer straight back to the 404 this change is fixing. When MAPPS-395 lands, theRoute::PortalHometarget in the success panel becomesRoute::PortalLogin.The recurrence gate is a unit test,
emailed_link_routes::every_emailed_link_resolves_to_a_routein src/lib.rs, which parses every link mokosh-server builds on the SPA origin and asserts none falls through toNotFound. The list is the full set of emitters swept on mokosh-server main at 38c8945, not just the two the issue named:/portal/set-password(contacts),/portal/quotes/{id}(quotes),/reset-password/{token}(auth password reset and staff welcome), and the bare origin (auth security notice, invitations). Verified as a real gate by deleting the new route and watching the test fail with "contacts::send_setup_email emails /portal/set-password, which falls through to the 404 catch-all".Checks green: cargo fmt --all --check, cargo clippy --all-targets -D warnings, cargo check --target wasm32-unknown-unknown, cargo test --lib (245 passed, 0 failed; was 244).
#MAPPS-396
mokosh-server has mailed `{CLIENT_ORIGIN}/portal/set-password?token={token}` on every portal-access grant since PMS-136 (`src/modules/contacts/service.rs`), but the SPA declared no such route, so every customer who clicked the link landed on the catch-all 404 and no contact could ever set a portal password. This adds the page behind that URL. `PortalSetPassword` sits in the portal block of the `Route` enum, outside `AuthGuard`: the emailed single-use token is the only credential the visitor holds, so the page reads it from `?token=` via `current_query_param` and POSTs `{token, password}` to `POST /api/v1/portal/auth/setup-password` with no bearer. The token is `{contact_id}.{64 alphanumerics}` (`ContactService::insert_setup_token`), so the raw query value needs no decoding. The form takes a password plus confirmation and enforces the 8-character floor client-side, mirroring the `length(min = 8)` validator on `PortalSetupPasswordRequest`. The endpoint answers 204 with an empty body, which `post_typed` cannot decode, so `hooks::fetch::api` gains `post_typed_no_content`: an unauthed POST that only inspects the status and keeps the typed `ApiError` so the three documented outcomes stay distinguishable. 204 shows the success panel, 410 reads "This link was already used. Your password is set, so sign in to the portal instead.", and 400 reads "This link is expired or invalid. Ask your account team for a new one." (preferring the server's own message when it carries one, since 400 also covers a rejected password). Success lands on the portal entry point rather than a sign-in form: the `/portal/login` page is MAPPS-395's deliverable and does not exist yet, so linking there today would send the customer straight back to the 404 this change is fixing. When MAPPS-395 lands, the `Route::PortalHome` target in the success panel becomes `Route::PortalLogin`. The recurrence gate is a unit test, `emailed_link_routes::every_emailed_link_resolves_to_a_route` in src/lib.rs, which parses every link mokosh-server builds on the SPA origin and asserts none falls through to `NotFound`. The list is the full set of emitters swept on mokosh-server main at 38c8945, not just the two the issue named: `/portal/set-password` (contacts), `/portal/quotes/{id}` (quotes), `/reset-password/{token}` (auth password reset and staff welcome), and the bare origin (auth security notice, invitations). Verified as a real gate by deleting the new route and watching the test fail with "contacts::send_setup_email emails /portal/set-password, which falls through to the 404 catch-all". Checks green: cargo fmt --all --check, cargo clippy --all-targets -D warnings, cargo check --target wasm32-unknown-unknown, cargo test --lib (245 passed, 0 failed; was 244). #MAPPS-396