feat(auth): lazily re-home default-tenant users to their org tenant (PMS-243) #177

Merged
longjacksonle merged 1 commit from feat/mapps-243-rehome into main 2026-06-11 18:12:32 +02:00

What

Implements PMS-243: lazily re-home users still parked in the shared default tenant to their real org tenant. Follow-up to PMS-240 (#175, now merged).

How

On the first org-claimed login, the bunyip path moves the user's users row out of the default tenant before the org-tenant lookup. AuthService::rehome_user_between_tenants(user, from, to) runs UPDATE users SET tenant_id = to WHERE id = user AND tenant_id = from - scoped to from_tenant (a correctly-placed user is never moved) and idempotent (no-op once moved). The middleware calls it only when the resolved tenant differs from the default.

Also a correctness fix for PMS-240

upsert_user_from_oidc does ON CONFLICT (id) DO UPDATE without touching tenant_id. So an existing default-tenant user presenting a bunyip_org_id claim would have: missed the org-tenant lookup, fallen through the JIT upsert (row stays in default), missed the re-lookup, and been dropped by the bunyip path - unable to log in. The re-home closes that gap.

Scope (per PMS-243 decision)

Only the users row moves. Data created while many orgs shared the default tenant is co-mingled and can't be auto-attributed to one org, so it stays put for separate triage.

Forward-compatible

Inert until Bunyip emits bunyip_org_id (BUNYIP-95): with no claim the resolved tenant IS the default, so the re-home guard is skipped.

Verification

  • cargo check --all-targets, cargo clippy --all-targets: clean.
  • tests/tenants.rs: new rehome_moves_default_tenant_user_to_org_tenant_once (move once -> idempotent no-op). tenants + auth suites green.

🤖 Generated with Claude Code

## What Implements PMS-243: lazily re-home users still parked in the shared default tenant to their real org tenant. Follow-up to PMS-240 (#175, now merged). ## How On the first org-claimed login, the bunyip path moves the user's `users` row out of the default tenant before the org-tenant lookup. `AuthService::rehome_user_between_tenants(user, from, to)` runs `UPDATE users SET tenant_id = to WHERE id = user AND tenant_id = from` - scoped to `from_tenant` (a correctly-placed user is never moved) and idempotent (no-op once moved). The middleware calls it only when the resolved tenant differs from the default. ## Also a correctness fix for PMS-240 `upsert_user_from_oidc` does `ON CONFLICT (id) DO UPDATE` without touching `tenant_id`. So an existing default-tenant user presenting a `bunyip_org_id` claim would have: missed the org-tenant lookup, fallen through the JIT upsert (row stays in default), missed the re-lookup, and been dropped by the bunyip path - unable to log in. The re-home closes that gap. ## Scope (per PMS-243 decision) Only the `users` row moves. Data created while many orgs shared the default tenant is co-mingled and can't be auto-attributed to one org, so it stays put for separate triage. ## Forward-compatible Inert until Bunyip emits `bunyip_org_id` (BUNYIP-95): with no claim the resolved tenant IS the default, so the re-home guard is skipped. ## Verification - `cargo check --all-targets`, `cargo clippy --all-targets`: clean. - `tests/tenants.rs`: new `rehome_moves_default_tenant_user_to_org_tenant_once` (move once -> idempotent no-op). tenants + auth suites green. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
feat(auth): lazily re-home default-tenant users to their org tenant (PMS-243)
Some checks failed
Check / fmt + clippy + compile + tests (pull_request) Failing after 18s
E2E (staging) / Playwright against staging (pull_request) Successful in 48s
Build OCI container / Build and push mokosh-api image (push) Successful in 3m47s
Create release / Create release from merged PR (pull_request) Has been skipped
b78fa59014
Stacked on PMS-240 (#175). Users JIT-mirrored into the shared default tenant before per-org tenants existed are re-homed to their resolved org tenant on their first org-claimed login: the bunyip path now moves the `users` row out of the default tenant before the org-tenant lookup.

This is also a correctness fix for #175, not just cleanup: `upsert_user_from_oidc` does `ON CONFLICT (id) DO UPDATE` without touching `tenant_id`, so an existing default-tenant user presenting a `bunyip_org_id` claim would have missed the org-tenant lookup, fallen through the JIT upsert (which leaves the row in the default tenant), missed the re-lookup, and been dropped by the bunyip path - i.e. unable to log in. The re-home closes that gap.

`AuthService::rehome_user_between_tenants(user, from, to)` issues `UPDATE users SET tenant_id = to WHERE id = user AND tenant_id = from`. Scoped to `from_tenant` so a user already correctly placed in some other tenant is never moved, and idempotent (a no-op once moved, via `rows_affected`). The middleware calls it only when the resolved tenant differs from the default. Only the `users` row moves: data created while many orgs shared the default tenant is co-mingled and cannot be auto-attributed to one org, so it stays put for separate triage (PMS-243 decision).

Forward-compatible and inert until Bunyip emits `bunyip_org_id` (BUNYIP-95): with no claim the resolved tenant IS the default, so the re-home guard is skipped entirely.

Verification: `cargo check --all-targets` + `cargo clippy --all-targets` clean; `tests/tenants.rs` adds `rehome_moves_default_tenant_user_to_org_tenant_once` (move once -> idempotent no-op); tenants (7) + auth (14) green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
longjacksonle deleted branch feat/mapps-243-rehome 2026-06-11 18:12: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!177
No description provided.