feat: lk impersonate (mint/use/revoke buyer-scoped Access Token) — LIN-5921#4
Conversation
LK_TOKEN is an env override for the *original* token only. Previously, LoadImpersonation delegated to Load(), which checks LK_TOKEN first — so any non-empty LK_TOKEN silently masked an active stored impersonation context (returning nil with no error or warning). Extract a private loadStored() helper in store.go that reads the keychain/file store directly, bypassing the env override. Load() keeps its existing behaviour (LK_TOKEN wins for original token lookups). LoadImpersonation() now uses loadStored() and drops the now-unnecessary src == SourceEnv guard. New tests (TDD, RED first): - TestLoadImpersonationNotMaskedByEnvToken (internal/auth) - TestResolveAPIImpersonationWinsOverLKToken (internal/commands)
Implements `lk impersonate <ref>`, `lk impersonate stop`, and `lk impersonate status` (TDD, 15 command tests + 3 client coverage tests, total coverage 95.2% ≥ 95% gate). Secret token is never emitted in JSON or styled output.
whoami emits a ⚠ banner to stderr (keeping stdout clean for the JSON contract) when an impersonation context is active. auth status appends an impersonation block (target email, buyer, expiry, impersonator, state ativa/EXPIRADA) after the normal auth output.
Assign fmt.Fprintf return values to _, _ in auth.go and whoami.go (errcheck). Rename unused http.Request param to _ in visibility test (revive unused-parameter). All three files are from Task C5.
…mpersonate - Guard impersonation human-readable line in `auth status` behind `formatFlag != JSON` so stdout remains valid JSON when impersonating (Finding 1: CRITICAL — was appending plain text after JSON object) - Best-effort revoke prior impersonation token before minting a new one in runImpersonateStart; print stderr warning on revoke failure (Finding 2) - Tighten whoami banner test: assert banner on errOut, absent from out (Finding 3) - Remove stale plan comment from impersonation_resolve_test.go (Finding 4)
LIN-5921 CLI: expor /srm/suppliers via JSON (format.json + jbuilder) + comandos supplier
Começar a expor os controllers SSR existentes via JSON pra CLI consumir, seguindo a receita Rails-way (sem API nova). Receita (acordada)
Primeiro endpoint: /srm/suppliers
Rotas existentes de supplier ( CLI (lk)Mapear 1-1 os REST methods. Nesta fatia: DependênciaPra testar o fluxo CLI→PAT→JSON ao vivo, precisa do auth (LIN-5916) mergeado — a Warden strategy que autentica o Bearer PAT vive lá. O endpoint JSON em si é independente (testável via sessão/ |
There was a problem hiding this comment.
5 issues found across 19 files
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
Specs em docs/superpowers/specs sao artefatos de planejamento local, nao pertencem ao repo versionado.
… status; round sub-second TTL
Finding A: impersonate status --format json with no context now emits `null` (not
plain text) via impersonateStatusView.MarshalJSON(); styled still shows the human
notice. Adapted three existing tests to use --format styled for text assertions.
Finding B: impersonate stop now routes through impersonateStopView which emits
{"stopped":true,"target_email":"..."} or {"stopped":false} in JSON mode; styled
keeps the original human sentences.
Finding C: auth status --format json now includes statusImpersonation block inside
statusResult (omitempty) with target_email, buyer_id, expires_at, impersonator_email,
and expired bool. Token never appears in output.
Finding D: LoadImpersonation error in auth status now prints a warning to stderr
(aviso: ...) instead of being silently discarded; command still exits 0.
Finding E: StartImpersonation uses math.Ceil(ttl.Seconds()) so a 500ms TTL sends
ttl_seconds=1 instead of 0, preventing the backend from silently applying 24h default.
There was a problem hiding this comment.
1 issue found across 9 files (changes from recent commits).
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
…ays JSON) The impersonation line was appended out-of-band guarded by the raw --format flag, so `--format auto` piped to a non-TTY (which Render resolves to JSON) emitted a JSON object followed by a human line — invalid JSON. Fold the line into statusResult.Styled() and let output.Render own format resolution.
There was a problem hiding this comment.
2 issues found across 21 files
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
cirdes
left a comment
There was a problem hiding this comment.
@djalmaaraujo, olha se o comentário do cubic faz sentido e depois pede revisão novamente!
…sonation Addresses cubic review on PR #4: - StartImpersonation rejects negative ttl (was sent as negative ttl_seconds or silently rounded to the backend default). - auth status now reflects active impersonation: true while a non-expired context is active (even without a base token), false when the context is expired (sticky, no fallback).
…sonation Addresses cubic review on PR #4: - StartImpersonation rejects negative ttl (was sent as a negative ttl_seconds or silently rounded to the backend default). - auth status "authenticated" now reflects active impersonation: true while a non-expired context is active (even without a base token), false when the context is expired (sticky, no fallback to the original token).
e670b15 to
6305199
Compare
|
@cirdes os 2 pontos do cubic faziam sentido, corrigi ambos em
Cobertos por teste; |
Linear: LIN-5995
O quê
lk impersonate <email|user_id>impersona o usuário@linkanade um buyer cunhando um Access Token real no backend, e o CLI passa a agir como aquele user nos comandos SRM. Comandos:impersonate <ref>,impersonate stop,impersonate status.PR par no backend:
linkanalabs/linkana#13683(endpointPOST/DELETE /impersonation).Modelo de segurança (núcleo)
Auth de duas camadas por origin: token original (login) + contexto de impersonação (token cunhado + alvo + expiry). Regras de resolução de credencial:
LK_TOKEN> PAT do keychain.LK_TOKENsó sobrescreve o token original; não desativa impersonação (fecha footgun ondeLK_TOKENsilenciava a impersonação).lk impersonate stopou re-impersonar.whoami/auth statusmostram as duas camadas; segredo (lkn_...) nunca sai em stdout/JSON (só persistido no storage: keychain + fallback arquivo 0600).Camadas
internal/client:Post/Delete+StartImpersonation/StopImpersonation(paramtarget).internal/auth: contexto de impersonação por origin (chave namespaced; lê o store ignorandoLK_TOKEN).internal/commands:authedClient(4-return) +resolveAPI/unauthorizedErrpegajosos; comandosimpersonate *; visibilidade emwhoami/auth status.CLAUDE.md: seções de impersonação/buyer-scope + ponteiros pro repo backend (../linkana).Testes & gates
make test(-race) verde;make covertotal 95.0% (gate ≥95%);make lint(golangci-lint v2) 0 issues. TDD em todas as tasks.Processo
Implementado via subagent-driven-development: 7 tasks, review (spec+qualidade) por task + review final whole-branch (opus) nos 2 repos. Achados corrigidos: footgun
LK_TOKEN, JSON inválido emauth statusao impersonar, revogação do token anterior ao re-impersonar, e endurecimento de testes.🤖 Generated with Claude Code
Summary by cubic
Adds
lk impersonateto mint, use, and revoke buyer‑scoped Access Tokens so the CLI can act as a buyer user for SRM commands. Implements a strict two‑layer auth model with sticky impersonation and no silent fallback (LIN-5921).New Features
lk impersonate <email|user_id>,lk impersonate stop,lk impersonate status.POST/DELETE /impersonation.json; client addsStartImpersonation/StopImpersonation.internal/auth; secret token is never printed.LK_TOKEN> keychain PAT; expired/401 is a hard error.whoamishows an impersonation banner on stderr;auth statusincludes target, buyer, expiry, impersonator, and “authenticated” reflects impersonation state.Bug Fixes
LK_TOKENfrom masking an active impersonation by reading stored creds directly inauth(loadStored).auth status(auto/piped stays JSON),impersonate status --format jsonreturnsnullwhen no context, andimpersonate stopemits{"stopped":...}.ttl_seconds=0) and reject negative TTL.auth status“authenticated” now derives from impersonation: non-expired = true; expired = false (no fallback).CLAUDE.local.mdbanning specs/plans commits; remove planning specs.Written for commit 6305199. Summary will update on new commits.