Skip to content

Add latchkey prepare command and PKCE for Google OAuth#92

Merged
hynek-urban merged 11 commits into
mainfrom
preston/google-oauth
Jun 23, 2026
Merged

Add latchkey prepare command and PKCE for Google OAuth#92
hynek-urban merged 11 commits into
mainfrom
preston/google-oauth

Conversation

@pseay-imbue

@pseay-imbue pseay-imbue commented Jun 22, 2026

Copy link
Copy Markdown
Contributor

Summary

Two related changes, both in order to add a more seemless OAuth experience with an official Imbue OAuth Client:

1. latchkey prepare <service> <json>

A new top-level command that stores a service's credentials directly from a validated JSON payload — the manual equivalent of what auth browser-prepare produces, without driving the Cloud Console.

  • Services opt in via an optional prepareFromJson hook on Service, backed by a Zod schema. The base default is "not supported", so every service stays closed until it declares a schema.
  • GoogleService implements it once: { "clientId": "...", "clientSecret": "..." } → token-less OAuthCredentials. Run auth browser <service> afterward to complete login (status is missing until then, same as browser-prepare).
  • Strict validation: malformed JSON, missing/unknown fields, or an unsupported service rejects the whole command and stores nothing.
  • Forwarded in gateway mode like browser-prepare.
  • auth browser-prepare (Console automation) remains as a fallback.

2. PKCE for the Google OAuth login flow

  • The authorization request now sends code_challenge + code_challenge_method=S256, and the token exchange sends the matching code_verifier.
  • The client_secret is still sent (confidential desktop client + PKCE — defense-in-depth). The refresh-token path is unchanged.
  • Reuses the existing PKCE helpers in oauthUtils.ts (already used by notion-mcp); the Google flow simply wasn't calling them.

Docs

  • Updated every Google service's info string (surfaced via services info) to recommend prepare first, with browser-prepare as the fallback.
  • Updated the README command overview.

Testing

  • npm run lint, npm run typecheck, npm run build, and npm test all pass.
  • Added coverage for prepareService (happy path, overwrite, unknown/unsupported service, malformed JSON, missing/unknown fields, empty fields), the CLI command (success, error exits, gateway forwarding), the gateway endpoint (schema validation + dispatch), and strengthened the PKCE generateCodeChallenge test with the RFC 7636 known-answer vector.

Notes

  • The optional-hook design (rather than a required/abstract member) was deliberate: it keeps every existing object-literal Service mock compiling and leaves all current services' behavior unchanged.

🤖 Generated with Claude Code

Add a top-level `latchkey prepare <service> <json>` command that stores a
service's credentials from a validated JSON payload. Services opt in via an
optional `prepareFromJson` hook backed by a Zod schema (the base default is
"not supported"); Google services accept `{clientId, clientSecret}` and store
token-less OAuth credentials. This lets users register the official Imbue OAuth
client directly instead of minting their own via the Cloud Console. The older
`auth browser-prepare` flow remains as a fallback.

Wire PKCE (S256) into the Google OAuth login flow: the authorization request
now sends code_challenge/code_challenge_method and the token exchange sends the
matching code_verifier, while still sending the client secret (confidential
desktop client + PKCE, defense-in-depth).

- Forward `prepare` through gateway mode like `browser-prepare`
- Update Google service info strings + README to recommend `prepare`
- Tests for prepareService, the CLI command, the gateway endpoint, and PKCE

Co-authored-by: Sculptor <sculptor@imbue.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Vet found 0 issues.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Vet found 0 issues.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Vet found 0 issues.

Wrap the long `.description()` line in the new `prepare` command to satisfy
prettier.

Co-authored-by: Sculptor <sculptor@imbue.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@pseay-imbue pseay-imbue force-pushed the preston/google-oauth branch from 4eabf45 to 367bf2b Compare June 22, 2026 19:18

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Vet found 0 issues.

Revert the per-service `info` strings to recommend `auth browser-prepare`
(the default OAuth-client setup) rather than `latchkey prepare`. Reword the
README so `prepare` is documented as an alternative without demoting
`browser-prepare`.

Co-authored-by: Sculptor <sculptor@imbue.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Vet found 0 issues.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Vet found 0 issues.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Vet found 0 issues.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Vet found 1 issue.

/**
* Google services accept an official OAuth client's id/secret via
* `latchkey auth prepare`, stored as token-less OAuth credentials until login.
*/

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 Vet Issue commit_message_mismatch severity: 3/5, confidence: 0.80

The request explicitly states 'Updated every Google service's info string (surfaced via services info) to recommend prepare first, with browser-prepare as the fallback' and 'Updated the README command overview.' Neither the README nor any service info strings are modified in the diff, leaving the documentation portion of the request unimplemented.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Vet found 0 issues.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Vet found 0 issues.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Vet found 0 issues.

@hynek-urban

Copy link
Copy Markdown
Collaborator

@pseay-imbue Thanks, Preston! I finalized this by doing the following:

  • For consistency, I changed latchkey prepare to latchkey auth prepare.
  • Added auth prepare support to the notion-mcp service.
  • Fixed an edge case in Google's auth browser implementation that I discovered during manual testing. (This is technically not related to this PR but I just pushed it alongside the other changes.)
  • And adjusted the wording of various help texts.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Vet found 1 issue.

/**
* Google services accept an OAuth client's id/secret prepared
* in advance via `latchkey auth prepare`, stored as token-less OAuth credentials until login.
*/

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 Vet Issue documentation_implementation_mismatch severity: 3/5, confidence: 0.80

The user request states 'Updated every Google service's info string (surfaced via services info) to recommend prepare first, with browser-prepare as the fallback' and 'Updated the README command overview.' However, the diff contains no changes to any service's info string nor to the README. The documentation updates described in the request are missing.

@hynek-urban hynek-urban merged commit d56d9be into main Jun 23, 2026
3 checks 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