Files
sdarbinyan 8d9eb97e9e
Some checks failed
Architecture Governance / architecture (push) Has been cancelled
docs: remove superseded super-admin Phase 1 design doc
superuser.md (already tracked from prior session) supersedes
2026-08-15-platform-super-admin-design.md. Committing the prior
session's uncommitted rename.
2026-08-17 22:43:09 +04:00

6.5 KiB

Platform Super-Admin — Phase 1 Design

Status: Approved Date: 2026-08-15 Audience: Internal admin & risk team ("super puper user")

Purpose

A cross-tenant view for internal admin/risk staff: see every project (store/tenant) on the platform, drill into one, and review its access list, audit log, admin edit history, and purchase history. Read-only in this phase.

Editing project data / impersonating a store's admin ("edit all", with a per-change "notify this store's admin" toggle) is explicitly out of scope for this phase — see Phase 2 below. Phase 1 exists first because Phase 2's edit and notify plumbing depends on the tenant-context switch this phase builds.

Non-goals (Phase 1)

  • No editing of any tenant's data.
  • No impersonation of a store's admin.
  • No "notify store admin" mechanism (that's a Phase 2 concern, tied to edit actions that don't exist yet).
  • No real backend — this repo is frontend-only; the backend contract is specified here for whoever owns that service, not implemented here.

Architecture

  • New top-level feature module: src/app/features/platform-admin/.
  • New route tree /platform-admin/**, own shell/layout. Not nested under any tenant's /admin/** — a project is not "logged into" the way a store admin is.
  • New platformAdminAuthGuard (parallel to, but sharing no state with, adminAuthGuard in core/admin-auth/admin-auth.guard.ts).
  • PlatformAuthService — session/login state for the super-admin, backed by a PlatformAuthGateway interface: login(credentials), logout(), session().
    • PlatformAuthLocalGateway — dev-only implementation. Reads the expected credential from a git-ignored local file (platform-auth.local-secret.ts, added to .gitignore), never committed, never present in a production build path.
    • PlatformAuthApiGateway — later swap-in once the backend endpoint exists; same interface, no caller changes needed.

Data model

interface PlatformProjectSummary {
  id: UUID;
  name: string;
  slug: string;
  host: string;
  status: 'active' | 'suspended';
  createdAt: number;
  adminCount: number;
  lastActivityAt: number | null;
}

interface PlatformProjectAccessEntry {
  userId: UUID;
  displayName: string;
  telegramUsername: string;
  roleId: string; // maps to existing AdminRole / ROLE_PERMISSIONS
}

type PlatformProjectHistoryEntry =
  | { kind: 'access'; tenantId: UUID; actorLabel: string; timestamp: number; summary: string }
  | { kind: 'edit'; tenantId: UUID; actorLabel: string; timestamp: number; summary: string }
  | { kind: 'purchase'; tenantId: UUID; actorLabel: string; timestamp: number; summary: string };
  • PlatformProjectSummary[] is produced by PlatformProjectsGateway.list(), which aggregates the existing TenantConfig fixture list plus derived stats. Mock gateway now; real aggregation is a backend concern later.
  • PlatformProjectAccessEntry reuses the existing AdminRole / ROLE_PERMISSIONS shape from core/auth/models/permission.model.ts — no new role system.
  • PlatformProjectHistoryEntry is a discriminated union covering all three history types the user asked for (access/audit, admin edit history, purchase history). Mock gateway simulates aggregation from existing per-tenant sources (e.g. the pattern in AdminDashboardHistoryService, admin-transactions); real aggregation is a backend concern.
  • Every super-admin view into a project also writes its own kind: 'access' entry (platform.viewedProject) — the risk team needs to know who looked at what, not just what changed.

Components / pages

  • PlatformProjectsListPageComponent — table of all projects: name, status, admin count, last activity. Search/filter by status.
  • PlatformProjectDetailPageComponent — project overview stats, then tabs:
    • AccessPlatformProjectAccessEntry[] for that tenant.
    • Audit Loghistory filtered to kind: 'access'.
    • Edit Historyhistory filtered to kind: 'edit'.
    • Purchase Historyhistory filtered to kind: 'purchase'.
    • All read-only in this phase.

Security

  • platformAdminAuthGuard denies unless the session carries platform.superadmin. Like the existing AdminPermissionsService, the frontend check is defense-in-depth only — real enforcement must happen server-side once the backend endpoint exists. This is called out explicitly so it's never mistaken for the source of truth.
  • No credential is ever hardcoded in committed source. Dev-only credential lives in a git-ignored local file; production auth goes through the real backend endpoint below.
  • Session timeout for platform-admin: 15 minutes idle (shorter than regular tenant-admin sessions — higher-privilege session, smaller blast radius if a session is left open).
  • Every super-admin action (including read-only views) is itself audit-logged.
  • After implementation, run /security-audit on this feature specifically before it ships.

Backend contract (for whoever owns that service — not implemented in this repo)

Add to BACKEND-API-REFERENCE.md:

  • POST /platform-admin/auth — verifies a hashed credential server-side, returns a session token scoped to platform.superadmin. Never a plaintext credential check in a client-shipped artifact.
  • GET /platform-admin/projects — returns PlatformProjectSummary[].
  • GET /platform-admin/projects/:id/history — returns PlatformProjectHistoryEntry[] for that tenant, paginated.

Testing

  • Unit tests: platformAdminAuthGuard, PlatformProjectsGateway (mock), history-aggregation mapping logic.
  • No E2E in this phase — no real backend to exercise end-to-end yet.

Phase 2 (out of scope here)

A separate spec/plan cycle, once Phase 1 ships:

  • Full edit / impersonation: super-admin acts as a tenant's admin across every existing admin module (products, orders, categories, settings, etc.), reusing those modules under a tenant-context switch.
  • Per-edit-action "notify this store's admin about this change" checkbox, default unchecked. Uses the existing in-app notification pattern (the one behind admin-order-watcher.service.ts's unread-badge flow) so the affected tenant's admin sees it in their notification feed. Unchecked-by-default matters: some super-admin edits are discreet technical fixes where alerting the store admin would be noise or a reputational concern, not every edit should ping them.
  • This phase needs the tenant-context switch and audit-logging plumbing this Phase 1 spec establishes, which is why it's sequenced after.