feat(login): handle the emailed sign-in approval code #458

Merged
Claude-Run merged 1 commit from feat/MAPPS-397-spa-login-approval-code into main 2026-08-02 00:07:36 +02:00
Member

With LOGIN_APPROVAL_ENABLED on, mokosh-server's suspicious-login gate (PMS-658) emails a single-use six-digit code and answers the login with empty tokens, user: None and approval_required: true (crates/mokosh-types/src/auth.rs, src/modules/auth/service.rs). The SPA's standalone form never declared that field, so serde dropped it, the response fell into the resp.user match, and the user got "Sign-in succeeded but no account was returned. Try again." Retrying issued a fresh code and repeated forever: a permanent lockout with a message that never mentions the email.

LoginResp now decodes approval_required behind a serde default (older servers omit it) and LoginBody carries approval_code, serialized only when present. A new approval_required arm sits ahead of both the mfa_required arm and the resp.user match, reveals an "Approval code" input, and prompts "Enter the code we emailed you to approve this sign-in."; the next submit re-POSTs the same login with the code. Once the challenge is showing, a 401 means the emailed code was wrong or expired rather than a bad password (the server returns Unauthorized from verify_login_approval), so it maps to "That code is not valid, check the email or try signing in again."

The recurrence gate the issue asks for is a compile-time one rather than the literal type swap. mokosh_types::auth::LoginResponse derives only Serialize and LoginRequest only Deserialize, verified against mokosh-server main at 38c8945, so neither can be used on the client side of the wire yet; the swap also needs the SPA's hand-copied CurrentUser replaced, which MAPPS-383 left out of scope. Instead two #[cfg(test)] functions destructure both shared types exhaustively and map every field onto LoginResp / LoginBody. Dropping approval_required from the pattern fails the build with E0027, so a field added server-side cannot go unread. Adding Serialize / Deserialize upstream and finishing the swap wants its own issue; this run is not permitted to file one in YouTrack.

Gate green via just pre-commit: fmt --check, clippy --all-targets -D warnings, cargo check --target wasm32-unknown-unknown, cargo test --lib (244 passed, 0 failed; was 240).

#MAPPS-397

With LOGIN_APPROVAL_ENABLED on, mokosh-server's suspicious-login gate (PMS-658) emails a single-use six-digit code and answers the login with empty tokens, `user: None` and `approval_required: true` (`crates/mokosh-types/src/auth.rs`, `src/modules/auth/service.rs`). The SPA's standalone form never declared that field, so serde dropped it, the response fell into the `resp.user` match, and the user got "Sign-in succeeded but no account was returned. Try again." Retrying issued a fresh code and repeated forever: a permanent lockout with a message that never mentions the email. `LoginResp` now decodes `approval_required` behind a serde default (older servers omit it) and `LoginBody` carries `approval_code`, serialized only when present. A new `approval_required` arm sits ahead of both the `mfa_required` arm and the `resp.user` match, reveals an "Approval code" input, and prompts "Enter the code we emailed you to approve this sign-in."; the next submit re-POSTs the same login with the code. Once the challenge is showing, a 401 means the emailed code was wrong or expired rather than a bad password (the server returns `Unauthorized` from `verify_login_approval`), so it maps to "That code is not valid, check the email or try signing in again." The recurrence gate the issue asks for is a compile-time one rather than the literal type swap. `mokosh_types::auth::LoginResponse` derives only `Serialize` and `LoginRequest` only `Deserialize`, verified against mokosh-server main at 38c8945, so neither can be used on the client side of the wire yet; the swap also needs the SPA's hand-copied `CurrentUser` replaced, which MAPPS-383 left out of scope. Instead two `#[cfg(test)]` functions destructure both shared types exhaustively and map every field onto `LoginResp` / `LoginBody`. Dropping `approval_required` from the pattern fails the build with E0027, so a field added server-side cannot go unread. Adding `Serialize` / `Deserialize` upstream and finishing the swap wants its own issue; this run is not permitted to file one in YouTrack. Gate green via `just pre-commit`: fmt --check, clippy --all-targets -D warnings, cargo check --target wasm32-unknown-unknown, cargo test --lib (244 passed, 0 failed; was 240). #MAPPS-397
feat(login): handle the emailed sign-in approval code
All checks were successful
Check / fmt + clippy + tests (pull_request) Successful in 1m33s
Create release / Create release from merged PR (pull_request) Has been skipped
a713853488
With LOGIN_APPROVAL_ENABLED on, mokosh-server's suspicious-login gate (PMS-658) emails a single-use six-digit code and answers the login with empty tokens, `user: None` and `approval_required: true` (`crates/mokosh-types/src/auth.rs`, `src/modules/auth/service.rs`). The SPA's standalone form never declared that field, so serde dropped it, the response fell into the `resp.user` match, and the user got "Sign-in succeeded but no account was returned. Try again." Retrying issued a fresh code and repeated forever: a permanent lockout with a message that never mentions the email.

`LoginResp` now decodes `approval_required` behind a serde default (older servers omit it) and `LoginBody` carries `approval_code`, serialized only when present. A new `approval_required` arm sits ahead of both the `mfa_required` arm and the `resp.user` match, reveals an "Approval code" input, and prompts "Enter the code we emailed you to approve this sign-in."; the next submit re-POSTs the same login with the code. Once the challenge is showing, a 401 means the emailed code was wrong or expired rather than a bad password (the server returns `Unauthorized` from `verify_login_approval`), so it maps to "That code is not valid, check the email or try signing in again."

The recurrence gate the issue asks for is a compile-time one rather than the literal type swap. `mokosh_types::auth::LoginResponse` derives only `Serialize` and `LoginRequest` only `Deserialize`, verified against mokosh-server main at 38c8945, so neither can be used on the client side of the wire yet; the swap also needs the SPA's hand-copied `CurrentUser` replaced, which MAPPS-383 left out of scope. Instead two `#[cfg(test)]` functions destructure both shared types exhaustively and map every field onto `LoginResp` / `LoginBody`. Dropping `approval_required` from the pattern fails the build with E0027, so a field added server-side cannot go unread. Adding `Serialize` / `Deserialize` upstream and finishing the swap wants its own issue; this run is not permitted to file one in YouTrack.

Gate green via `just pre-commit`: fmt --check, clippy --all-targets -D warnings, cargo check --target wasm32-unknown-unknown, cargo test --lib (244 passed, 0 failed; was 240).

#MAPPS-397
Claude-Run deleted branch feat/MAPPS-397-spa-login-approval-code 2026-08-02 00:07:37 +02:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
psa-systems/mokosh-apps!458
No description provided.