RBAC seems to be working
This commit is contained in:
54
supabase/migrations/20250719000001_rbac_schema.sql
Normal file
54
supabase/migrations/20250719000001_rbac_schema.sql
Normal 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;
|
||||
63
supabase/migrations/20250719000002_rls_policies.sql
Normal file
63
supabase/migrations/20250719000002_rls_policies.sql
Normal 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());
|
||||
65
supabase/migrations/20250719000003_seed_admin_user.sql
Normal file
65
supabase/migrations/20250719000003_seed_admin_user.sql
Normal 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();
|
||||
Reference in New Issue
Block a user