Skip to content

feat: ChittyID SSO via OAuth 2.0 PKCE#72

Merged
chitcommit merged 1 commit intomainfrom
feat/chittyid-sso
Mar 28, 2026
Merged

feat: ChittyID SSO via OAuth 2.0 PKCE#72
chitcommit merged 1 commit intomainfrom
feat/chittyid-sso

Conversation

@chitcommit
Copy link
Copy Markdown
Contributor

@chitcommit chitcommit commented Mar 28, 2026

Summary

  • Adds ChittyID single sign-on via OAuth 2.0 authorization code flow with PKCE (S256)
  • "Sign in with ChittyID" button on login page alongside existing email/password
  • Auto-links ChittyID to existing accounts matched by email
  • Session response includes chittyId field for frontend identity display

Architecture

  • GET /api/auth/chittyid/authorize — redirects to auth.chitty.cc/v1/oauth/authorize with PKCE challenge
  • GET /api/auth/chittyid/callback — exchanges code for token, resolves/links user, creates KV session
  • PKCE verifier stored in FINANCE_KV with 10-min TTL, keyed by CSRF state token
  • Gracefully degrades: returns 503 if CHITTYAUTH_CLIENT_ID not configured

Activation

  1. Register ChittyFinance as OAuth client: POST auth.chitty.cc/v1/oauth/register
  2. Set secrets: wrangler secret put CHITTYAUTH_CLIENT_ID + CHITTYAUTH_CLIENT_SECRET

Test plan

  • Verify TypeScript compiles clean (npm run check)
  • Verify 150/150 tests pass (npx vitest run)
  • Verify login page renders ChittyID button + email form
  • Verify /api/auth/chittyid/authorize returns 503 when unconfigured
  • E2E: configure secrets, complete OAuth flow, verify session created

Generated with Claude Code

Summary by CodeRabbit

New Features

  • Introduced ChittyID as an alternative sign-in method on the login page, enabling users to authenticate via ChittyID OAuth in addition to email/password credentials
  • Enhanced login error handling with improved error messaging that supports error code parameters from the login flow
  • Extended session information to include ChittyID details for authenticated users

Adds ChittyID single sign-on alongside existing email/password auth.

Backend:
- New /api/auth/chittyid/authorize + /callback routes (OAuth 2.0 + PKCE S256)
- getUserByChittyId + linkChittyId storage methods for user resolution
- PKCE verifier stored in KV with 10-min TTL
- Auto-links ChittyID to existing users matched by email
- Session response now includes chittyId field
- CHITTYAUTH_CLIENT_ID + CHITTYAUTH_CLIENT_SECRET env bindings

Frontend:
- "Sign in with ChittyID" button on Login page (above email/password form)
- OAuth error codes mapped to user-friendly messages
- URL param error display for callback failures

Activation requires:
1. Register OAuth client with ChittyAuth (POST /v1/oauth/register)
2. Set CHITTYAUTH_CLIENT_ID + CHITTYAUTH_CLIENT_SECRET as Worker secrets

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings March 28, 2026 05:05
@cloudflare-workers-and-pages
Copy link
Copy Markdown

cloudflare-workers-and-pages bot commented Mar 28, 2026

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Updated (UTC)
✅ Deployment successful!
View logs
chittyfinance bf931e9 Mar 28 2026, 05:06 AM

@chatgpt-codex-connector
Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@github-actions
Copy link
Copy Markdown
Contributor

@coderabbitai review

Please evaluate:

  • Security implications
  • Credential exposure risk
  • Dependency supply chain concerns
  • Breaking API changes

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 28, 2026

📝 Walkthrough

Walkthrough

This pull request introduces ChittyID OAuth authentication support. The changes include client-side UI updates to the login page offering ChittyID sign-in, a complete server-side OAuth authorize and callback flow with PKCE and state validation, new database methods for user lookup and linking by ChittyID, environment configuration for OAuth credentials, and session response updates including the chittyId field.

Changes

Cohort / File(s) Summary
Client Login UI
client/src/pages/Login.tsx
Added error message mapping for query parameters; introduced ChittyID authorization link (/api/auth/chittyid/authorize) alongside existing email/password form with visual "or" separator; updated error handling to lookup mapped error codes with fallback.
Server Configuration & Routing
server/app.ts, server/env.ts
Mounted new ChittyID auth routes as public route group; extended Env interface with optional CHITTYAUTH_CLIENT_ID and CHITTYAUTH_CLIENT_SECRET fields for OAuth credentials.
OAuth Authorization Flow
server/routes/chittyid-auth.ts
Implemented complete ChittyID OAuth flow: GET /api/auth/chittyid/authorize generates PKCE challenge/verifier pair, OAuth state token, and redirects to ChittyID authorization endpoint; GET /api/auth/chittyid/callback validates state, exchanges code for tokens, resolves/provisions user, and creates session with HTTP-only cookie.
Session & Storage Updates
server/routes/session.ts, server/storage/system.ts
Extended session responses to include chittyId field; added getUserByChittyId() and linkChittyId() methods to SystemStorage for user lookup and ChittyID linking by email matching.

Sequence Diagram

sequenceDiagram
    participant User as User/Browser
    participant Client as Login Page
    participant Backend as Backend Server
    participant KV as KV Store
    participant ChittyID as ChittyID OAuth
    participant DB as Database

    User->>Client: Click "Sign in with ChittyID"
    Client->>Backend: GET /api/auth/chittyid/authorize
    Backend->>Backend: Generate PKCE verifier/challenge
    Backend->>Backend: Generate OAuth state token
    Backend->>KV: Store PKCE verifier (10 min TTL)
    Backend->>User: Redirect to ChittyID auth endpoint
    
    User->>ChittyID: Authenticate & authorize
    ChittyID->>User: Redirect to callback with code & state
    
    User->>Backend: GET /api/auth/chittyid/callback?code=...&state=...
    Backend->>Backend: Validate state token
    Backend->>KV: Retrieve PKCE verifier
    Backend->>ChittyID: Exchange code for tokens (PKCE)
    ChittyID->>Backend: Return access token + identity data
    
    Backend->>DB: Lookup user by chittyId
    alt User found
        Backend->>DB: Retrieve user
    else User not found by chittyId
        Backend->>DB: Lookup user by email
        alt Email match found
            Backend->>DB: Link chittyId to existing user
        else No match
            Backend->>DB: Create new user with chittyId
        end
    end
    
    Backend->>KV: Store session (SESSION_TTL)
    Backend->>User: Set session cookie & redirect to /
    User->>Client: Redirected to home
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

🐰 A new path joins the warren, with ChittyID's name,
PKCE hops guard the flow, state tokens stake their claim,
From authorize to callback, through KV's swift store,
Users link and session bloom—more doors, more doors!

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: ChittyID SSO via OAuth 2.0 PKCE' clearly summarizes the main change: adding ChittyID single sign-on using OAuth 2.0 with PKCE. It is concise, specific, and directly related to the primary purpose of the changeset.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/chittyid-sso

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@claude
Copy link
Copy Markdown

claude bot commented Mar 28, 2026

Code Review — ChittyID SSO (OAuth 2.0 PKCE)

Overall this is a clean implementation. PKCE is the right choice here, the KV-backed verifier storage is appropriate for Workers, and the graceful 503 degradation is good practice. A few things worth addressing before merge:


Bugs / Correctness

1. clientId non-null assertion in callback is unsafe

// server/routes/chittyid-auth.ts:100
const clientId = c.env.CHITTYAUTH_CLIENT_ID!;

The callback route can be hit directly (e.g. by a crafted request) without passing through /authorize. The 503 guard only lives on the authorize route. Add an explicit check here:

const clientId = c.env.CHITTYAUTH_CLIENT_ID;
if (!clientId) return c.redirect('/login?error=server_config');

2. Identity extracted from token response body — non-standard

// lines 126–127
const chittyId = tokenData.chitty_id || tokenData.sub;
const email = tokenData.email;

Standard OAuth 2.0 token endpoints return access_token / token_type — not sub, chitty_id, or email. If ChittyAuth exposes an OIDC-style ID token or a custom extension, that should be documented and the type annotation should reflect it. If there's a /v1/userinfo endpoint, calling it with the access_token is the more robust and interoperable path.

3. Two error codes not mapped in ERROR_MESSAGES

// Login.tsx — missing entries
return c.redirect('/login?error=missing_params');  // not in map → shows raw string
return c.redirect('/login?error=server_config');   // not in map → shows raw string

Either add them to ERROR_MESSAGES or use a generic fallback that doesn't leak internal codes to users.


Security

4. OAuth provider error codes forwarded verbatim to users

// lines 70–72
if (error) {
  return c.redirect(`/login?error=${encodeURIComponent(error)}`);
}

An error string returned by ChittyAuth (or a malicious redirect) is displayed directly if it isn't in ERROR_MESSAGES. The || err fallback in the frontend means arbitrary strings can be shown. Map provider errors to your own codes here, or use a generic message for unmapped values on the frontend (i.e. change || err to || "Authentication failed").


Schema Gap

5. chittyId column not shown in diff
getUserByChittyId and linkChittyId reference schema.users.chittyId, but there's no schema migration in this PR. If this column doesn't yet exist in database/system.schema.ts, both methods will fail at runtime. Please confirm the column exists (or add it here), and note that drizzle-kit push is the process per CLAUDE.md.


Minor

6. autoFocus removed from email input
The email input lost autoFocus in the restructuring. Low priority but it was a nice UX touch — worth keeping.

7. Token not persisted
The access token from ChittyAuth is discarded after identity resolution. This is fine if ChittyFinance only needs identity at login time. If future features need to call ChittyAuth APIs on behalf of the user (e.g. to read DID profile data), you'll need to store/refresh it. Worth a // TODO comment now so it doesn't get forgotten.


Summary

# Severity Item
1 Medium Missing null-check on clientId in callback
2 Medium Non-standard identity extraction from token body
3 Low Two error codes unmapped in ERROR_MESSAGES
4 Low Raw provider error codes shown to users
5 Medium chittyId schema column not in diff
6 Trivial autoFocus dropped from email input
7 Info Access token not persisted (document intent)

Items 1, 2, and 5 should be resolved before merge. The rest are low-risk but easy to address.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds ChittyID single sign-on (OAuth 2.0 Authorization Code + PKCE) to ChittyFinance, including backend routes to initiate/complete the OAuth flow and frontend login UI updates to expose “Sign in with ChittyID”.

Changes:

  • Introduces /api/auth/chittyid/authorize and /api/auth/chittyid/callback routes implementing PKCE + session creation.
  • Adds storage helpers to resolve/link users by chittyId, and includes chittyId in session responses.
  • Updates the login page to add a ChittyID sign-in button and improved error rendering.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
server/storage/system.ts Adds getUserByChittyId and linkChittyId helpers for account linking.
server/routes/session.ts Includes chittyId in session JSON responses.
server/routes/chittyid-auth.ts New public OAuth PKCE authorize/callback flow; creates KV-backed sessions.
server/env.ts Adds ChittyID OAuth env bindings (CHITTYAUTH_CLIENT_ID/SECRET).
server/app.ts Mounts the new ChittyID auth routes.
client/src/pages/Login.tsx Adds “Sign in with ChittyID” entry point and error-code mapping.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (4)
server/storage/system.ts (1)

195-201: Consider setting updatedAt for consistency with other update methods.

All other update* methods in this class set updatedAt: new Date(). This omission could lead to stale updatedAt values when ChittyID linking occurs.

♻️ Suggested fix
 async linkChittyId(userId: string, chittyId: string) {
   const [row] = await this.db.update(schema.users)
-    .set({ chittyId })
+    .set({ chittyId, updatedAt: new Date() })
     .where(eq(schema.users.id, userId))
     .returning();
   return row;
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@server/storage/system.ts` around lines 195 - 201, The linkChittyId method
omits updating updatedAt, causing inconsistent timestamps; modify linkChittyId
to include updatedAt: new Date() in the .set(...) call (i.e., set both {
chittyId, updatedAt: new Date() }) when performing the
this.db.update(schema.users) so it matches other update* methods and returns the
updated row as before.
client/src/pages/Login.tsx (2)

5-5: Unused import: setLocation is destructured but never used.

The component uses window.location.href = "/" for navigation instead of setLocation.

♻️ Suggested fix
-import { useLocation } from "wouter";
+// Remove if setLocation is truly unused, or replace window.location.href with setLocation("/")

If programmatic navigation via Wouter is preferred:

-      window.location.href = "/";
+      setLocation("/");
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@client/src/pages/Login.tsx` at line 5, The import and destructure from
useLocation includes setLocation but it's unused while the component navigates
with window.location.href = "/", so either remove setLocation from the
useLocation destructure or replace direct window.location.href navigation with
programmatic Wouter navigation by calling setLocation("/") where the redirect
happens (update the Login component to use the setLocation symbol from
useLocation instead of window.location.href, or simply remove setLocation if you
prefer native navigation).

22-26: Consider defaulting unmapped error codes to a generic message.

Line 25 falls back to displaying the raw error code (err) if it's not in ERROR_MESSAGES. This could expose internal error identifiers to users.

♻️ Suggested fix
 const [error, setError] = useState(() => {
   const params = new URLSearchParams(window.location.search);
   const err = params.get("error");
-  return err ? (ERROR_MESSAGES[err] || err) : "";
+  return err ? (ERROR_MESSAGES[err] || "An error occurred — try again") : "";
 });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@client/src/pages/Login.tsx` around lines 22 - 26, The initializer for the
error state currently returns the raw error code when it's not found in
ERROR_MESSAGES (useState -> error/setError), which can leak internal
identifiers; change the logic so when err exists but ERROR_MESSAGES[err] is
undefined you return a generic user-facing message (e.g., ERROR_MESSAGES.UNKNOWN
or "An unexpected error occurred") instead of err; update the initializer that
uses URLSearchParams(window.location.search) to prefer mapped messages and
fallback to the generic constant, and optionally add a DEFAULT_ERROR/UNKNOWN key
to ERROR_MESSAGES for consistency.
server/routes/chittyid-auth.ts (1)

11-15: Duplicate generateSessionId function exists in session.ts.

This function is identical to the one in server/routes/session.ts. Consider extracting it to a shared location like server/lib/session.ts.

♻️ Suggested refactor

In server/lib/session.ts:

export function generateSessionId(): string {
  const bytes = new Uint8Array(32);
  crypto.getRandomValues(bytes);
  return Array.from(bytes, (b) => b.toString(16).padStart(2, '0')).join('');
}

Then import and use in both session.ts and chittyid-auth.ts.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@server/routes/chittyid-auth.ts` around lines 11 - 15, The duplicate
generateSessionId function should be extracted to a shared module and reused:
move the implementation of generateSessionId() into a single exported function
in a common module, export it, then replace the local implementations in both
session.ts and chittyid-auth.ts with imports that call the shared
generateSessionId; ensure the signature and behavior remain identical and update
imports where the function was used.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@server/routes/chittyid-auth.ts`:
- Around line 97-98: Remove the non-null assertion on c.env.CHITTYAUTH_CLIENT_ID
and add a guard that validates both CHITTYAUTH_CLIENT_ID (and optionally
CHITTYAUTH_CLIENT_SECRET) before using them; specifically, replace the forced
access to clientId with a safe check (e.g., if (!c.env.CHITTYAUTH_CLIENT_ID) {
log error and return an appropriate HTTP error/redirect) and only proceed to use
clientId and clientSecret after they've been validated, updating any code paths
that assume clientId is present (the variables clientId and clientSecret in
server/routes/chittyid-auth.ts) to handle the missing-config case gracefully.
- Around line 147-153: The current flow that calls storage.getUserByEmail(...)
then storage.linkChittyId(user.id, chittyId) can race: another request may
already link the same chittyId and cause linkChittyId to throw a
unique-constraint DB error; modify the code around the fallback block so
linkChittyId is wrapped in a try/catch that detects the unique constraint /
conflict on chittyId and handles it gracefully (e.g., treat as a controlled
failure path that redirects back to login with an error key like
"chittyid_conflict" rather than letting the exception bubble to a 500). Update
the client error mapping by adding chittyid_conflict to ERROR_MESSAGES in
Login.tsx so the frontend shows "This ChittyID is already linked to another
account".
- Around line 116-129: The fetch to exchange the token can hang because it has
no timeout; update the POST to `${CHITTYAUTH_BASE}/v1/oauth/token` to include an
AbortSignal timeout (e.g. add signal:
AbortSignal.timeout(TOKEN_FETCH_TIMEOUT_MS) to the fetch options) and define a
sensible TOKEN_FETCH_TIMEOUT_MS constant near the handler; also handle aborts in
the catch block (detect AbortError and redirect with a specific timeout error)
so tokenRes/tokenData handling no longer blocks indefinitely.

---

Nitpick comments:
In `@client/src/pages/Login.tsx`:
- Line 5: The import and destructure from useLocation includes setLocation but
it's unused while the component navigates with window.location.href = "/", so
either remove setLocation from the useLocation destructure or replace direct
window.location.href navigation with programmatic Wouter navigation by calling
setLocation("/") where the redirect happens (update the Login component to use
the setLocation symbol from useLocation instead of window.location.href, or
simply remove setLocation if you prefer native navigation).
- Around line 22-26: The initializer for the error state currently returns the
raw error code when it's not found in ERROR_MESSAGES (useState ->
error/setError), which can leak internal identifiers; change the logic so when
err exists but ERROR_MESSAGES[err] is undefined you return a generic user-facing
message (e.g., ERROR_MESSAGES.UNKNOWN or "An unexpected error occurred") instead
of err; update the initializer that uses URLSearchParams(window.location.search)
to prefer mapped messages and fallback to the generic constant, and optionally
add a DEFAULT_ERROR/UNKNOWN key to ERROR_MESSAGES for consistency.

In `@server/routes/chittyid-auth.ts`:
- Around line 11-15: The duplicate generateSessionId function should be
extracted to a shared module and reused: move the implementation of
generateSessionId() into a single exported function in a common module, export
it, then replace the local implementations in both session.ts and
chittyid-auth.ts with imports that call the shared generateSessionId; ensure the
signature and behavior remain identical and update imports where the function
was used.

In `@server/storage/system.ts`:
- Around line 195-201: The linkChittyId method omits updating updatedAt, causing
inconsistent timestamps; modify linkChittyId to include updatedAt: new Date() in
the .set(...) call (i.e., set both { chittyId, updatedAt: new Date() }) when
performing the this.db.update(schema.users) so it matches other update* methods
and returns the updated row as before.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 661595ec-5683-480e-8282-5852b549e9c3

📥 Commits

Reviewing files that changed from the base of the PR and between 7070253 and bf931e9.

📒 Files selected for processing (6)
  • client/src/pages/Login.tsx
  • server/app.ts
  • server/env.ts
  • server/routes/chittyid-auth.ts
  • server/routes/session.ts
  • server/storage/system.ts

@chitcommit chitcommit merged commit f8a331c into main Mar 28, 2026
26 checks passed
@chitcommit chitcommit deleted the feat/chittyid-sso branch March 28, 2026 11:19
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