138 lines
6.5 KiB
Markdown
138 lines
6.5 KiB
Markdown
|
|
# 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](#phase-2-out-of-scope-here) 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
|
||
|
|
|
||
|
|
```ts
|
||
|
|
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:
|
||
|
|
- **Access** — `PlatformProjectAccessEntry[]` for that tenant.
|
||
|
|
- **Audit Log** — `history` filtered to `kind: 'access'`.
|
||
|
|
- **Edit History** — `history` filtered to `kind: 'edit'`.
|
||
|
|
- **Purchase History** — `history` 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.
|