feat(internal-admin): readonly impersonation for Süper Panel #23
Reference in New Issue
Block a user
Delete Branch "dev"
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?
Readonly impersonation endpoint + session-row + readonly guard. Migration 0005.
Lets the founder open a target user's session in a new tab from the panel for debugging. Read-only enforced server-side — any non-GET request from an impersonated session returns 403. Schema - sessions.impersonated_by (uuid, nullable) — founder Better Auth user id - sessions.impersonation_readonly (bool, default false) - index on impersonated_by Service - ImpersonationService.createReadonlySession({ targetUserId, founderId, ttlMinutes, reason, ipAddress, userAgent }): - Random sessionId + token (32 bytes hex each) - TTL clamped 1..60 min, default 15 - Refuses to impersonate admin users - Inserts sessions row; signs cookie value with HMAC-SHA256(BETTER_AUTH_SECRET) matching better-call's signCookieValue format - Returns { cookieName, cookieValue, expiresAt, sessionId } Guard - ImpersonationReadonlyGuard runs after AuthGuard, before RolesGuard. - GET/HEAD/OPTIONS pass through. - For other methods: looks up sessions.impersonated_by + impersonation_readonly by request.session.id; throws ForbiddenException if both truthy. Endpoints (InternalAdminModule) - POST /internal/admin/users/:id/impersonate-readonly [InternalTokenGuard] body: { ttlMinutes, reason, founderId } returns: { redirectUrl, expiresAt, sessionIdPrefix } Hand-off is via signed consume URL (cross-origin Set-Cookie limitations). - GET /admin/impersonate/consume?t=<signed> [@Public] Verifies HMAC-signed payload (<=60s validity), sets the Better Auth session cookie on sase.tr, redirects to /. One-shot. Wiring - InternalAdminModule imported in AppModule. - ImpersonationReadonlyGuard registered as APP_GUARD between Auth and Roles. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>