fix(auth): close invite, signup, password, and validation defects #203

Merged
David merged 2 commits from fix/pms-192-invite-signup-password-validation into main 2026-06-13 23:10:16 +02:00
Owner

Make new-user invite acceptance atomic: the tenant membership INSERT moves into the SERIALIZABLE accept_once tx in mokosh-auth-storage, so a crash can no longer leave a user with no membership. The org-role is derived from the invite role via MembershipRole::from_user_role instead of being hardcoded to Admin in the HTTP handler, so Member/ReadOnly invites are no longer over-privileged. The fire-and-forget membership create in handlers/invites.rs is removed.

Enforce caller-email == invite-email (case-insensitive) in accept_invitation (handlers/orgs.rs) so a forwarded invite link cannot let a different authenticated user join the org.

Stop returning expired invites: add AND expires_at > NOW() to invite::list_open and org_invitation::list_open_for_tenant.

Return the DB-clock consumed_at from mfa_challenge::consume_once via RETURNING consumed_at instead of echoing the app clock.

Check the confirmation match before the password policy and argon2 work in handlers/password_reset.rs, so a mismatch never triggers an expensive hash or leaks policy detail.

Measure password length by Unicode scalar count (chars().count()) in policy.rs, not byte length, so multi-byte passwords are not miscounted.

Stop the pre-2FA data leak: LoginResponse.user is now Option<CurrentUser>, set to None (and omitted from JSON) while mfa_required is true; the legacy auth service sets it accordingly.

Validate the shared request types: must_match on confirm_password for ResetPasswordRequest and ChangePasswordRequest, and a digit-only check on MfaEnableRequest.code.

Return NotFound from membership::set_status when no row is updated, matching the rows_affected guard already used by set_role.

#PMS-192

Co-Authored-By: Claude Opus 4.8 noreply@anthropic.com

Make new-user invite acceptance atomic: the tenant membership INSERT moves into the SERIALIZABLE `accept_once` tx in mokosh-auth-storage, so a crash can no longer leave a user with no membership. The org-role is derived from the invite role via `MembershipRole::from_user_role` instead of being hardcoded to Admin in the HTTP handler, so Member/ReadOnly invites are no longer over-privileged. The fire-and-forget membership create in handlers/invites.rs is removed. Enforce caller-email == invite-email (case-insensitive) in `accept_invitation` (handlers/orgs.rs) so a forwarded invite link cannot let a different authenticated user join the org. Stop returning expired invites: add `AND expires_at > NOW()` to `invite::list_open` and `org_invitation::list_open_for_tenant`. Return the DB-clock `consumed_at` from `mfa_challenge::consume_once` via `RETURNING consumed_at` instead of echoing the app clock. Check the confirmation match before the password policy and argon2 work in handlers/password_reset.rs, so a mismatch never triggers an expensive hash or leaks policy detail. Measure password length by Unicode scalar count (`chars().count()`) in policy.rs, not byte length, so multi-byte passwords are not miscounted. Stop the pre-2FA data leak: `LoginResponse.user` is now `Option<CurrentUser>`, set to None (and omitted from JSON) while `mfa_required` is true; the legacy auth service sets it accordingly. Validate the shared request types: `must_match` on confirm_password for ResetPasswordRequest and ChangePasswordRequest, and a digit-only check on MfaEnableRequest.code. Return NotFound from `membership::set_status` when no row is updated, matching the rows_affected guard already used by `set_role`. #PMS-192 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
fix(auth): close invite, signup, password, and validation defects
Some checks failed
E2E / Playwright against staging (pull_request) Failing after 32s
Check / fmt + clippy + compile + tests (pull_request) Successful in 2m31s
Build OCI container / Build and push mokosh-api image (push) Successful in 3m37s
2eb89770fa
Make new-user invite acceptance atomic: the tenant membership INSERT moves into the SERIALIZABLE `accept_once` tx in mokosh-auth-storage, so a crash can no longer leave a user with no membership. The org-role is derived from the invite role via `MembershipRole::from_user_role` instead of being hardcoded to Admin in the HTTP handler, so Member/ReadOnly invites are no longer over-privileged. The fire-and-forget membership create in handlers/invites.rs is removed.

Enforce caller-email == invite-email (case-insensitive) in `accept_invitation` (handlers/orgs.rs) so a forwarded invite link cannot let a different authenticated user join the org.

Stop returning expired invites: add `AND expires_at > NOW()` to `invite::list_open` and `org_invitation::list_open_for_tenant`.

Return the DB-clock `consumed_at` from `mfa_challenge::consume_once` via `RETURNING consumed_at` instead of echoing the app clock.

Check the confirmation match before the password policy and argon2 work in handlers/password_reset.rs, so a mismatch never triggers an expensive hash or leaks policy detail.

Measure password length by Unicode scalar count (`chars().count()`) in policy.rs, not byte length, so multi-byte passwords are not miscounted.

Stop the pre-2FA data leak: `LoginResponse.user` is now `Option<CurrentUser>`, set to None (and omitted from JSON) while `mfa_required` is true; the legacy auth service sets it accordingly.

Validate the shared request types: `must_match` on confirm_password for ResetPasswordRequest and ChangePasswordRequest, and a digit-only check on MfaEnableRequest.code.

Return NotFound from `membership::set_status` when no row is updated, matching the rows_affected guard already used by `set_role`.

#PMS-192

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Merge origin/main into fix/pms-192-invite-signup-password-validation
Some checks failed
Create release / Create release from merged PR (pull_request) Has been skipped
Check / fmt + clippy + compile + tests (pull_request) Failing after 26s
E2E / Playwright against staging (pull_request) Failing after 24s
52b32c5108
#PMS-192
David merged commit 66367d536f into main 2026-06-13 23:10:16 +02:00
David deleted branch fix/pms-192-invite-signup-password-validation 2026-06-13 23:10:16 +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-server!203
No description provided.