feat: Elysia YouTube channel-info POC with typed client and OpenAPI#1626
feat: Elysia YouTube channel-info POC with typed client and OpenAPI#1626arpitgupta1214 wants to merge 1 commit intotestfrom
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughThis pull request migrates YouTube channel info fetching from a Next.js API route to an Elysia-based REST API framework. It introduces authentication plugins for API key and Privy token validation, updates hooks and components to use the new API client, and removes the legacy fetch implementation. Changes
Sequence Diagram(s)sequenceDiagram
actor Client
participant NextApi as Next.js API<br/>/api/[...slug]
participant Elysia as Elysia App<br/>Channel Info Route
participant AuthPlugin as Auth Plugin
participant ApiKeyLookup as API Key Lookup<br/>(Supabase)
participant PrivyAuth as Privy Auth<br/>Token Verification
participant YouTube as YouTube API
Client->>NextApi: GET /api/youtube/channel-info<br/>?artist_account_id=123<br/>Header: x-api-key or Cookie: privy-token
NextApi->>Elysia: Forward request
Elysia->>AuthPlugin: Check authentication
alt x-api-key present
AuthPlugin->>ApiKeyLookup: Lookup account ID
ApiKeyLookup-->>AuthPlugin: Return account ID
AuthPlugin->>AuthPlugin: Set user context
else privy-token present
AuthPlugin->>PrivyAuth: Verify token
PrivyAuth-->>AuthPlugin: Return user ID
AuthPlugin->>AuthPlugin: Set user context
else No credentials
AuthPlugin-->>Elysia: Return 401
Elysia-->>NextApi: 401 Unauthorized
NextApi-->>Client: 401 Unauthorized
end
Elysia->>Elysia: Validate artist_account_id
Elysia->>Elysia: Get stored YouTube tokens
Elysia->>YouTube: Fetch channel info<br/>with access token
YouTube-->>Elysia: Channel data
Elysia-->>NextApi: 200 + channelData
NextApi-->>Client: 200 + channelData
sequenceDiagram
actor React as React Component
participant Hook as useYoutubeChannel<br/>Hook
participant Client as Treaty Client<br/>api.youtube["channel-info"]
participant API as API Server
React->>Hook: Call with artistAccountId
activate Hook
Hook->>Hook: Set up useQuery
Hook->>Client: Call .get() with<br/>artist_account_id query
Client->>API: GET /api/youtube/channel-info?artist_account_id=...
API-->>Client: Response with channelData
Client-->>Hook: queryFn receives response
Hook->>Hook: Validate response<br/>(check error, data exists)
Hook-->>React: Return channel array
deactivate Hook
React->>React: Extract channel[0]<br/>for rendering
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes The changes introduce a new authentication framework and API layer architecture with multiple interacting components (auth plugins, API routing, token validation). The migration spans hooks, components, and infrastructure files with varying logic density. Authentication logic in particular requires careful scrutiny for security correctness, token handling, and error edge cases. Suggested reviewers
Poem
🚥 Pre-merge checks | ❌ 1❌ Failed checks (1 warning)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
lib/chat/validateHeaders.ts (1)
64-66:⚠️ Potential issue | 🟡 Minor
accessTokenmay be undefined in cookie-only authentication scenarios.Cookie-based authentication (when neither
authorizationnorx-api-keyheaders are present) results inundefinedaccessToken. WhilevalidateChatRequest.tsguards against this with explicit validation requiring a non-empty token, theauth.tsplugin inlib/api/elysia/plugins/auth.tspassesvalidated.accessTokendirectly without validation. This creates inconsistent handling across code paths. If MCP auth or other consumers use the auth macro directly, they could receive undefined tokens. Verify that all consumers explicitly check for token presence or add validation to the auth plugin.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@lib/chat/validateHeaders.ts` around lines 64 - 66, The auth plugin is passing validated.accessToken that can be undefined when auth comes from cookies; update the auth macro in auth.ts (the code that consumes validateHeaders.ts/validateChatRequest.ts) to explicitly assert and throw or return a validation error if validated.accessToken is missing (empty/undefined) before passing it on, or alternatively add a non-optional fallback/explicit error inside validateHeaders.ts where accessToken is derived (the const accessToken = ... line) so that accessToken is never undefined; reference validated.accessToken and the accessToken creation in validateHeaders.ts and ensure every consumer (including auth.ts) either checks for non-empty token or receives a guaranteed non-undefined token.
🧹 Nitpick comments (4)
hooks/useArtistFromRoom.ts (1)
28-28: Nitpick:Boolean(roomId)vs!!roomId— equivalent but verbose.Both forms are semantically identical.
Boolean(roomId)is slightly more explicit/readable, while!!roomIdis the more common idiom in TypeScript. Either works fine — just noting the style inconsistency with line 28's!!userData?.idon the same line.🔧 Optional: Consistent style
- enabled: Boolean(roomId) && !!userData?.id, + enabled: !!roomId && !!userData?.id,🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@hooks/useArtistFromRoom.ts` at line 28, In hooks/useArtistFromRoom.ts update the enabled flag to use a consistent boolean coercion style: change the expression currently using Boolean(roomId) in the enabled property (alongside !!userData?.id) to the shorter idiomatic form !!roomId so both checks use the same coercion pattern; locate the enabled: Boolean(roomId) && !!userData?.id line in the useArtistFromRoom hook and replace Boolean(roomId) with !!roomId.components/VercelChat/chat.tsx (1)
30-39: Consider the coupling betweenidprop and URL-derivedroomId.The
Chatcomponent receivesidas a prop and passes it toVercelChatProvideraschatId. Meanwhile,ChatContentMemoizedindependently derivesroomIdfromuseParams()foruseArtistFromRoom.In practice, these should always match since the page component (
app/chat/[roomId]/page.tsx) passes the route param asid. However, there's now an implicit assumption that they're identical without explicit coupling.This is likely fine given the current architecture, but worth noting if the component is ever used in contexts where
idprop might differ from the URL param (e.g., embedded chat, modals).🔧 Optional: Make the relationship explicit via context
If you want to ensure consistency, you could expose
chatIdfromVercelChatProvidercontext and use it inChatContentMemoizedinstead of callinguseParams()again:// In ChatContentMemoized: const { chatId } = useVercelChatContext(); // Add chatId to context useArtistFromRoom(chatId);This would guarantee the artist fetch uses the same ID as the chat provider.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@components/VercelChat/chat.tsx` around lines 30 - 39, The Chat component currently passes id to VercelChatProvider as chatId while ChatContentMemoized independently calls useParams() to derive roomId for useArtistFromRoom, creating an implicit coupling; update the VercelChatProvider context to include chatId (expose it via useVercelChatContext) and change ChatContentMemoized to consume chatId from that context and call useArtistFromRoom(chatId) instead of using useParams(), ensuring both provider and consumer always use the same chat identifier (adjust VercelChatProvider, its context type, and the ChatContentMemoized usage accordingly).components/YouTube/ChatInputYoutubeButtonPopover/index.tsx (1)
31-32: Consider keyboard accessibility for the mobile toggle.The mobile popover toggle only responds to
onClick. For full accessibility, consider addingonKeyDownto handle Enter/Space activation, and appropriate ARIA attributes (aria-expanded,aria-haspopup).♿ Proposed accessibility improvement
- <div onClick={() => setIsOpen(!isOpen)}> + <div + role="button" + tabIndex={0} + aria-expanded={isOpen} + aria-haspopup="dialog" + onClick={() => setIsOpen(!isOpen)} + onKeyDown={(e) => { + if (e.key === "Enter" || e.key === " ") { + e.preventDefault(); + setIsOpen(!isOpen); + } + }} + >🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@components/YouTube/ChatInputYoutubeButtonPopover/index.tsx` around lines 31 - 32, The mobile popover toggle currently only uses onClick; update the interactive element that calls setIsOpen(!isOpen) (the div wrapping {children}) to be keyboard-accessible by adding onKeyDown that toggles on Enter/Space, give it role="button" and tabIndex={0}, and include ARIA attributes aria-expanded={isOpen} and aria-haspopup="true" so assistive tech can detect state and purpose; ensure the onKeyDown handler prevents default for Space and calls setIsOpen(!isOpen) similarly to the onClick handler.lib/api/elysia/routes/youtube/channelInfo.ts (1)
40-44:t.Any()weakens type safety in the OpenAPI spec.Using
t.Array(t.Any())for thechannelsresponse loses the type information in the generated OpenAPI documentation. Consider defining a proper schema forYouTubeChannelDatato provide accurate API documentation and runtime validation.♻️ Example channel schema
const channelDataSchema = t.Object({ id: t.String(), title: t.String(), description: t.String(), // ... other required fields from YouTubeChannelData }); // Then use: channels: t.Array(channelDataSchema),As per coding guidelines: "Use strict TypeScript types with zero 'any' types".
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@lib/api/elysia/routes/youtube/channelInfo.ts` around lines 40 - 44, The response currently uses t.Array(t.Any()) in channelInfo.ts which weakens type safety; define a strict schema (e.g., channelDataSchema or YouTubeChannelData) using t.Object with the required fields (id, title, description, etc.) and replace t.Any() with that schema in the response: change channels: t.Array(t.Any()) to channels: t.Array(channelDataSchema) and ensure any runtime/type exports (YouTubeChannelData or channelDataSchema) are imported/used where the route is declared so OpenAPI generation and validation use the precise shape.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@lib/api/elysia/client.ts`:
- Around line 1-8: The import and client creation are using the legacy
edenTreaty API; replace the import and factory call by importing treaty from
"@elysiajs/eden" and call treaty<ElysiaApi>(...) instead of
edenTreaty<ElysiaApi>(...), keeping the same options object (the eden
constant/name and $fetch credentials setting should remain unchanged).
In `@lib/api/elysia/routes/youtube/channelInfo.ts`:
- Around line 13-18: The handler currently ignores the authenticated user and
only reads query, allowing authorization bypass; update the route handler
signature to destructure auth and query (i.e., async ({ auth, query }) => ...)
and after validateYouTubeTokens succeeds, check that query.artist_account_id ===
auth.accountId and if not throw an HttpError(403, "Unauthorized: cannot access
other accounts"); ensure this check occurs before any token operations
(reference the validateYouTubeTokens call and the anonymous async route handler
in channelInfo.ts).
---
Outside diff comments:
In `@lib/chat/validateHeaders.ts`:
- Around line 64-66: The auth plugin is passing validated.accessToken that can
be undefined when auth comes from cookies; update the auth macro in auth.ts (the
code that consumes validateHeaders.ts/validateChatRequest.ts) to explicitly
assert and throw or return a validation error if validated.accessToken is
missing (empty/undefined) before passing it on, or alternatively add a
non-optional fallback/explicit error inside validateHeaders.ts where accessToken
is derived (the const accessToken = ... line) so that accessToken is never
undefined; reference validated.accessToken and the accessToken creation in
validateHeaders.ts and ensure every consumer (including auth.ts) either checks
for non-empty token or receives a guaranteed non-undefined token.
---
Nitpick comments:
In `@components/VercelChat/chat.tsx`:
- Around line 30-39: The Chat component currently passes id to
VercelChatProvider as chatId while ChatContentMemoized independently calls
useParams() to derive roomId for useArtistFromRoom, creating an implicit
coupling; update the VercelChatProvider context to include chatId (expose it via
useVercelChatContext) and change ChatContentMemoized to consume chatId from that
context and call useArtistFromRoom(chatId) instead of using useParams(),
ensuring both provider and consumer always use the same chat identifier (adjust
VercelChatProvider, its context type, and the ChatContentMemoized usage
accordingly).
In `@components/YouTube/ChatInputYoutubeButtonPopover/index.tsx`:
- Around line 31-32: The mobile popover toggle currently only uses onClick;
update the interactive element that calls setIsOpen(!isOpen) (the div wrapping
{children}) to be keyboard-accessible by adding onKeyDown that toggles on
Enter/Space, give it role="button" and tabIndex={0}, and include ARIA attributes
aria-expanded={isOpen} and aria-haspopup="true" so assistive tech can detect
state and purpose; ensure the onKeyDown handler prevents default for Space and
calls setIsOpen(!isOpen) similarly to the onClick handler.
In `@hooks/useArtistFromRoom.ts`:
- Line 28: In hooks/useArtistFromRoom.ts update the enabled flag to use a
consistent boolean coercion style: change the expression currently using
Boolean(roomId) in the enabled property (alongside !!userData?.id) to the
shorter idiomatic form !!roomId so both checks use the same coercion pattern;
locate the enabled: Boolean(roomId) && !!userData?.id line in the
useArtistFromRoom hook and replace Boolean(roomId) with !!roomId.
In `@lib/api/elysia/routes/youtube/channelInfo.ts`:
- Around line 40-44: The response currently uses t.Array(t.Any()) in
channelInfo.ts which weakens type safety; define a strict schema (e.g.,
channelDataSchema or YouTubeChannelData) using t.Object with the required fields
(id, title, description, etc.) and replace t.Any() with that schema in the
response: change channels: t.Array(t.Any()) to channels:
t.Array(channelDataSchema) and ensure any runtime/type exports
(YouTubeChannelData or channelDataSchema) are imported/used where the route is
declared so OpenAPI generation and validation use the precise shape.
🪄 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: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 013d14d4-8485-4456-b18f-f4385cc35bee
⛔ Files ignored due to path filters (3)
package.jsonis excluded by none and included by nonepnpm-lock.yamlis excluded by!**/pnpm-lock.yamland included by nonetypes/youtube.tsis excluded by none and included by none
📒 Files selected for processing (19)
app/api/openapi/[[...slug]]/route.tsapp/api/youtube/channel-info/route.tscomponents/ArtistSetting/StandaloneYoutubeComponent/ChannelInfo.tsxcomponents/VercelChat/chat.tsxcomponents/YouTube/ChatInputYoutubeButtonPopover/index.tsxhooks/useArtistFromRoom.tshooks/useVercelChat.tshooks/useYouTubeLoginSuccess.tshooks/useYoutubeChannel.tsxhooks/useYoutubeStatus.tslib/api/elysia/app.tslib/api/elysia/client.tslib/api/elysia/errors.tslib/api/elysia/plugins/auth.tslib/api/elysia/routes/index.tslib/api/elysia/routes/youtube/channelInfo.tslib/api/elysia/routes/youtube/index.tslib/chat/validateHeaders.tslib/youtube/fetchYouTubeChannel.ts
💤 Files with no reviewable changes (1)
- lib/youtube/fetchYouTubeChannel.ts
lib/api/elysia/client.ts
Outdated
| import { edenTreaty } from "@elysiajs/eden"; | ||
| import type { ElysiaApi } from "@/lib/api/elysia/app"; | ||
|
|
||
| const eden = edenTreaty<ElysiaApi>("", { | ||
| $fetch: { | ||
| credentials: "include", | ||
| }, | ||
| }); |
There was a problem hiding this comment.
🧩 Analysis chain
🌐 Web query:
What is the current API for @elysiajs/eden client? Is edenTreaty deprecated?
💡 Result:
The current API for the @elysiajs/eden client is Eden Treaty (imported as treaty from '@elysiajs/eden'), which is the recommended object-like RPC client for end-to-end type-safe interactions with Elysia servers. It provides tree-like syntax (e.g., app.hi.get), full type safety, auto-completion, error handling with type narrowing, and support for dynamic paths, streaming, WebSockets, and config options like custom fetchers. Example: import { treaty } from '@elysiajs/eden' import type { App } from './server' const app = treaty('http://localhost:3000') const { data, error } = await app.hi.get Alternative: Eden Fetch (edenFetch(url)), a fetch-like API for those preferring standard fetch syntax. edenTreaty (Eden Treaty 1, imported as edenTreaty) is the legacy version. Official docs label it as "Eden Treaty Legacy" and recommend Eden Treaty 2 (treaty) for new projects. It is not formally deprecated but superseded by the improved v2 with better performance, inference, and features (introduced around v1.0.0 in Mar 2024, latest @elysiajs/eden v1.4.8 as of Feb 2026). Continue using legacy only for compatibility; migrate for latest optimizations.
Citations:
- 1: https://elysiajs.com/eden/treaty/overview
- 2: https://registry.npmjs.org/%40elysiajs%2Feden
- 3: https://github.com/elysiajs/eden
- 4: https://elysiajs.com/eden/overview
- 5: https://elysiajs.com/eden/fetch
- 6: https://elysiajs.com/eden/treaty/response
- 7: https://beta.elysiajs.com/eden/treaty/legacy
- 8: https://elysiajs.com/eden/treaty/legacy
- 9: https://elysiajs.com/eden/treaty/config
- 10: https://raw.githubusercontent.com/elysiajs/eden/f92338c0158ccd7ca591769aad8a9a21020dacfe/CHANGELOG.md
Migrate from legacy edenTreaty to the current treaty API.
The edenTreaty import is the legacy Eden Treaty 1 API. Since @elysiajs/eden v1.0.0, the recommended API is treaty, which provides better performance, inference, and features. Update the import and client initialization:
import { treaty } from "@elysiajs/eden";
import type { ElysiaApi } from "@/lib/api/elysia/app";
const eden = treaty<ElysiaApi>("", {
$fetch: {
credentials: "include",
},
});🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@lib/api/elysia/client.ts` around lines 1 - 8, The import and client creation
are using the legacy edenTreaty API; replace the import and factory call by
importing treaty from "@elysiajs/eden" and call treaty<ElysiaApi>(...) instead
of edenTreaty<ElysiaApi>(...), keeping the same options object (the eden
constant/name and $fetch credentials setting should remain unchanged).
| async ({ query }) => { | ||
| const tokenValidation = await validateYouTubeTokens(query.artist_account_id); | ||
|
|
||
| if (!tokenValidation.success || !tokenValidation.tokens) { | ||
| throw new HttpError(403, "YouTube authentication required"); | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Check if artist_account_id should match the authenticated accountId
rg -n 'artist_account_id|accountId' lib/api/elysia/routes/youtube/channelInfo.ts lib/youtube/token-validator.ts -C2Repository: recoupable/chat
Length of output: 2016
🏁 Script executed:
# Get the full route handler to see lines around auth: true
cat -n lib/api/elysia/routes/youtube/channelInfo.tsRepository: recoupable/chat
Length of output: 2226
🏁 Script executed:
# Search for how auth macro is typically used in other routes
rg -A5 'auth:\s*true' lib/api/elysia/routes --type ts --type tsx | head -50Repository: recoupable/chat
Length of output: 86
🏁 Script executed:
# Check if there's validation that artist_account_id should match authenticated accountId
rg -n 'artist_account_id.*auth|accountId.*artist_account_id' lib --type ts --type tsxRepository: recoupable/chat
Length of output: 86
🏁 Script executed:
# Find auth macro definition or middleware
find lib -name "*.ts" -o -name "*.tsx" | xargs grep -l "auth.*macro\|createAuthMacro\|authMiddleware" | head -10Repository: recoupable/chat
Length of output: 89
🏁 Script executed:
# Look at other routes using auth: true to see the pattern
rg -A10 'auth:\s*true' lib/api/elysia/routes -A 5 | head -80Repository: recoupable/chat
Length of output: 465
🏁 Script executed:
# Check if Elysia auth plugin is defined or used
rg -n "\.plugin.*auth|\.derive.*auth" lib/api/elysia --type ts | head -20Repository: recoupable/chat
Length of output: 41
🏁 Script executed:
# Look for the route handler parameter type to see what auth provides
rg -n "async.*{.*auth" lib/api/elysia/routes --type ts | head -20Repository: recoupable/chat
Length of output: 163
🏁 Script executed:
# Read the auth plugin to understand what it provides
cat -n lib/api/elysia/plugins/auth.tsRepository: recoupable/chat
Length of output: 1682
🏁 Script executed:
# Check how auth is used in other route handlers that destructure it
rg -B2 -A10 'async.*{.*auth.*query' lib/api/elysia/routes --type ts | head -60Repository: recoupable/chat
Length of output: 170
🏁 Script executed:
# Look for other routes handling artist_account_id to understand the pattern
rg -B5 -A10 'artist_account_id' lib/api/elysia/routes --type tsRepository: recoupable/chat
Length of output: 2403
🏁 Script executed:
# Check if there are other routes that properly use auth.accountId
rg -B2 -A8 'async.*{.*auth' lib/api/elysia/routes --type ts | head -80Repository: recoupable/chat
Length of output: 163
🏁 Script executed:
# Find routes that use auth object in destructuring
rg 'async.*\{' lib/api/elysia/routes --type ts -A 1 | grep -E '(auth|query|body)' | head -30Repository: recoupable/chat
Length of output: 255
🏁 Script executed:
# Check how other routes handle authenticated operations
find lib/api/elysia/routes -name "*.ts" -exec grep -l "auth:" {} \; | xargs -I {} sh -c 'echo "=== {} ===" && head -20 {}'Repository: recoupable/chat
Length of output: 824
Fix authorization bypass: destructure auth and validate artist_account_id matches authenticated user.
The route declares auth: true which validates the request and provides auth.accountId, but the handler ignores it:
async ({ query }) => {Should be:
async ({ auth, query }) => {This creates a security vulnerability: any authenticated user can request YouTube tokens for any artist_account_id by manipulating the query parameter. The authenticated user's identity is never validated against the requested account.
After auth validation, verify the requested account matches the authenticated user:
if (query.artist_account_id !== auth.accountId) {
throw new HttpError(403, "Unauthorized: cannot access other accounts");
}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@lib/api/elysia/routes/youtube/channelInfo.ts` around lines 13 - 18, The
handler currently ignores the authenticated user and only reads query, allowing
authorization bypass; update the route handler signature to destructure auth and
query (i.e., async ({ auth, query }) => ...) and after validateYouTubeTokens
succeeds, check that query.artist_account_id === auth.accountId and if not throw
an HttpError(403, "Unauthorized: cannot access other accounts"); ensure this
check occurs before any token operations (reference the validateYouTubeTokens
call and the anonymous async route handler in channelInfo.ts).
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: efcd9d5540
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
lib/chat/validateHeaders.ts
Outdated
| const hasAuthCredential = | ||
| request.headers.has("authorization") || | ||
| request.headers.has("x-api-key") || | ||
| request.headers.has("cookie"); |
There was a problem hiding this comment.
Restrict cookie-based auth detection before upstream lookup
Treating any cookie header as an auth credential causes requests with unrelated browser cookies (but no authorization/x-api-key) to call /api/accounts/id anyway, turning the previous fast no-auth path into an upstream-dependent one. That introduces unnecessary latency/load and can surface 500s when the account lookup service is unavailable or returns an unexpected body, whereas the old behavior returned {} immediately and let callers handle unauthenticated requests locally.
Useful? React with 👍 / 👎.
| youtubeStatus?.status === "invalid" || | ||
| isLoading || | ||
| isChannelInfoLoading || | ||
| !channel |
There was a problem hiding this comment.
Hide YouTube popover when status is error
This guard only blocks on status === "invalid", but after moving channel-info to Elysia the disconnected/expired-auth path now commonly returns HTTP errors, which useYoutubeStatus maps to "error". Because React Query can retain prior successful channelInfo on refetch failure, !channel may still be false and the popover remains visible with stale channel data even though YouTube auth has failed.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
🧹 Nitpick comments (2)
hooks/useYouTubeLoginSuccess.ts (2)
54-72: Thedata.successcheck is redundant.Looking at the endpoint definition in
lib/api/elysia/routes/youtube/channelInfo.ts(lines 41-44), the 200 response schema is{ success: t.Literal(true), channels: t.Array(t.Any()) }. IfunwrapResponsesucceeds (no error, data not null),data.successwill always betrue. The conditional adds no value here.♻️ Simplify by removing redundant check
.then((youtubeChannel) => { const data = unwrapResponse( youtubeChannel, "Failed to fetch YouTube channel information", ); - if (data.success) { - const successMessage = { - id: generateUUID(), - role: "user" as const, - parts: [ - { - type: "text", - text: "Great! I've successfully connected my YouTube account. Please continue with what you were helping me with.", - }, - ], - } as UIMessage; - - append(successMessage); - } + const successMessage: UIMessage = { + id: generateUUID(), + role: "user" as const, + parts: [ + { + type: "text", + text: "Great! I've successfully connected my YouTube account. Please continue with what you were helping me with.", + }, + ], + }; + + append(successMessage); })🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@hooks/useYouTubeLoginSuccess.ts` around lines 54 - 72, Remove the redundant data.success conditional after unwrapResponse: since unwrapResponse(youtubeChannel, ...) guarantees a successful 200 response shape (success === true), always construct the successMessage (using generateUUID() and UIMessage shape) and call append(successMessage) directly; locate this logic around the unwrapResponse call that consumes youtubeChannel and simplify by eliminating the if (data.success) branch so the message creation and append happen unconditionally after a successful unwrapResponse.
74-76: Silent error swallowing may hinder debugging.While the comment explains the intent, completely silencing errors can make production issues harder to diagnose. Consider at minimum a
console.debugor structured logging for observability.🔍 Optional: Add debug logging
.catch(() => { - // Ignore auth/check errors here; normal flow continues. + // Ignore auth/check errors here; normal flow continues. + // Uncomment for debugging: console.debug("YouTube channel check failed silently"); });🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@hooks/useYouTubeLoginSuccess.ts` around lines 74 - 76, The current promise catch block in hooks/useYouTubeLoginSuccess.ts (the .catch(() => { ... }) attached to the login/check flow) silently swallows errors; change the catch to accept the error (e.g., .catch((err) => { ... })) and emit a low-level debug/trace log with the error (console.debug or the app's logger) while keeping the original behavior (no user-facing disruption); locate the catch in the useYouTubeLoginSuccess flow and add the error parameter and a single-line structured debug log message including the error and context.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@hooks/useYouTubeLoginSuccess.ts`:
- Around line 54-72: Remove the redundant data.success conditional after
unwrapResponse: since unwrapResponse(youtubeChannel, ...) guarantees a
successful 200 response shape (success === true), always construct the
successMessage (using generateUUID() and UIMessage shape) and call
append(successMessage) directly; locate this logic around the unwrapResponse
call that consumes youtubeChannel and simplify by eliminating the if
(data.success) branch so the message creation and append happen unconditionally
after a successful unwrapResponse.
- Around line 74-76: The current promise catch block in
hooks/useYouTubeLoginSuccess.ts (the .catch(() => { ... }) attached to the
login/check flow) silently swallows errors; change the catch to accept the error
(e.g., .catch((err) => { ... })) and emit a low-level debug/trace log with the
error (console.debug or the app's logger) while keeping the original behavior
(no user-facing disruption); locate the catch in the useYouTubeLoginSuccess flow
and add the error parameter and a single-line structured debug log message
including the error and context.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 4becc339-10b8-401f-b3b5-59cfd9bca214
⛔ Files ignored due to path filters (1)
types/youtube.tsis excluded by none and included by none
📒 Files selected for processing (6)
app/api/[...slug]/route.tsapp/api/youtube/channel-info/route.tshooks/useYouTubeLoginSuccess.tshooks/useYoutubeChannel.tsxlib/api/elysia/client.tslib/api/elysia/unwrapResponse.ts
💤 Files with no reviewable changes (1)
- app/api/youtube/channel-info/route.ts
🚧 Files skipped from review as they are similar to previous changes (2)
- lib/api/elysia/client.ts
- hooks/useYoutubeChannel.tsx
There was a problem hiding this comment.
Actionable comments posted: 2
♻️ Duplicate comments (1)
lib/api/elysia/routes/youtube/channelInfo.ts (1)
12-15:⚠️ Potential issue | 🔴 CriticalAuthorization bypass: handler ignores authenticated user identity.
The handler destructures
{ query, status }but notauth, so any authenticated user can request data for anyartist_account_id. This was flagged in a previous review. However, the root cause is thatauthMacroinlib/api/elysia/plugins/auth.tsdoesn't exposeaccountIdto handlers.Once the auth macro is fixed to expose
auth.accountId, update this handler:- async ({ query, status }) => { + async ({ query, status, auth }) => { if (!query.artist_account_id) { return status(400, { error: "Missing artist_account_id parameter" }); } + + if (query.artist_account_id !== auth.accountId) { + return status(403, { error: "Unauthorized: cannot access other accounts" }); + }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@lib/api/elysia/routes/youtube/channelInfo.ts` around lines 12 - 15, The route handler currently destructures only { query, status } and therefore allows any caller to request data for any artist_account_id; after fixing authMacro to expose auth.accountId, update this handler (the async route function in channelInfo.ts) to also destructure auth (e.g. async ({ query, status, auth })) and enforce authorization by verifying auth.accountId exists and equals query.artist_account_id (return status(403, { error: "Forbidden" }) on mismatch), and keep the existing 400 check for missing artist_account_id; this ensures only the authenticated account can request its own channel info.
🧹 Nitpick comments (3)
lib/api/elysia/routes/youtube/channelInfo.ts (2)
51-54: Consider adding proper typing for the channels array.Using
t.Array(t.Any())loses type safety and results in a less useful OpenAPI schema. Consider defining a proper channel schema based onYouTubeChannelDatatype.✨ Define typed channel schema
const youtubeChannelSchema = t.Object({ id: t.String(), title: t.String(), description: t.Optional(t.String()), thumbnails: t.Optional(t.Any()), // or define thumbnail schema // Add other fields as needed }); // Then in response: success: t.Literal(true), channels: t.Array(youtubeChannelSchema), }),🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@lib/api/elysia/routes/youtube/channelInfo.ts` around lines 51 - 54, The response schema currently uses t.Array(t.Any()) for channels which loses type safety; define a typed schema (e.g., youtubeChannelSchema) that maps the fields of YouTubeChannelData (id, title, description, thumbnails, etc.) and replace t.Array(t.Any()) with t.Array(youtubeChannelSchema) in the response object so the channels property is strongly typed and reflected in OpenAPI; ensure optional fields use t.Optional(...) and add/adjust thumbnail or nested schemas as needed to match YouTubeChannelData.
47-49: Schema declaresartist_account_idas optional but handler requires it.The query schema marks
artist_account_idast.Optional(t.String()), but line 13 immediately returns 400 if it's missing. This inconsistency makes the OpenAPI spec misleading.♻️ Make schema match handler requirements
query: t.Object({ - artist_account_id: t.Optional(t.String()), + artist_account_id: t.String(), }),With this change, Elysia validates the parameter before the handler runs, so the manual check on line 13-15 becomes unnecessary:
- if (!query.artist_account_id) { - return status(400, { error: "Missing artist_account_id parameter" }); - }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@lib/api/elysia/routes/youtube/channelInfo.ts` around lines 47 - 49, The query schema currently marks artist_account_id as optional (query: t.Object({ artist_account_id: t.Optional(t.String()) })), but the handler still requires it and returns 400 when missing; update the schema to make artist_account_id required (t.String()) so Elysia validates it before the handler runs, and then remove the redundant manual presence check and early 400 return in the handler that references artist_account_id.lib/api/elysia/plugins/auth.ts (1)
110-111: Use the recommended authenticated user-fetching method from Privy after token verification.In an authentication context (after
verifyAuthToken()), useprivy.users.get({ id_token: token })instead of_get(). The_get()method is for unauthenticated user lookups and is rate-limited;privy.users.get()is the recommended pattern for authenticated operations and aligns with official Privy best practices for this use case.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@lib/api/elysia/plugins/auth.ts` around lines 110 - 111, The code currently calls privyClient.users()._get(verified.user_id) after verifyAuthToken, which uses an unauthenticated, rate-limited lookup; replace this with the authenticated pattern privyClient.users().get({ id_token: authToken }) (or equivalent privy.users.get call) using the same authToken returned/used in verifyAuthToken so the user is fetched via the recommended authenticated API; update any variable references from the old _get result to the new get result and remove the use of privyClient.users()._get in function(s) where verifyAuthToken and authToken are 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 `@lib/api/elysia/plugins/auth.ts`:
- Around line 132-167: The auth macro's beforeHandle currently authenticates and
obtains accountId via getAccountIdFromApiKey and getAccountIdFromPrivyAuthToken
but never stores it on the request context, so downstream handlers can't access
the authenticated user; update the authMacro (beforeHandle) to attach the
resolved accountId into the request context using Elysia's supported mechanism
(e.g., derive or state) so handlers can read auth.accountId, ensuring you set
the value before returning and still return 401 on missing/invalid credentials
and propagate errors as currently done.
- Around line 118-129: Summary: The email lookup uses .maybeSingle() on the
account_emails table which can return an arbitrary account_id when duplicate
emails exist, creating an account isolation risk. Fix: replace the
.maybeSingle() call with .single() in the email lookup block (the
supabase.from("account_emails").select(...).eq("email", email) call), and
explicitly handle the duplicate-row error by throwing a clear error instead of
returning a value; alternatively enforce a UNIQUE constraint on
account_emails.email at the DB level or add a deterministic filter (e.g.,
primary_email flag) to disambiguate; follow the same pattern used by
getAccountByEmail.ts and getUserInfo.ts for consistent duplicate detection and
error handling.
---
Duplicate comments:
In `@lib/api/elysia/routes/youtube/channelInfo.ts`:
- Around line 12-15: The route handler currently destructures only { query,
status } and therefore allows any caller to request data for any
artist_account_id; after fixing authMacro to expose auth.accountId, update this
handler (the async route function in channelInfo.ts) to also destructure auth
(e.g. async ({ query, status, auth })) and enforce authorization by verifying
auth.accountId exists and equals query.artist_account_id (return status(403, {
error: "Forbidden" }) on mismatch), and keep the existing 400 check for missing
artist_account_id; this ensures only the authenticated account can request its
own channel info.
---
Nitpick comments:
In `@lib/api/elysia/plugins/auth.ts`:
- Around line 110-111: The code currently calls
privyClient.users()._get(verified.user_id) after verifyAuthToken, which uses an
unauthenticated, rate-limited lookup; replace this with the authenticated
pattern privyClient.users().get({ id_token: authToken }) (or equivalent
privy.users.get call) using the same authToken returned/used in verifyAuthToken
so the user is fetched via the recommended authenticated API; update any
variable references from the old _get result to the new get result and remove
the use of privyClient.users()._get in function(s) where verifyAuthToken and
authToken are used.
In `@lib/api/elysia/routes/youtube/channelInfo.ts`:
- Around line 51-54: The response schema currently uses t.Array(t.Any()) for
channels which loses type safety; define a typed schema (e.g.,
youtubeChannelSchema) that maps the fields of YouTubeChannelData (id, title,
description, thumbnails, etc.) and replace t.Array(t.Any()) with
t.Array(youtubeChannelSchema) in the response object so the channels property is
strongly typed and reflected in OpenAPI; ensure optional fields use
t.Optional(...) and add/adjust thumbnail or nested schemas as needed to match
YouTubeChannelData.
- Around line 47-49: The query schema currently marks artist_account_id as
optional (query: t.Object({ artist_account_id: t.Optional(t.String()) })), but
the handler still requires it and returns 400 when missing; update the schema to
make artist_account_id required (t.String()) so Elysia validates it before the
handler runs, and then remove the redundant manual presence check and early 400
return in the handler that references artist_account_id.
🪄 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: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 825c080a-9a4e-4597-ba63-1d9a58698432
⛔ Files ignored due to path filters (2)
package.jsonis excluded by none and included by nonepnpm-lock.yamlis excluded by!**/pnpm-lock.yamland included by none
📒 Files selected for processing (3)
lib/api/elysia/app.tslib/api/elysia/plugins/auth.tslib/api/elysia/routes/youtube/channelInfo.ts
🚧 Files skipped from review as they are similar to previous changes (1)
- lib/api/elysia/app.ts
lib/api/elysia/plugins/auth.ts
Outdated
| export const authMacro = new Elysia({ name: "auth-macro" }).macro({ | ||
| auth(enabled: boolean) { | ||
| if (!enabled) return; | ||
|
|
||
| return { | ||
| async beforeHandle({ request, status }) { | ||
| const apiKey = request.headers.get("x-api-key"); | ||
|
|
||
| try { | ||
| if (apiKey) { | ||
| const accountId = await getAccountIdFromApiKey(apiKey); | ||
| if (!accountId) { | ||
| return status(401, { error: "Unauthorized" }); | ||
| } | ||
| return; | ||
| } | ||
|
|
||
| const privyAuthToken = getPrivyAuthTokenFromRequest(request); | ||
| if (!privyAuthToken) { | ||
| return status(401, { error: "Authentication required" }); | ||
| } | ||
|
|
||
| const accountId = await getAccountIdFromPrivyAuthToken(privyAuthToken); | ||
| if (!accountId) { | ||
| return status(401, { error: "Unauthorized" }); | ||
| } | ||
| } catch (error) { | ||
| return status(401, { | ||
| error: "Authentication failed", | ||
| details: error instanceof Error ? error.message : "Unknown authentication error", | ||
| }); | ||
| } | ||
| }, | ||
| }; | ||
| }, | ||
| }); |
There was a problem hiding this comment.
Auth macro validates credentials but doesn't expose accountId to route handlers.
The macro successfully authenticates requests and retrieves accountId (lines 142, 154), but this value is never stored in the request context. Route handlers that set auth: true cannot access the authenticated user's identity, making authorization checks impossible.
Routes like youtubeChannelInfoRoute need auth.accountId to verify the user is authorized to access the requested artist_account_id.
🔧 Proposed fix to expose accountId via derive
export const authMacro = new Elysia({ name: "auth-macro" }).macro({
auth(enabled: boolean) {
if (!enabled) return;
return {
- async beforeHandle({ request, status }) {
+ async beforeHandle({ request, status, store }) {
const apiKey = request.headers.get("x-api-key");
try {
if (apiKey) {
const accountId = await getAccountIdFromApiKey(apiKey);
if (!accountId) {
return status(401, { error: "Unauthorized" });
}
+ store.auth = { accountId };
return;
}
const privyAuthToken = getPrivyAuthTokenFromRequest(request);
if (!privyAuthToken) {
return status(401, { error: "Authentication required" });
}
const accountId = await getAccountIdFromPrivyAuthToken(privyAuthToken);
if (!accountId) {
return status(401, { error: "Unauthorized" });
}
+ store.auth = { accountId };
} catch (error) {
return status(401, {
error: "Authentication failed",
details: error instanceof Error ? error.message : "Unknown authentication error",
});
}
},
};
},
});Note: The exact mechanism depends on Elysia's macro API. Using derive or state might be more appropriate than store. Verify against Elysia 1.4.28 documentation.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@lib/api/elysia/plugins/auth.ts` around lines 132 - 167, The auth macro's
beforeHandle currently authenticates and obtains accountId via
getAccountIdFromApiKey and getAccountIdFromPrivyAuthToken but never stores it on
the request context, so downstream handlers can't access the authenticated user;
update the authMacro (beforeHandle) to attach the resolved accountId into the
request context using Elysia's supported mechanism (e.g., derive or state) so
handlers can read auth.accountId, ensuring you set the value before returning
and still return 401 on missing/invalid credentials and propagate errors as
currently done.
There was a problem hiding this comment.
♻️ Duplicate comments (2)
lib/api/elysia/routes/youtube/channelInfo.ts (1)
9-19:⚠️ Potential issue | 🔴 CriticalCritical: Authorization bypass - authenticated user can access any artist's YouTube data.
The route authenticates users via
auth: truebut never verifies that the authenticated user is authorized to access the requestedartist_account_id. Any authenticated user can fetch YouTube channel info for any artist account by manipulating the query parameter.The handler should destructure
userfrom the context and verify authorization:🔐 Proposed fix to add authorization check
- async ({ query, status }) => { + async ({ query, status, user }) => { if (!query.artist_account_id) { return status(400, "Missing artist_account_id parameter"); } + if (query.artist_account_id !== user.userId) { + return status(403, "Not authorized to access this artist's YouTube data"); + } + try { const tokenValidation = await validateYouTubeTokens(query.artist_account_id);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@lib/api/elysia/routes/youtube/channelInfo.ts` around lines 9 - 19, The route handler currently only accepts ({ query, status }) and calls validateYouTubeTokens without checking that the authenticated user is allowed to access the requested artist_account_id, allowing authorization bypass; update the handler to destructure the authenticated user from the context (e.g., async ({ query, status, user })) and verify that user.id or user.artist_account_id (depending on your auth shape) matches query.artist_account_id (or that the user has admin/scoped access) before calling validateYouTubeTokens; if the check fails return status(403, "Forbidden") to prevent cross-artist access and keep validateYouTubeTokens usage (and existing responses) otherwise.lib/api/elysia/plugins/auth.ts (1)
40-41:⚠️ Potential issue | 🟡 MinorMultiple accounts with same email could cause non-deterministic account resolution.
When
getAccountDetailsByEmailsreturns multiple rows (the schema showsisOneToOne: false), the code takesaccountDetails[0]arbitrarily. If duplicate emails exist across accounts, authentication could resolve to an unintended account.Consider adding explicit handling:
🛡️ Proposed defensive check
const accountDetails = email ? await getAccountDetailsByEmails([email]) : []; + if (accountDetails.length > 1) { + console.warn(`Multiple accounts found for email, using first match`); + } const accountId = accountDetails[0]?.account_id ?? null;Alternatively, ensure at the database level that
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@lib/api/elysia/plugins/auth.ts` around lines 40 - 41, The current logic reads accountDetails = await getAccountDetailsByEmails([email]) and then picks accountDetails[0] arbitrarily, which is non-deterministic if multiple rows exist; update the auth flow to detect when accountDetails.length > 1 and handle it explicitly (e.g., log a warning/error and reject authentication or choose a deterministic row using a clear criterion such as most recently updated), or enforce uniqueness at the DB layer; specifically modify the block that computes accountId (referencing getAccountDetailsByEmails, accountDetails, accountId, and email) to perform the duplicate-check and either return a deterministic accountId or fail with a clear error.
🧹 Nitpick comments (2)
lib/api/elysia/routes/youtube/channelInfo.ts (2)
46-47: Response typet.Array(t.Any())is overly permissive.Using
t.Any()defeats the purpose of typed responses and OpenAPI documentation. Consider defining a proper schema based onYouTubeChannelData:const youtubeChannelSchema = t.Object({ title: t.String(), description: t.Optional(t.String()), thumbnails: t.Optional(t.Object({ default: t.Optional(t.Object({ url: t.String() })), })), statistics: t.Optional(t.Object({ subscriberCount: t.Optional(t.String()), })), // ... other fields });🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@lib/api/elysia/routes/youtube/channelInfo.ts` around lines 46 - 47, The route currently returns an overly permissive response type (response: { 200: t.Array(t.Any()) }); define a proper typed schema (e.g., youtubeChannelSchema) that mirrors YouTubeChannelData (fields like title, description?, thumbnails?.default?.url?, statistics?.subscriberCount?, etc.), export or inline that schema, and replace t.Array(t.Any()) with t.Array(youtubeChannelSchema) in the route response so the handler (channelInfo route) and OpenAPI docs use the specific type.
43-45: Query parameter declared asOptionalbut handler requires it.The schema declares
artist_account_idast.Optional(t.String()), but line 10-12 immediately returns 400 if it's missing. Consider making it required in the schema for clearer API documentation:♻️ Proposed schema fix
query: t.Object({ - artist_account_id: t.Optional(t.String()), + artist_account_id: t.String(), }),This will also provide automatic 400 validation from Elysia, removing the need for the manual check on lines 10-12.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@lib/api/elysia/routes/youtube/channelInfo.ts` around lines 43 - 45, The query schema currently marks artist_account_id as optional (query: t.Object({ artist_account_id: t.Optional(t.String()) })) but the route handler still manually rejects requests when artist_account_id is missing (the manual 400 check around lines 10-12); update the schema to make artist_account_id required (remove Optional so it is t.String()) so Elysia will validate and return 400 automatically, and then remove the manual presence check in the handler to avoid redundant validation.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Duplicate comments:
In `@lib/api/elysia/plugins/auth.ts`:
- Around line 40-41: The current logic reads accountDetails = await
getAccountDetailsByEmails([email]) and then picks accountDetails[0] arbitrarily,
which is non-deterministic if multiple rows exist; update the auth flow to
detect when accountDetails.length > 1 and handle it explicitly (e.g., log a
warning/error and reject authentication or choose a deterministic row using a
clear criterion such as most recently updated), or enforce uniqueness at the DB
layer; specifically modify the block that computes accountId (referencing
getAccountDetailsByEmails, accountDetails, accountId, and email) to perform the
duplicate-check and either return a deterministic accountId or fail with a clear
error.
In `@lib/api/elysia/routes/youtube/channelInfo.ts`:
- Around line 9-19: The route handler currently only accepts ({ query, status })
and calls validateYouTubeTokens without checking that the authenticated user is
allowed to access the requested artist_account_id, allowing authorization
bypass; update the handler to destructure the authenticated user from the
context (e.g., async ({ query, status, user })) and verify that user.id or
user.artist_account_id (depending on your auth shape) matches
query.artist_account_id (or that the user has admin/scoped access) before
calling validateYouTubeTokens; if the check fails return status(403,
"Forbidden") to prevent cross-artist access and keep validateYouTubeTokens usage
(and existing responses) otherwise.
---
Nitpick comments:
In `@lib/api/elysia/routes/youtube/channelInfo.ts`:
- Around line 46-47: The route currently returns an overly permissive response
type (response: { 200: t.Array(t.Any()) }); define a proper typed schema (e.g.,
youtubeChannelSchema) that mirrors YouTubeChannelData (fields like title,
description?, thumbnails?.default?.url?, statistics?.subscriberCount?, etc.),
export or inline that schema, and replace t.Array(t.Any()) with
t.Array(youtubeChannelSchema) in the route response so the handler (channelInfo
route) and OpenAPI docs use the specific type.
- Around line 43-45: The query schema currently marks artist_account_id as
optional (query: t.Object({ artist_account_id: t.Optional(t.String()) })) but
the route handler still manually rejects requests when artist_account_id is
missing (the manual 400 check around lines 10-12); update the schema to make
artist_account_id required (remove Optional so it is t.String()) so Elysia will
validate and return 400 automatically, and then remove the manual presence check
in the handler to avoid redundant validation.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 447e8d39-842d-4f8d-ac37-c4f4a2eb1653
📒 Files selected for processing (8)
components/ArtistSetting/StandaloneYoutubeComponent/ChannelInfo.tsxcomponents/YouTube/ChatInputYoutubeButtonPopover/index.tsxhooks/useYouTubeLoginSuccess.tshooks/useYoutubeChannel.tsxlib/api/elysia/app.tslib/api/elysia/plugins/auth.tslib/api/elysia/routes/youtube/channelInfo.tslib/supabase/account_api_keys/getAccountIdFromApiKey.ts
✅ Files skipped from review due to trivial changes (1)
- lib/api/elysia/app.ts
🚧 Files skipped from review as they are similar to previous changes (1)
- hooks/useYoutubeChannel.tsx
1507a6a to
8a2952f
Compare
There was a problem hiding this comment.
Actionable comments posted: 4
♻️ Duplicate comments (1)
lib/api/elysia/routes/youtube/channelInfo.ts (1)
9-12:⚠️ Potential issue | 🔴 CriticalEnforce account-level authorization before token validation.
The route authenticates the caller (
auth: true) but never checks thatquery.artist_account_idbelongs to the authenticated user. That allows authenticated access attempts against other accounts.🔧 Suggested fix
- async ({ query, status }) => { + async ({ query, status, user }) => { try { + if (!user || user.userId !== query.artist_account_id) { + return status(403, "Unauthorized"); + } + const tokenValidation = await validateYouTubeTokens(query.artist_account_id);As per coding guidelines, "Implement built-in security practices for authentication and data handling."
Also applies to: 38-41
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@lib/api/elysia/routes/youtube/channelInfo.ts` around lines 9 - 12, The route handler calls validateYouTubeTokens(query.artist_account_id) without verifying the caller owns that artist account; before calling validateYouTubeTokens, enforce account-level authorization by comparing the authenticated user's id (from the handler context/status or auth payload available as status, req, or similar) against query.artist_account_id and return a 403/unauthorized response if they differ; update the async handler to perform this ownership check (using the same status/auth object available in the function) for both the initial call and the repeated check around lines 38-41 to prevent cross-account access.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@hooks/useYoutubeChannel.tsx`:
- Around line 12-17: The hook useYoutubeChannel currently returns raw channel
data but consumers (notably useYoutubeStatus.ts) expect a response shape with a
success flag; update useYoutubeChannel to always return a unified shape like {
success: boolean, data?: ChannelType, error?: Error } instead of throwing raw
errors — on success return { success: true, data: response.data } and on failure
return { success: false, error: capturedError } (or keep throwing but then
update all consumers such as useYoutubeStatus to handle thrown errors instead);
ensure the function name useYoutubeChannel and its callers check
channelResponse.success and channelResponse.data accordingly so the contract is
consistent across the codebase.
In `@lib/api/elysia/plugins/auth.ts`:
- Around line 14-17: The code in the user construction (user = { userId: await
getAccountIdFromApiKey(apiKey), identifier: apiKey }) stores the raw API
key/token in the request context; remove the raw secret from the context and
replace it with a non-secret identifier such as authMethod and/or a derived
non-sensitive ID (e.g., tokenId, the account ID, or a hashed/truncated
fingerprint) instead; update the same pattern used around lines 25-28 so
functions like getAccountIdFromApiKey are used to populate userId while
identifier no longer holds the raw apiKey/Privy token.
In `@lib/auth/getAccountIdFromPrivyToken.ts`:
- Line 23: Replace the internal underscore method call
privyClient.users()._get(verified.user_id) with the documented public API: call
privyClient.users.get(...) using an id_token or the equivalent authenticated
parameter (or perform a GET /v1/users/{user_id} via the REST client) to fetch
the user; update getAccountIdFromPrivyToken to construct/pass the id_token or
authenticated context and use privyClient.users.get to retrieve the user record
for verified.user_id so you avoid relying on the underscore-prefixed internal
method.
- Around line 30-35: The code currently picks accountDetails[0]?.account_id
which can select an arbitrary row when multiple accounts share the same email;
update getAccountIdFromPrivyToken so it deterministically resolves the right
account by scanning the accountDetails array returned from
getAccountDetailsByEmails and selecting the single record that matches the Privy
user's identifier or primary/verified account metadata (e.g., match on privy
user id / provider_user_id or an is_primary/is_verified flag), assign its
account_id to accountId, and if no unique match is found throw an error instead
of using the first element; replace the current accountDetails[0]?.account_id
usage with this deterministic selection logic.
---
Duplicate comments:
In `@lib/api/elysia/routes/youtube/channelInfo.ts`:
- Around line 9-12: The route handler calls
validateYouTubeTokens(query.artist_account_id) without verifying the caller owns
that artist account; before calling validateYouTubeTokens, enforce account-level
authorization by comparing the authenticated user's id (from the handler
context/status or auth payload available as status, req, or similar) against
query.artist_account_id and return a 403/unauthorized response if they differ;
update the async handler to perform this ownership check (using the same
status/auth object available in the function) for both the initial call and the
repeated check around lines 38-41 to prevent cross-account access.
🪄 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: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 1dbef680-78f4-488c-a081-85058cc0a441
⛔ Files ignored due to path filters (3)
package.jsonis excluded by none and included by nonepnpm-lock.yamlis excluded by!**/pnpm-lock.yamland included by nonetypes/youtube.tsis excluded by none and included by none
📒 Files selected for processing (16)
app/api/[...slug]/route.tsapp/api/youtube/channel-info/route.tscomponents/ArtistSetting/StandaloneYoutubeComponent/ChannelInfo.tsxcomponents/YouTube/ChatInputYoutubeButtonPopover/index.tsxhooks/useYouTubeLoginSuccess.tshooks/useYoutubeChannel.tsxhooks/useYoutubeStatus.tslib/api/elysia/app.tslib/api/elysia/client.tslib/api/elysia/plugins/auth.tslib/api/elysia/routes/index.tslib/api/elysia/routes/youtube/channelInfo.tslib/api/elysia/routes/youtube/index.tslib/auth/getAccountIdFromPrivyToken.tslib/supabase/account_api_keys/getAccountIdFromApiKey.tslib/youtube/fetchYouTubeChannel.ts
💤 Files with no reviewable changes (2)
- lib/youtube/fetchYouTubeChannel.ts
- app/api/youtube/channel-info/route.ts
✅ Files skipped from review due to trivial changes (7)
- lib/api/elysia/client.ts
- lib/api/elysia/routes/youtube/index.ts
- components/ArtistSetting/StandaloneYoutubeComponent/ChannelInfo.tsx
- lib/api/elysia/routes/index.ts
- hooks/useYouTubeLoginSuccess.ts
- lib/api/elysia/app.ts
- app/api/[...slug]/route.ts
🚧 Files skipped from review as they are similar to previous changes (3)
- components/YouTube/ChatInputYoutubeButtonPopover/index.tsx
- hooks/useYoutubeStatus.ts
- lib/supabase/account_api_keys/getAccountIdFromApiKey.ts
hooks/useYoutubeChannel.tsx
Outdated
| if (response.error || !response.data) { | ||
| throw new Error("Failed to fetch YouTube channel information"); | ||
| } | ||
|
|
||
| return response.data; | ||
| }, |
There was a problem hiding this comment.
Unify the hook response contract across consumers.
This hook now returns raw channel data, but hooks/useYoutubeStatus.ts still reads channelResponse?.success. That mismatch will cause incorrect status derivation. Please normalize the hook return shape (or update all consumers to one agreed shape).
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@hooks/useYoutubeChannel.tsx` around lines 12 - 17, The hook useYoutubeChannel
currently returns raw channel data but consumers (notably useYoutubeStatus.ts)
expect a response shape with a success flag; update useYoutubeChannel to always
return a unified shape like { success: boolean, data?: ChannelType, error?:
Error } instead of throwing raw errors — on success return { success: true,
data: response.data } and on failure return { success: false, error:
capturedError } (or keep throwing but then update all consumers such as
useYoutubeStatus to handle thrown errors instead); ensure the function name
useYoutubeChannel and its callers check channelResponse.success and
channelResponse.data accordingly so the contract is consistent across the
codebase.
| user = { | ||
| userId: await getAccountIdFromApiKey(apiKey), | ||
| identifier: apiKey, | ||
| }; |
There was a problem hiding this comment.
Do not store raw API keys/tokens in request context.
identifier currently contains the raw API key or Privy token. Keep only non-secret identity fields (e.g., userId, authMethod) to reduce credential exposure risk.
🔧 Suggested fix
- let user: { userId: string; identifier: string } | null = null;
+ let user: { userId: string; authMethod: "apiKey" | "privy" } | null = null;
@@
user = {
userId: await getAccountIdFromApiKey(apiKey),
- identifier: apiKey,
+ authMethod: "apiKey",
};
@@
user = {
userId: accountId,
- identifier: privyToken,
+ authMethod: "privy",
};As per coding guidelines, "Implement built-in security practices for authentication and data handling."
Also applies to: 25-28
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@lib/api/elysia/plugins/auth.ts` around lines 14 - 17, The code in the user
construction (user = { userId: await getAccountIdFromApiKey(apiKey), identifier:
apiKey }) stores the raw API key/token in the request context; remove the raw
secret from the context and replace it with a non-secret identifier such as
authMethod and/or a derived non-sensitive ID (e.g., tokenId, the account ID, or
a hashed/truncated fingerprint) instead; update the same pattern used around
lines 25-28 so functions like getAccountIdFromApiKey are used to populate userId
while identifier no longer holds the raw apiKey/Privy token.
| } | ||
|
|
||
| const verified = await privyClient.utils().auth().verifyAccessToken(privyToken); | ||
| const privyUser = await privyClient.users()._get(verified.user_id); |
There was a problem hiding this comment.
🧩 Analysis chain
🌐 Web query:
In @privy-io/nodev0.12.0, isprivyClient.users()._get(userId) a public supported API? What is the documented public method to fetch a user by id?
💡 Result:
No, privyClient.users._get(userId) is not a public supported API in the conventional sense. The underscore prefix indicates it is likely an internal or low-level method. The documented public method to fetch a user by ID is privyClient.users.get({ id_token: '...' }) for authenticated users via identity token (recommended), or the REST API endpoint GET /v1/users/{user_id}. Multiple docs pages explicitly show and describe users._get(userId) as the way to get a user by Privy ID for unauthenticated users, but underscore naming suggests it's not "public" in the public API surface. For v0.12.0 specifically (released Mar 30, 2026), no changelog mentions changes to this method, so it remains as documented.
Citations:
- 1: https://docs.privy.io/user-management/users/managing-users/querying-users
- 2: https://docs.privy.io/api-reference/users/get
- 3: https://docs.privy.io/guide/server/users/get
- 4: https://docs.privy.io/changelogs/node
- 5: https://docs.privy.io/basics/nodeJS/quickstart
Use documented public API instead of underscore-prefixed method.
While users()._get(userId) is documented in Privy's node SDK, the underscore prefix indicates a lower-level API surface not intended for primary use. The documented public alternatives are users.get() with an id_token for authenticated access, or the REST API endpoint GET /v1/users/{user_id}. Using the public API surface reduces coupling to internal implementation details and improves maintainability.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@lib/auth/getAccountIdFromPrivyToken.ts` at line 23, Replace the internal
underscore method call privyClient.users()._get(verified.user_id) with the
documented public API: call privyClient.users.get(...) using an id_token or the
equivalent authenticated parameter (or perform a GET /v1/users/{user_id} via the
REST client) to fetch the user; update getAccountIdFromPrivyToken to
construct/pass the id_token or authenticated context and use
privyClient.users.get to retrieve the user record for verified.user_id so you
avoid relying on the underscore-prefixed internal method.
| const accountDetails = await getAccountDetailsByEmails([email]); | ||
| const accountId = accountDetails[0]?.account_id; | ||
|
|
||
| if (!accountId) { | ||
| throw new Error("No account found for Privy user email"); | ||
| } |
There was a problem hiding this comment.
Resolve account-id deterministically for authenticated email.
Using accountDetails[0]?.account_id can authenticate against an arbitrary row when multiple records share the same email. In auth code, this is an account isolation risk.
🔧 Suggested fix
- const accountDetails = await getAccountDetailsByEmails([email]);
- const accountId = accountDetails[0]?.account_id;
-
- if (!accountId) {
- throw new Error("No account found for Privy user email");
- }
-
- return accountId;
+ const accountDetails = await getAccountDetailsByEmails([email]);
+ const accountIds = [
+ ...new Set(accountDetails.map((row) => row.account_id).filter((id): id is string => Boolean(id))),
+ ];
+
+ if (accountIds.length !== 1) {
+ throw new Error("Unable to resolve a unique account for Privy user email");
+ }
+
+ return accountIds[0];As per coding guidelines, "Implement built-in security practices for authentication and data handling."
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const accountDetails = await getAccountDetailsByEmails([email]); | |
| const accountId = accountDetails[0]?.account_id; | |
| if (!accountId) { | |
| throw new Error("No account found for Privy user email"); | |
| } | |
| const accountDetails = await getAccountDetailsByEmails([email]); | |
| const accountIds = [ | |
| ...new Set(accountDetails.map((row) => row.account_id).filter((id): id is string => Boolean(id))), | |
| ]; | |
| if (accountIds.length !== 1) { | |
| throw new Error("Unable to resolve a unique account for Privy user email"); | |
| } | |
| return accountIds[0]; |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@lib/auth/getAccountIdFromPrivyToken.ts` around lines 30 - 35, The code
currently picks accountDetails[0]?.account_id which can select an arbitrary row
when multiple accounts share the same email; update getAccountIdFromPrivyToken
so it deterministically resolves the right account by scanning the
accountDetails array returned from getAccountDetailsByEmails and selecting the
single record that matches the Privy user's identifier or primary/verified
account metadata (e.g., match on privy user id / provider_user_id or an
is_primary/is_verified flag), assign its account_id to accountId, and if no
unique match is found throw an error instead of using the first element; replace
the current accountDetails[0]?.account_id usage with this deterministic
selection logic.
8a2952f to
bb42817
Compare
Summary
lib/api/elysia/routes/youtube/channelInfo.ts/api/youtube/channel-infoto Elysia while keeping Next route as thin adapterapi.youtube.channelInfolib/youtube/fetchYouTubeChannel.tshelper/api/openapiand/api/openapi/jsontokenStatusdependencyNotes
credentials: includefor cookie auth.pnpm exec tsc --noEmitstill shows pre-existing baseline errors inlib/emails/__tests__/extractSendEmailResults.test.tsunrelated to this PR.Summary by CodeRabbit
New Features
Refactor