Skip to main content

Command Center Integration: Synchronous Tenant-Admin Provisioning

Status: Built and tested in RoundTrip. Not yet safe for live traffic — two real, open questions below need answers from Command Center/Keystone's side before this carries real signups.

Built: 2026-09-07, in response to Command Center's new signup orchestration needing a synchronous, idempotent way to provision a tenant's first TenantAdmin — the existing ProvisionTenantJob is a Hangfire background job, which can't guarantee "done by the time this request returns."


What this is, in one paragraph​

RoundTrip's existing tenant-provisioning logic — creating the first TenantAdmin's real Entra identity and TenantUsers row — has been extracted from ProvisionTenantJob (a Hangfire background job) into a shared service both the existing job and a new, real, synchronous HTTP endpoint call. RoundTrip's own live registration flow is completely unchanged — same job, same behavior, now just delegating to the shared logic instead of containing it directly.

Microsoft Entra: Once RoundTrip is migrated to Keystone, Microsoft Entra will not be used in any capacity including optional login.

The new endpoint​

POST /v1/tenant-admin/provision

Request:

{
"tenantId": "guid",
"keystoneUserId": "guid",
"firstName": "string",
"lastName": "string",
"email": "string",
"phone": "string"
}

Response (200):

{
"tenantId": "guid",
"entraObjectId": "guid",
"email": "string",
"wasAlreadyProvisioned": "bool"
}

Other responses: 404 if tenantId doesn't already exist in Provisioning/ProvisioningFailed status — this endpoint does not create tenants, it only provisions the admin for one that already exists. 409 on a real identity collision (the same defense-in-depth checks ProvisionTenantJob has always had).

What's proven, not just claimed​

  • The existing Hangfire job's behavior is unchanged — it's now a one-line delegator to the same shared service, same retry behavior on failure.
  • Idempotency is genuinely proven, not assumed. A real, deliberate mutation-testing pass caught that the first idempotency test didn't actually prove the claim — it passed even with the safety guard completely disabled, because a normal second call never reaches that code path at all. A second, more targeted test was built specifically to force execution through the one path where a duplicate-insert risk is real; that test correctly fails when the guard is broken and passes when it's restored. Calling this endpoint twice with the same input is confirmed safe — one row, one Entra identity, one email, both times.
  • Not yet verified against a real Entra tenant — all proof so far is against fakes/doubles in RoundTrip's test suite. Recommend one real run against a dev Entra tenant before this carries production traffic.

Two real, open questions: need answers before this goes live​

Question 1: Does Keystone already have a usable Entra identity, or not?​

2026-09-28 Entra has been disabled as an optional login path.

The current build assumes not. It creates a brand-new Entra Member identity (temp password, invitation email) exactly the way ProvisionTenantJob always has. keystoneUserId is accepted and logged for correlation, but not used for any resolution logic — today's live system still resolves every user by EntraObjectId (TenantScopingBehavior's JWT oid claim), and no KeystoneUserId column or Keystone-aware code exists anywhere in RoundTrip yet.

If Keystone's federation already produces a real Entra object ID for this person before calling RoundTrip, this needs to change to a smaller, different variant — skip CreateMemberUserAsync entirely, just record the given identity. The current implementation would otherwise be doing unnecessary, confusing work (a temporary password and invitation email nobody needs) for every admin Command Center provisions.

This can't be answered from RoundTrip's side — it depends entirely on how Keystone's federation is actually built, which doesn't exist yet.

Question 2: How should Command Center actually authenticate to this endpoint?​

Nothing exists today. Every RoundTrip endpoint is either JWT-authenticated (a real signed-in user) or fully AllowAnonymous() for genuinely public flows like registration. This new endpoint has to be anonymous too — no RoundTrip user is signed in when Command Center calls it — but unlike registration, it accepts a caller-supplied TenantId and creates a real, live sign-in credential for it.

As built, this is not safe to expose to real traffic. Nothing stops an arbitrary caller who can guess or enumerate a TenantId from provisioning an admin account for a tenant they don't own. This needs a real decision — an API key header, network-level restriction, mTLS via a gateway, or something else — before this endpoint is reachable outside a trusted internal network.

5. What was deliberately not changed​

  • ProvisionTenantJob's Hangfire registration, retry behavior, and dispatcher are all untouched — real production risk of disturbing a live, working path was treated as more important than a clean rewrite.
  • phone's parameter is preserved even though it was already unused before this change (confirmed pre-existing, unrelated) — kept so no caller's signature has to change.