Skip to content

Add --scope flag to xero login (env: XERO_SCOPES)#8

Open
DrBradStanfield wants to merge 1 commit into
XeroAPI:mainfrom
DrBradStanfield:add-scope-flag
Open

Add --scope flag to xero login (env: XERO_SCOPES)#8
DrBradStanfield wants to merge 1 commit into
XeroAPI:mainfrom
DrBradStanfield:add-scope-flag

Conversation

@DrBradStanfield
Copy link
Copy Markdown

Summary

Adds a --scope flag (env: XERO_SCOPES) to xero login that overrides the hardcoded SCOPES list. Required OAuth scopes (openid profile email offline_access) are auto-prepended so callers can pass just the API scopes they need without breaking refresh tokens.

Motivation

The current SCOPES constant in src/lib/oauth.ts requests broad write scopes (accounting.contacts, accounting.invoices, accounting.payments, accounting.banktransactions, accounting.manualjournals) plus accounting.journals.read. This is fine for full-featured workflows but fails or over-grants in two real cases:

  1. Read-only integrations. Building a finance dashboard, agent integration, or read-only reporting tool? You don't want a token that can mutate. Currently the only options are forking the CLI or accepting write capability the integration will never use.

  2. Apps created after 2 March 2026. Per Xero's scope migration, new apps lose access to umbrella scopes like accounting.transactions[.read] and (without an Advanced plan) accounting.journals.read. The hardcoded SCOPES set includes both, so plain xero login returns Error 500 / unauthorized_client / "Unknown client or client not enabled" on a new Starter-plan PKCE app — which is misleading because it sounds like the Client ID is wrong.

In both cases users currently must fork the CLI just to change one line.

Changes

  • src/commands/login.ts: new scope flag (--scope <value>, env XERO_SCOPES). Passed to performLogin(clientId, scope).
  • src/lib/oauth.ts:
    • Extracted REQUIRED_OAUTH_SCOPES = ['openid', 'profile', 'email', 'offline_access'] as a const, and rebuilt the existing SCOPES to spread from it. Single source of truth — eliminates the drift risk where someone could edit one list and forget the other.
    • New resolveScopes(scopes?) helper: when scopes is provided, returns REQUIRED_OAUTH_SCOPES plus the user-supplied scopes (deduped via Set, preserves insertion order). When omitted, returns the existing hardcoded SCOPES — behaviour unchanged for callers not using --scope.

No new dependencies. No behaviour change for existing users who don't pass --scope.

Example

# Read-only integration on a new (post-2026-03-02) Starter-plan app
xero login --scope "accounting.contacts.read accounting.settings.read accounting.invoices.read accounting.payments.read accounting.banktransactions.read accounting.manualjournals.read accounting.reports.balancesheet.read accounting.reports.profitandloss.read accounting.reports.trialbalance.read accounting.reports.aged.read accounting.budgets.read accounting.attachments.read"

# Or via env var (same effect)
XERO_SCOPES="accounting.invoices.read accounting.contacts.read" xero login

Tests

All 63 existing tests pass. Patch is additive — no existing tests modified.

Notes for reviewers

  • Considered adding a built-in "read-only preset" (e.g. --scope-preset readonly) but it's brittle: the right scope set depends on which CLI subcommands the user wants to run, and Xero's scope catalog changes (the 2026-03-02 migration is a recent example). A raw --scope flag is more durable.
  • Auto-prepending the OpenID scopes is opinionated but safe — without offline_access you can't refresh, and without openid you can't /connections. Worth the very small loss of flexibility to prevent the most common footgun.
  • Documentation update for README.md would be a follow-up if this is accepted.

Allows callers to override the hardcoded SCOPES list when authenticating.
Required openid/profile/email/offline_access are auto-prepended via a new
resolveScopes() helper to prevent footguns (refresh tokens require offline_access).

Also extracts REQUIRED_OAUTH_SCOPES as a const so the existing SCOPES list
spreads from it — single source of truth, no drift if one is edited later.

Use case: Xero apps created after 2 March 2026 cannot grant umbrella scopes
like accounting.transactions or accounting.journals.read on the Starter plan,
so the hardcoded SCOPES set causes login to fail with 'unauthorized_client'.
This flag lets users request a narrower scope set (e.g. read-only granular
scopes for a finance dashboard / agent integration) without forking.
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