feat(data): admin-only tenant data export endpoint (PMS-647) #438
Loading…
Reference in a new issue
No description provided.
Delete branch "feat/PMS-647-data-export"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
First slice of PMS-646 (tenant data import/export via the admin UI): the backend export endpoint.
What
GET /api/v1/data/export(admin-only) returns a single versioned JSON snapshot of the caller's tenant business data as a browser download, and audits it as anexportaction.How
New
src/modules/data_transfermodule, schema-driven (no per-table code):tenant_idfrominformation_schema(auto-includes new business tables as migrations add them - no hand-maintained list to drift), ordered for a deterministic file.EXCLUDE_TABLES): integration secrets (payment_gateway_configs,credential_vault,api_keys,rmm_connections), auth identity/session/one-shot-token tables, and the audit/log tables.SECRET_COLUMN_SUBSTRINGS:encrypted,password_hash,_secret,mfa_secret,api_key,api_secret,private_key) - defense-in-depth for secrets that live inside otherwise-business tables (e.g.contacts.portal_password_hash,email_mailboxes.smtp_password_encrypted).row_to_json(t) ... WHERE tenant_id = $1underbegin_with_tenant(RLS) - tenant-scoped on both belts. Table names come from the catalog and are additionally guarded by an identifier check before interpolation.AuditAction::Export(the variant was already defined, previously unused), then returns the envelope (schema_version,tenant_id,included_tables,excluded_tables,redacted_column_patterns,notes,entities) as anapplication/jsonattachment download.Admin-gated (
RequireAuth+RequireAdmin), mounted on the/api/v1PSA router next to settings/audit.Scope / follow-ups (siblings under PMS-646)
notes).Tests
Unit tests on the security-critical redaction logic: secret columns are dropped and benign ones kept, every known secret column (from the migrations) is caught, and the identifier guard rejects non-identifiers. The endpoint-level round-trip (tenant scoping, full no-secret-substring scan of the output) is integration-level and lands with the import sibling.
#PMS-647