feat(auth): add whoami and logout commands with credential storage#25
Open
omermorad wants to merge 13 commits into
Open
feat(auth): add whoami and logout commands with credential storage#25omermorad wants to merge 13 commits into
omermorad wants to merge 13 commits into
Conversation
…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
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
Author
|
Stainless compatibility note All custom code for login/logout/whoami lives in new files, not in Stainless-generated ones:
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 |
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.
4 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
~/.nimble/credentials.jsonwith 0600 permissions and atomic writesnimble whoamicommand that shows current auth source (env var or stored credential) with masked API keynimble logoutcommand that removes stored credentialsgetDefaultRequestOptions()when--api-key/NIMBLE_API_KEYare not set--api-keyflag /NIMBLE_API_KEYenv > stored credential > no authTest plan
TestWhoamiNoAuth: no credentials, exits 1 with "Not authenticated"TestWhoamiWithStoredCredential: pre-written credentials.json, exits 0, shows masked key and accountTestWhoamiEnvOverridesStored: env var takes priority over stored credentialTestLogoutWithCredentials: deletes credentials.json, confirms logoutTestLogoutNoCredentials: no credentials, exits 0 with "Not currently logged in"NIMBLE_CONFIG_DIR=t.TempDir()for isolation (blackbox tests viaexec.Command)WEBIT-1194