Skip to content

fix(api): use internal user ID instead of WorkOS ID for conversation …#42

Open
JStaRFilms wants to merge 5 commits intomainfrom
claude-saving-logic
Open

fix(api): use internal user ID instead of WorkOS ID for conversation …#42
JStaRFilms wants to merge 5 commits intomainfrom
claude-saving-logic

Conversation

@JStaRFilms
Copy link
Copy Markdown
Owner

…operations

All conversation API routes now look up the internal database user ID from the WorkOS ID before performing operations. This ensures consistency with the database schema where conversations are linked to internal user IDs.

Also relaxed message schema validation to support all AI SDK part types (text, image, tool-call, tool-result, reasoning, file, etc.) and added passthrough to prevent sync failures with legitimate messages.

…operations

All conversation API routes now look up the internal database user ID from the WorkOS ID before performing operations. This ensures consistency with the database schema where conversations are linked to internal user IDs.

Also relaxed message schema validation to support all AI SDK part types (text, image, tool-call, tool-result, reasoning, file, etc.) and added passthrough to prevent sync failures with legitimate messages.
@vercel
Copy link
Copy Markdown

vercel bot commented Dec 14, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Review Updated (UTC)
jstarfilms Ready Ready Preview, Comment Dec 15, 2025 0:54am

@github-actions
Copy link
Copy Markdown

🟢 J Star Code Audit

Score Verdict 🚨 Critical 🔶 High 🔹 Medium 🔧 Nitpick
85/100 REQUEST_CHANGES - 3 1 -

📄 src/app/api/conversations/[id]/route.ts

Warning

N+1 Query Pattern in All Routes
Every request repeats the same internal user lookup, creating an N+1 query pattern that will explode under load.

Warning

Missing Ownership Check on PATCH
The route allows any authenticated user to update any conversation by ID; it never verifies the conversation belongs to the requesting user.

Warning

Missing Ownership Check on DELETE
Any authenticated user can delete any conversation; no ownership check is enforced.

🛠️ Recommended Fixes

  • N+1 Query Pattern in All Routes: Cache the internal user lookup result in a request-scoped variable or middleware so the same user.id → internalUser.id mapping is reused across all API calls in a single request.
  • Missing Ownership Check on PATCH: Inside ConversationService.updateConversation, add a WHERE clause that restricts updates to rows where userId = internalUser.id, or return 403 if the conversation does not belong to the user.
  • Missing Ownership Check on DELETE: Inside ConversationService.deleteConversation, add a WHERE clause that restricts deletion to rows where userId = internalUser.id, or return 403 if the conversation does not belong to the user.

📄 src/features/john-gpt/schema.ts

🔹 Overly Permissive MessagePartSchema

Category: LOGIC

Using .passthrough() on MessagePartSchema disables validation for AI SDK parts, letting malformed or malicious data into the database.

🛠️ Recommended Fixes

  • Overly Permissive MessagePartSchema: Replace .passthrough() with an explicit union of known safe part shapes (text, image, tool-invocation) and reject unknown types instead of allowing arbitrary fields.

Powered by J Star Sentinel ⚡

…lidation

- Created `src/lib/user-auth.ts` with `getAuthenticatedUser()` to eliminate N+1 user lookup patterns
- Added standard response helpers: `unauthorizedResponse()`, `forbiddenResponse()`, `notFoundResponse()`
- Refactored `/api/conversations/[id]/route.ts` to use shared auth helper
- Added explicit 403 responses for ownership violations on PATCH/DELETE operations
- Hardened `MessagePartSchema` with discriminated union of known AI SDK part types (replaced `.passthrough()`)
- Updated documentation with auth helper reference and message schema validation details
@github-actions
Copy link
Copy Markdown

🟢 J Star Code Audit

Score Verdict 🚨 Critical 🔶 High 🔹 Medium 🔧 Nitpick
90/100 COMMENT - 1 2 -

📄 src/app/api/conversations/route.ts

Warning

N+1 Query Pattern Re-introduced in /conversations Route
The refactor missed the main /conversations route; it still performs two sequential DB calls (WorkOS auth + internal user lookup) instead of using the new getAuthenticatedUser helper.

🛠️ Recommended Fixes

  • N+1 Query Pattern Re-introduced in /conversations Route: Replace the separate withAuth() and prisma.user.findUnique calls in both GET and POST handlers with a single call to getAuthenticatedUser().

📄 src/features/john-gpt/schema.ts

🔹 MessageSchema.parts Array May Contain Invalid Part Types

Category: LOGIC

MessageSchema allows parts to be optional but does not enforce that every element in the array is validated by the discriminated union, so unknown part types could slip through.

🛠️ Recommended Fixes

  • MessageSchema.parts Array May Contain Invalid Part Types: Change parts: z.array(MessagePartSchema).optional() to parts: z.array(MessagePartSchema).optional().refine(arr => !arr || arr.every(p => MessagePartSchema.safeParse(p).success), { message: 'Invalid message part' }) or ensure downstream code always re-validates each part.

📄 src/lib/user-auth.ts

🔹 Missing Error Handling for Prisma Lookup Failure

Category: MAINTAINABILITY

If prisma.user.findUnique throws (network, schema, etc.), the helper will propagate an unhandled rejection instead of returning null, forcing every caller to wrap in try/catch.

🛠️ Recommended Fixes

  • Missing Error Handling for Prisma Lookup Failure: Wrap the prisma call in a try/catch block inside getAuthenticatedUser and return null on any exception to keep the contract that the function never throws.

Powered by J Star Sentinel ⚡

@JStaRFilms
Copy link
Copy Markdown
Owner Author

/review

@github-actions
Copy link
Copy Markdown

🟢 J Star Code Audit

Score Verdict 🚨 Critical 🔶 High 🔹 Medium 🔧 Nitpick
97/100 APPROVE - 1 1 -

📄 src/app/api/conversations/route.ts

Warning

N+1 User Lookup Still Present in /conversations
The /conversations route still performs a separate Prisma lookup after WorkOS auth instead of using the new getAuthenticatedUser() helper, re-introducing the exact N+1 query pattern the PR aims to eliminate.

🔹 Inconsistent Auth Pattern in Route Handlers

Category: LOGIC

The /conversations route uses the old withAuth() + manual lookup pattern while /conversations/[id] uses the new helper, creating an inconsistent and error-prone auth flow across the same feature.

🛠️ Recommended Fixes

  • N+1 User Lookup Still Present in /conversations: Refactor both GET and POST handlers in src/app/api/conversations/route.ts to call getAuthenticatedUser() once and use the returned internalUser.id, removing the redundant prisma.user.findUnique call.
  • Inconsistent Auth Pattern in Route Handlers: Replace the withAuth() and prisma.user.findUnique calls in src/app/api/conversations/route.ts with a single getAuthenticatedUser() call and handle the null case with unauthorizedResponse().

Powered by J Star Sentinel ⚡

@JStaRFilms
Copy link
Copy Markdown
Owner Author

/review

@github-actions
Copy link
Copy Markdown

🟢 J Star Code Audit

Score Verdict 🚨 Critical 🔶 High 🔹 Medium 🔧 Nitpick
90/100 APPROVE - 1 1 -

📄 src/app/api/conversations/route.ts

Warning

N+1 Query Still Present in conversations/route.ts
This route keeps the old N+1 pattern (withAuth + findUnique) while the other routes were refactored to use getAuthenticatedUser().

🔹 Inconsistent Error Responses

Category: STYLE

This file still returns hand-crafted JSON instead of the new standard helpers (unauthorizedResponse, notFoundResponse).

🛠️ Recommended Fixes

  • N+1 Query Still Present in conversations/route.ts: Replace the withAuth + prisma.user.findUnique calls in both GET and POST handlers with a single call to getAuthenticatedUser() from the new helper, exactly like the sibling [id]/route.ts file does.
  • Inconsistent Error Responses: Import the standard response helpers from '@/lib/user-auth' and replace NextResponse.json({ error: 'Unauthorized' }, { status: 401 }) with unauthorizedResponse(), and the 404 case with notFoundResponse('User').

Powered by J Star Sentinel ⚡

…e API fetch

Previously, loadConversation would return empty cached metadata from list endpoint
and skip API fetch. Now validates cache has messages before returning, and fetches
from API if cache is empty or metadata-only.

Also improves listConversations to await API fetch on empty cache (fresh window
scenario) instead of returning empty list, and adds comprehensive debug logging
throughout sync operations.
- Add isRefreshingList flag to prevent concurrent/recursive refreshes
- Only notify listeners when actual changes are detected
- Fix userId mapping for API responses that don't include userId
- Handle null values in optional fields (personaId, selectedModelId)
- Add detailed logging for sync payload debugging
- Improve Zod validation error logging with formatted output
- Ensure messages array defaults to empty array if undefined
Add comprehensive escalation handoff reports documenting two critical issues:
- Sidebar conversation loading from database (resolved)
- Timestamp updates when viewing conversations (unresolved)

Update StorageAndPersistence.md with current system status, working features table,
and known issues. Disable Google Drive sync UI in ConversationSidebar (backend
remains available). Implement message count tracking in useBranchingChat to prevent
timestamp updates on view-only operations. Improve db-sync-manager to detect and
fix userId mismatches during conversation sync.
@github-actions
Copy link
Copy Markdown

🟢 J Star Code Audit

Score Verdict 🚨 Critical 🔶 High 🔹 Medium 🔧 Nitpick
81/100 REQUEST_CHANGES 2 5 8 -

📄 src/app/api/conversations/[id]/route.ts

🔹 Sequential Auth & Param Await

Category: PERFORMANCE

Auth check and params destructuring run one after another even though they are independent. This adds ~20-40ms per request.

🔹 Sequential DB & Service Calls

Category: PERFORMANCE

Conversation fetch and subsequent checks execute in series even when they could be parallelized.

🔹 Sequential Body Parse & Validation

Category: PERFORMANCE

req.json() and schema.parse run sequentially despite being independent CPU/IO tasks.

🔹 Sequential Delete & Response

Category: PERFORMANCE

Awaiting delete result before returning success response adds unnecessary latency.

Warning

Missing Ownership Check
PATCH endpoint does not verify the conversation belongs to the requesting user before update, allowing potential data tampering.

Warning

Missing Ownership Check on DELETE
DELETE lacks pre-check that the conversation belongs to the user, enabling malicious deletion of another user's data.

🛠️ Recommended Fixes

  • Sequential Auth & Param Await: Wrap getAuthenticatedUser() and props.params in Promise.all to run concurrently.
  • Sequential DB & Service Calls: If additional fetches are added here, group them with Promise.all to reduce latency.
  • Sequential Body Parse & Validation: Use Promise.all to parse body and validate in parallel if validation is CPU-bound.
  • Sequential Delete & Response: Return NextResponse immediately and handle errors in catch block; no need to await if not using result.
  • Missing Ownership Check: Inside ConversationService.updateConversation, add a where clause ensuring userId matches internalUser.id to enforce ownership.
  • Missing Ownership Check on DELETE: Inside ConversationService.deleteConversation, add a where clause ensuring userId equals internalUser.id before deletion.

📄 src/features/john-gpt/schema.ts

Warning

Passthrough Schema Allows Arbitrary Injection
Using .passthrough() on MessageSchema lets attackers store extra fields like malicious metadata or override critical AI SDK properties, breaking downstream safety checks.

🔹 Nullable content Without Null Union Type

Category: LOGIC

content is marked optional().nullable() but the inferred TypeScript type won’t include null unless the schema is built with z.string().nullable().optional(), causing runtime vs type mismatch.

🔹 ToolResultPartSchema Allows Any Result

Category: MAINTAINABILITY

result: z.any() accepts huge payloads or circular objects that can crash JSON serialization when storing messages in SQLite.

🛠️ Recommended Fixes

  • Passthrough Schema Allows Arbitrary Injection: Remove .passthrough() and explicitly list every safe AI SDK field (e.g., toolInvocations, experimental_metadata) so unknown keys are stripped during validation.
  • Nullable content Without Null Union Type: Change to content: z.string().nullable().optional() so the generated type correctly allows null and undefined.
  • ToolResultPartSchema Allows Any Result: Replace z.any() with z.unknown() and add a max-depth refinement or use z.record(z.string(), z.unknown()) to limit structure.

📄 src/lib/storage/db-sync-manager.ts

🔹 Sequential fetches can run in parallel

Category: PERFORMANCE

Lines 18 and 20 fire two independent awaits back-to-back. Wrap them in Promise.all to cut wait time in half.

Warning

Missing nullish coalescing for userId
mapApiToCache relies on an optional overrideUserId but never null-checks it, so a literal null can be written to IndexedDB and break downstream queries.

Caution

No integrity check on server conversation list
refreshConversationList trusts the API response without validating shape or size, allowing a poisoned response to overwrite local data or crash the client.

🛠️ Recommended Fixes

  • Sequential fetches can run in parallel: Replace the two consecutive awaits with Promise.all([indexedDBClient.getConversation(conversationId), this.revalidateFromApi(...)]).
  • Missing nullish coalescing for userId: In mapApiToCache change the assignment to userId: overrideUserId ?? apiConv.userId ?? this.userId!
  • No integrity check on server conversation list: Add runtime validation: assert(Array.isArray(serverConversations) && serverConversations.length < 1000) before processing, and wrap the loop in try/catch to discard malformed items.

📄 src/lib/user-auth.ts

Caution

Missing Error Handling in Authentication Flow
The getAuthenticatedUser function lacks try-catch blocks around both the withAuth call and the Prisma query, causing unhandled promise rejections that crash the entire API route.

Warning

Silent Failure on Missing Internal User
Returning null when the internal user doesn't exist creates a silent authentication failure that masks legitimate signup flows and makes debugging impossible.

🔹 Missing Import for Logging Utility

Category: MAINTAINABILITY

The authentication module has no logging mechanism, making production debugging extremely difficult when authentication issues occur.

🛠️ Recommended Fixes

  • Missing Error Handling in Authentication Flow: Wrap the entire getAuthenticatedUser function body in a try-catch block that logs the error and returns null to gracefully handle authentication failures without crashing the API route.
  • Silent Failure on Missing Internal User: Add detailed logging before returning null, and consider creating the internal user automatically if workosUser exists but internalUser is missing, or return a specific error code to distinguish between unauthenticated and unlinked accounts.
  • Missing Import for Logging Utility: Import a logging utility (like pino or winston) and add structured logging at key points: auth attempts, user lookups, and failures with relevant context like workosUser.id.

Powered by J Star Sentinel ⚡

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.

1 participant