RBAC seems to be working

This commit is contained in:
Alireza Vaezi
2025-07-20 11:05:58 -04:00
parent 033229989a
commit 6a9ab6afaa
12 changed files with 987 additions and 25 deletions

View File

@@ -0,0 +1,54 @@
-- RBAC Schema Migration
-- Creates the foundational tables for Role-Based Access Control
-- Enable necessary extensions
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
-- Create roles table
CREATE TABLE IF NOT EXISTS public.roles (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
name TEXT UNIQUE NOT NULL CHECK (name IN ('admin', 'conductor', 'analyst')),
description TEXT NOT NULL,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
-- Create user_profiles table to extend auth.users
CREATE TABLE IF NOT EXISTS public.user_profiles (
id UUID PRIMARY KEY REFERENCES auth.users(id) ON DELETE CASCADE,
email TEXT NOT NULL,
role_id UUID NOT NULL REFERENCES public.roles(id),
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
-- Create indexes for better performance
CREATE INDEX IF NOT EXISTS idx_user_profiles_role_id ON public.user_profiles(role_id);
CREATE INDEX IF NOT EXISTS idx_user_profiles_email ON public.user_profiles(email);
-- Create updated_at trigger function
CREATE OR REPLACE FUNCTION public.handle_updated_at()
RETURNS TRIGGER AS $$
BEGIN
NEW.updated_at = NOW();
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
-- Create triggers for updated_at
CREATE TRIGGER set_updated_at_roles
BEFORE UPDATE ON public.roles
FOR EACH ROW
EXECUTE FUNCTION public.handle_updated_at();
CREATE TRIGGER set_updated_at_user_profiles
BEFORE UPDATE ON public.user_profiles
FOR EACH ROW
EXECUTE FUNCTION public.handle_updated_at();
-- Insert the three required roles
INSERT INTO public.roles (name, description) VALUES
('admin', 'Full system access with user management capabilities'),
('conductor', 'Operational access for conducting experiments and managing data'),
('analyst', 'Read-only access for data analysis and reporting')
ON CONFLICT (name) DO NOTHING;

View File

@@ -0,0 +1,63 @@
-- Row Level Security Policies for RBAC
-- Implements role-based access control at the database level
-- Enable RLS on tables
ALTER TABLE public.roles ENABLE ROW LEVEL SECURITY;
ALTER TABLE public.user_profiles ENABLE ROW LEVEL SECURITY;
-- Helper function to get current user's role
CREATE OR REPLACE FUNCTION public.get_user_role()
RETURNS TEXT AS $$
BEGIN
RETURN (
SELECT r.name
FROM public.user_profiles up
JOIN public.roles r ON up.role_id = r.id
WHERE up.id = auth.uid()
);
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;
-- Helper function to check if user is admin
CREATE OR REPLACE FUNCTION public.is_admin()
RETURNS BOOLEAN AS $$
BEGIN
RETURN public.get_user_role() = 'admin';
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;
-- Roles table policies
-- Everyone can read roles (needed for UI dropdowns, etc.)
CREATE POLICY "Anyone can read roles" ON public.roles
FOR SELECT USING (true);
-- Only admins can modify roles
CREATE POLICY "Only admins can insert roles" ON public.roles
FOR INSERT WITH CHECK (public.is_admin());
CREATE POLICY "Only admins can update roles" ON public.roles
FOR UPDATE USING (public.is_admin());
CREATE POLICY "Only admins can delete roles" ON public.roles
FOR DELETE USING (public.is_admin());
-- User profiles policies
-- Users can read their own profile, admins can read all profiles
CREATE POLICY "Users can read own profile, admins can read all" ON public.user_profiles
FOR SELECT USING (
auth.uid() = id OR public.is_admin()
);
-- Only admins can insert user profiles (user creation)
CREATE POLICY "Only admins can insert user profiles" ON public.user_profiles
FOR INSERT WITH CHECK (public.is_admin());
-- Users can update their own profile (except role), admins can update any profile
CREATE POLICY "Users can update own profile, admins can update any" ON public.user_profiles
FOR UPDATE USING (
auth.uid() = id OR public.is_admin()
);
-- Only admins can delete user profiles
CREATE POLICY "Only admins can delete user profiles" ON public.user_profiles
FOR DELETE USING (public.is_admin());

View File

@@ -0,0 +1,65 @@
-- Seed Admin User
-- Creates the initial admin user with specified credentials
-- Function to create admin user
CREATE OR REPLACE FUNCTION public.create_admin_user()
RETURNS VOID AS $$
DECLARE
admin_user_id UUID;
admin_role_id UUID;
BEGIN
-- Get admin role ID
SELECT id INTO admin_role_id FROM public.roles WHERE name = 'admin';
-- Check if admin user already exists
IF NOT EXISTS (
SELECT 1 FROM auth.users WHERE email = 's.alireza.v@gmail.com'
) THEN
-- Insert user into auth.users (this simulates user registration)
-- Note: In production, this would be done through Supabase Auth API
INSERT INTO auth.users (
instance_id,
id,
aud,
role,
email,
encrypted_password,
email_confirmed_at,
created_at,
updated_at,
confirmation_token,
email_change,
email_change_token_new,
recovery_token
) VALUES (
'00000000-0000-0000-0000-000000000000',
uuid_generate_v4(),
'authenticated',
'authenticated',
's.alireza.v@gmail.com',
crypt('2517392', gen_salt('bf')), -- Hash the password
NOW(),
NOW(),
NOW(),
'',
'',
'',
''
) RETURNING id INTO admin_user_id;
-- Insert user profile
INSERT INTO public.user_profiles (id, email, role_id)
VALUES (admin_user_id, 's.alireza.v@gmail.com', admin_role_id);
RAISE NOTICE 'Admin user created successfully with email: s.alireza.v@gmail.com';
ELSE
RAISE NOTICE 'Admin user already exists';
END IF;
END;
$$ LANGUAGE plpgsql;
-- Execute the function to create admin user
SELECT public.create_admin_user();
-- Drop the function as it's no longer needed
DROP FUNCTION public.create_admin_user();