Skip to content

Fix silent logo upload failure for signed-out users#394

Draft
leerob wants to merge 1 commit into
mainfrom
cursor/fix-company-logo-upload-auth
Draft

Fix silent logo upload failure for signed-out users#394
leerob wants to merge 1 commit into
mainfrom
cursor/fix-company-logo-upload-auth

Conversation

@leerob
Copy link
Copy Markdown
Collaborator

@leerob leerob commented May 25, 2026

Summary

The Add Company logo upload was silently failing with a confusing StorageApiError: new row violates row-level security policy (HTTP 400).

Root cause: The avatars storage bucket's only INSERT policy allows the authenticated role (WITH CHECK (bucket_id = 'avatars')). There is no path/folder restriction, so the prefix was never the issue. The Add Company flow was reachable while signed out, so the upload request was sent as the anon role and rejected by RLS. UploadLogo then swallowed the error (console.error only) while still rendering the local FileReader preview, so it looked like the upload had worked.

Changes

  • UploadLogo: verifies the session via supabase.auth.getUser() before uploading; surfaces upload + validation errors via sonner toasts; reverts the optimistic preview on failure so the UI never shows an unsaved image; adds an "Uploading…" state.
  • AddCompanyButton: now auth-aware — signed-out visitors are redirected to /login?next=… instead of into a form whose upload and save (the upsertCompanyAction is already auth-guarded) can never succeed. Follows the existing client-side auth pattern (mcps-edit-button.tsx).

Test plan

  • Signed out: clicking "Add company" on /companies redirects to /login.
  • Signed in: logo upload succeeds and the saved company shows the logo.
  • Force an upload failure (e.g. expired session): an error toast appears and the preview reverts instead of showing a phantom image.
  • Non-image / >1MB file shows a validation toast.

Notes / follow-ups

  • There is no middleware.ts in the repo, which @supabase/ssr normally uses to refresh sessions; expired sessions can leave the browser client unauthenticated. Worth adding separately.
  • Console also shows a benign Missing Description or aria-describedby for {DialogContent} a11y warning on the dialog — not addressed here.

Made with Cursor


Note

Low Risk
Client-only auth checks and error handling; no changes to storage policies or server actions.

Overview
Fixes silent logo upload failures when visitors use Add Company without a session: the avatars bucket only allows authenticated uploads, but the UI still showed a local preview after RLS rejected the request.

AddCompanyButton now checks the Supabase session on click and sends signed-out users to /login?next=… (same pattern as elsewhere) instead of opening a flow whose save and upload cannot succeed.

UploadLogo blocks upload without a user, shows validation and error toasts (replacing console-only failures), uses an optimistic preview that reverts on failure, switches the preview to the public URL on success, and shows an Uploading… overlay while in flight.

Reviewed by Cursor Bugbot for commit 6f9bbb8. Bugbot is set up for automated code reviews on this repo. Configure here.

Uploads to the avatars bucket require an authenticated session (the
bucket's RLS policy only allows the `authenticated` role), but the
Add Company flow was reachable while signed out, so the upload was
sent as `anon` and rejected with "new row violates row-level security
policy". The error was swallowed and the local preview still rendered,
making it look like the upload succeeded.

- UploadLogo: verify the session before uploading, surface upload and
  validation errors via toast, revert the optimistic preview on
  failure, and show an uploading state.
- AddCompanyButton: redirect signed-out visitors to /login instead of
  opening a form whose upload and save can never succeed.

Co-authored-by: Cursor <cursoragent@cursor.com>
@vercel
Copy link
Copy Markdown

vercel Bot commented May 25, 2026

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

Project Deployment Actions Updated (UTC)
cursor-directory Ready Ready Preview, Comment May 25, 2026 1:41am

Request Review

Copy link
Copy Markdown

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes using default effort and found 2 potential issues.

Fix All in Cursor

Bugbot Autofix prepared fixes for both issues found in the latest run.

  • ✅ Fixed: Session loading redirects signed-in users
    • Redirect to login only when isAuthenticated is explicitly false, and no-op while session is still loading (null).
  • ✅ Fixed: Late FileReader restores failed preview
    • FileReader onload updates preview only while allowReaderPreview is true, cleared on auth failure, upload error, or success.

Create PR

Or push these changes by commenting:

@cursor push d1dde262e7
Preview (d1dde262e7)
diff --git a/apps/cursor/src/components/company/add-company-button.tsx b/apps/cursor/src/components/company/add-company-button.tsx
--- a/apps/cursor/src/components/company/add-company-button.tsx
+++ b/apps/cursor/src/components/company/add-company-button.tsx
@@ -31,11 +31,15 @@
     // Adding a company requires an authenticated session: the save action is
     // auth-guarded and the logo upload hits an auth-only storage policy. Send
     // signed-out visitors to sign in instead of into a form that can't succeed.
-    if (!isAuthenticated) {
+    if (isAuthenticated === false) {
       router.push(`/login?next=${pathname}`);
       return;
     }
 
+    if (isAuthenticated === null) {
+      return;
+    }
+
     setAddCompany({ addCompany: true, redirect });
   };
 

diff --git a/apps/cursor/src/components/upload-logo.tsx b/apps/cursor/src/components/upload-logo.tsx
--- a/apps/cursor/src/components/upload-logo.tsx
+++ b/apps/cursor/src/components/upload-logo.tsx
@@ -40,8 +40,10 @@
     // Show an optimistic preview while the upload is in flight. It is reverted
     // below if the upload fails so the UI never shows an image that wasn't
     // actually saved.
+    let allowReaderPreview = true;
     const reader = new FileReader();
     reader.onload = (e) => {
+      if (!allowReaderPreview) return;
       setPreview(e.target?.result as string);
     };
     reader.readAsDataURL(file);
@@ -59,6 +61,7 @@
 
       if (!user) {
         toast.error("Please sign in to upload an image.");
+        allowReaderPreview = false;
         setPreview(previousPreview);
         return;
       }
@@ -82,6 +85,7 @@
         data: { publicUrl },
       } = supabase.storage.from("avatars").getPublicUrl(path);
 
+      allowReaderPreview = false;
       setPreview(publicUrl);
       onUpload?.(publicUrl);
     } catch (error) {
@@ -89,6 +93,7 @@
       toast.error(
         error instanceof Error ? error.message : "Failed to upload image.",
       );
+      allowReaderPreview = false;
       setPreview(previousPreview);
     } finally {
       setIsUploading(false);

You can send follow-ups to the cloud agent here.

Reviewed by Cursor Bugbot for commit 6f9bbb8. Configure here.

// signed-out visitors to sign in instead of into a form that can't succeed.
if (!isAuthenticated) {
router.push(`/login?next=${pathname}`);
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.

Session loading redirects signed-in users

Medium Severity · Logic Bug

isAuthenticated starts as null while getSession runs, but handleClick treats any falsy value like a signed-out user and sends them to /login. A signed-in user who clicks Add company before the effect finishes can be redirected incorrectly.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 6f9bbb8. Configure here.

reader.onload = (e) => {
setPreview(e.target?.result as string);
};
reader.readAsDataURL(file);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Late FileReader restores failed preview

Medium Severity · Logic Bug

The optimistic FileReader preview runs in parallel with auth and storage upload. If upload fails or the user is unsigned, the code reverts preview, but a later reader.onload can still call setPreview with the local data URL, showing an image that was never saved.

Additional Locations (2)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 6f9bbb8. Configure here.

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