Skip to content

feat(connect): Tier 2 bring-your-own OAuth app profiles (#146)#147

Merged
pawellisowski merged 10 commits into
mainfrom
feat/tier2-byo-oauth
May 24, 2026
Merged

feat(connect): Tier 2 bring-your-own OAuth app profiles (#146)#147
pawellisowski merged 10 commits into
mainfrom
feat/tier2-byo-oauth

Conversation

@pawellisowski
Copy link
Copy Markdown
Contributor

Summary

Implements Tier 2 of the hybrid cloud-auth model (#146): orgs can point aware connect at their own registered Google / M365 / Trimble OAuth app — their tenant, client_id, secret, scopes — as first-class config, overriding the bundled first-party (Tier 1) app. Closes #146.

  • ~/.aware/oauth/<integration>[.<alias>].yaml holds non-secret app config (client_id, tenant, scopes, optional endpoint URLs). UTF-8 BOM tolerant.
  • aware connect <int> --set-app-secret reads the client secret from stdin into the OS keychain — never plaintext config, never argv.
  • Resolution precedence: profile ▸ env var ▸ bundled default. AppSource is keyed to client_id ownership (first-party / byo-profile / byo-env).
  • PKCE, refresh, and device-code all consume the resolved config; device-code honors profile endpoint overrides (sovereign cloud / proxy) for all providers; CLI --tenant overrides a profile tenant for PKCE.
  • aware connect --list / doctor report the active app per integration.
  • AWARE_DISABLE_KEYRING forces the credentials-file fallback (headless / locked-down machines; also isolates tests, since the OS keychain is global and not scoped by AWARE_HOME).
  • Tier 2 self-registration runbook added to 10-core/oauth-registration.md.

Review

Primary review by Codex (codex exec review --base main) — 7 rounds, every finding addressed, final pass clean. Notable fixes: keychain-unavailable no longer aborts connect; stale/mismatched secrets never paired with the wrong client; app-secret storage/read scopes aligned for aliases; CLI --tenant precedence in PKCE. Two real bugs caught en route: a Windows BOM footgun in profile YAML, and global keychain pollution from integration tests (now isolated).

Test plan

  • cargo test — 369 unit + new cli/tests/connect_byo.rs (5) + all other integration tests green
  • Changed files rustfmt- and clippy --all-targets -D warnings-clean (pre-existing crate-wide drift in untouched files unchanged; CI builds only)
  • Manual smoke: connect --list shows byo-profile for a profile with client_id and first-party otherwise; --set-app-secret stores via stdin
  • Post-merge: tag v0.43.0 to trigger the release (GitHub + npm)

Note: the first commit relaxes the CLAUDE.md "never auto-commit" rule to standing commit approval, per repo owner request.

Let an org point `aware connect` at its own registered Google / M365 /
Trimble OAuth app (its tenant, client_id, secret, scopes) without env vars,
overriding the bundled first-party (Tier 1) app.

- profile.rs: OAuthAppProfile loaded from ~/.aware/oauth/<int>[.<alias>].yaml
  (alias-shadowing, UTF-8 BOM tolerant for Windows-edited files)
- keychain: store/load/delete_app_secret under a dedicated oauth-app.<int> slot
  (file fallback); BYO secret never written to plaintext config
- config: with_profile overlay; resolution precedence profile > env > bundled;
  pure resolve_client_secret (a BYO client_id suppresses the bundled secret)
- PKCE / refresh / device-code all consume the resolved config; device-code
  honors the profile tenant when --tenant is absent
- connect: `--set-app-secret` reads the secret from stdin into the keychain;
  `--list` / `doctor` report the active app (first-party | byo-profile | byo-env)
- docs: Tier 2 org self-registration runbook in oauth-registration.md
…eview)

load_app_secret treated a keyring-unavailable backend (headless Linux/CI
with no libsecret/dbus) as a hard error. Because with_profile reads the
app-secret slot on every connect path, this aborted normal first-party
flows — even --from-file — before the token file-fallback. A BYO app secret
is optional config, so any keyring error now falls through to the file
fallback and yields None when absent. Found by Codex review.
…point overrides (#146 review)

Two P2s from Codex review:

- client_secret resolution gated on an active BYO client (profile/env id).
  A keychain app secret left over from a removed BYO profile no longer pairs
  with the first-party client id; first-party now resolves env > bundled and
  ignores the stale keychain secret. BYO resolves keychain > env, never bundled.
- device-code now honors an explicit profile token_url / device_authorization_url
  override (sovereign cloud / proxy) instead of rebuilding the public-cloud
  tenant URL; falls back to tenant-built URLs (still honoring CLI --tenant) when
  no override is set. New optional profile field device_authorization_url + docs.
…s lines (#146 review)

Two more from Codex review:

- AppSource is now derived from client_id ownership, not mere profile presence.
  A profile that only customizes scopes/tenant/endpoints (no client_id) stays
  first-party, so its bundled secret is no longer wrongly suppressed in release
  builds. BYO (profile/env client_id) still suppresses the bundled secret.
- `aware connect --list` text output now shows [app: ...] on missing, expired,
  and keyring-unavailable lines too, so a BYO app can be verified before the
  first connect.
…ides, isolate keychain in tests (#146 review)

Round 3 of Codex review:

- connect: with_profile is now loaded only on the --oauth / --device-code paths,
  so a malformed BYO profile no longer blocks token-import paths (--from-file,
  --from-env, paste) that don't use OAuth config.
- device-code: profile token_url / device_authorization_url overrides are now
  honored for all providers (Google proxy/alternate endpoints), not just M365.
- keychain: add AWARE_DISABLE_KEYRING (and cfg!(test) default) to force the
  credentials-file fallback. The OS keychain is global and not scoped by
  AWARE_HOME, so this is needed for test isolation (integration tests no longer
  pollute the real keychain) and is useful on headless/locked-down machines.
- tests: new cli/tests/connect_byo.rs covering the malformed-profile import
  regression, --set-app-secret stdin, and --list app-source reporting.
load_profile falls back from <int>.<alias>.yaml to <int>.yaml, but
load_app_secret only checked the alias-specific keychain slot. An aliased
account inheriting the default profile's client_id therefore got no secret,
breaking OAuth/refresh unless the secret was duplicated per alias. Now the
secret lookup mirrors the profile fallback: alias-specific slot, then default.
…back (#146 review)

The previous alias→default secret fallback was unconditional, which would pair an
alias's own client_id with the default app's secret. Now the secret slot mirrors
the resolved profile scope: with_profile reads the alias secret slot only when an
alias-specific profile file exists, else the default slot (matching load_profile's
alias→default fallback). load_app_secret is back to an exact-account lookup; the
policy lives in config::with_profile via profile::alias_profile_exists.
…#146 review)

- The keychain oauth-app.<int> secret slot belongs to a profile-based BYO app,
  so it is now loaded only for ByoProfile. Env-sourced (ByoEnv) apps no longer
  pick up a stale keychain secret over their AWARE_OAUTH_<INT>_CLIENT_SECRET; the
  resolution stays keychain(None) > env for them.
- New with_tenant() applies a CLI --tenant override (M365) with precedence over a
  profile tenant, matching the device-code path; connect --oauth now applies it
  before PKCE builds the auth/token URLs (unless the profile set explicit
  sovereign-cloud endpoints).
…review)

--set-app-secret now uses the same alias/default scope decision as
with_profile: the alias slot only when an alias-specific profile exists, else
the default slot. Previously `--as personal --set-app-secret` with only a
default profile wrote an orphaned alias slot the OAuth flow never reads, so the
subsequent `--as personal --oauth` would fail. Prints a note when an aliased
secret is stored for the default app. Covered by a new integration test.
@pawellisowski pawellisowski merged commit 17514bc into main May 24, 2026
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.

Tier 2: first-class bring-your-own OAuth app registration (per-org)

1 participant