5 KiB
| phase | plan | subsystem | tags | dependency_graph | tech_stack | key_files | decisions | metrics | requirements | |||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 31-puter.js-zero-config-cloud | 02 | server/google-oauth |
|
|
|
|
|
|
|
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 challengegenerateAuthUrl(redirectUri, state)— builds Google OAuth URL with PKCE S256, Gemini scopes (openid + cloud-platform + generative-language.retriever), access_type=offline, prompt=consentexchangeCode(code, redirectUri, verifier)— POSTs to https://oauth2.googleapis.com/token with authorization_code grant and code_verifierstoreTokens(companyId, tokens)— upserts JSON-stringified tokens under name "google_gemini_oauth_token" via secretServiceresolveTokens(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_keysecret (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
-
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. -
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.
-
Client ID is public —
812546505895-ag9nvbqvf8cpqk3mfem1glig0jtl5i31.apps.googleusercontent.comis 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: