# Admin Login-as-Tenant (Impersonation)

**Date:** 2026-05-31  
**Status:** Draft  
**Spec:** 72  
**Tier:** SUPER_ADMIN only (system admin plane)  
**Depends on:** `admin-dashboard`, `foundation-auth-rbac`, `audit-compliance`  
**Referenced by:** `admin-dashboard`

---

## Overview

SUPER_ADMIN staff can impersonate a tenant OWNER session for support and debugging. Impersonation creates a time-limited shadow session that gives the admin access to the tenant's app as if they were the tenant OWNER. Every action taken during impersonation is tagged in the audit log.

Spec 8 (`admin-dashboard`) describes the admin plane at `admin.zync.is`. Impersonation sessions land in `app.zync.is`.

---

## Access Point

In `/admin/tenants/:slug` (spec 8 Tenant Detail page), SUPER_ADMIN sees:

```
┌────────────────────────────────────────────────────────────┐
│  Acme Corp                                                 │
│  business · 48 members · 2026-01-15 signup                 ��
│                                                            │
│  [View Audit Log]  [Freeze Tenant]  [Login as Tenant →]    │
└────────────────────────────────────────────────────────────┘
```

"Login as Tenant" requires:
- Caller is `SUPER_ADMIN`
- Tenant is not frozen

---

## Impersonation Session

`POST /api/admin/tenants/:slug/impersonate`  
Returns: `{ impersonationToken, expiresAt }` (token is short-lived: 15 minutes, not renewable)

Client redirects to `https://app.zync.is/impersonate?token={token}`.

The `impersonationToken` is a signed JWT:
```json
{
  "sub": "admin:{adminUserId}",
  "tenant_id": "...",
  "impersonated_as": "owner",  // always OWNER role
  "exp": 1717170000,
  "type": "impersonation"
}
```

Signed with a separate `IMPERSONATION_SECRET` key (not the regular session JWT secret). `app.zync.is/impersonate` handler validates this token + creates a temporary session cookie with a `impersonation: true` flag.

---

## In-App Impersonation Banner

While impersonating, a persistent top banner replaces the normal app shell header:

```
┌──────────────────────────────────────────────────────────────┐
│  🔒 Admin mode: Viewing as [Acme Corp] OWNER   [End session] │
│  Session expires in: 14:32                                   │
└──────────────────────────────────────────────────────────────┘
```

- Banner is orange (`--color-warning` background), non-dismissible
- Countdown timer counts down from 15:00
- "End session" → invalidates the impersonation session cookie, redirects back to `admin.zync.is/tenants/{slug}`
- Impersonation sessions auto-expire: cookie TTL = 15 min; server-side check on every request

---

## Permissions During Impersonation

Impersonation grants OWNER-level permissions within the tenant. The admin can:
- View all tenant data
- Simulate actions (create, edit, send) to reproduce reported bugs

The admin cannot:
- Delete the tenant account
- Downgrade the subscription tier
- Modify billing payment methods

These operations are blocked at the API layer when `session.impersonation === true`. Each blocked endpoint returns `403 Impersonation sessions cannot perform this action`.

---

## Audit Trail

Every action during an impersonation session is logged to `tenant_audit_log` with a distinct `actor_context`:

```json
{
  "actorId": "{adminUserId}",
  "actorType": "admin_impersonation",
  "actorName": "Admin: {adminUserEmail}",
  "tenantId": "...",
  "action": "invoice.sent",
  "...": "..."
}
```

This ensures:
- Tenant OWNER can see in their audit log that an admin accessed their account (spec 50 tenant audit log UI shows `actorType = 'admin_impersonation'` with a 🔒 icon)
- Admin plane audit (spec 28) also captures the impersonation start/end events

System admin audit events:
- `POST /api/admin/tenants/:slug/impersonate` → emits `admin.impersonation_started` to system audit log
- Session end or expiry → emits `admin.impersonation_ended` (with duration)

---

## API Endpoints

```
POST /api/admin/tenants/:slug/impersonate
  → create impersonation token (SUPER_ADMIN only)
     returns: { token, expiresAt }

GET  /impersonate?token={token}    (app.zync.is — tenant app)
  → validate token, create session, redirect to /

POST /api/admin/impersonation/end  (app.zync.is — tenant app)
  → invalidate impersonation session, return to admin plane
```

---

## Foundation Deltas

**New secret:** `IMPERSONATION_SECRET` — JWT signing key for impersonation tokens. Separate from `JWT_SECRET` to allow key rotation without affecting regular sessions.

---

## Architecture Decisions

| Decision | Choice | Reason |
|----------|--------|--------|
| Short-lived token (15 min, no renewal) | Not role-assumption | Impersonation is a support tool — short TTL limits blast radius if token is intercepted; non-renewable prevents prolonged unmonitored access |
| Always OWNER role | Not configurable | OWNER is the minimum needed for support; granting arbitrary roles is complex and rarely needed; OWNER grants access to all data without billing/deletion risk |
| Tenant-visible audit | Mandatory | Tenant must know when their account is accessed by Zync staff — transparency + trust + potential legal requirement (GDPR Art. 30) |
| Blocked destructive ops | Not trust-on-impersonation | Admin accident risk is real; billing changes and account deletion should never happen via an impersonation session |
