feat(auth): GET /v1/auth/memberships synthetic single-tenant stub #65

Merged
YousifShkara merged 1 commit from feat/auth-memberships-stub into main 2026-06-05 09:15:36 +02:00
Owner

The mokosh-clients SPA hits <issuer>/v1/auth/memberships after sign-in to populate its tenant switcher and brand label (see mokosh-clients src/hooks/auth.rs:240+). Bunyip returns 404 today because the endpoint was always slated for the phase-04 multi-tenant work (per AUDIT.md and dev-docs/milestone-1-handoff.md, which records the M1 decision: "a paying MSP is modelled as one owner-user account; org-level billing and seats stay deferred to multi-tenant work"). The 404 surfaces in the SPA as memberships load failed: HTTP 404 and an empty brand area; non-fatal but ugly.

This PR ships the route with a synthetic single-tenant response derived from the authenticated user, so the SPA can decode something coherent without bunyip having to grow an org/membership domain yet. When the real phase-04 multi-tenant story lands, this handler is replaced by a real-row query against an org_memberships table; the wire shape stays.

What the endpoint returns:

{
"memberships": [{
"tenant_id": "00000000-0000-0000-0000-000000000001",
"tenant_name": "<user.email>",
"tenant_kind": "personal",
"role": "owner",
"status": "active",
"is_active": true
}],
"active_tenant_id": "00000000-0000-0000-0000-000000000001"
}

The tenant_id is Uuid::from_u128(1) to match mokosh-server's OIDC_DEFAULT_TENANT_ID fallback (auth/middleware.rs::default_bunyip_tenant_id). Every user JIT-provisioned from a bunyip at+jwt lands in that mokosh tenant today, so the SPA seeing the same id here makes its active_membership() lookup line up with the data scope its PSA API calls actually run against. tenant_name is the user's email so the SPA brand area shows something informative.

Response body is raw JSON, not wrapped in ApiResponse, because the SPA decodes the body directly via issuer_get_authed::<T> (mokosh-clients src/modules/oidc/flow.rs:361). Field names + types are pinned in a unit test against the SPA's MembershipView + Body shapes.

Files:

  • bunyip-api/src/handlers/auth.rs: new synthesise_memberships_response helper + get_memberships handler + unit test pinning the shape.
  • bunyip-api/src/handlers/mod.rs: re-export get_memberships.
  • bunyip-api/src/routes/auth.rs: register GET /memberships under the existing /v1/auth/* scope.

Verified locally:

  • SQLX_OFFLINE=true cargo check --tests
  • SQLX_OFFLINE=true cargo clippy --tests -- -Dwarnings
  • cargo fmt --all --check
  • cargo test synthesise_memberships_response_matches_spa_shape (1/1 passing)

A pre-existing config::tests::test_config_defaults failure in bunyip-domain reproduces on main with RUST_LOG in env; unrelated.

The mokosh-clients SPA hits `<issuer>/v1/auth/memberships` after sign-in to populate its tenant switcher and brand label (see mokosh-clients src/hooks/auth.rs:240+). Bunyip returns 404 today because the endpoint was always slated for the phase-04 multi-tenant work (per AUDIT.md and dev-docs/milestone-1-handoff.md, which records the M1 decision: "a paying MSP is modelled as one owner-user account; org-level billing and seats stay deferred to multi-tenant work"). The 404 surfaces in the SPA as `memberships load failed: HTTP 404` and an empty brand area; non-fatal but ugly. This PR ships the route with a synthetic single-tenant response derived from the authenticated user, so the SPA can decode something coherent without bunyip having to grow an org/membership domain yet. When the real phase-04 multi-tenant story lands, this handler is replaced by a real-row query against an `org_memberships` table; the wire shape stays. What the endpoint returns: { "memberships": [{ "tenant_id": "00000000-0000-0000-0000-000000000001", "tenant_name": "<user.email>", "tenant_kind": "personal", "role": "owner", "status": "active", "is_active": true }], "active_tenant_id": "00000000-0000-0000-0000-000000000001" } The `tenant_id` is `Uuid::from_u128(1)` to match mokosh-server's OIDC_DEFAULT_TENANT_ID fallback (`auth/middleware.rs::default_bunyip_tenant_id`). Every user JIT-provisioned from a bunyip at+jwt lands in that mokosh tenant today, so the SPA seeing the same id here makes its `active_membership()` lookup line up with the data scope its PSA API calls actually run against. `tenant_name` is the user's email so the SPA brand area shows something informative. Response body is raw JSON, not wrapped in `ApiResponse`, because the SPA decodes the body directly via `issuer_get_authed::<T>` (mokosh-clients src/modules/oidc/flow.rs:361). Field names + types are pinned in a unit test against the SPA's `MembershipView` + `Body` shapes. Files: - bunyip-api/src/handlers/auth.rs: new `synthesise_memberships_response` helper + `get_memberships` handler + unit test pinning the shape. - bunyip-api/src/handlers/mod.rs: re-export `get_memberships`. - bunyip-api/src/routes/auth.rs: register `GET /memberships` under the existing `/v1/auth/*` scope. Verified locally: - SQLX_OFFLINE=true cargo check --tests - SQLX_OFFLINE=true cargo clippy --tests -- -Dwarnings - cargo fmt --all --check - cargo test synthesise_memberships_response_matches_spa_shape (1/1 passing) A pre-existing `config::tests::test_config_defaults` failure in `bunyip-domain` reproduces on main with `RUST_LOG` in env; unrelated.
feat(auth): GET /v1/auth/memberships synthetic single-tenant stub
All checks were successful
Create release / Create release from merged PR (pull_request) Has been skipped
Check / fmt / clippy / build / test (pull_request) Successful in 2m29s
ff79eb95d2
The mokosh-clients SPA hits `<issuer>/v1/auth/memberships` after sign-in to populate its tenant switcher and brand label (see mokosh-clients src/hooks/auth.rs:240+). Bunyip returns 404 today because the endpoint was always slated for the phase-04 multi-tenant work (per AUDIT.md and dev-docs/milestone-1-handoff.md, which records the M1 decision: "a paying MSP is modelled as one owner-user account; org-level billing and seats stay deferred to multi-tenant work"). The 404 surfaces in the SPA as `memberships load failed: HTTP 404` and an empty brand area; non-fatal but ugly.

This PR ships the route with a synthetic single-tenant response derived from the authenticated user, so the SPA can decode something coherent without bunyip having to grow an org/membership domain yet. When the real phase-04 multi-tenant story lands, this handler is replaced by a real-row query against an `org_memberships` table; the wire shape stays.

What the endpoint returns:

  {
    "memberships": [{
      "tenant_id":   "00000000-0000-0000-0000-000000000001",
      "tenant_name": "<user.email>",
      "tenant_kind": "personal",
      "role":        "owner",
      "status":      "active",
      "is_active":   true
    }],
    "active_tenant_id": "00000000-0000-0000-0000-000000000001"
  }

The `tenant_id` is `Uuid::from_u128(1)` to match mokosh-server's OIDC_DEFAULT_TENANT_ID fallback (`auth/middleware.rs::default_bunyip_tenant_id`). Every user JIT-provisioned from a bunyip at+jwt lands in that mokosh tenant today, so the SPA seeing the same id here makes its `active_membership()` lookup line up with the data scope its PSA API calls actually run against. `tenant_name` is the user's email so the SPA brand area shows something informative.

Response body is raw JSON, not wrapped in `ApiResponse`, because the SPA decodes the body directly via `issuer_get_authed::<T>` (mokosh-clients src/modules/oidc/flow.rs:361). Field names + types are pinned in a unit test against the SPA's `MembershipView` + `Body` shapes.

Files:
- bunyip-api/src/handlers/auth.rs: new `synthesise_memberships_response` helper + `get_memberships` handler + unit test pinning the shape.
- bunyip-api/src/handlers/mod.rs: re-export `get_memberships`.
- bunyip-api/src/routes/auth.rs: register `GET /memberships` under the existing `/v1/auth/*` scope.

Verified locally:
- SQLX_OFFLINE=true cargo check --tests
- SQLX_OFFLINE=true cargo clippy --tests -- -Dwarnings
- cargo fmt --all --check
- cargo test synthesise_memberships_response_matches_spa_shape (1/1 passing)

A pre-existing `config::tests::test_config_defaults` failure in `bunyip-domain` reproduces on main with `RUST_LOG` in env; unrelated.
YousifShkara deleted branch feat/auth-memberships-stub 2026-06-05 09:15: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/bunyip!65
No description provided.