Organization Access (C2)
QNSP uses a C2 organization access model:
Organization Access (C2)
QNSP uses a C2 organization access model:
- A tenant is claimed by verifying control of a domain via DNS.
- Users with emails on a verified domain can submit a request to join.
- Tenant admins approve/deny join requests.
- Tenant membership is stored in auth-service (
auth_users).
1) Claiming a tenant via domain verification
Start verification
Tenant admins start a challenge. The tenant-service returns a DNS TXT record name/value.
POST /tenant/v1/tenants/:tenantId/domains/verification/start
Response includes:
txtName(example:_qnsp-verify.example.com)txtValue(example:qnsp-domain-verification=<token>)
Verify
After publishing the TXT record, the admin verifies it:
POST /tenant/v1/tenants/:tenantId/domains/verification/verify
In production, tenant-service performs a real DNS TXT lookup.
On success, tenant-service:
- Adds/updates
tenant_domains(verified=true) - Updates tenant metadata:
tenantType = "organization"claimed = trueclaimedAtclaimedByUserId(fromX-User-ID)joinRequestsEnabled = true
List verified domains
GET /tenant/v1/tenants/:tenantId/domains
2) Request-to-join flow
Submit join request (public)
Unauthenticated users submit join requests through edge-gateway:
POST /public/join-requests
Input:
tenant(slug or tenant UUID, deployment-specific)emailrequestedRole(defaults toDeveloper)
Tenant-service enforces:
joinRequestsEnabledmust be true- Email domain must match a verified domain in
tenant_domains
Admin review (approve/deny)
Tenant admins list and review requests:
GET /tenant/v1/tenants/:tenantId/join-requests?status=pendingPOST /tenant/v1/tenants/:tenantId/join-requests/:requestId/review
On approve, Cloud provisions membership in auth-service.
3) Membership is stored in auth-service
List members
GET /auth/tenants/:tenantId/users
This endpoint is admin-protected and tenant-scoped.
Invite acceptance model
Invites are implemented as:
-
Create user in auth-service with
status = "locked" -
Trigger password reset email (
/auth/forgot-password) -
When the invited user completes
/auth/reset-password, auth-service automatically flips: -
locked → active
This makes the password reset link act as the invite acceptance link.
Authorization model (high level)
- Edge Gateway is the primary entrypoint in production.
- Public join requests go through edge-gateway
POST /public/join-requests. - Admin actions require a Bearer JWT with
adminrole and correcttenant_id.
Related docs
/api/authentication/identity/authentication-flows/identity/rbac-and-policies