Skip to content

feat(script): user impersonation script for local dev - #212

Draft
jerryzhou196 wants to merge 2 commits into
mainfrom
feat/impersonate-script
Draft

feat(script): user impersonation script for local dev#212
jerryzhou196 wants to merge 2 commits into
mainfrom
feat/impersonate-script

Conversation

@jerryzhou196

Copy link
Copy Markdown
Member

What

Adds script/impersonate.sh — mint a UW Flow session token for any user so you can view the site exactly as they see it during local development.

How it works

The backend hands out an HS256 JWT at login (flow/api/serde/jwt.go); the frontend just stashes it in localStorage under token + user_id (src/utils/Auth.tsx). Nothing ties that token to how you logged in — it's a signature over the Hasura claims keyed by HASURA_GRAPHQL_JWT_KEY. So the script:

  1. Looks the user up over a direct psql connection using the POSTGRES_* creds in .env (host psql, or docker exec postgres psql when no client is installed).
  2. Signs byte-for-byte the same claims as serde.NewSignedJwt (single user role + iat/nbf/exp).

No impersonation endpoint, no DB writes — the token is indistinguishable from a real login, which is why it's dev-only.

Usage

script/impersonate.sh                        # list everyone, filter interactively (fzf)
script/impersonate.sh 4021                   # by user id
script/impersonate.sh someone@uwaterloo.ca   # by email
script/impersonate.sh "Sandy Wu"             # by fuzzy name
script/impersonate.sh -i sandy               # picker pre-filtered to "sandy"

Flags: -i/--interactive, -e/--env-file, -t/--ttl, -c/--copy, --token-only, --json. Prints a browser-console snippet that sets token + user_id and reloads, plus a one-liner to drop back out.

Verified against a live stack

  • Minted token accepted by Hasura{ user { email } } returns only that user's row (row-level x-hasura-user-id honored)
  • Accepted by the Go API/auth/refresh validated it and issued a fresh token
  • Tampered signature rejected by Hasura
  • Lookup by id / email / fuzzy name, ambiguous-match listing, no-match, bad-ttl, missing-env all handled
  • fzf pipeline (data → match → id extraction) verified; degrades gracefully with no TTY or no fzf

🤖 Generated with Claude Code

https://claude.ai/code/session_01TrJWMd1itog7bn3UfdNvg6

jerryzhou196 and others added 2 commits August 28, 2026 15:09
Mint a UW Flow session token for any user via a direct psql lookup and
the JWT key in .env, so you can view the site exactly as they see it.

Signs byte-for-byte the same HS256 claims as serde.NewSignedJwt, so both
Hasura and the Go API accept the token; nothing is written to the DB.

- looks users up by id, email, or fuzzy name
- interactive fzf picker over the full user table when no user is named
- reaches Postgres via host psql or `docker exec postgres psql`
- emits a browser-console snippet (localStorage token + user_id) plus
  --token-only / --json for scripting

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TrJWMd1itog7bn3UfdNvg6
pick_user runs inside a $(...) capture, so stdout is always a pipe even
in an interactive shell — the old `[ -t 1 ]` guard therefore blocked the
fzf picker in every real run, not just non-interactive ones. fzf draws
on /dev/tty regardless of where stdout goes, so test /dev/tty instead.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TrJWMd1itog7bn3UfdNvg6
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant