Skip to content

feat: interactive browser login (OAuth2 authorization code flow)#15

Merged
burnedikt merged 3 commits into
mainfrom
feat/interactive-cli-auth
Jul 23, 2026
Merged

feat: interactive browser login (OAuth2 authorization code flow)#15
burnedikt merged 3 commits into
mainfrom
feat/interactive-cli-auth

Conversation

@burnedikt

Copy link
Copy Markdown
Member

What

Adds an innoactive-portal auth command group so users can authenticate interactively via the browser instead of manually obtaining and exporting an access token.

auth login runs the OAuth2 authorization code flow with PKCE:

  1. Generates a PKCE code_verifier/code_challenge (S256) and a random state.
  2. Opens the browser to Portal's /oauth/authorize/.
  3. Catches the redirect on a local loopback server (http://localhost:8723/callback by default).
  4. Validates state, then exchanges the code at /oauth/token/.
  5. Persists the access token to ~/.config/innoactive-portal/credentials.json (mode 0600, honoring XDG_CONFIG_HOME).

Supports both public clients (PKCE only) and confidential clients (client secret sent via HTTP Basic).

Also adds auth logout (clears stored credentials) and auth status (shows current login + token expiry).

Configuring the OAuth client

Each setting resolves with precedence CLI flag → env var → config file → default:

  • Env vars: PORTAL_BACKEND_CLIENT_ID, PORTAL_BACKEND_CLIENT_SECRET, PORTAL_BACKEND_REDIRECT_URI, PORTAL_BACKEND_OAUTH_SCOPE
  • Config file: ~/.config/innoactive-portal/config.json (client_id, client_secret, redirect_uri, scope)

With either configured, innoactive-portal auth login needs no arguments.

How existing commands benefit

utils.get_bearer_authorization_header() now falls back to the stored token when PORTAL_BACKEND_ACCESS_TOKEN is unset, so every existing command works after a single login. Explicit env vars still take precedence.

Notes for reviewers

  • Endpoints (/oauth/authorize/, /oauth/token/) target Django OAuth Toolkit, relative to PORTAL_BACKEND_ENDPOINT.
  • The default redirect URI (http://localhost:8723/callback) must be registered on the OAuth client in Portal, or the flow fails — override via --redirect-uri/env/config.

Testing

  • 21 new tests in tests/test_auth.py (PKCE, URL building, token exchange for public/confidential clients, credential persistence, settings-resolution precedence, full login flow incl. state-mismatch + error handling, header fallback).
  • Full suite: 30 passed; ruff clean on changed files.

🤖 Generated with Claude Code

burnedikt and others added 3 commits July 23, 2026 09:55
Add an `innoactive-portal auth` command group so users can authenticate
interactively instead of manually managing access tokens.

`auth login` runs the OAuth2 authorization code flow with PKCE: it opens
the browser to Portal's /oauth/authorize/ endpoint, catches the redirect
on a local loopback server, validates state, exchanges the code at
/oauth/token/ and persists the access token to
~/.config/innoactive-portal/credentials.json (0600, honoring
XDG_CONFIG_HOME). Supports both public clients (PKCE only) and
confidential clients (client secret via HTTP Basic).

The OAuth client can be configured via CLI flags, the PORTAL_BACKEND_*
env vars, or the config file, resolved in that order of precedence.

get_bearer_authorization_header() now falls back to the stored token, so
existing commands work after one login; explicit env vars still win.
Adds `auth logout` and `auth status`, and covers the flow with tests.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Set `tool.uv.package = true` again so `uv run innoactive-portal` runs this
repository's code instead of falling through to a globally installed copy.
This makes the documented `innoactive-portal ...` commands work in a dev
checkout and matches what pip-installed users get.

Pin `[tool.setuptools] packages = ["portal_client"]` so the build does not
rely on flat-layout auto-discovery, which fails when other top-level
directories (tests, specs, ...) are present.

The uv.lock diff is mostly format noise from a newer uv (revision 1 -> 3 and
added upload-time fields); the only semantic change is portal-client now
being recorded as an editable source. CI installs uv unpinned and runs
`uv sync --locked`, so the regenerated lock is required and compatible.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@burnedikt
burnedikt merged commit ce0168f into main Jul 23, 2026
2 checks passed
@burnedikt
burnedikt deleted the feat/interactive-cli-auth branch July 23, 2026 08:43
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