4. Multi-Tenancy & Configuration¶
How tenants are isolated, provisioned, configured, and retired — the multi-tenancy model every entity inherits.
4.1 Overview¶
The platform is multi-tenant by design. Every resource — user, device, consent record, audit event, OAuth client, KYC session — belongs to exactly one tenant. This section defines the isolation model, tenant lifecycle, and cross-tenant rules. Entity schemas (tenant, user, device, …) are defined in §13 Data Model.
4.2 Data Isolation Model¶
Isolation Requirements:
| Requirement | Description |
|---|---|
| Tenant-scoped data | Every data entity includes a tenant_id field. All queries are scoped to the requesting tenant. |
| No cross-tenant data access | A tenant's API credentials can never read, modify, or search data belonging to another tenant. This includes palm identification — 1:N search scopes to the tenant's enrolled templates only. |
| Tenant-scoped authentication | OAuth client credentials are bound to exactly one tenant. The tenant_id is embedded in the access token and validated on every request. |
| Tenant-scoped devices | All devices (personal scanner, pos, gate, kiosk) are registered to a specific tenant. The device certificate's SAN URI identifies both the device_id and tenant_id. |
| Platform-level visibility | Only Platform Admins can view data across multiple tenants (for operational monitoring, not business operations). All such access is audit-logged. |
Enforcement Points:
| Layer | Mechanism |
|---|---|
| API Gateway | tenant_id extracted from token/cert, injected into request context |
| Middleware | tenant_middleware.py validates tenant_id on every request, rejects if missing or mismatched |
| Domain Services | All repository queries include tenant_id as a mandatory filter |
| Database | Row-level filtering on tenant_id (implementation may use RLS or application-level enforcement) |
4.3 Tenant Lifecycle¶
Tenant States:
| State | Description | Data Access | API Access |
|---|---|---|---|
provisioning |
Being set up, not yet active | Write-only (setup) | Disabled |
active |
Fully operational | Full | Enabled |
suspended |
Temporarily disabled (non-payment, security incident, policy violation) | Read-only | Disabled (returns HTTP 403, tenant_suspended) |
deactivating |
Marked for deletion, grace period active | Read-only | Disabled |
deleted |
All data permanently removed | None | None |
Lifecycle Flow:
stateDiagram-v2
[*] --> provisioning
provisioning --> active
active --> suspended
suspended --> active
active --> deactivating
deactivating --> deleted
deleted --> [*]
Suspension (Platform Admin — non-payment, security incident, policy violation): API calls return HTTP 403 tenant_suspended; data stays intact and readable (the Tenant Admin console is read-only). Reactivation restores full access.
Deletion (Platform Admin): a configurable grace period (default 30 days) holds the tenant in deactivating (read-only, API disabled); afterward all user data, palm templates (via the vendor delete API), OAuth clients, device certificates, audit logs, and consent records are permanently removed. Deletion is irreversible; the deletion audit record itself is retained at the platform level for compliance.
4.4 Tenant Provisioning¶
Diagram — Tenant provisioning
sequenceDiagram
autonumber
participant Admin as Platform Admin
participant Console as Web Console
participant Identity as Identity Platform
participant DB as PostgreSQL
Admin->>Console: Create tenant
Console->>Identity: POST /v1/admin/tenants
Identity->>Identity: Generate tenant_id (slug, e.g. link-wallet)
Identity->>DB: Insert tenant (status provisioning)
Admin->>Console: Configure settings (auth, KYC, palm vendor — see §4.5)
Console->>Identity: PUT /v1/admin/tenants/{id}/settings
Identity->>DB: Persist settings
Identity->>DB: Create first Tenant Admin (email + temp password)
Identity->>Identity: Tenant status to active
Identity-->>Console: Tenant active
Tenant entity schema: see §13.2.
4.5 Tenant Settings¶
Each tenant configures capabilities individually. All settings have sensible defaults. There are no preset tenant types — each tenant is configured individually.
User ID handling is per-request: POST /v1/users accepts an optional user_id — if provided, the platform maps it; if omitted, the platform generates one. Auth signup flows (OTP, password, social) always generate platform-managed IDs.
| Setting | Description | Default |
|---|---|---|
auth_methods |
Enabled auth methods | ["otp", "password", "google", "apple"] |
kyc_required |
Require KYC | false |
kyc_provider |
KYC provider | null |
kyc_level |
Required KYC level | null |
kyc_required_for_enrollment |
KYC before palm | false |
kyc_required_for_transactions |
Require KYC for certain transactions / limits — [POST-MVP], §6 | false |
palm_provider |
Palm verification vendor | biowave |
palm_match_policy |
How vendor scores map to match decision (all_thresholds / majority / any) — see Section 7.4 |
all_thresholds |
palm_duplicate_check_enabled |
Run pre-enrollment similarity check to detect duplicate biometrics — see Section 7.10 | false |
palm_duplicate_action |
Action on duplicate detection (reject / flag) |
reject |
require_email_verified |
Require email verification before palm enrollment | false |
require_mobile_verified |
Require mobile verification before palm enrollment | false |
consent_required |
Require consent | false |
data_subject_rights_enabled |
Enable DSR | false |
audit_enabled |
Enable audit logging | true |
4.6 Example Configurations¶
| Tenant | Auth | KYC | KYC Provider | Palm Vendor |
|---|---|---|---|---|
| Link Holdings (one tenant — Wallet, Access + sub-products are products within it) | All | Per-product (Wallet: full / Nafath; Access: none) | Nafath | X-Telcom BioWave Pass |
| Link Social | All | Optional (basic) | Onfido | X-Telcom BioWave Pass |
| InvestGlass | Not enabled | Not enabled | - | X-Telcom BioWave Pass |
| PartnerBank | OTP | Required (full) | Nafath | X-Telcom BioWave Pass |
| HealthApp | All | Required (full) | Onfido | X-Telcom BioWave Pass |
4.7 Cross-Tenant Rules¶
Specific rules enforcing the §4.2 isolation model:
- No cross-tenant queries. API calls cannot specify a different
tenant_idthan the one in their credential. There is no "impersonation" or "on behalf of" mechanism. - No cross-tenant user sharing. A user belongs to exactly one tenant; distinct integrators are distinct tenants. (Link's own first-party products share one Link Holdings tenant — §4.6.)
- No cross-tenant palm matching. 1:N identification searches only within the requesting tenant's enrolled templates — a palm enrolled in Tenant A will not match in Tenant B.
Tenant isolation on X-Telcom BioWave Pass. The vendor API has a single global
user_idnamespace, so the platform prefixes everyuser_idsent to the vendor with<tenant_slug>__(e.g.link-holdings__user_456) and strips it on response. Within-tenant sharing (Wallet ↔ Access) uses the same namespace. 4. Platform Admin visibility. The sole cross-tenant exception, audit-logged (§4.2). 5. Webhook isolation. Each tenant configures its own webhook endpoints. Events from one tenant are never delivered to another tenant's endpoints.