feat(auth): close PMS-138 - subdomain-driven tenant hint on login + forgot-password + Google JIT #89

Merged
YousifShkara merged 1 commit from feat/pms-138-closeout into main 2026-06-05 09:46:31 +02:00
Owner

Adds tenant_id: Option<Uuid> to LoginRequest and ForgotPasswordRequest so the SPA can disambiguate multi-tenant deployments where the same email exists under several tenants. Omitted clients fall back to the default tenant Uuid::from_u128(1), matching db::tenant::default_tenant_id and the OIDC_DEFAULT_TENANT_ID convention so legacy login behaviour converges with the Bunyip at+jwt path.

Renames find_user_by_email and find_user_by_email_optional to find_user_by_email_for_tenant[_optional], binding both tenant_id and email in WHERE. The users.UNIQUE(tenant_id, email) constraint guarantees at most one row, so the old ORDER BY created_at ASC LIMIT 1 tiebreaker (which silently routed colliding-email logins to the oldest tenant) is gone.

Scope expansion vs the stated AC list: the PR also fixes the sibling email-only lookups in login_with_google (JIT-link) and request_password_reset (forgot-password). All three callers now thread the optional hint through the new Self::resolve_tenant_for_login(hint) helper. Multi-tenant Google login remains a future story because the OAuth state cookie does not carry a tenant hint yet.

The Bunyip-issued at+jwt OIDC validation path in RequireAuth (JWKS fetch, audience check, EdDSA verify, claim mapping) is untouched: this PR only hardens the legacy HS256 paths so they match the OIDC path's tenant resolution.

Tests:

  • login_with_tenant_hint_resolves_to_correct_tenant (two tenants with same email, the hint picks the right user)
  • login_omitting_tenant_id_falls_back_to_default (backward compat for current single-tenant SPAs)
  • login_wrong_tenant_hint_returns_401 (wrong hint must not cross-authenticate)
  • forgot_password_with_tenant_hint_targets_correct_user (reset token row scoped to the hinted tenant)

Closes cross-cutting issue #8 in dev-docs/codebase-state.md for the auth module.

#PMS-138

Adds `tenant_id: Option<Uuid>` to LoginRequest and ForgotPasswordRequest so the SPA can disambiguate multi-tenant deployments where the same email exists under several tenants. Omitted clients fall back to the default tenant `Uuid::from_u128(1)`, matching `db::tenant::default_tenant_id` and the OIDC_DEFAULT_TENANT_ID convention so legacy login behaviour converges with the Bunyip at+jwt path. Renames `find_user_by_email` and `find_user_by_email_optional` to `find_user_by_email_for_tenant[_optional]`, binding both `tenant_id` and `email` in WHERE. The `users.UNIQUE(tenant_id, email)` constraint guarantees at most one row, so the old `ORDER BY created_at ASC LIMIT 1` tiebreaker (which silently routed colliding-email logins to the oldest tenant) is gone. Scope expansion vs the stated AC list: the PR also fixes the sibling email-only lookups in `login_with_google` (JIT-link) and `request_password_reset` (forgot-password). All three callers now thread the optional hint through the new `Self::resolve_tenant_for_login(hint)` helper. Multi-tenant Google login remains a future story because the OAuth state cookie does not carry a tenant hint yet. The Bunyip-issued at+jwt OIDC validation path in `RequireAuth` (JWKS fetch, audience check, EdDSA verify, claim mapping) is untouched: this PR only hardens the legacy HS256 paths so they match the OIDC path's tenant resolution. Tests: - `login_with_tenant_hint_resolves_to_correct_tenant` (two tenants with same email, the hint picks the right user) - `login_omitting_tenant_id_falls_back_to_default` (backward compat for current single-tenant SPAs) - `login_wrong_tenant_hint_returns_401` (wrong hint must not cross-authenticate) - `forgot_password_with_tenant_hint_targets_correct_user` (reset token row scoped to the hinted tenant) Closes cross-cutting issue #8 in `dev-docs/codebase-state.md` for the auth module. #PMS-138
feat(auth): close PMS-138 - subdomain-driven tenant hint on login + forgot-password + Google JIT
All checks were successful
Create release / Create release from merged PR (pull_request) Has been skipped
Check / fmt + clippy + compile + tests (pull_request) Successful in 1m38s
Build OCI container / Build and push mokosh-api image (push) Successful in 6m33s
c7d40ed9f3
Adds `tenant_id: Option<Uuid>` to LoginRequest and ForgotPasswordRequest so the SPA can disambiguate multi-tenant deployments where the same email exists under several tenants. Omitted clients fall back to the default tenant `Uuid::from_u128(1)`, matching `db::tenant::default_tenant_id` and the OIDC_DEFAULT_TENANT_ID convention so legacy login behaviour converges with the Bunyip at+jwt path.

Renames `find_user_by_email` and `find_user_by_email_optional` to `find_user_by_email_for_tenant[_optional]`, binding both `tenant_id` and `email` in WHERE. The `users.UNIQUE(tenant_id, email)` constraint guarantees at most one row, so the old `ORDER BY created_at ASC LIMIT 1` tiebreaker (which silently routed colliding-email logins to the oldest tenant) is gone.

Scope expansion vs the stated AC list: the PR also fixes the sibling email-only lookups in `login_with_google` (JIT-link) and `request_password_reset` (forgot-password). All three callers now thread the optional hint through the new `Self::resolve_tenant_for_login(hint)` helper. Multi-tenant Google login remains a future story because the OAuth state cookie does not carry a tenant hint yet.

The Bunyip-issued at+jwt OIDC validation path in `RequireAuth` (JWKS fetch, audience check, EdDSA verify, claim mapping) is untouched: this PR only hardens the legacy HS256 paths so they match the OIDC path's tenant resolution.

Tests:
- `login_with_tenant_hint_resolves_to_correct_tenant` (two tenants with same email, the hint picks the right user)
- `login_omitting_tenant_id_falls_back_to_default` (backward compat for current single-tenant SPAs)
- `login_wrong_tenant_hint_returns_401` (wrong hint must not cross-authenticate)
- `forgot_password_with_tenant_hint_targets_correct_user` (reset token row scoped to the hinted tenant)

Closes cross-cutting issue #8 in `dev-docs/codebase-state.md` for the auth module.

#PMS-138
YousifShkara deleted branch feat/pms-138-closeout 2026-06-05 09:46:32 +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!89
No description provided.