feat(auth): browser-assisted CLI login + profiles + auth/team commands#84
Merged
Conversation
Collaborator
Author
|
@greptile review please |
…sist legacy migration, don't overwrite default profile on login, reset poll backoff + fail-fast on approved-without-key, honor LATITUDESH_TOKEN in auth status, env api-version for --with-token, stderr warnings, filepath.Dir)
Collaborator
Author
|
@greptile review |
leandroh
approved these changes
Jun 9, 2026
…e run hooks for robust root PreRunE
Collaborator
Author
|
@greptile review please |
…le UX, atomic config, project-picker pagination, hydration exemption; config/authclient/cli tests)
Collaborator
Author
|
@greptile review please |
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
Adds a browser-assisted login flow to
lsh, so users no longer need to manually copy an API token from the dashboard. Adds multi-team support via profiles, plus commands to inspect and switch the active context.New commands
lsh loginlsh login --with-token <T>/user/profileand stores it under a profile.lsh auth statuslsh auth logout [--profile X | --all]browser.lsh profile use <name>lsh profile listNew global flag and env vars
--profile <name>LATITUDESH_TOKENLSH_PROFILELSH_PROJECT--projectflag (skips the interactive prompt).Interactive project picker
Commands that take
--project(e.g.lsh servers list,lsh servers create) now prompt interactively when the flag is missing and stdin is a TTY:For list commands, picking All projects (or passing
--all-projects) lists across every project.In non-interactive contexts the command fails with an actionable message:
Config file
The config at
~/.config/lsh/config.jsonnow supports multiple profiles. Existing single-token configs are migrated automatically on first run.Before:
{ "Authorization": "ak_xxx", "API-Version": "2023-06-01" }After (auto-migrated as profile
default):{ "default_profile": "acme", "profiles": { "acme": { "authorization": "ak_xxx", "team_id": "...", "team_name": "Acme", "team_slug": "acme", "email": "you@example.com", "source": "with-token", "api_version": "2023-06-01" } } }Permissions:
0700directory,0600file.Backward compatibility
lsh login <token>(positional) still works but prints a deprecation warning.--project=<id>etc.) unchanged.Breaking change
lsh servers listandlsh virtual-networks listno longer silently list across all projects when--projectis missing. Behaviour now:Existing scripts can opt back into the old behaviour with
--all-projects,--project=<id>, orLSH_PROJECT=<id>.Notes
cli/get_servers_operation.go,cli/get_virtual_networks_operation.go) gained a manually-added--all-projectsflag. Lines are tagged// MANUAL — keep when regeneratingto survive future swagger regenerations.How to test
go build -o ./lsh-dev .1. Token login (covers most of the flow without needing the browser page)
Expected:
auth statusshowsEmail,Team,Source: with-token.profile listshows the profile with a*marker.profilesmap with the team slug as key.2. Switch between profiles
Run
login --with-tokentwice with tokens from different teams (or pass--profile=<name>to override the auto-naming).3. Logout
Logout on a profile with
source: browserrevokes the API key remotely (you can verify under Settings → API Keys); logout on a--with-tokenprofile only clears it locally.4. Project picker
After logging in, run a command that needs a project:
Expected: an interactive picker listing projects + an All projects entry. Selecting a project filters results; selecting All projects lists everything.
Other invocations to test:
5. Browser-assisted login
Requires the matching dashboard page deployed (the page lives in a separate repo and is being shipped in parallel — you'll see the URL the CLI prints).
Expected: prints the URL + a
user_code; opens the browser if available; polls until you approve. After approval, prints✅ Logged in as .... If the browser cannot be opened (SSH session, no DISPLAY, piped stdin), the CLI prints the URL and waits — you open it from another machine.6. Config migration
Manually drop a legacy config and verify it gets migrated:
Expected: a
defaultprofile materialized from the legacy field,source: with-token. Subsequent runs are no-ops.Greptile Summary
This PR adds a complete browser-assisted login flow (
lsh login), multi-team profile support, and three new command groups (auth,profile,login) to the CLI. It migrates the existing single-token config format to a multi-profile store, adds an interactive project picker for commands that need--project, and introducesLATITUDESH_TOKEN/LSH_PROFILE/LSH_PROJECTenvironment overrides.POST /auth/cli_sessionscreates a session; the CLI polls withX-CLI-Secretuntilstatus=approved, extracts the returned API key/team/user, and saves a named profile.--with-tokentakes the fast path viaGET /user/profile+GET /team.~/.config/lsh/config.jsonis rewritten as a{default_profile, profiles:{…}}map with an atomic temp-file rename and 0600 perms; old single-token files are auto-migrated on first load and the migration is persisted immediately.resolveProjectFlagruns inPersistentPreRunE; on an interactive TTY it fetches projects and launches a bubbletea list; on a non-TTY it returns an actionable error.--all-projects(manually added to two generated files) bypasses the requirement.Confidence Score: 4/5
The core auth and config logic is well-tested and covers migration, atomic writes, profile resolution order, and both login flows. The known previous issues are all addressed in this diff.
The implementation is thorough and previously raised blocking issues have been fixed. Two UX concerns remain: the headless banner is misleading before the headless note appears, and in the --all logout path API keys are permanently revoked server-side before the local config write succeeds, which can leave profiles on disk pointing to revoked tokens if the write fails. Neither breaks correctness in the common path.
cli/auth_login_browser.go (headless banner wording) and cli/auth_logout.go (revoke ordering before Save in both single and --all paths)
Sequence Diagram
sequenceDiagram participant User participant CLI as lsh CLI participant API as Latitude API participant Browser Note over CLI: InitViperConfigs → HydrateFromActiveProfile("") Note over CLI: PersistentPreRunE runs after flag parse alt lsh login (browser flow) User->>CLI: lsh login CLI->>API: POST /auth/cli_sessions API-->>CLI: "{id, secret, user_code, authorize_url}" CLI->>Browser: Open authorize_url (if not headless) CLI->>User: Print URL + user_code loop Poll every 2s (max 5m30s) CLI->>API: "GET /auth/cli_sessions/{id} [X-CLI-Secret: secret]" API-->>CLI: "{status: pending} or {status: approved, api_key, team, user}" end CLI->>CLI: saveProfile(team_slug, profile) CLI->>User: Logged in else lsh login --with-token T User->>CLI: lsh login --with-token ak_xxx CLI->>API: GET /user/profile [Authorization: ak_xxx] API-->>CLI: "{email}" CLI->>API: GET /team [Authorization: ak_xxx] API-->>CLI: "[{id, name, slug}]" CLI->>CLI: "saveProfile(team_slug, profile source=with-token)" CLI->>User: Logged in end alt lsh auth logout User->>CLI: lsh auth logout CLI->>CLI: config.Load() opt "source == browser" CLI->>API: "DELETE /auth/api_keys/{key_id}" end CLI->>CLI: config.Save() atomic rename CLI->>User: Removed profile end alt lsh servers list User->>CLI: lsh servers list Note over CLI: PersistentPreRunE: HydrateFromActiveProfile alt --project set or LSH_PROJECT CLI->>API: "GET /servers?filter[project]=X" else interactive TTY CLI->>API: GET /projects paginated API-->>CLI: "[{id, name, slug}]" CLI->>User: Interactive project picker User-->>CLI: select project or All projects CLI->>API: GET /servers with or without project filter else non-TTY no --project CLI->>User: Error --project is required end endPrompt To Fix All With AI
Reviews (4): Last reviewed commit: "fix(auth): review fixes + tests for PD-6..." | Re-trigger Greptile