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 = true
    • claimedAt
    • claimedByUserId (from X-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)
  • email
  • requestedRole (defaults to Developer)

Tenant-service enforces:

  • joinRequestsEnabled must 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=pending
  • POST /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 admin role and correct tenant_id.
  • /api/authentication
  • /identity/authentication-flows
  • /identity/rbac-and-policies