Admin impersonation
Let an admin act as another user from the DevTools, with a warning bar, time-limited tokens and an audit log.
Impersonation lets an admin see and use the app exactly as one of its users does, which is the fastest way to reproduce “it looks wrong for me” reports. It is off unless a project enables it, it is started from the DevTools Access tab, and while it lasts the app is unmistakable: the page becomes a rounded sheet on an amber backdrop, with a bar above it naming who you are acting as and a Stop button.
The page is scaled, not pushed down: one factor for both axes, so the aspect ratio is untouched, 100vh layouts resolve to the framed area, and an app-shell layout keeps its bottom row on screen and its internal scrolling intact.
An impersonating admin has the target user’s full access, writes included. Grant spky admin add only to people you would trust with every account. Every session and every write is recorded.
Enable it
Add the impersonation block to sp00ky.yml and redeploy (or restart spky dev):
Then make sure the people who may impersonate are admins:
With enabled absent or false, every deploy removes the impersonation access method and functions from the database, so no impersonation token can be issued or used, including tokens handed out before you turned it off. The SSP and scheduler also refuse to sign tokens unless SPKY_IMPERSONATION is on, and they never sign one without a non-empty SPKY_AUTH_SECRET.
Use it from the DevTools
- Sign in to the app as an admin and open the DevTools Access tab.
- Under Impersonate, search for a user. Admins are listed but cannot be picked.
- Enter a reason (it goes into the audit log), click Impersonate, and confirm.
The page switches to the user’s session: queries, permissions and live updates behave exactly as they do for that user. Their data lives in its own local bucket, which is deleted from your device when you stop. Stop impersonating in the warning bar or in the Access tab returns you to your own session.
How it is secured
- Server-enforced. The client only asks.
fn::_00_impersonate::startruns in the admin’s own SurrealDB session and is permitted only for_00_adminmembers. The backend then signs a token for a dedicated_00_impersonateaccess method, with a key derived fromSPKY_AUTH_SECRETthat never leaves the backend. - Checked on every authenticate. The access method re-validates the session row each time the token is used. The session must be open and unexpired, its admin must still be on the roster, and its target must exist and must not be an admin.
- Short-lived. A token lives
tokenTtland the client renews it before it expires. Nothing is renewed pastmaxDuration. - Revocable. Stopping, ending the row server-side, removing the admin from the roster, disabling the feature, or rotating
SPKY_AUTH_SECRETall end the session. An open connection notices within a minute. - No escalation. An impersonated session cannot start another one, cannot read the audit tables, and session rows cannot be edited except to end them.
- Visible. The warning lives in a closed shadow root, and the framing is written as
!importantinline styles, so no app stylesheet can hide or undo either. Both are reverted exactly when impersonation ends. An app can restyle or replace the banner (see Make it yours); replacing it keeps a fallback, and only an explicitmode: 'none'turns the warning off.
While impersonating, $access in SurrealDB is _00_impersonate, not the user’s usual access method. Rules written against $auth.id behave exactly as for the user, and so does the SSP’s view permission check. A table rule that compares $access directly (for example $access = "account") does not match an impersonated session.
Audit trail
In your app
By default the warning is shown and the page is shifted and rounded for you; you do not have to render anything. The shift publishes its height as a CSS variable, which anything of yours that is fixed to the top of the viewport should use:
Make it yours
impersonationBanner in the client config covers three cases.
Restyle the built-in one. Pass a theme; anything you leave out keeps its default. Colours take any CSS value, so your own tokens work.
| Prop | Type | Default | Description |
|---|---|---|---|
| heightPx | number | 40 | Height of the bar above the page. |
| insetPx | number | 10 | Gutter left, right and below the page. `0` frames from the top only. |
| radiusPx | number | 14 | The framed page's corners. `0` squares them off. |
| background | string | amber wash + rake | Any CSS `background` for the backdrop behind the page. |
| text | string | dark brown | Warning text colour on the backdrop. |
| accent | string | dark brown | Stop button background. |
| accentText | string | off-white | Stop button text colour. |
| stopLabel | string | 'Stop' | Label of the button that ends the session. |
| label | (info) => string | Impersonating … | The sentence. Wrap a part in `**` to emphasise it; the result is text, never markup. |
| noPageShift | boolean | false | Keep the bar but leave the page layout untouched (do your own offset with the CSS variable). |
Render your own. Set mode: 'custom' and build the bar yourself from useImpersonation(). Acknowledge it while it is on screen, so the client knows a warning is actually visible; the Solid hook does that for you with rendersBanner: true. If nothing acknowledges within about 2.5 seconds, the built-in banner appears, on the assumption that your banner failed to render rather than that nobody should be warned.
Show nothing. mode: 'none' renders nothing, ever, and does not fall back. It is your call, and then it is your job to make an impersonated session obvious: the page otherwise looks exactly like an ordinary session of someone else’s account. The client logs one console warning to say so.
To react to impersonation yourself, use the hook (Solid) or the auth service (any client):
| Prop | Type | Default | Description |
|---|---|---|---|
| auth.impersonation | ImpersonationInfo | null | - | The active impersonation, derived from the token in use. |
| auth.impersonate(targetId, reason) | Promise<ImpersonationInfo> | - | Start. Admins only; `reason` needs at least 3 characters. |
| auth.stopImpersonating() | Promise<void> | - | Return to the admin session. Always leaves the target, even offline. |
| auth.subscribeImpersonation(cb) | () => void | - | Called immediately and on every change. Returns an unsubscribe. |
| auth.searchImpersonationTargets(search) | Promise<ImpersonationCandidate[]> | - | The DevTools user search. Admins only. |
| auth.listActiveImpersonations() | Promise<ActiveImpersonation[]> | - | Open sessions. Admins only. |
| useImpersonation() | { impersonation, isImpersonating, stop } | - | Solid hook in `@spooky-sync/client-solid` and `client-solid2`. |
Signing out while impersonating ends the impersonation and signs the admin out too.