0

If I try to insert a new row into the table, I get an error: "New row violates row-level security policy for table".

I have a table "user_profiles" that have a foreign key "user_id" to auth table "auth.users.id"

enter image description here

I am using "supabase.auth.onAuthStateChange" hook where I want to add new row when user logs in.

enter image description here

In policies section, I have RLS enabled, but I didn't add any policies.

enter image description here

Then, I tried to add a policy from template that only authenticated users can add a row, but it didn't change anything.

enter image description here

Then, I came across this SO question and and tried to add a policy from template that only authenticated users can select, but I am still getting the same error.

enter image description here enter image description here

I implemented authentication in chrome browser extension in background script:

supabase.ts

import { createClient } from '@supabase/supabase-js'
import qs from 'qs'

const supaUrl = import.meta.env.VITE_SUPABASE_URL
const supaANONKey = import.meta.env.VITE_SUPABASE_ANON_KEY
export const supabase = createClient(supaUrl, supaANONKey, {
  global: {
    fetch: (...args) => fetch(...args),
  },
})

export const extensionSupabaseLogin = async () => {
  const redirectUri = chrome.identity.getRedirectURL('supabase-auth')
  const options = {
    provider: 'notion',
    redirect_to: redirectUri,
    ux_mode: 'redirect',
  }
  const url = `${supaUrl}/auth/v1/authorize?${qs.stringify(options)}`

  const originalTab = (await chrome.tabs.query({ active: true, currentWindow: true }))[0]
  const authenticationTab = await chrome.tabs.create({ url: 'about:blank' })
  chrome.tabs.onUpdated.addListener(function notionAuthHook(tabId, _changeInfo, tab) {
    if (tabId === authenticationTab.id && tab.url?.startsWith(redirectUri)) {
      const splitUrl = tab.url?.split('#')[1]
      // Prompt user to sign in again if unparsable (error)
      if (!splitUrl) {
        console.error('Unparsable sign-in URL', tab.url)
        return
      }
      // Parse tokens from URL
      const authResult = qs.parse(splitUrl)
      const refreshToken = authResult.refresh_token as string
      const accessToken = authResult.access_token as string

      // Prompt user to sign in again if no access_token (error)
      if (!refreshToken || !accessToken) {
        console.error('No access token or refresh token returned', authResult)
        return
      }

      ;(async () => {
        const { error } = await supabase.auth.setSession({
          access_token: accessToken,
          refresh_token: refreshToken,
        })
        if (error) console.error(error)
      })()

      chrome.tabs.onUpdated.removeListener(notionAuthHook)
      chrome.tabs.remove(tabId)
      if (originalTab?.id) {
        chrome.tabs.update(originalTab.id, { active: true })
      }
    }
  })
  // Send users to the sign-in flow
  if (authenticationTab.id) {
    await chrome.tabs.update(authenticationTab.id, { url: url })
  }
}

background.ts

import { Client } from '@notionhq/client'
import { extensionSupabaseLogin, supabase } from './supabase'

chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
  if (request.action === 'signIn') {
    extensionSupabaseLogin();
  } else if (request.type === 'GET_NOTION_PAGE') {
     const { accessToken, ...options } = request.options as GetPageNotionOptions
     debugger
     if (!notionClient) {
       setNotionClient(accessToken)
     }
     if (notionClient) {
       const response = await notionClient.pages.retrieve({ ...options })
       sendResponse({ type: 'SUCCESS', data: JSON.stringify(response) })
     }
  }
});

let notionClient: Client | null = null

const setNotionClient = (notionCode: string) => {
   notionClient = new Client({
      auth: notionCode,
   })
}

const storeNotionIdentity = async (notionIdentity: { accessToken: string; refreshToken?: string }): Promise<void> => {
  setNotionClient(notionIdentity.accessToken)
  await chrome.storage.local.set({
    notionIdentity,
  })
}

const storeNotionUser = async (user: User): Promise<void> => {
  await chrome.storage.local.set({
    user,
  })
}

const setupAuthListener = () => {
    supabase.auth.onAuthStateChange(async (event, session) => {
      if (event === 'SIGNED_IN' || event === 'USER_UPDATED') {
        //this user has just signed in or their session has been updated
        console.log('user is signed in :', session)
        if (session?.access_token && session?.refresh_token) {
          debugger
          await storeNotionIdentity({ accessToken: session.access_token, refreshToken: session.refresh_token })
        }
        if (session?.user) {
          await storeNotionUser({
            name: session.user.user_metadata?.name,
            fullName: session.user.user_metadata?.full_name,
            email: session.user?.email ?? session.user.user_metadata?.email,
            userId: session.user.id,
          })
        }
        if (typeof loginTabId === 'number') {
          chrome.tabs.sendMessage(loginTabId, `notion login success`)
        }
      } else if (event === 'SIGNED_OUT') {
        //the user has just signed out or their account has been deleetd
        console.log('user is signed out')
        loginTabId = null
        await chrome.storage.local.remove(['notionIdentity', 'user'])
      }
    })
  }
  
  setupAuthListener()
Matt
  • 8,195
  • 31
  • 115
  • 225
  • 1
    Did you try [testing your RLS policies](https://blog.mansueli.com/using-custom-claims-testing-rls-with-supabase) directly in SQL? – Mansueli Apr 26 '23 at 13:53
  • @Mansueli I am using only Notion social login. Can I use social login in SQL? – Matt Apr 26 '23 at 14:23
  • 1
    Yes. Even if you are using OAuth, you can simulate the access as the user in PSQL. Then you'll be able to try inserting/ selecting data to check if the policies are working. – Mansueli Apr 26 '23 at 14:28
  • @Mansueli thanks, I checked your blog post. How can I be sure that procedures are actually stored? If I run [this snippet](https://blog.mansueli.com/using-custom-claims-testing-rls-with-supabase#heading-helper-sql-procedures-setting-it-up) snippet in sql editor, I get a response "Success. No rows returned". Then, I run another query: `call auth.login_as_user('test@test.com');` and I got the same response: "Success. No rows returned" – Matt Apr 26 '23 at 14:43
  • 1
    Maybe I need to be more clear in the blog. But you'll need to use PSQL. You can install PSQL in [macOS](https://stackoverflow.com/a/49689589/2188186) and [Windows](https://www.postgresql.org/download/windows/) following these links and instructions there. for Linux (debian): `sudo apt-get updatesudo apt-get install postgresql-client` Then you can run this command to connect with the database: `psql -U postgres -h db..supabase.co -d postgres -p 6543` – Mansueli Apr 26 '23 at 14:49
  • @Mansueli Thanks, I tried that out and RLS policies works directly through SQL. – Matt Apr 26 '23 at 15:58
  • Please update the question with the full setupAuthListener() method and whether or not you are trying to use [Auth-Helpers](https://github.com/supabase/auth-helpers). But now we isolated that the issue wasn't in RLS. – Mansueli Apr 26 '23 at 16:24
  • @Mansueli I updated my question with implementation. I don't use auth-helpers. – Matt Apr 26 '23 at 16:33
  • @Mansueli sorry, but I checked it again and it seems like RLS doesn't work if I try it through SQL, I was wrong. If I logged out `postgres=> call auth.logout();` and then I try to insert a row, it works. It shouldn't because I am logged out – Matt Apr 26 '23 at 17:20

1 Answers1

0

It is recommended to create a trigger for inserting a row in the user_profiles table upon a new signup. https://supabase.com/docs/guides/auth/managing-user-data#advanced-techniques

Here is an sample:

-- inserts a row into public.profiles
create function public.handle_new_user()
returns trigger
language plpgsql
security definer set search_path = public
as $$
begin
  insert into public. user_profiles(id)
  values (new.id);
  return new;
end;
$$;

-- trigger the function every time a user is created
create trigger on_auth_user_created
  after insert on auth.users
  for each row execute procedure public.handle_new_user();
dshukertjr
  • 15,244
  • 11
  • 57
  • 94