docs(31-02): complete Google OAuth PKCE plan summary and state updates

This commit is contained in:
Nexus Dev 2026-04-03 00:38:00 +00:00
parent 4068d3de08
commit fe65166ecd
3 changed files with 113 additions and 8 deletions

View file

@ -19,9 +19,9 @@
- [x] **CLOUD-01**: User gets working AI via Puter.js with zero API keys and no sign-up required
- [x] **CLOUD-02**: Puter.js integrated as server-proxied adapter (not browser-direct) with full cost tracking
- [ ] **CLOUD-03**: User can sign in via Google OAuth to access Gemini free tier
- [x] **CLOUD-03**: User can sign in via Google OAuth to access Gemini free tier
- [ ] **CLOUD-04**: System auto-detects installed tools (Hermes, Claude Code, OpenClaw) and pre-fills configuration
- [ ] **CLOUD-05**: User can enter API keys for subscription providers during onboarding
- [x] **CLOUD-05**: User can enter API keys for subscription providers during onboarding
### Voice
@ -75,9 +75,9 @@
| ONBD-07 | Phase 30 | Complete |
| CLOUD-01 | Phase 31 | Complete |
| CLOUD-02 | Phase 31 | Complete |
| CLOUD-03 | Phase 31 | Pending |
| CLOUD-03 | Phase 31 | Complete |
| CLOUD-04 | Phase 31 | Pending |
| CLOUD-05 | Phase 31 | Pending |
| CLOUD-05 | Phase 31 | Complete |
| ONBD-04 | Phase 32 | Pending |
| ONBD-05 | Phase 32 | Pending |
| ONBD-06 | Phase 32 | Pending |

View file

@ -3,8 +3,8 @@ gsd_state_version: 1.0
milestone: v1.5
milestone_name: Smart Onboarding + Personal AI Assistant
status: executing
stopped_at: Completed 31-puter.js-zero-config-cloud/31-01
last_updated: "2026-04-03T00:37:39.937Z"
stopped_at: Completed 31-puter.js-zero-config-cloud/31-02 (all 3 tasks, 11 tests pass)
last_updated: "2026-04-03T00:37:50.371Z"
last_activity: 2026-04-03
progress:
total_phases: 6
@ -76,6 +76,8 @@ Key constraints for v1.5 (established at roadmap):
- [Phase 30-hardware-detection-mode-selection]: Hardware probe is non-blocking — wizard step 1 always has an enabled Continue button regardless of probe outcome
- [Phase 30-hardware-detection-mode-selection]: Mode save on wizard completion is non-blocking — wrapped in try/catch, defaults to 'both' on failure
- [Phase 31-puter.js-zero-config-cloud]: agentId is optional in puterProxyService.chatStream — cost recording skipped when null/undefined to avoid FK violation in cost_events
- [Phase 31-puter.js-zero-config-cloud]: pendingPkce stores only verifier (no companyId) — company does not exist at authorize time during onboarding
- [Phase 31-puter.js-zero-config-cloud]: pendingTokens pattern: callback parks tokens by stateId, claim endpoint links to real companyId post-company-creation
### Pending Todos
@ -90,6 +92,6 @@ None yet.
## Session Continuity
Last session: 2026-04-03T00:37:30.288Z
Stopped at: Completed 31-puter.js-zero-config-cloud/31-01
Last session: 2026-04-03T00:37:50.368Z
Stopped at: Completed 31-puter.js-zero-config-cloud/31-02 (all 3 tasks, 11 tests pass)
Resume file: None

View file

@ -0,0 +1,103 @@
---
phase: 31-puter.js-zero-config-cloud
plan: "02"
subsystem: server/google-oauth
tags: [oauth, pkce, google, gemini, api-keys, secrets]
dependency_graph:
requires: []
provides: [googleOAuthService, googleOAuthRoutes, google-oauth-routes]
affects: [server/src/app.ts]
tech_stack:
added: []
patterns: [PKCE-S256, pendingTokens-claim-pattern, secretService-upsert]
key_files:
created:
- server/src/services/google-oauth.ts
- server/src/routes/google-oauth.ts
- server/src/__tests__/31-google-oauth.test.ts
modified:
- server/src/app.ts
decisions:
- pendingPkce stores only verifier (no companyId) — company does not exist at authorize time
- pendingTokens uses stateId as key — claim endpoint links tokens to companyId post-company-creation
- Google token exchange uses fetch (Node built-in) not axios
- secretService upsert pattern: getByName -> rotate if exists, create if not
metrics:
duration: "3 minutes 22 seconds"
completed: "2026-04-03T00:36:57Z"
tasks_completed: 3
files_created: 3
files_modified: 1
requirements: [CLOUD-03, CLOUD-05]
---
# Phase 31 Plan 02: Google OAuth PKCE Service and Routes Summary
**One-liner:** Google OAuth PKCE flow for Gemini free tier access — generateAuthUrl/exchangeCode/storeTokens service plus authorize/callback/claim/api-keys routes with in-memory pendingTokens pattern separating callback from company creation.
## Tasks Completed
| Task | Name | Commit | Files |
|------|------|--------|-------|
| 1 | googleOAuthService — PKCE generation, code exchange, token storage | 72045513 | server/src/services/google-oauth.ts |
| 2 | googleOAuthRoutes (pendingTokens pattern) + API key route + mount in app.ts | c41ec162 | server/src/routes/google-oauth.ts, server/src/app.ts |
| 3 | Unit tests for Google OAuth service and routes | d750d15f | server/src/__tests__/31-google-oauth.test.ts |
## What Was Built
### googleOAuthService (`server/src/services/google-oauth.ts`)
- `generatePkce()` — crypto.randomBytes(32).toString("base64url") verifier, SHA256 base64url challenge
- `generateAuthUrl(redirectUri, state)` — builds Google OAuth URL with PKCE S256, Gemini scopes (openid + cloud-platform + generative-language.retriever), access_type=offline, prompt=consent
- `exchangeCode(code, redirectUri, verifier)` — POSTs to https://oauth2.googleapis.com/token with authorization_code grant and code_verifier
- `storeTokens(companyId, tokens)` — upserts JSON-stringified tokens under name "google_gemini_oauth_token" via secretService
- `resolveTokens(companyId)` — retrieves and JSON.parses stored tokens
### googleOAuthRoutes (`server/src/routes/google-oauth.ts`)
- `POST /oauth/google/authorize` — assertBoard, generates UUID state, stores PKCE verifier in pendingPkce (NO companyId), returns `{ url, stateId }`
- `GET /oauth/google/callback` — exchanges code via pendingPkce verifier, parks tokens in pendingTokens by stateId, redirects to `/?google_oauth=success&state=...`
- `POST /oauth/google/claim` — assertBoard + assertCompanyAccess, moves tokens from pendingTokens to secretService under real companyId, returns `{ ok: true }`
- `POST /api-keys/store` — assertBoard + assertCompanyAccess, upserts `${provider}_api_key` secret (openai/anthropic/groq), returns `{ ok: true }`
- Cleanup of entries older than 10 minutes on each request
### Test Coverage (`server/src/__tests__/31-google-oauth.test.ts`)
All 11 tests pass:
- Tests 1-2: PKCE generation format and auth URL contents
- Test 3: token exchange HTTP call
- Tests 4-5: storeTokens create and rotate paths
- Test 6: authorize returns { url, stateId } with no companyId
- Test 7: callback exchanges code and redirects with success
- Test 8: callback with invalid state returns 400
- Test 9: full authorize→callback→claim flow
- Test 10: claim with missing stateId returns 404
- Test 11: api-keys/store upsert
## Decisions Made
1. **pendingPkce stores only `{ verifier, createdAt }`** — no companyId stored at authorize time because the company has not been created yet during onboarding step 3. This was the key design requirement.
2. **pendingTokens pattern** — callback stores tokens in memory by stateId; claim endpoint later links them to a real companyId. This separates the OAuth callback timing from company creation.
3. **Client ID is public**`812546505895-ag9nvbqvf8cpqk3mfem1glig0jtl5i31.apps.googleusercontent.com` is the publicly documented Gemini CLI installed-app client_id (per RESEARCH.md). PKCE flow is appropriate for public clients.
## Deviations from Plan
None — plan executed exactly as written.
## Known Stubs
None — all functionality is fully wired.
## Self-Check: PASSED
Files created:
- server/src/services/google-oauth.ts: EXISTS
- server/src/routes/google-oauth.ts: EXISTS
- server/src/__tests__/31-google-oauth.test.ts: EXISTS
Commits:
- 72045513: feat(31-02): add googleOAuthService with PKCE generation and token management
- c41ec162: feat(31-02): add googleOAuthRoutes with pendingTokens pattern and mount in app.ts
- d750d15f: test(31-02): add 11 unit tests for Google OAuth service and routes