/

Developer Guide

Domains

A “Domain” represents a distinct organization, campus, or physical environment. Every piece of spatial data and every user account is scoped to one or more of these domains.


🌍 The Domain Entity

The Domain model is managed by the Auth Service. It dictates not just the existence of an organization, but the rules for how users can join and authenticate against it.

export interface IDomain extends Document {
name: string;
subdomains: Types.ObjectId[];
authStrategy: "internal" | "oidc";
totpSecrets: IDomainTOTPSecrets;
ssoConfig?: ISSOConfig;
isVisibleFromOutside?: boolean;
}

Key Field Breakdown:


🔗 The Tripartite Relationship (Account ↔ Domain ↔ Building)

Because CrowdVision separates Authentication and Twin data into different databases, multi-tenancy must be enforced symmetrically across the service boundary.

erDiagram
    ACCOUNT {
        String _id
        IDomainMembership[] memberships
    }
    DOMAIN {
        String _id
        String name
        String authStrategy
    }
    BUILDING {
        String id
        String[] domains
    }

    ACCOUNT ||--o{ DOMAIN : "joins via membership"
    BUILDING }|--|{ DOMAIN : "is assigned to"

How Isolation is Enforced: When a user attempts to load the buildings for university.edu, the frontend calls GET /twin/buildings/university.edu. The twin-service intercepts the request, verifies the JWT signature, and checks the embedded payload. If the payload does not contain a membership for university.edu, the request is rejected with 403 Forbidden. If authorized, it queries its own database for Building.find({ domains: "university.edu" }). This guarantees absolute data isolation between tenants without requiring cross-service HTTP calls.


🔐 Authentication Strategies

Organizations have wildly different security requirements. The authStrategy field dictates how CrowdVision handles sign-ins for a specific domain.

Strategy 1: internal This is the default strategy. User credentials (passwords) are hashed and managed entirely within CrowdVision’s Auth database.

Strategy 2: oidc (Enterprise SSO) For enterprise clients using platforms like Keycloak, Auth0, or Entra ID, CrowdVision acts as an OIDC relying party. When a domain uses oidc, it must populate the ssoConfig subdocument:

export interface ISSOConfig {
    issuerUrl: string;
    clientId: string;
    clientSecret: string;
}

The PKCE Security Flow: To prevent state-tampering and CSRF attacks during the SSO redirect dance, CrowdVision implements strict PKCE (code_challenge_method: S256).

  1. When a user clicks “Login via SSO”, the Auth Service generates a PKCE code verifier and challenge.
  2. It generates a random, opaque state string.
  3. It securely stores the (state → { verifier, domainName }) mapping server-side (in Redis or memory).
  4. The user is redirected to the external IdP.
  5. Upon returning, the Auth Service extracts the state query parameter and looks up the server-side mapping. If it matches, the exchange proceeds.

Warning

Security Mandate: Never encode sensitive data (like the target domain or username) directly into the state parameter payload (e.g., as base64 JSON). A malicious actor can easily manipulate client-side state payloads. Always rely on server-side lookups using an opaque state key.

Once the OIDC exchange completes successfully, the Auth Service maps the external user to an internal Account, embeds the IDomainMembership (using the externalId), and issues a standard CrowdVision JWT. From the perspective of the Twin or Notification services, an OIDC user is completely indistinguishable from an internal user.