Add support for multiple named connection profiles#2
Merged
paulmeller merged 4 commits intomainfrom Mar 18, 2026
Merged
Conversation
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
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
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
cmd/connection.gowith a newconnectioncommand and subcommands:connection list- List all configured connections with active indicatorconnection add <name>- Add a new named connection with optional--switchflagconnection remove <name>- Remove a connection (prevents removing active connection)connection switch <name>- Set the active connectionconnection current- Show the currently active connectionConfig Structure Refactoring
internal/config/config.goto support multiple connections:Connectionstruct to hold per-connection credentials (ClientID, ClientSecret, GrantType, ActiveTenant, etc.)Connectionsmap toConfigto store named connectionsActiveConnectionfield to track the currently active connection name (empty string means "default")ActiveConnectionName(),ActiveConn(),SetConnection(),RemoveConnection(),ConnectionNames(),GetConnection()LoadFileWithConnection()andLoadWithConnection()to support connection overridesTokenPathFor()to generate per-connection token file pathsToken Management Updates
internal/auth/token.goto support per-connection token storage:LoadToken(),SaveToken(), andDeleteToken()to accept connection name parametertokens.jsonfor default,tokens-{name}.jsonfor named connectionsPersistentTokenSourceto track and use connection nameAuth Command Updates
cmd/auth.goto work with active connection:auth loginnow saves credentials to the active connectionauth logoutremoves tokens for the active connectionConfig Command Updates
cmd/config.goto display and manage active connection settingsconfig showdisplays the active connection name and its settingsconfig setmodifies settings on the active connectionTenants Command Updates
cmd/tenants.goto work with per-connection active tenant:tenants listmarks the active tenant for the current connectiontenants switchsaves the tenant to the active connectiontenants currentshows the active tenant for the current connectionAPI Client Updates
internal/api/client.goto use active connection when creating clientsRoot Command Updates
--connectionpersistent flag to allow overriding the active connectionXERO_CONNECTIONenvironment variableinternal/cmdutil/factory.goto wire connection name through the factoryFile Permissions
Notable Implementation Details
ActiveConnectionfield means "default"XERO_CLIENT_ID,XERO_CLIENT_SECRET, etc.) are applied to the active connection at runtimeLoadFile()paths to prevent baking environment variable secrets into fileshttps://claude.ai/code/session_01Ty7WX3b8CvE5tcwu2wyG1D