Skip to main content

Try the sample

How to run the sample, what its UI shows, one call followed to its refusal, and the presets, which are calls that each break one rule.

Starting the sample​

The sample needs Docker and nothing else. From the repository's root:

dotnet run --project Examples/Tenancy/Examples.Tenancy.AppHost
  • Aspire's dashboard is at http://localhost:15105, with the login link that the terminal prints. It shows the containers (db, mail, auth), the steps that run once (roles, migrate), the API (api) and the UI (ui).
  • The API seeds the two tenants when Sample:SeedDemoData is on. appsettings.Development.json turns it on. A start that finds the data already there seeds nothing.
  • The AppHost creates new containers on every run, so every run starts with a fresh seed. Restart the AppHost to start over.
  • Sign in with a card on the dev login page, or at Supabase Auth as <name>@example.test with the demo-password parameter from the dashboard.
  • To run without Aspire, on the stack that the Supabase CLI starts, see the samples' page.
warning

The dev login signs anyone in as anyone. The host maps it only in Development. The host refuses to start with the dev login on in any other environment, with a JWT secret under 32 bytes, or with a Supabase URL that is not on the local machine. Dev login tokens are ordinary Supabase access tokens, and the same bearer handler checks them. With the dev login off and no JWT secret, the host checks tokens against the keys that a real Supabase project publishes.

What the UI shows​

The UI is a Blazor app. It calls the API with the signed-in person's token and the Tenant header, as any client does.

PageShows
Sign inA card per person. Each person starts in the first tenant, by slug, where they have an active seat. Seth's only seat is suspended, so he sees its refusal first. The top bar switches tenant.
Who am IThe seat, its placements with their dated grants, and its roles, marked when they manage access. Every permission held now, with the units it reaches.
My projectsThe projects that the seat sees, its roles on each, and whether it reaches each project through the crew or the organization.
A projectIts crew, its inspections and its actions. A caller who manages the crew also sees the members' dated roles. An action button is filled when the seat may use it, and outlined ("not allowed, try anyway") when not. Every button sends the call and shows the answer.
Crew rolesThe tenant's project roles. Make, rename and archive them, and set their permissions.
Try itAny call by hand, as the person, as nobody, or in another tenant. The directory, asked about one id. The presets.
Access historyWho changed whose access, newest first, for a caller with tenancy.​history.​view.
InvitationsThe open invitations. Revoke one, or invite by address. The token is shown once, as a link.
Accept an invitationAccepts that token as the signed-in person. For example, Juno accepts Tove's invitation into Meadow Gardens, and the tenant picker then lists Meadow Gardens.

The pages ask the directory for the names behind the API's ids (DirectoryNames). They ask once per id per page load, with one call per kind. The project roles come in one GET /project-roles. An id that the directory does not return shows as its first characters.

One refused call, step by step​

Every call takes the same path:

  1. The bearer handler finds the person from the token.
  2. The Tenant header and the person's seats give the seat.
  3. The request's requirement lets through a caller who may send the request.
  4. Tenancy's manager refuses what the seat may not do.

In the example, Rhea grants Leo a role that manages access, with permissions she lacks. The requirement lets her through, because she holds tenancy.grants.manage at the unit. SeatManager.GrantAsync refuses. This is why the manager checks again. Green: the requirement lets the call through. Red: the manager refuses it.

The response, shortened. It also carries the problem's type and a traceId.

{
"status": 403,
"title": "That reaches past what you hold yourself, there or for that long: tenancy.roles.manage, tenancy.settings.manage.",
"code": "tenancy.grant-exceeds-own",
"arguments": {
"Role": "e0000000-0000-4000-8000-000000000107",
"Missing": "tenancy.roles.manage, tenancy.settings.manage"
}
}
  • The header only names a tenant. The seat comes from the person's own seats, found by the token's verified identity. So a tenant where the caller has no seat returns tenancy.not-seated, the same as a tenant that does not exist.
  • The status follows the kind of refusal: 400, 403, 404 or 409. A broken aggregate rule returns 422. A lost race returns 409 concurrency-conflict.
  • The host's pipeline runs authentication, tenant selection, localization and authorization. Then come the route groups for signed-in callers, for seated callers and for operators (Program.cs).

The presets​

The Try it page runs the presets one by one or all at once (DevAttempts.cs). One preset is an allowed read. All the others are refusals of the seeded data, so running them changes nothing.

PresetWho tries whatResponse
other-tenants-projectTove, in Harbor Works, opens Garden shed of Meadow Gardens.404 projects.​not-found
other-tenants-slugRhea sends meadow in the Tenant header.403 tenancy.​not-seated
outside-your-regionRhea opens Bay bridge.404 projects.​not-found
through-an-expired-grantVic opens Inland depot.404 projects.​not-found
close-as-observerVic closes Pier 7.403 projects.​not-permitted (projects.​close)
rename-as-access-adminMaud renames Pier 7.403 projects.​not-permitted (projects.​edit)
crew-role-without-crew-managementJuno gives Vic Crew lead on Pier 7.403 projects.​not-permitted (projects.​crew.​manage)
organization-role-on-a-crewLeo gives Vic Area manager, an organization role, on Pier 7.400 projects.​role-not-for-members
crew-role-held-twiceLeo gives Juno Surveyor on Pier 7, a role she already holds there.409 projects.​crew-role-held
inspection-outside-the-planned-rangeRhea records an inspection on Inland depot for days in 2000.409 inspections.​outside-planned-range
owner-change-from-the-crewLeo, the owner of Pier 7, names Juno as the owner.403 projects.​not-permitted (projects.​owner.​change)
grant-above-your-rightsRhea grants Access admin to Leo at North Coast.403 tenancy.​grant-exceeds-own
grant-outside-your-regionRhea grants Observer to Tove at South Bay.403 tenancy.​not-permitted
give-a-role-that-manages-accessHana gives Leo Area manager at North Coast.403 tenancy.​grant-exceeds-own
give-access-admin-from-the-people-officeHana gives Leo Access admin at North Coast.403 tenancy.​grant-exceeds-own
give-crew-lead-from-the-people-officeHana gives Leo Crew lead at North Coast.403 tenancy.​grant-exceeds-own
take-away-a-role-that-manages-accessHana revokes Rhea's Area manager at North.403 tenancy.​grant-exceeds-own
appoint-yourselfHana gives herself Area manager at the root.403 tenancy.​self-appointment
appoint-yourself-holding-its-permissionRhea gives herself People office at North.403 tenancy.​self-appointment
only-administrator-revokesTove, in Meadow Gardens, revokes her own Tenant admin grant.409 tenancy.​last-admin
only-administrator-suspendsTove, in Meadow Gardens, suspends her own seat.409 tenancy.​last-admin
only-administrator-withdrawsTove, in Meadow Gardens, withdraws her placement at the root.409 tenancy.​last-admin
remove-the-ownerLeo, the owner, leaves the crew of Pier 7.409 projects.​owner-protected
give-the-owner-a-roleLeo, the owner, gives himself Surveyor on Pier 7.409 projects.​owner-protected
history-with-the-permissionMaud reads the access history.200
history-without-the-permissionRhea reads the access history.403 tenancy.​not-permitted (tenancy.​history.​view)
invite-without-seat-managementHana invites a person.403 tenancy.​not-permitted
accept-an-invitation-nobody-sentLeo accepts with a token that nobody was given.404 tenancy.​invitation-not-found
suspended-seatSeth asks who he is.403 tenancy.​seat-suspended
no-tokenRhea's projects are asked for without a token.401
no-tenant-headerRhea asks who she is, without a Tenant header.400 tenancy.​tenant-required
  • A role given from the UI first can make a preset succeed. For example, with Crew lead at North Coast, Juno may give Vic Crew lead on Pier 7. Restart the AppHost to have every preset refused again.
  • The buttons on a project page send real calls. After Leo or Rhea closes Pier 7, three presets return 409 projects.closed instead: crew-role-held-twice, remove-the-owner and give-the-owner-a-role. Reopen Pier 7 with the button that replaces Close.
  • Examples.Tenancy.Host.http, beside the host, calls the same API from an editor.