use-viewer-user-id.ts2.3 KBView on GitHub import { useSession } from '@/modules/auth/utils/auth-client';
import { useAdminViewingUserId } from '@/modules/store';
/**
* Whose data this client is showing — the ONLY correct answer to "who am I" on a screen
* that reads user-scoped data.
*
* `useSession()` ALONE IS WRONG HERE, AND IT CANNOT BE FIXED. Under Cedar-staff admin view
* every tRPC request carries `X-Admin-View-User` (providers/query-provider.tsx) and the
* server swaps `ctx.sessionUser` to the impersonated user, so the whole client is already
* acting as somebody else. The session is the one thing that does NOT move with it: better-auth
* talks to `/auth/get-session`, and the impersonation middleware explicitly skips `/auth/*`
* (apps/server/src/http/app.ts), so `useSession()` reports the signed-in staff member forever.
*
* Mixing the two is not a cosmetic slip; it mints a CROSS-USER request. A `user` file scope
* built from the session asks, as the impersonated user, for the staff member's own workspace,
* which `files.listChildren` correctly refuses as cross-user (`assertAuthorizedScope` in
* trpc/routes/files.ts) — and the refusal is a tRPC FORBIDDEN that nothing logs, so the
* symptom is an agent's Files tab that is simply empty with a clean server.
*
* `targetUserId` IS DELIBERATELY NOT READ HERE. The org-admin picker
* (`modules/administeredUser`) is a property of one SURFACE — the screens that mount
* `AdministeredUserBar` — and an ambient read of it resolves someone's own document against
* whichever teammate happened to be selected; see the note on `useScopedInput`. Admin view is
* the opposite: it is a property of the whole CLIENT, which is exactly why it is safe to read
* from anywhere. A surface that has both still takes the target as a prop and falls back to
* this: `targetUserId ?? useViewerUserId()`.
*
* Null means "not positively known" — pending session, or signed out. Callers must treat it as
* unknown rather than as a user: gate the query on it (`enabled`) instead of substituting a
* guess, because a query fired against the wrong id is the defect above.
*/
export function useViewerUserId(): string | null {
const adminViewingUserId = useAdminViewingUserId();
const { data: session } = useSession();
return adminViewingUserId ?? session?.user?.id ?? null;
}