Skip to content

feat: enhance memory retrieval API with authentication and room valid#1617

Open
pradipthaadhi wants to merge 1 commit intorecoupable:mainfrom
pradipthaadhi:yuliusupwork/myc-4513-fix-unauthorized-access-in-get-apimemoriesget-enforce-room
Open

feat: enhance memory retrieval API with authentication and room valid#1617
pradipthaadhi wants to merge 1 commit intorecoupable:mainfrom
pradipthaadhi:yuliusupwork/myc-4513-fix-unauthorized-access-in-get-apimemoriesget-enforce-room

Conversation

@pradipthaadhi
Copy link
Copy Markdown
Collaborator

@pradipthaadhi pradipthaadhi commented Mar 31, 2026

  • Added header validation and room ownership checks to the GET /api/memories/get endpoint.
  • Implemented error handling for missing room ID, unauthorized access, room not found, and forbidden access.
  • Created unit tests for various scenarios including authentication, room existence, and memory retrieval success.
  • Updated useMessageLoader and related hooks to include access token for fetching messages.

Summary by CodeRabbit

  • Security
    • Message retrieval API now validates authentication headers
    • Access control checks enforced for room-level authorization
    • Message loading now requires authentication tokens

…ation

- Added header validation and room ownership checks to the GET /api/memories/get endpoint.
- Implemented error handling for missing room ID, unauthorized access, room not found, and forbidden access.
- Created unit tests for various scenarios including authentication, room existence, and memory retrieval success.
- Updated useMessageLoader and related hooks to include access token for fetching messages.
@vercel
Copy link
Copy Markdown
Contributor

vercel bot commented Mar 31, 2026

@pradipthaadhi is attempting to deploy a commit to the Recoupable Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 31, 2026

📝 Walkthrough

Walkthrough

This PR adds authentication and authorization checks to the memory retrieval API endpoint and updates the client-side message loading flow to propagate access tokens, ensuring requests validate headers, confirm room ownership, and enforce account-based access control.

Changes

Cohort / File(s) Summary
API Endpoint Security
app/api/memories/get/route.ts
Added request header validation with early return on failure, 401 Unauthorized when accountId is missing, room existence check returning 404, and access control verification comparing room.account_id against authResult.accountId, returning 403 Forbidden on mismatch.
Hook Parameter Propagation
hooks/useMessageLoader.ts, hooks/useVercelChat.ts
Updated useMessageLoader to accept accessToken parameter and included in dependency array; added early bailout when token is missing; updated useVercelChat to pass accessToken to the loader.
Client API Integration
lib/supabase/getClientMessages.tsx
Updated getClientMessages signature to accept accessToken parameter and include Authorization: Bearer ${accessToken} header in fetch request to /api/memories/get.

Sequence Diagram

sequenceDiagram
    participant Client as Client App
    participant Loader as useMessageLoader Hook
    participant API as /api/memories/get
    participant DB as Database/Room Service

    Client->>Loader: useVercelChat passes accessToken
    Loader->>Loader: Check accessToken exists
    alt Missing accessToken
        Loader->>Loader: Set isLoading=true, bail early
    else accessToken Present
        Loader->>API: getClientMessages(roomId, accessToken)
        API->>API: validateHeaders(req)
        alt Header Validation Fails
            API-->>Loader: Return Response (early exit)
        else Validation Passes
            API->>API: Extract accountId from headers
            alt No accountId
                API-->>Loader: 401 Unauthorized
            else accountId Present
                API->>DB: getRoom(roomId)
                alt Room Not Found
                    API-->>Loader: 404 Room not found
                else Room Exists
                    API->>API: Compare room.account_id vs authResult.accountId
                    alt Access Denied
                        API-->>Loader: 403 Forbidden
                    else Access Granted
                        API->>DB: Query memories
                        API-->>Loader: Return memory data
                        Loader->>Client: Update messages state
                    end
                end
            end
        end
    end
Loading

Estimated Code Review Effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Suggested Reviewers

  • sweetmantech

Poem

🔐 Headers validate, tokens flow through,
Authorization checks what users can do,
Room by room, account by account—
Security measures now properly mount,
Access control in every route. ✨

🚥 Pre-merge checks | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Solid & Clean Code ⚠️ Warning PR violates SOLID principles: inconsistent error response shapes, missing input validation/encoding, poor error state handling, and SRP violations with overloaded route handler. Implement centralized error handler for consistent responses, extract authorization/validation to middleware, add URL encoding utility, and fix loading state logic in useMessageLoader hook.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f84db0a94d

ℹ️ 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".

Comment on lines +33 to +35
if (!accessToken) {
setIsLoading(true);
return;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Stop spinner when access token cannot be retrieved

This branch keeps isLoading stuck at true whenever accessToken is falsy, but never sets an error or terminal state. If useAccessToken() returns null (e.g., token fetch fails or user session is partially initialized), the chat page remains on the loading skeleton indefinitely with no recovery path. This became user-visible after making message loading depend on the token.

Useful? React with 👍 / 👎.

Comment on lines +21 to +23
const room = await getRoom(roomId);
if (!room) {
return Response.json({ error: "Room not found" }, { status: 404 });
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Don’t map room lookup failures to 404

The handler treats any falsy getRoom result as "Room not found", but getRoom returns null for all Supabase errors as well as missing rows. That means transient DB/query failures are now reported as 404 instead of a server error, which can mislead clients and hide operational issues.

Useful? React with 👍 / 👎.

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
lib/supabase/getClientMessages.tsx (1)

8-11: ⚠️ Potential issue | 🟠 Major

Handle non-2xx responses explicitly instead of treating them as empty history.

Right now 401/403/500 can silently resolve to [], which hides real failures and makes auth issues look like “no messages”.

Proposed fix
-    const data = await response.json();
+    if (!response.ok) {
+      const err = await response.json().catch(() => null);
+      throw new Error(
+        err?.error ?? `Failed to load messages (${response.status})`,
+      );
+    }
+    const data = await response.json();
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/supabase/getClientMessages.tsx` around lines 8 - 11, The code currently
treats any non-OK HTTP response as an empty history by immediately reading
response.json() and using data?.data || [], so update getClientMessages (the
code handling response, response.json(), and the memories variable) to
explicitly check response.ok (or response.status) before parsing; if
!response.ok, read the response body for details and throw or return a clear
error (including response.status and body) instead of returning []; this ensures
401/403/500 are surfaced rather than silently becoming an empty memories array.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@app/api/memories/get/route.ts`:
- Around line 18-27: The route returns errors with two different shapes ({
error: ... } in early exits vs { message: ... } in the catch), so change the
catch-path response to use the same error key and shape as the rest of the
handler; locate the handler in app/api/memories/get/route.ts (references:
getRoom, authResult, Response.json) and update the catch block(s) that return {
message: ... } to return { error: "<same content>" } with the appropriate HTTP
status, and ensure any other error-returning lines (including the one around
line 40) follow this unified { error: ... } format.

In `@hooks/useMessageLoader.ts`:
- Around line 33-36: The hook useMessageLoader sets setIsLoading(true) and
immediately returns when accessToken is falsy, which can leave the component
permanently loading; update the early-accessToken branch in useMessageLoader to
clear the loading state and handle the unauthenticated case (e.g., call
setIsLoading(false) before return and optionally set an error or empty messages
via setHasError/setMessages) so the hook doesn't remain stuck when accessToken
is missing.

In `@lib/supabase/getClientMessages.tsx`:
- Line 3: The request URL uses chatId directly which can break for non-UUID IDs;
update the fetch call that constructs `/api/memories/get?roomId=${chatId}` (in
getClientMessages / the function performing this fetch) to URL-encode the chatId
using encodeURIComponent(chatId) when composing the query string so the
resulting request is always well-formed.

---

Outside diff comments:
In `@lib/supabase/getClientMessages.tsx`:
- Around line 8-11: The code currently treats any non-OK HTTP response as an
empty history by immediately reading response.json() and using data?.data || [],
so update getClientMessages (the code handling response, response.json(), and
the memories variable) to explicitly check response.ok (or response.status)
before parsing; if !response.ok, read the response body for details and throw or
return a clear error (including response.status and body) instead of returning
[]; this ensures 401/403/500 are surfaced rather than silently becoming an empty
memories array.
🪄 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: 7a7c9968-9fd4-4b0a-b35c-7b080a0dece8

📥 Commits

Reviewing files that changed from the base of the PR and between 8d0e33a and f84db0a.

⛔ Files ignored due to path filters (1)
  • app/api/memories/get/__tests__/route.test.ts is excluded by !**/*.test.* and included by app/**
📒 Files selected for processing (4)
  • app/api/memories/get/route.ts
  • hooks/useMessageLoader.ts
  • hooks/useVercelChat.ts
  • lib/supabase/getClientMessages.tsx

Comment on lines +18 to +27
return Response.json({ error: "Unauthorized" }, { status: 401 });
}

const room = await getRoom(roomId);
if (!room) {
return Response.json({ error: "Room not found" }, { status: 404 });
}
if (room.account_id !== authResult.accountId) {
return Response.json({ error: "Forbidden" }, { status: 403 });
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Unify error response shape across this route.

Early exits return { error: ... }, but the catch path returns { message: ... }. This creates an inconsistent API contract for clients handling failures.

Proposed fix
 export async function GET(req: NextRequest) {
+  const jsonError = (status: number, error: string) =>
+    Response.json({ error }, { status });
+
   const roomId = req.nextUrl.searchParams.get("roomId");

   if (!roomId) {
-    return Response.json({ error: "Room ID is required" }, { status: 400 });
+    return jsonError(400, "Room ID is required");
   }
@@
   if (!authResult.accountId) {
-    return Response.json({ error: "Unauthorized" }, { status: 401 });
+    return jsonError(401, "Unauthorized");
   }
@@
   if (!room) {
-    return Response.json({ error: "Room not found" }, { status: 404 });
+    return jsonError(404, "Room not found");
   }
   if (room.account_id !== authResult.accountId) {
-    return Response.json({ error: "Forbidden" }, { status: 403 });
+    return jsonError(403, "Forbidden");
   }
@@
   } catch (error) {
     console.error("[api/memories/get] Error:", error);
     const message = error instanceof Error ? error.message : "failed";
-    return Response.json({ message }, { status: 400 });
+    return jsonError(400, message);
   }
 }

As per coding guidelines, API routes must “Return consistent response formats”.

Also applies to: 40-40

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

In `@app/api/memories/get/route.ts` around lines 18 - 27, The route returns errors
with two different shapes ({ error: ... } in early exits vs { message: ... } in
the catch), so change the catch-path response to use the same error key and
shape as the rest of the handler; locate the handler in
app/api/memories/get/route.ts (references: getRoom, authResult, Response.json)
and update the catch block(s) that return { message: ... } to return { error:
"<same content>" } with the appropriate HTTP status, and ensure any other
error-returning lines (including the one around line 40) follow this unified {
error: ... } format.

Comment on lines +33 to +36
if (!accessToken) {
setIsLoading(true);
return;
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Avoid permanent loading state when accessToken is missing.

This branch sets loading to true and exits, so if token retrieval fails or user stays unauthenticated, loading can remain stuck.

Proposed fix
     if (!accessToken) {
-      setIsLoading(true);
+      setIsLoading(false);
+      setError(new Error("Authentication required to load messages"));
       return;
     }

As per coding guidelines, hooks should “Handle edge cases and errors”.

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

In `@hooks/useMessageLoader.ts` around lines 33 - 36, The hook useMessageLoader
sets setIsLoading(true) and immediately returns when accessToken is falsy, which
can leave the component permanently loading; update the early-accessToken branch
in useMessageLoader to clear the loading state and handle the unauthenticated
case (e.g., call setIsLoading(false) before return and optionally set an error
or empty messages via setHasError/setMessages) so the hook doesn't remain stuck
when accessToken is missing.

const getClientMessages = async (chatId: string, accessToken: string) => {
try {
const response = await fetch(`/api/memories/get?roomId=${chatId}`);
const response = await fetch(`/api/memories/get?roomId=${chatId}`, {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Encode chatId before composing the query string.

chatId should be URL-encoded to avoid malformed requests for non-UUID IDs.

Proposed fix
-    const response = await fetch(`/api/memories/get?roomId=${chatId}`, {
+    const response = await fetch(
+      `/api/memories/get?roomId=${encodeURIComponent(chatId)}`,
+      {
       headers: {
         Authorization: `Bearer ${accessToken}`,
       },
-    });
+      },
+    );
📝 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.

Suggested change
const response = await fetch(`/api/memories/get?roomId=${chatId}`, {
const response = await fetch(
`/api/memories/get?roomId=${encodeURIComponent(chatId)}`,
{
headers: {
Authorization: `Bearer ${accessToken}`,
},
},
);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/supabase/getClientMessages.tsx` at line 3, The request URL uses chatId
directly which can break for non-UUID IDs; update the fetch call that constructs
`/api/memories/get?roomId=${chatId}` (in getClientMessages / the function
performing this fetch) to URL-encode the chatId using encodeURIComponent(chatId)
when composing the query string so the resulting request is always well-formed.

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