Skip to content

feat(auth): add whoami and logout commands with credential storage#25

Open
omermorad wants to merge 13 commits into
mainfrom
omerm/webit-1194-add-nimble-loginlogoutwhoami-cli-commands-with-browser-based
Open

feat(auth): add whoami and logout commands with credential storage#25
omermorad wants to merge 13 commits into
mainfrom
omerm/webit-1194-add-nimble-loginlogoutwhoami-cli-commands-with-browser-based

Conversation

@omermorad

Copy link
Copy Markdown

Summary

  • Add persistent credential storage at ~/.nimble/credentials.json with 0600 permissions and atomic writes
  • Add nimble whoami command that shows current auth source (env var or stored credential) with masked API key
  • Add nimble logout command that removes stored credentials
  • Wire stored credentials as fallback in getDefaultRequestOptions() when --api-key/NIMBLE_API_KEY are not set
  • Auth priority chain: --api-key flag / NIMBLE_API_KEY env > stored credential > no auth

Test plan

  • TestWhoamiNoAuth: no credentials, exits 1 with "Not authenticated"
  • TestWhoamiWithStoredCredential: pre-written credentials.json, exits 0, shows masked key and account
  • TestWhoamiEnvOverridesStored: env var takes priority over stored credential
  • TestLogoutWithCredentials: deletes credentials.json, confirms logout
  • TestLogoutNoCredentials: no credentials, exits 0 with "Not currently logged in"
  • All tests use NIMBLE_CONFIG_DIR=t.TempDir() for isolation (blackbox tests via exec.Command)

WEBIT-1194

…ial storage

Add persistent credential storage (~/.nimble/credentials.json) and two new CLI
commands under the AUTH category. The whoami command displays the current auth
source (env var or stored credential) with a masked API key. The logout command
removes stored credentials. Stored credentials are used as a fallback when
--api-key flag and NIMBLE_API_KEY env var are not set.

WEBIT-1194
@omermorad omermorad self-assigned this May 12, 2026
omermorad added 11 commits May 12, 2026 08:06
Add the login command with interactive method selection prompt. The API key
paste flow validates the key against the whoami endpoint before saving
credentials. Supports re-authentication when already logged in.

WEBIT-1194
Show the specific error from the whoami endpoint instead of a generic
"API key is invalid" message, so users can distinguish between invalid
keys, network issues, and server errors.

WEBIT-1194
Reorder auth priority to match WEBIT-1194 spec:
1. --api-key flag (explicit per-command override)
2. Stored API key (from nimble login)
3. NIMBLE_API_KEY env var (legacy fallback)

Previously the env var and flag were treated at the same level via
urfave/cli's Sources mechanism. Now isAPIKeyFlag() checks os.Args
directly to distinguish the flag from the env var.

WEBIT-1194
Security fixes:
- Add 10s HTTP timeout to ValidateAPIKey (prevents hanging on slow servers)
- Use os.CreateTemp for unique temp file names (prevents concurrent write race)
- Add defer os.Remove on temp file (prevents credential leak on rename failure)
- Propagate os.UserHomeDir error instead of silently using empty path
- Pass context.Context into ValidateAPIKey for Ctrl+C cancellation
- Raise maskAPIKey threshold from 8 to 12 (short keys no longer leak most chars)

Error handling fixes:
- Replace os.Exit(1) with cli.Exit/error returns (preserves deferred cleanup)
- Check scanner.Err() after Scan failures (surface I/O errors)
- Surface LoadCredentials errors via log.Printf in getDefaultRequestOptions

WEBIT-1194
Walks through the full auth lifecycle in 13 sequential steps sharing
one config dir. Requires NIMBLE_TEST_API_KEY env var; skips otherwise.
Implements the full OAuth flow: AS metadata discovery, dynamic client
registration, PKCE S256 challenge, localhost callback server, token
exchange, and API key fetch. Cross-platform browser launch supports
darwin/linux/windows with NIMBLE_BROWSER env var override for testing.
…h coverage

Clean env allowlist prevents parent NIMBLE_* vars from leaking into
tests. Mock OAuth server now validates PKCE verifier against challenge
and checks auth headers. Adds TestLoginBrowserStateMismatch.
Fixes goroutine leak on duplicate callback hits via done channel,
escapes HTML in error page, adds constant-time state comparison,
server read/write timeouts, response body size caps, and empty
API key rejection. Falls back to email when AccountName is empty.
Splits OAuth callback logic into callbackServer struct (callback.go)
with clean lifecycle methods. Introduces doJSON and decodeBody helpers
to eliminate repeated HTTP request/decode boilerplate. Moves httpClient
to client.go. Extracts completeLogin to remove duplication between
browser and API key login paths.
Each auth command gets its own file: login.go, logout.go, whoami.go.
Shared helpers (maskAPIKey, init registration) stay with their primary
consumer.
oauth.go (96 lines): flow orchestration and PKCE generation
exchange.go (103 lines): OAuth HTTP operations (discovery, DCR, token)
apikey.go (54 lines): API key fetch and create
client.go (29 lines): shared HTTP client, doJSON, decodeBody
@omermorad

Copy link
Copy Markdown
Author

Stainless compatibility note

All custom code for login/logout/whoami lives in new files, not in Stainless-generated ones:

  • internal/auth/ (8 files): OAuth flow, credentials, validation, browser launch
  • pkg/cmd/login.go, logout.go, whoami.go: CLI commands
  • pkg/cmd/cmdutil.go: auth priority chain (hand-written file, not generated)

Per Stainless docs, files that Stainless didn't generate are never touched during regeneration. Edits to generated files would be preserved via semantic three-way merge, but we didn't need to make any. No configuration changes needed on the Stainless platform.

Commands register via Go init(), so the generated cmd.go doesn't reference our custom commands directly.

Browser login no longer validates the API key against the whoami
endpoint (api.webit.live), which uses a different key store than the
OAuth server (api.nimbleway.com). The OAuth flow itself is the
authentication; the API key and account name are saved directly from
the token exchange response.

Also: logout now warns when NIMBLE_API_KEY env var is still set,
and the browser callback page matches the Nimble consent page styling.
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