RLS context issue.
publishSession: {
code: '42501',
details: null,
hint: null,
message: 'new row violates row-level security policy for table "sessions"'
}
POST /api/sessions/publish 500 1203.073 ms - 37
publishSession: {
code: '42501',
details: null,
hint: null,
message: 'new row violates row-level security policy for table "sessions"'
}
POST /api/sessions/publish 500 1203.073 ms - 37
8 Replies
const jwt = require('jsonwebtoken')
const auth = (req, res, next) => {
try {
const h = req.get('authorization') || req.get('Authorization')
if (!h || !h.startsWith('Bearer ')) {
return res.status(401).json({ error: 'Token missing' })
}
const token = h.split(' ')[1]
const decoded = jwt.verify(token, process.env.SUPABASE_JWT_SECRET)
req.user = {
id: decoded.sub,
_id: decoded.sub,
email: decoded.email || null
}
next()
} catch (err) {
if (err.name === 'JsonWebTokenError') {
return res.status(401).json({ error: 'Invalid token' })
}
if (err.name === 'TokenExpiredError') {
return res.status(401).json({ error: 'Expired token' })
}
console.error('Auth error:', err)
res.status(500).json({ error: 'Authentication error' })
}
}
module.exports = auth
const jwt = require('jsonwebtoken')
const auth = (req, res, next) => {
try {
const h = req.get('authorization') || req.get('Authorization')
if (!h || !h.startsWith('Bearer ')) {
return res.status(401).json({ error: 'Token missing' })
}
const token = h.split(' ')[1]
const decoded = jwt.verify(token, process.env.SUPABASE_JWT_SECRET)
req.user = {
id: decoded.sub,
_id: decoded.sub,
email: decoded.email || null
}
next()
} catch (err) {
if (err.name === 'JsonWebTokenError') {
return res.status(401).json({ error: 'Invalid token' })
}
if (err.name === 'TokenExpiredError') {
return res.status(401).json({ error: 'Expired token' })
}
console.error('Auth error:', err)
res.status(500).json({ error: 'Authentication error' })
}
}
module.exports = auth
const supabase = createClient(process.env.SUPABASE_URL, process.env.SUPABASE_ANON_KEY, {
auth: { autoRefreshToken: false,
persistSession: false,
detectSessionInUrl: false }
})
const supabase = createClient(process.env.SUPABASE_URL, process.env.SUPABASE_ANON_KEY, {
auth: { autoRefreshToken: false,
persistSession: false,
detectSessionInUrl: false }
})
exports.publishSession = async (req, res) => {
try {
const userId = req.user._id
const {
id,
title,
tags = [],
video_url = null,
description = '',
difficulty = 'Beginner'
} = req.body
if (!title || typeof title !== 'string') {
return res.status(400).json({ error: 'Title is required' })
}
const payload = {
user_id: userId,
title,
tags,
video_url,
description,
difficulty,
status: 'published'
}
let query
if (id) {
query = supabase
.from('sessions')
.update(payload)
.eq('id', id)
.eq('user_id', userId)
.select()
.single()
} else {
query = supabase
.from('sessions')
.insert(payload)
.select()
.single()
}
const { data: session, error } = await query
if (error) throw error
const profiles = await fetchProfilesForSessions([session])
res.status(id ? 200 : 201).json({ data: { session: mapRow(session, profiles) } })
} catch (err) {
console.error('publishSession:', err)
res.status(500).json({ error: 'Failed to publish session' })
}
}
exports.publishSession = async (req, res) => {
try {
const userId = req.user._id
const {
id,
title,
tags = [],
video_url = null,
description = '',
difficulty = 'Beginner'
} = req.body
if (!title || typeof title !== 'string') {
return res.status(400).json({ error: 'Title is required' })
}
const payload = {
user_id: userId,
title,
tags,
video_url,
description,
difficulty,
status: 'published'
}
let query
if (id) {
query = supabase
.from('sessions')
.update(payload)
.eq('id', id)
.eq('user_id', userId)
.select()
.single()
} else {
query = supabase
.from('sessions')
.insert(payload)
.select()
.single()
}
const { data: session, error } = await query
if (error) throw error
const profiles = await fetchProfilesForSessions([session])
res.status(id ? 200 : 201).json({ data: { session: mapRow(session, profiles) } })
} catch (err) {
console.error('publishSession:', err)
res.status(500).json({ error: 'Failed to publish session' })
}
}
create table if not exists public.profiles (
id uuid primary key references auth.users(id) on delete cascade,
email text unique
);
-- Sessions table
create table if not exists public.sessions (
id uuid primary key default gen_random_uuid(),
user_id uuid not null references auth.users(id) on delete cascade,
title text not null,
tags text[] not null default '{}',
video_url text,
description text default '',
difficulty text not null default 'Beginner' check (difficulty in ('Beginner','Intermediate','Advanced')),
status text not null default 'draft' check (status in ('draft','published')),
created_at timestamptz not null default now(),
updated_at timestamptz not null default now()
);
-- RLS
alter table public.sessions enable row level security;
-- Read published sessions by anyone
drop policy if exists "read published sessions" on public.sessions;
create policy "read published sessions" on public.sessions
for select using (status = 'published');
-- Owners can do everything on their own rows
drop policy if exists "owner full access" on public.sessions;
create policy "owner full access" on public.sessions
for all
using (auth.uid() = user_id)
with check (auth.uid() = user_id);
create table if not exists public.profiles (
id uuid primary key references auth.users(id) on delete cascade,
email text unique
);
-- Sessions table
create table if not exists public.sessions (
id uuid primary key default gen_random_uuid(),
user_id uuid not null references auth.users(id) on delete cascade,
title text not null,
tags text[] not null default '{}',
video_url text,
description text default '',
difficulty text not null default 'Beginner' check (difficulty in ('Beginner','Intermediate','Advanced')),
status text not null default 'draft' check (status in ('draft','published')),
created_at timestamptz not null default now(),
updated_at timestamptz not null default now()
);
-- RLS
alter table public.sessions enable row level security;
-- Read published sessions by anyone
drop policy if exists "read published sessions" on public.sessions;
create policy "read published sessions" on public.sessions
for select using (status = 'published');
-- Owners can do everything on their own rows
drop policy if exists "owner full access" on public.sessions;
create policy "owner full access" on public.sessions
for all
using (auth.uid() = user_id)
with check (auth.uid() = user_id);
Are you signed-in when you query Supabase? I suspect the RLS policies apply to anon/public role (it is missing a
to <role>
clause), but the query might be performed using the authenticated
role.What supabase call is getting the RLS error? You do both an insert and an update.
Check the API Gateway log for your failing call and see what role the user is making the call.
code fall backs to insert in my case which is ok
but even when the user is authenticated the insert sent is anon


If the dashboard log shows anon then your code is not getting a user session or the authenticated header set at the time of your call. So not an RLS issue.
am i stupid to do this in my node backend

im signing in frontend
both different client