Some checks are pending
Bidi Control Character Guard / bidi-control-guard (push) Waiting to run
Circular Dependency Check / Check for new circular dependencies (push) Waiting to run
Citus Migration Smoke / Combined migrations on single-node Citus (push) Waiting to run
E2E Fresh Install Tests / fresh-install-e2e (push) Waiting to run
ext-v2 guardrails / Run ext-v2 guard and ESLint (push) Waiting to run
Integration Tests / Check for relevant changes (push) Waiting to run
Integration Tests / ${{ (github.event_name == 'schedule' || github.event.inputs.suite == 'full') && 'Full integration suite' || 'Tier-1 integration subset' }} (push) Blocked by required conditions
Mobile checks / Mobile lint + typecheck (push) Waiting to run
Mobile checks / Mobile unit tests (push) Waiting to run
Mobile checks / Mobile dependency audit (report) (push) Waiting to run
Mobile checks / Mobile reproducibility checks (push) Waiting to run
Secrets guard (env backups) / Ensure no tracked env backup files (push) Waiting to run
Temporal Readiness / fast-readiness (push) Waiting to run
Temporal Readiness / docker-parity (push) Waiting to run
TypeScript Type Check / Nx affected typecheck (push) Waiting to run
Unit Tests / Skipped-test budget (push) Waiting to run
Unit Tests / Nx affected unit tests (push) Waiting to run
Unit Tests / Server unit coverage (informational) (push) Waiting to run
Validate Tenant Management Schema / Check for relevant changes (push) Waiting to run
Validate Tenant Management Schema / Validate Tenant Management Schema (push) Blocked by required conditions
EE Workflows Build Guard / ee-workflows-build-guard (push) Waiting to run
Excluded: .git, node_modules, secrets/, compose.env, assemblyscript tgz Source: /opt/alga-psa on psa.joliet.tech
163 lines
17 KiB
Markdown
163 lines
17 KiB
Markdown
# PRD — Hudu Integration
|
|
|
|
- Slug: `hudu-integration`
|
|
- Date: `2026-06-08`
|
|
- Status: Draft
|
|
- Edition: Enterprise (EE) only
|
|
- Phase: 1 (pull-only, Hudu → AlgaPSA)
|
|
|
|
## Summary
|
|
|
|
Add a Hudu integration to AlgaPSA that lets an MSP connect a Hudu instance (Hudu Cloud or self-hosted) and surface that Hudu data — companies, assets, knowledge-base articles, and asset passwords — read-only inside AlgaPSA, scoped to the AlgaPSA client it belongs to.
|
|
|
|
Hudu is the MSP's IT-documentation system of record. AlgaPSA is the PSA (system of record for clients/tickets/billing). This integration does **not** copy Hudu's documentation into AlgaPSA; it persists only the **client ↔ Hudu-company mapping**, fetches a mapped company's lists **on demand** (cached, with manual refresh), and **deep-links** to Hudu for the actual content. This matches how the wider Hudu ecosystem integrates (scheduled/low-frequency reads + deep-links; never duplicating Hudu's docs, especially secrets).
|
|
|
|
Phase 1 is **pull-only** and **EE-only**, gated behind a `hudu-integration` feature flag. The data model is kept direction-agnostic so a future push (AlgaPSA → Hudu) phase is additive.
|
|
|
|
## Problem
|
|
|
|
MSP technicians using AlgaPSA constantly need a client's documentation — devices, network notes, runbooks, and especially credentials — which lives in Hudu. Today they context-switch to Hudu, find the right company, and dig for the record.
|
|
|
|
There is no link between an AlgaPSA client and its Hudu company, and no way to see Hudu content in context while working a ticket. Hudu's `asset_passwords` are **company-scoped** — they are not attached to a specific device — so credentials cannot be mapped to an individual AlgaPSA asset; they belong at the client level. (AlgaPSA does ship an asset "Documents & Passwords" tab whose Passwords half is an unimplemented stub, but that is a different, asset-scoped concern and is left for the separate future native-password effort — see Non-goals.)
|
|
|
|
## Goals
|
|
|
|
- Let an EE tenant connect a Hudu instance with an API key + base URL, and validate the connection.
|
|
- Map AlgaPSA clients to Hudu companies (auto-suggested, admin-confirmed).
|
|
- Surface a mapped client's Hudu data read-only, all on the **Client** detail page:
|
|
- a **"Hudu" tab** for **assets** and **articles**, and
|
|
- a separate **"Passwords" tab** for the company's Hudu **asset passwords** (visible only when the Hudu integration is enabled and the client is mapped). Credentials live here, not on the asset, because Hudu passwords are company-scoped.
|
|
- Deep-link every surfaced record back to Hudu as the system of record.
|
|
- Keep the integration safe: never persist a Hudu password value anywhere in AlgaPSA (DB, Vault, or cache).
|
|
|
|
## Non-goals
|
|
|
|
- **Push / write-back to Hudu** (Alga → Hudu create/update). Deferred; model stays direction-agnostic.
|
|
- **Importing Hudu records as native AlgaPSA records** (no creating Alga `assets`/`documents` rows from Hudu).
|
|
- **Websites** entity (no clean AlgaPSA mapping target). Deferred.
|
|
- **Scheduled background sync engine / Temporal workflows.** Phase 1 fetches on demand + manual refresh. (A low-frequency scheduled refresh is a documented future enhancement.)
|
|
- **Contacts, Procedures, Magic Dash, Networks, Relations** and other Hudu resources beyond the four named entities.
|
|
- **CE availability.** EE only; CE routes return 501.
|
|
- **Persisting or caching Hudu passwords** anywhere in AlgaPSA (DB column, Vault, or cache). Phase 1 reveals on demand via live fetch only.
|
|
- **A native AlgaPSA password store** (first-class password management in Alga) and **importing Hudu passwords into it**. Deliberately deferred to a separate future effort/plan; added only if demanded.
|
|
- **Modifying the asset "Documents & Passwords" tab.** Left as-is (still a stub) in Phase 1 — Hudu passwords are company-scoped, so they surface on the client's Passwords tab, not the asset. The asset stub is the future native-password effort's concern.
|
|
- Monitoring/metrics/observability infrastructure beyond what the UI needs to show connection/fetch status.
|
|
|
|
## Users and Primary Flows
|
|
|
|
**Personas**
|
|
- **MSP Admin** — connects Hudu, manages client↔company mappings.
|
|
- **MSP Technician** — views a client's / asset's Hudu data in context while working.
|
|
|
|
**Primary flows**
|
|
1. **Connect Hudu** (Admin): Settings → Integrations → IT Documentation → Hudu → enter base URL + API key → Test → Connected.
|
|
2. **Map companies** (Admin): Hudu settings → Company Mapping → review auto-suggested matches (by `id_in_integration`, then name) → confirm/override per row → Save. Refresh companies re-pulls the Hudu company list.
|
|
3. **View client documentation** (Technician): open a mapped Client → "Hudu" tab → see assets / articles lists with counts → click any item → opens in Hudu.
|
|
4. **View client credentials** (Technician): open a mapped Client → "Passwords" tab → list of the company's Hudu credentials (name, username, URL) → "Reveal" fetches the value live from Hudu (masked, reveal-on-click, audited, never stored); "Open in Hudu" links to the record.
|
|
|
|
## UX / UI Notes
|
|
|
|
- **Settings entry**: new category **"IT Documentation"** in `IntegrationsSettingsPage.tsx` (`categories` array), with a single `HuduIntegrationSettings` item (`isEE: true`). Hidden unless EE + `hudu-integration` flag enabled.
|
|
- **Connection panel** (`HuduIntegrationSettings.tsx`): base URL field, API key field (write-only/masked), Test Connection button (calls `GET /api/v1/companies?page=1`), status badge (Not connected / Connected / Error), Disconnect button. On connect success, show the resolved instance + password-permission status of the key (so the admin knows whether passwords will be visible).
|
|
- **Company mapping** (`HuduCompanyMappingManager.tsx`): modeled on NinjaOne `OrganizationMappingManager` — counters (mapped / unmapped), a table with columns **Hudu Company** (name + id) | **AlgaPSA Client** (`ClientPicker` dropdown, pre-filled with the suggested match) | **Status** (badge: Suggested / Mapped / Unmapped), and a **Refresh companies** button. Selecting a client persists immediately.
|
|
- **Client "Hudu" tab**: read-only sections for Assets and Articles; each row deep-links to Hudu; show counts; "Refresh" button; empty/disconnected/unmapped states. Visible only when EE + flag + Hudu connected + client mapped.
|
|
- **Client "Passwords" tab** (separate tab on the Client detail page, shown only when the Hudu integration is enabled, connected, and the client is mapped): read-only list of the mapped company's Hudu `asset_passwords` (name, username, URL) with an inline **Reveal** (live fetch, masked, reveal-on-click, audited, never stored) and an "Open in Hudu" link. Empty/error states for unmapped / disconnected / key-lacks-password-permission.
|
|
- All Hudu-derived UI must visibly attribute Hudu as the source and link out.
|
|
|
|
## Requirements
|
|
|
|
### Functional Requirements
|
|
|
|
**Connection**
|
|
- FR1. Store Hudu `base_url` and `api_key` per tenant via the secret provider; never expose the key back to the client.
|
|
- FR2. Validate a connection by calling Hudu and surfacing success/failure + the key's password-access capability.
|
|
- FR3. Persist connection state (active, connected_at, last fetch) in a `hudu_integrations` table.
|
|
- FR4. Disconnect clears the connection (deletes secrets, marks inactive) but retains mappings.
|
|
|
|
**Hudu API client**
|
|
- FR5. Axios client using `x-api-key` + base URL, resolving credentials tenant-secret → env fallback.
|
|
- FR6. Page-based pagination helper (25/page; stop when a page returns < 25).
|
|
- FR7. Rate-limit handling: on HTTP 429 back off using `Retry-After` (+ jitter) and retry; cap retries.
|
|
- FR8. Map Hudu errors to typed results: 401 (bad key), 403 (no password permission), 404 (bad base URL/id), 429 (rate limited), 5xx (retry).
|
|
- FR9. UI→API naming is handled internally (`asset_passwords`, `procedures`, etc.).
|
|
|
|
**Company mapping**
|
|
- FR10. Fetch the Hudu company list for the connected tenant.
|
|
- FR11. Auto-suggest each Hudu company → AlgaPSA client by `id_in_integration` (exact, when it equals an Alga client id), then exact name, then fuzzy name; expose a confidence/source.
|
|
- FR12. Persist mappings in `tenant_external_entity_mappings` (`integration_type='hudu'`, `alga_entity_type='client'`, `external_entity_id=<hudu_company_id>`, `metadata={hudu_company_name, id_in_integration, url}`). One Hudu company ↔ one client.
|
|
- FR13. Admin can confirm a suggestion, override the client, or clear a mapping.
|
|
|
|
**Surfacing (read-only)**
|
|
- FR14. For a mapped client, fetch its Hudu assets, articles, and asset passwords (company-scoped) on demand, with a short server-side cache and a manual Refresh.
|
|
- FR15. Client "Hudu" tab renders the assets and articles lists with counts and deep-links.
|
|
- FR16. Client "Passwords" tab (separate from the Hudu tab, shown only when Hudu is enabled/connected and the client is mapped) renders the mapped company's Hudu passwords list with inline Reveal (live fetch, audited, never stored) and Open-in-Hudu links.
|
|
- FR17. Every surfaced record links to its Hudu URL (per-record URL or `/companies/jump` deep-link).
|
|
- FR18. Clear empty/error states for: not connected, client unmapped, Hudu unreachable, key lacks password permission (403).
|
|
- FR19. Reveal a single credential on demand via a targeted live GET; return the value transiently to the browser (masked, reveal-on-click); never persist it (DB/Vault/cache) or log it.
|
|
- FR20. Audit every reveal (who, when, which password/company); the audit entry never contains the value.
|
|
|
|
### Non-functional Requirements
|
|
|
|
- NFR1. **Security**: no Hudu password value is ever persisted anywhere in AlgaPSA — not a DB column, not Vault, not a cache. Reveal is on demand: a targeted live GET returns the value transiently to the browser (masked, reveal-on-click); every reveal is audited; the value is never logged or written server-side. The connection's own `api_key` is stored only via the secret provider (Vault).
|
|
- NFR2. **EE gating**: all server entry points require EE + `hudu-integration` flag; CE routes return 501 via the `@enterprise` lazy-import stub.
|
|
- NFR3. **Tenant isolation**: all queries tenant-scoped via `createTenantKnex()`; tables are Citus-distributed by `tenant`.
|
|
- NFR4. **Rate-limit safety**: on-demand fetches are per-mapped-company and paginated; never bulk-fetch across all clients on a page view.
|
|
- NFR5. **i18n**: all user-facing strings use translation keys.
|
|
- NFR6. **Direction-agnostic data model**: mapping rows carry no pull-only assumptions, enabling a later push phase.
|
|
- NFR7. **EE/CE deletion boundary**: `hudu_integrations` is EE-only, so every read/write/**delete** against it — disconnect, and any client-/tenant-delete cascade that removes its rows — must live in EE-only code paths and EE migrations. No CE migration or CE runtime path may name `hudu_integrations` (it does not exist in CE). Mapping cleanup is exempt: those rows live in the shared CE table `tenant_external_entity_mappings` (filtered `integration_type='hudu'`) and are safe to delete from CE.
|
|
|
|
## Data / API / Integrations
|
|
|
|
**New table — `hudu_integrations`** (connection state), **EE migration in `ee/server/migrations/`** (Hudu is EE-only; CE never touches it — the CE route 501s before any DB access). Follow the Entra EE precedent `ee/server/migrations/20260220143000_create_entra_phase1_schema.cjs` for the Citus-distributed EE tenant-table pattern (`ee/server/migrations` runs `isCitusEnabled` logic). Columns:
|
|
- `tenant uuid`, `integration_id uuid` (PK `(tenant, integration_id)`), `base_url text`, `is_active boolean`, `connected_at timestamptz`, `last_synced_at timestamptz`, `settings jsonb` (e.g. cached capability flags), `created_at`, `updated_at` (+ `on_update_timestamp` trigger). Unique `(tenant)` (one Hudu connection per tenant in Phase 1). Citus-distributed by `tenant`.
|
|
- (`rmm_integrations` is CE only because the RMM layer is partly CE — not a precedent for a wholly-EE table.)
|
|
|
|
**Reused table — `tenant_external_entity_mappings`** for company↔client links (no new migration).
|
|
|
|
**Secrets**: `hudu_api_key`, `hudu_base_url` via `getSecretProviderInstance().setTenantSecret(tenant, …)`.
|
|
|
|
**Hudu API surfaces used** (all GET): `/api/v1/companies` (+ `?id_in_integration=`, `?page=`), `/api/v1/assets?company_id=`, `/api/v1/articles?company_id=`, `/api/v1/asset_passwords?company_id=`, per-record URLs, and `/api/v1/companies/jump?integration_id=&integration_slug=` for deep-linking. Auth `x-api-key`. Limits: 300 req/min, 25/page, no consumer webhooks.
|
|
|
|
**Code locations**
|
|
- Client lib: `ee/server/src/lib/integrations/hudu/` (`huduClient.ts`, `contracts.ts`, `secrets.ts`, mapping/suggest helpers).
|
|
- Server actions: `huduConnectionActions.ts`, `huduCompanyMappingActions.ts`, `huduDataActions.ts` (fetch lists).
|
|
- EE API routes: `ee/server/src/app/api/integrations/hudu/...`; CE stubs: `server/src/app/api/integrations/hudu/...` (501).
|
|
- Settings UI: `IntegrationsSettingsPage.tsx` (category), `HuduIntegrationSettings.tsx`, `HuduCompanyMappingManager.tsx`.
|
|
- Client tabs: client detail tab host — `HuduClientTab` (assets + articles) and `HuduClientPasswordsTab` (passwords). The asset `packages/assets/src/components/tabs/DocumentsPasswordsTab.tsx` is **not** modified in Phase 1.
|
|
|
|
## Security / Permissions
|
|
|
|
- EE + `hudu-integration` feature flag required on every server entry point (guard mirrors `requireEntraUiFlagEnabled`).
|
|
- **Reuse the existing `system_settings` RBAC resource** — `read` to view surfaced Hudu data, `update` to connect/disconnect and manage mappings. This matches Entra, Teams, Tactical RMM, and SSO (all `system_settings`); accounting/billing integrations use `billing_settings`, which is the wrong resource here. **No new RBAC resource and no permission seeding** are required (`system_settings` already exists). Note: reuse the `tenant_external_entity_mappings` table for mappings, but do **not** call the `billing_settings`-gated `externalMappingActions` wrappers — Hudu mapping actions enforce `system_settings`.
|
|
- API key stored only in the secret provider; masked in UI; excluded from logs and error payloads.
|
|
- Password values: handled per NFR1 — revealed on demand via a transient live GET, never persisted (DB/Vault/cache) or logged; every reveal is audited. Respect Hudu key password permission (403 → "password access not enabled for this key").
|
|
|
|
## Observability
|
|
|
|
- Minimal: surface connection status, last-fetch time, and fetch errors in the UI. No new metrics/telemetry infrastructure in Phase 1.
|
|
|
|
## Rollout / Migration
|
|
|
|
- One **EE migration** (`ee/server/migrations/`) creates `hudu_integrations` (greenfield tenant table: create `tenant uuid` first, distribute inline under the `isCitusEnabled` guard, `transaction:false`), per the Entra precedent.
|
|
- No RBAC seeding migration needed — reuse the existing `system_settings` resource.
|
|
- Ship dark behind `hudu-integration` (default off). Enable per-tenant for pilot MSPs.
|
|
- No data backfill (pull-only, on-demand).
|
|
|
|
## Open Questions
|
|
|
|
- OQ1. **RESOLVED** — Phase 1 reveals credentials inline via a targeted live GET (masked, reveal-on-click, audited, never persisted to DB/Vault/cache). A Vault-backed reveal cache and a native AlgaPSA password store (with optional Hudu import) are explicitly deferred to separate future efforts.
|
|
- OQ2. **RESOLVED** — Hudu `asset_passwords` are company-scoped and cannot be mapped to a specific asset, so they are **not** surfaced on the asset tab. They get a dedicated **"Passwords" tab on the Client page** (shown only when the Hudu integration is enabled and the client is mapped). The asset "Documents & Passwords" stub is untouched in Phase 1.
|
|
- OQ3. **RESOLVED** — Reuse the existing `system_settings` RBAC resource (`read`=view, `update`=manage), matching Entra/Teams/Tactical-RMM/SSO. No new resource, no seeding.
|
|
- OQ4. **RESOLVED** — One Hudu instance per tenant (enforced by `unique(tenant)`). Multiple instances are rare in practice (only unmerged M&A or sandbox/prod splits); multi-instance support is a possible future enhancement, not Phase 1.
|
|
|
|
## Acceptance Criteria (Definition of Done)
|
|
|
|
- An EE tenant with the flag on can connect a Hudu instance (base URL + key), see a Connected status, and Disconnect.
|
|
- A CE build returns 501 for all Hudu routes; the settings item is hidden in CE.
|
|
- Admin can see Hudu companies auto-matched to clients, override/confirm, and persist mappings in `tenant_external_entity_mappings`.
|
|
- On a mapped client's "Hudu" tab, assets / articles lists render with counts and working deep-links.
|
|
- On a mapped client's separate "Passwords" tab (shown only when Hudu is enabled/connected and the client is mapped), the company's Hudu passwords render with inline Reveal (live fetch, never stored) and Open-in-Hudu links. The asset "Documents & Passwords" stub is unchanged.
|
|
- No AlgaPSA store (DB column, Vault, or cache) ever contains a Hudu password value; every reveal is audited; the `api_key` is never returned to the client or logged.
|
|
- 401/403/404/429/unreachable all produce clear, non-crashing UI states.
|
|
- All strings are translated; all server entry points enforce EE + flag + `system_settings` (no new RBAC resource).
|