Skip to content

Add support for multiple named connection profiles#2

Merged
paulmeller merged 4 commits intomainfrom
claude/multi-connection-cli-YFQc6
Mar 18, 2026
Merged

Add support for multiple named connection profiles#2
paulmeller merged 4 commits intomainfrom
claude/multi-connection-cli-YFQc6

Conversation

@paulmeller
Copy link
Copy Markdown
Owner

Summary

This PR adds support for managing multiple named Xero app connections, allowing users to switch between different Xero apps (e.g., dev vs production) without reconfiguring credentials each time.

Key Changes

New Connection Management Command

  • Added cmd/connection.go with a new connection command and subcommands:
    • connection list - List all configured connections with active indicator
    • connection add <name> - Add a new named connection with optional --switch flag
    • connection remove <name> - Remove a connection (prevents removing active connection)
    • connection switch <name> - Set the active connection
    • connection current - Show the currently active connection

Config Structure Refactoring

  • Restructured internal/config/config.go to support multiple connections:
    • Introduced Connection struct to hold per-connection credentials (ClientID, ClientSecret, GrantType, ActiveTenant, etc.)
    • Added Connections map to Config to store named connections
    • Added ActiveConnection field to track the currently active connection name (empty string means "default")
    • Implemented migration logic to automatically convert legacy flat-field configs to the new connections map
    • Added helper methods: ActiveConnectionName(), ActiveConn(), SetConnection(), RemoveConnection(), ConnectionNames(), GetConnection()
    • Added connection name validation with regex pattern (alphanumeric, hyphens, underscores, 1-64 chars)
    • Added LoadFileWithConnection() and LoadWithConnection() to support connection overrides
    • Added TokenPathFor() to generate per-connection token file paths

Token Management Updates

  • Updated internal/auth/token.go to support per-connection token storage:
    • Modified LoadToken(), SaveToken(), and DeleteToken() to accept connection name parameter
    • Updated keyring user key to include connection name (e.g., "oauth-token:staging")
    • Token files now use naming convention: tokens.json for default, tokens-{name}.json for named connections
    • Updated PersistentTokenSource to track and use connection name

Auth Command Updates

  • Updated cmd/auth.go to work with active connection:
    • auth login now saves credentials to the active connection
    • auth logout removes tokens for the active connection
    • Prompts now reference the active connection name

Config Command Updates

  • Updated cmd/config.go to display and manage active connection settings
    • config show displays the active connection name and its settings
    • config set modifies settings on the active connection

Tenants Command Updates

  • Updated cmd/tenants.go to work with per-connection active tenant:
    • tenants list marks the active tenant for the current connection
    • tenants switch saves the tenant to the active connection
    • tenants current shows the active tenant for the current connection

API Client Updates

  • Updated internal/api/client.go to use active connection when creating clients

Root Command Updates

  • Added --connection persistent flag to allow overriding the active connection
  • Added support for XERO_CONNECTION environment variable
  • Updated internal/cmdutil/factory.go to wire connection name through the factory

File Permissions

  • Added explicit file permission setting (0600) when saving config to protect client secrets

Notable Implementation Details

  • Legacy flat-field configs are automatically migrated to the connections map on load
  • The "default" connection is special-cased: empty ActiveConnection field means "default"
  • Environment variables (XERO_CLIENT_ID, XERO_CLIENT_SECRET, etc.) are applied to the active connection at runtime
  • Config files are only saved from LoadFile() paths to prevent baking environment variable secrets into files
  • Connection names are validated to ensure they're safe for use in file paths, keyring keys, and TOML map keys

https://claude.ai/code/session_01Ty7WX3b8CvE5tcwu2wyG1D

claude added 4 commits March 18, 2026 03:22
Introduces named connection profiles so users can manage multiple
client ID/secret pairs (e.g. dev vs production) with independent
OAuth tokens and active tenants.

Key changes:
- Add Connection struct and connections map to config (TOML sections)
- Namespace token storage by connection name (keyring + file fallback)
- Add --connection/-C global flag and XERO_CONNECTION env var
- Add `xero connection list|add|remove|switch|current` commands
- Thread connection name through auth, tenants, and config commands
- Full backward compatibility: existing flat config works as "default"

https://claude.ai/code/session_01Ty7WX3b8CvE5tcwu2wyG1D
…ti-connection support

- ActiveConn() and GetConnection() now return copies instead of map pointers,
  preventing defaults from being baked into the config file on Save()
- Add ValidateConnectionName() to prevent path traversal and keyring injection
  via malicious connection names (alphanumeric + hyphens/underscores, max 64 chars)
- LoadFileWithConnection() now validates that the named connection exists
- ConnectionNames() returns deterministic sorted order
- Config.Save() sets 0600 permissions to protect client_secret values
- connection switch to "default" errors when no default credentials exist

https://claude.ai/code/session_01Ty7WX3b8CvE5tcwu2wyG1D
All connections (including "default") now live in the Connections map.
Legacy configs with flat client_id/client_secret fields are automatically
migrated into connections["default"] on load and cleared so they don't
persist on save.

This eliminates the hybrid flat+map architecture that caused:
- Inconsistent code paths for default vs named connections
- Duplicate branching in config set, connection list, etc.
- The root cause of the pointer mutation bugs (fixed in prior commit)

Key changes:
- Config.migrate() moves flat fields → connections["default"] on load
- SetActiveTenant/SetActiveCredentials create the connection entry on demand
- connection add/remove/switch work uniformly for all names including "default"
- config set grant_type/redirect_uri simplified to single code path
- LoadWithConnection env overlay simplified (single write-back path)

https://claude.ai/code/session_01Ty7WX3b8CvE5tcwu2wyG1D
Critical:
- config set grant_type/redirect_uri now use SetActiveGrantType/
  SetActiveRedirectURI which call ensureActiveConn(), preventing
  silent no-ops when no connection entry exists yet
- LoadWithConnection now validates that a named connection override
  exists before using it, giving clear "not found" errors

Medium:
- LoadWithConnection normalizes "default" → "" like LoadFileWithConnection
- connection list uses GetConnection() for defensive copy instead of
  direct map access
- tenants list/switch now propagate config load errors instead of
  swallowing them (prevented nil panics)

Nit:
- connection list/current no longer redact ClientID (it's not a secret)

https://claude.ai/code/session_01Ty7WX3b8CvE5tcwu2wyG1D
@paulmeller paulmeller merged commit e99fc55 into main Mar 18, 2026
1 check passed
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.

2 participants