feat(invitations): tenant invite CRUD - Mokosh-owned org membership (PMS-244 phase 1) #178
Loading…
Reference in a new issue
No description provided.
Delete branch "feat/mapps-244-invitations"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
What
Phase 1 of PMS-244 (orgs live in Mokosh, not Bunyip - replaces the closed BUNYIP-95). The additive half: the invitation data model + admin CRUD. Does not touch the login path - phase 2 wires acceptance into the bunyip auth resolution.
How
tenant_invitations(tenant_id FK cascade, email, role, invited_by, status pending|accepted|revoked, 14-day expiry, accepted_at/by). Partial unique index on(tenant_id, lower(email)) WHERE status='pending'(one live invite per tenant+email; re-invite upserts). Second partial index onlower(email)for phase-2's cross-tenant lookup.InvitationsService-create/list_pending/revoke. Emails lowercased; roles validated against the invitable set (all except platformsuper_admin); sending the first invite from apersonaltenant promotes it toorg.POST/GET/DELETE /api/v1/invitations, admin-only, tenant-scoped viau.tenant().Verification
cargo check --all-targets,cargo clippy --all-targets: clean.tests/invitations.rs: create/list/revoke roundtrip, re-invite upsert (no dup), non-invitable role rejected, personal→org promotion - 4 pass.Next (phase 2, separate PR)
Wire acceptance into
auth/middleware.rs::ensure_user_from_bunyip: resolve a pending invite by email → place/re-home the user into that tenant on login; renameensure_tenant_for_bunyip_org→ensure_personal_tenant; remove the deadAtClaims.bunyip_org_idclaim path.🤖 Generated with Claude Code
Phase 2 of PMS-244: wire org membership into the bunyip auth path and retire the dead `bunyip_org_id` claim. With this, orgs live entirely in Mokosh. `ensure_user_from_bunyip` now resolves the user's tenant from Mokosh's own state instead of a token claim, in priority order: a pending invite for the user's verified email wins; else their existing placement; else a brand-new user gets their own `personal` tenant (self-signup). An already-placed user is re-homed into the target on invite acceptance (or the PMS-243 default-tenant backfill) via `rehome_user_between_tenants`; the invite is marked accepted once the user is placed, and a new invited user is seeded with the invite's role. An invite is consumed only for a verified email (gated on userinfo `email_verified`). Supporting pieces: `AuthService::find_user_tenant(sub)` (tenant by global user id), `InvitationsService::{newest_pending_for, accept}`, and `AuthMiddleware::with_invitations`. The router shares the invitations Arc with both the routes and the middleware. Reconciles PMS-240/243: `TenantService::ensure_tenant_for_bunyip_org` -> `ensure_personal_tenant(owner_id)` (same provision + `copy_default_config`, keyed on a new `tenants.personal_owner_id`); migration 036 drops `bunyip_org_id` and adds `personal_owner_id` (no FK to users - the tenant is provisioned before the owner's user row exists); `AtClaims.bunyip_org_id` removed. `rehome_user_between_tenants` is kept and reused as the invite-accept mechanism. Verification: `cargo check --all-targets` + `cargo clippy --all-targets` clean; `tests/invitations.rs` (5, incl. newest-pending lookup -> accept), `tests/tenants.rs` (7, incl. `ensure_personal_tenant` provisioning + re-home), `tests/auth.rs` (14), doctest guard - all pass. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>