fix/pms-241-fmt-bunyip-org-tenant #176

Closed
David wants to merge 0 commits from david/fix/pms-241-fmt-bunyip-org-tenant into main AGit
Owner
feat(auth): resolve SSO users to a per-org tenant via the Bunyip org claim (PMS-240)
Replaces the v1 stopgap where every Bunyip/OIDC user JIT-landed in one shared default tenant (the root cause behind PMS-239). The bunyip path now reads an opaque bunyip_org_id claim and resolves it to a dedicated mokosh tenant, auto-provisioning one on first login of a new org.

Design (per the agreed contract): Bunyip emits an org id, mokosh maps it to a tenant via a new nullable tenants.bunyip_org_id column (partial unique index, so legacy tenants stay NULL while each org maps to exactly one tenant). TenantService::ensure_tenant_for_bunyip_org does select-or-provision: on first sight it inserts an org-kind tenant ON CONFLICT (bunyip_org_id) DO NOTHING (concurrent first-logins converge on one tenant - the race loser re-reads the winner), initialises the ticket/invoice sequences, and runs copy_default_config so statuses/priorities/queues/SLA + notification templates exist out of the box. The SSO user is JIT-mirrored into the resolved tenant as before.

Forward-compatible and inert until Bunyip ships the claim: AtClaims.bunyip_org_id is #[serde(default)], so current tokens deserialize it to None and fall back to default_bunyip_tenant_id() - byte-for-byte the pre-PMS-240 behaviour. The tenant service is threaded into AuthMiddleware via with_tenants; when it (or the claim) is absent the default-tenant fallback is used. A failed org→tenant resolution drops the bunyip path rather than landing the user in the wrong tenant.

Security: the claim rides the already-verified Bunyip at+jwt (iss/aud/signature checked by BunyipVerifier), so it is as authoritative as sub / bunyip_role.

Out of scope (follow-ups): the Bunyip side must actually emit bunyip_org_id; backfilling users already mirrored into the default tenant; and a human-facing tenant name/slug (auto-provisioned tenants get Org <id> + a uuid-derived slug, renamable later).

Verification: cargo check --all-targets + cargo clippy --all-targets clean; tests/tenants.rs adds ensure_tenant_for_bunyip_org_provisions_then_is_idempotent (provision -> same-org idempotent -> distinct-org distinct -> default config copied), full tenants suite (6) and auth suite (14) green.

Co-Authored-By: Claude Opus 4.8 (1M context) noreply@anthropic.com

style(auth): cargo fmt the with_tenants signature (PMS-241)
The check.yml cargo fmt --check step failed on the feat/mapps-240-bunyip-org-tenant branch (PR #175, run #845): the with_tenants builder signature in src/modules/auth/middleware.rs was spread across multiple lines and rustfmt wanted it collapsed to one. Ran cargo fmt --all; only that signature changed.

#PMS-241

feat(auth): resolve SSO users to a per-org tenant via the Bunyip org claim (PMS-240) : Replaces the v1 stopgap where every Bunyip/OIDC user JIT-landed in one shared default tenant (the root cause behind PMS-239). The bunyip path now reads an opaque `bunyip_org_id` claim and resolves it to a dedicated mokosh tenant, auto-provisioning one on first login of a new org. Design (per the agreed contract): Bunyip emits an org id, mokosh maps it to a tenant via a new nullable `tenants.bunyip_org_id` column (partial unique index, so legacy tenants stay NULL while each org maps to exactly one tenant). `TenantService::ensure_tenant_for_bunyip_org` does select-or-provision: on first sight it inserts an `org`-kind tenant `ON CONFLICT (bunyip_org_id) DO NOTHING` (concurrent first-logins converge on one tenant - the race loser re-reads the winner), initialises the ticket/invoice sequences, and runs `copy_default_config` so statuses/priorities/queues/SLA + notification templates exist out of the box. The SSO user is JIT-mirrored into the resolved tenant as before. Forward-compatible and inert until Bunyip ships the claim: `AtClaims.bunyip_org_id` is `#[serde(default)]`, so current tokens deserialize it to `None` and fall back to `default_bunyip_tenant_id()` - byte-for-byte the pre-PMS-240 behaviour. The tenant service is threaded into `AuthMiddleware` via `with_tenants`; when it (or the claim) is absent the default-tenant fallback is used. A failed org→tenant resolution drops the bunyip path rather than landing the user in the wrong tenant. Security: the claim rides the already-verified Bunyip at+jwt (iss/aud/signature checked by `BunyipVerifier`), so it is as authoritative as `sub` / `bunyip_role`. Out of scope (follow-ups): the Bunyip side must actually emit `bunyip_org_id`; backfilling users already mirrored into the default tenant; and a human-facing tenant name/slug (auto-provisioned tenants get `Org <id>` + a uuid-derived slug, renamable later). Verification: `cargo check --all-targets` + `cargo clippy --all-targets` clean; `tests/tenants.rs` adds `ensure_tenant_for_bunyip_org_provisions_then_is_idempotent` (provision -> same-org idempotent -> distinct-org distinct -> default config copied), full tenants suite (6) and auth suite (14) green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> style(auth): cargo fmt the with_tenants signature (PMS-241) : The check.yml `cargo fmt --check` step failed on the feat/mapps-240-bunyip-org-tenant branch (PR #175, run #845): the `with_tenants` builder signature in src/modules/auth/middleware.rs was spread across multiple lines and rustfmt wanted it collapsed to one. Ran `cargo fmt --all`; only that signature changed. #PMS-241
feat(auth): resolve SSO users to a per-org tenant via the Bunyip org claim (PMS-240)
Some checks failed
Check / fmt + clippy + compile + tests (pull_request) Failing after 15s
E2E (staging) / Playwright against staging (pull_request) Successful in 47s
Build OCI container / Build and push mokosh-api image (push) Successful in 6m49s
Create release / Create release from merged PR (pull_request) Has been skipped
6b8ed51e9a
Replaces the v1 stopgap where every Bunyip/OIDC user JIT-landed in one shared default tenant (the root cause behind PMS-239). The bunyip path now reads an opaque `bunyip_org_id` claim and resolves it to a dedicated mokosh tenant, auto-provisioning one on first login of a new org.

Design (per the agreed contract): Bunyip emits an org id, mokosh maps it to a tenant via a new nullable `tenants.bunyip_org_id` column (partial unique index, so legacy tenants stay NULL while each org maps to exactly one tenant). `TenantService::ensure_tenant_for_bunyip_org` does select-or-provision: on first sight it inserts an `org`-kind tenant `ON CONFLICT (bunyip_org_id) DO NOTHING` (concurrent first-logins converge on one tenant - the race loser re-reads the winner), initialises the ticket/invoice sequences, and runs `copy_default_config` so statuses/priorities/queues/SLA + notification templates exist out of the box. The SSO user is JIT-mirrored into the resolved tenant as before.

Forward-compatible and inert until Bunyip ships the claim: `AtClaims.bunyip_org_id` is `#[serde(default)]`, so current tokens deserialize it to `None` and fall back to `default_bunyip_tenant_id()` - byte-for-byte the pre-PMS-240 behaviour. The tenant service is threaded into `AuthMiddleware` via `with_tenants`; when it (or the claim) is absent the default-tenant fallback is used. A failed org→tenant resolution drops the bunyip path rather than landing the user in the wrong tenant.

Security: the claim rides the already-verified Bunyip at+jwt (iss/aud/signature checked by `BunyipVerifier`), so it is as authoritative as `sub` / `bunyip_role`.

Out of scope (follow-ups): the Bunyip side must actually emit `bunyip_org_id`; backfilling users already mirrored into the default tenant; and a human-facing tenant name/slug (auto-provisioned tenants get `Org <id>` + a uuid-derived slug, renamable later).

Verification: `cargo check --all-targets` + `cargo clippy --all-targets` clean; `tests/tenants.rs` adds `ensure_tenant_for_bunyip_org_provisions_then_is_idempotent` (provision -> same-org idempotent -> distinct-org distinct -> default config copied), full tenants suite (6) and auth suite (14) green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
style(auth): cargo fmt the with_tenants signature (PMS-241)
Some checks failed
E2E (staging) / Playwright against staging (pull_request) Failing after 19s
Check / fmt + clippy + compile + tests (pull_request) Successful in 1m27s
Create release / Create release from merged PR (pull_request) Has been skipped
366a1fb068
The check.yml `cargo fmt --check` step failed on the feat/mapps-240-bunyip-org-tenant branch (PR #175, run #845): the `with_tenants` builder signature in src/modules/auth/middleware.rs was spread across multiple lines and rustfmt wanted it collapsed to one. Ran `cargo fmt --all`; only that signature changed.

#PMS-241
Author
Owner

Closing as redundant. Despite the description leading with the PMS-240 feature, the only commit ahead of main here is 366a1fb (style(auth): cargo fmt the with_tenants signature, PMS-241), and its entire diff collapses the with_tenants signature in src/modules/auth/middleware.rs from four lines to one. main already has that signature on one line (identical), and the PMS-240 feature itself is already in main via PR #175 (commit 6b8ed51, this PR's merge-base).

The merge conflict is a single stale doc comment, not code: this PR's comment describes the PMS-240 bunyip_org_id claim design, which main has since superseded with the PMS-243 / PMS-244 invite + self-signup placement (/// Wire the tenant service so the bunyip path can provision a user's personal tenant on self-signup (PMS-244).). Resolving toward main (the current truth) drops this commit to empty, so there is nothing to merge. Taking this PR's side would regress the doc comment to a removed design.

It is an AGit PR with no server branch (head only at refs/pull/176/head), so "Update Branch" was never available.

Closing as redundant. Despite the description leading with the PMS-240 feature, the only commit ahead of `main` here is `366a1fb` (`style(auth): cargo fmt the with_tenants signature`, PMS-241), and its entire diff collapses the `with_tenants` signature in `src/modules/auth/middleware.rs` from four lines to one. `main` already has that signature on one line (identical), and the PMS-240 feature itself is already in `main` via PR #175 (commit `6b8ed51`, this PR's merge-base). The merge conflict is a single stale doc comment, not code: this PR's comment describes the PMS-240 `bunyip_org_id` claim design, which `main` has since superseded with the PMS-243 / PMS-244 invite + self-signup placement (`/// Wire the tenant service so the bunyip path can provision a user's personal tenant on self-signup (PMS-244).`). Resolving toward `main` (the current truth) drops this commit to empty, so there is nothing to merge. Taking this PR's side would regress the doc comment to a removed design. It is an AGit PR with no server branch (head only at `refs/pull/176/head`), so "Update Branch" was never available.
David closed this pull request 2026-06-13 12:49:15 +02:00
Some checks are pending
E2E (staging) / Playwright against staging (pull_request) Failing after 19s
Check / fmt + clippy + compile + tests (pull_request) Successful in 1m27s
Required
Details
Create release / Create release from merged PR (pull_request) Has been skipped
E2E / * (pull_request)
Required

Pull request closed

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!176
No description provided.