# ZeroTier Network Lifecycle ## Overview This document covers the full lifecycle of ZeroTier networks in Gatehouse — how networks are created, who can see them, how members request access, and how devices are activated and deactivated. ## Organization Membership Roles Every user belongs to an organization via an `OrganizationMember` record. Roles determine what a user can see and do: | Role | Can list networks? | Can see invite-only networks? | Can create/update/delete networks? | Can approve access requests? | |---|---|---|---|---| | `owner` | Yes | Yes | Yes | Yes | | `admin` | Yes | Yes | Yes | Yes | | `member` | Yes | **No** | No | No | | `guest` | Yes | **No** | No | No | Role checks happen via the `OrganizationMember.is_admin()` method, which returns `True` for `owner` and `admin`. ## Network Request Modes Every `PortalNetwork` has a `request_mode` field that controls how users gain access: | Mode | Value | Behavior | |---|---|---| | `open` | `"open"` | Any org member can join directly without approval | | `approval_required` | `"approval_required"` | User requests access; a manager must approve | | `invite_only` | `"invite_only"` | Only managers can assign access; invisible to non-admins | ## Network Listing Visibility `GET /organizations/{org_id}/networks` The listing endpoint applies two visibility filters: 1. **Soft-delete filter** — networks with a non-null `deleted_at` are always excluded. 2. **Active filter** — by default, only networks where `is_active = True` are returned. Pass `?include_inactive=true` to include disabled networks. 3. **Invite-only filter** — networks with `request_mode = "invite_only"` are hidden from non-admin users (`member` and `guest` roles). Admins and owners see all networks. ### Filtering logic The filtering happens in `portal_network_service.list_networks()`: ```python # Non-admin users cannot see invite-only networks if user_id is not None: membership = OrganizationMember.query.filter(...).first() is_admin = membership.is_admin() if membership else False if not is_admin: q = q.filter(PortalNetwork.request_mode != NetworkRequestMode.INVITE_ONLY) ``` ## Network CRUD | Action | Endpoint | Required Role | |---|---|---| | List networks | `GET /organizations/{id}/networks` | Any org member (visibility restricted as above) | | Create network | `POST /organizations/{id}/networks` | `admin` or `owner` | | Update network | `PUT /organizations/{id}/networks/{id}` | `admin` or `owner` | | Delete network | `DELETE /organizations/{id}/networks/{id}` | `admin` or `owner` | ## Device Registration Before a user can access a network, they must register a device: `POST /organizations/{org_id}/devices` A `Device` record ties a ZeroTier node (10-char `node_id`) to a user within an org. | Field | Purpose | |---|---| | `node_id` | ZeroTier 10-char node identifier | | `device_nickname` | Human-friendly label | | `hostname` | Optional hostname for identification | ## Network Access Request Lifecycle The core model is `NetworkAccessRequest` (table: `network_access_requests`). Each row represents one user + one device + one network. See [zerotier-device-membership.md](zerotier-device-membership.md) for the full schema. ### Flow by request mode **Open networks** — user calls `join_network_for_device()` directly: 1. Creates `NetworkAccessRequest` with `status=APPROVED`, `active=False` 2. Returns the request **Approval-required networks** — user calls `request_access()`: 1. Creates `NetworkAccessRequest` with `status=PENDING` 2. Admin calls `approve_request()` → sets `status=APPROVED` 3. User calls `activate_membership()` → sets `active=True`, creates `ActivationSession` **Invite-only networks** — only an admin can call `assign_access()`: 1. Admin creates `NetworkAccessRequest` with `status=APPROVED`, `grant_type=ASSIGNED` 2. User calls `activate_membership()` → sets `active=True`, creates `ActivationSession` ### The `active` flag | `status` | `active` | Meaning | |---|---|---| | `approved` | `false` | Has permission but not currently connected | | `approved` | `true` | Has permission and device is authorized on the controller | | `pending` | `false` | Awaiting approval | | `rejected` / `revoked` / `suspended` | `false` | Access denied or removed | ## Activation and Deactivation Activation creates an `ActivationSession` with a configurable TTL (default 8 hours). The session is tied to the `active=True` state. - `activate_membership()` — sets `active=True`, creates session, authorizes on ZeroTier controller - `deactivate_membership()` — sets `active=False`, ends session, de-authorizes on controller - Activation sessions expire automatically via the reconciliation worker, which sets `active=False` ### Kill switch Admins can trigger a kill switch to deactivate all active memberships on an organization or network: - `POST /organizations/{id}/kill-switch` — deactivates all memberships in the org - `POST /organizations/{id}/networks/{id}/kill-switch` — deactivates all memberships on a specific network ## Reconciliation Worker A scheduled job (runs every 2 minutes) performs: 1. **Expired activation cleanup** — finds expired `ActivationSession` records, de-authorizes in ZeroTier, sets `active=False` 2. **Drift detection** — compares portal state against ZeroTier controller state, repairs mismatches ## Key Source Files | File | Purpose | |---|---| | `gatehouse_app/models/zerotier/portal_network.py` | `PortalNetwork` model (network definition + request_mode) | | `gatehouse_app/models/zerotier/network_access_request.py` | `NetworkAccessRequest` model (per-device membership) | | `gatehouse_app/models/zerotier/activation_session.py` | `ActivationSession` model (TTL-based sessions) | | `gatehouse_app/models/zerotier/device.py` | `Device` model | | `gatehouse_app/models/organization/organization_member.py` | `OrganizationMember` model (roles) | | `gatehouse_app/services/portal_network_service.py` | Network CRUD + listing logic | | `gatehouse_app/services/network_access_service.py` | Access request + activation logic | | `gatehouse_app/services/zerotier_reconciliation_service.py` | Expired session + drift reconciliation | | `gatehouse_app/api/v1/zerotier.py` | All ZeroTier API endpoints | | `gatehouse_app/utils/constants.py` | Enums (`OrganizationRole`, `NetworkRequestMode`, etc.) |