103 lines
5 KiB
Markdown
103 lines
5 KiB
Markdown
---
|
|
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
|