From 033229989a505bd49dfa39db408d8e2daf8072ed Mon Sep 17 00:00:00 2001 From: Alireza Vaezi Date: Fri, 18 Jul 2025 21:18:07 -0400 Subject: [PATCH] Revert "RBAC in place. Tailwind CSS working." This reverts commit 90d874b15f99ae17acbc52c3d8db5e5711b42f3a. --- src/App.tsx | 148 ++------ src/components/auth/LoginForm.tsx | 85 ----- src/components/auth/ProtectedRoute.tsx | 79 ----- src/components/auth/UserProfile.tsx | 78 ----- src/contexts/AuthContext.tsx | 174 ---------- src/index.css | 69 +++- src/lib/supabase.ts | 107 ------ src/types/auth.ts | 43 --- supabase/.gitignore | 8 - supabase/config.toml | 322 ------------------ .../migrations/20250717153538_setup_rbac.sql | 102 ------ 11 files changed, 96 insertions(+), 1119 deletions(-) delete mode 100644 src/components/auth/LoginForm.tsx delete mode 100644 src/components/auth/ProtectedRoute.tsx delete mode 100644 src/components/auth/UserProfile.tsx delete mode 100644 src/contexts/AuthContext.tsx delete mode 100644 src/lib/supabase.ts delete mode 100644 src/types/auth.ts delete mode 100644 supabase/.gitignore delete mode 100644 supabase/config.toml delete mode 100644 supabase/migrations/20250717153538_setup_rbac.sql diff --git a/src/App.tsx b/src/App.tsx index f4a9084..3d7ded3 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,126 +1,34 @@ -import React from 'react' -import { AuthProvider, useAuth } from './contexts/AuthContext' -import { LoginForm } from './components/auth/LoginForm' -import { UserProfile } from './components/auth/UserProfile' -import { ProtectedRoute, AdminOnly, ModeratorOrAdmin, AuthenticatedOnly } from './components/auth/ProtectedRoute' - -const AppContent: React.FC = () => { - const { user, loading } = useAuth() - - if (loading) { - return ( -
-
Loading...
-
- ) - } - - if (!user) { - return ( -
-
-

RBAC Demo Application

- -
-
- ) - } - - return ( -
-
-

RBAC Demo Application

- -
- {/* User Profile Section */} -
- -
- - {/* Role-Based Content Section */} -
- {/* Content for all authenticated users */} - -
-

- ✅ Authenticated User Content -

-

- This content is visible to all authenticated users. -

-
-
- - {/* Content for moderators and admins */} - -
-

- 🛡️ Moderator/Admin Content -

-

- This content is visible to moderators and administrators only. -

-
-
- - {/* Content for admins only */} - -
-

- 🔑 Admin Only Content -

-

- This content is visible to administrators only. You have full system access. -

-
-
- - {/* Custom role check example */} - -

You need 'user' role to see this content.

-
- } - > -
-

- 👤 User Role Content -

-

- This content is visible to users with the 'user' role. -

-
- -
-
- - {/* Instructions */} -
-

RBAC System Instructions

-
-

Admin User: s.alireza.v@gmail.com (password: ???????)

-

Features:

-
    -
  • Role-based content visibility
  • -
  • Protected routes and components
  • -
  • User profile with role display
  • -
  • Secure authentication with Supabase
  • -
  • Row Level Security (RLS) policies
  • -
-
-
-
- - ) -} +import { useState } from 'react' +import reactLogo from './assets/react.svg' +import viteLogo from '/vite.svg' +import './App.css' function App() { + const [count, setCount] = useState(0) + return ( - - - + <> +
+ + Vite logo + + + React logo + +
+

Vite + React

+
+ +

+ Edit src/App.tsx and save to test HMR +

+
+

+ Click on the Vite and React logos to learn more +

+ ) } diff --git a/src/components/auth/LoginForm.tsx b/src/components/auth/LoginForm.tsx deleted file mode 100644 index abbae49..0000000 --- a/src/components/auth/LoginForm.tsx +++ /dev/null @@ -1,85 +0,0 @@ -import React, { useState } from 'react' -import { useAuth } from '../../contexts/AuthContext' - -export const LoginForm: React.FC = () => { - const [email, setEmail] = useState('') - const [password, setPassword] = useState('') - const [loading, setLoading] = useState(false) - const [error, setError] = useState(null) - const { signIn } = useAuth() - - const handleSubmit = async (e: React.FormEvent) => { - e.preventDefault() - setLoading(true) - setError(null) - - try { - const { error } = await signIn(email, password) - if (error) { - setError(error.message) - } - } catch (err) { - setError('An unexpected error occurred') - } finally { - setLoading(false) - } - } - - return ( -
-

Sign In

- - {error && ( -
- {error} -
- )} - -
-
- - setEmail(e.target.value)} - required - className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500" - placeholder="Enter your email" - /> -
- -
- - setPassword(e.target.value)} - required - className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500" - placeholder="Enter your password" - /> -
- - -
- -
-

Test admin credentials:

-

Email: s.alireza.v@gmail.com

-

Password: ???????

-
-
- ) -} diff --git a/src/components/auth/ProtectedRoute.tsx b/src/components/auth/ProtectedRoute.tsx deleted file mode 100644 index 70b4ac0..0000000 --- a/src/components/auth/ProtectedRoute.tsx +++ /dev/null @@ -1,79 +0,0 @@ -import React from 'react' -import { useAuth } from '../../contexts/AuthContext' -import type { RoleName } from '../../types/auth' - -interface ProtectedRouteProps { - children: React.ReactNode - requiredRole?: RoleName - requiredRoles?: RoleName[] - fallback?: React.ReactNode - requireAuth?: boolean -} - -export const ProtectedRoute: React.FC = ({ - children, - requiredRole, - requiredRoles, - fallback =
Access denied. You don't have permission to view this content.
, - requireAuth = true -}) => { - const { user, loading } = useAuth() - - // Show loading state - if (loading) { - return ( -
-
Loading...
-
- ) - } - - // Check if authentication is required - if (requireAuth && !user) { - return ( -
-
Please sign in to access this content.
-
- ) - } - - // Check single required role - if (requiredRole && user && !user.roles?.includes(requiredRole)) { - return <>{fallback} - } - - // Check multiple required roles (user must have at least one) - if (requiredRoles && user && !requiredRoles.some(role => user.roles?.includes(role))) { - return <>{fallback} - } - - return <>{children} -} - -// Convenience components for common role checks -export const AdminOnly: React.FC<{ children: React.ReactNode; fallback?: React.ReactNode }> = ({ - children, - fallback -}) => ( - - {children} - -) - -export const ModeratorOrAdmin: React.FC<{ children: React.ReactNode; fallback?: React.ReactNode }> = ({ - children, - fallback -}) => ( - - {children} - -) - -export const AuthenticatedOnly: React.FC<{ children: React.ReactNode; fallback?: React.ReactNode }> = ({ - children, - fallback -}) => ( - - {children} - -) diff --git a/src/components/auth/UserProfile.tsx b/src/components/auth/UserProfile.tsx deleted file mode 100644 index 135b40b..0000000 --- a/src/components/auth/UserProfile.tsx +++ /dev/null @@ -1,78 +0,0 @@ -import React from 'react' -import { useAuth } from '../../contexts/AuthContext' - -export const UserProfile: React.FC = () => { - const { user, signOut, isAdmin } = useAuth() - - if (!user) { - return null - } - - return ( -
-

User Profile

- -
-
- -

{user.email}

-
- - {user.profile && ( - <> -
- -

{user.profile.first_name || 'Not set'}

-
- -
- -

{user.profile.last_name || 'Not set'}

-
- - )} - -
- -
- {user.roles && user.roles.length > 0 ? ( - user.roles.map((role) => ( - - {role} - - )) - ) : ( - No roles assigned - )} -
-
- - {isAdmin() && ( -
-

- 🔑 You have administrator privileges -

-
- )} - -
- -
-
-
- ) -} diff --git a/src/contexts/AuthContext.tsx b/src/contexts/AuthContext.tsx deleted file mode 100644 index 658f1c6..0000000 --- a/src/contexts/AuthContext.tsx +++ /dev/null @@ -1,174 +0,0 @@ -import React, { createContext, useContext, useEffect, useState } from 'react' -import type { User } from '@supabase/supabase-js' -import { supabase } from '../lib/supabase' -import type { AuthContextType, AuthUser, UserProfile } from '../types/auth' - -const AuthContext = createContext(undefined) - -export const useAuth = () => { - const context = useContext(AuthContext) - if (context === undefined) { - throw new Error('useAuth must be used within an AuthProvider') - } - return context -} - -interface AuthProviderProps { - children: React.ReactNode -} - -export const AuthProvider: React.FC = ({ children }) => { - const [user, setUser] = useState(null) - const [loading, setLoading] = useState(true) - - // Fetch user profile and roles - const fetchUserData = async (authUser: User): Promise => { - try { - // Fetch user profile - const { data: profile, error: profileError } = await supabase - .from('user_profiles') - .select('*') - .eq('id', authUser.id) - .single() - - if (profileError && profileError.code !== 'PGRST116') { - console.error('Error fetching user profile:', profileError) - } - - // Fetch user roles using the database function - const { data: rolesData, error: rolesError } = await supabase - .rpc('get_user_roles', { user_uuid: authUser.id }) - - if (rolesError) { - console.error('Error fetching user roles:', rolesError) - } - - const roles = rolesData?.map(r => r.role_name) || [] - - return { - ...authUser, - profile: profile as UserProfile, - roles - } - } catch (error) { - console.error('Error fetching user data:', error) - return { - ...authUser, - profile: undefined, - roles: [] - } - } - } - - // Refresh user data - const refreshUserData = async () => { - const { data: { user: authUser } } = await supabase.auth.getUser() - if (authUser) { - const userData = await fetchUserData(authUser) - setUser(userData) - } - } - - // Sign in - const signIn = async (email: string, password: string) => { - const { error } = await supabase.auth.signInWithPassword({ - email, - password - }) - return { error } - } - - // Sign up - const signUp = async ( - email: string, - password: string, - userData?: { first_name?: string; last_name?: string } - ) => { - const { data, error } = await supabase.auth.signUp({ - email, - password, - options: { - data: userData - } - }) - - // If signup successful and user data provided, create profile - if (!error && data.user && userData) { - const { error: profileError } = await supabase - .from('user_profiles') - .insert({ - id: data.user.id, - first_name: userData.first_name, - last_name: userData.last_name - }) - - if (profileError) { - console.error('Error creating user profile:', profileError) - } - - // Assign default 'user' role - const { error: roleError } = await supabase - .from('user_roles') - .insert({ - user_id: data.user.id, - role_id: 6 // 'user' role ID from our database - }) - - if (roleError) { - console.error('Error assigning default role:', roleError) - } - } - - return { error } - } - - // Sign out - const signOut = async () => { - await supabase.auth.signOut() - setUser(null) - } - - // Check if user has specific role - const hasRole = (roleName: string): boolean => { - return user?.roles?.includes(roleName) || false - } - - // Check if user is admin - const isAdmin = (): boolean => { - return hasRole('admin') - } - - // Handle auth state changes - useEffect(() => { - const { data: { subscription } } = supabase.auth.onAuthStateChange( - async (event, session) => { - if (session?.user) { - const userData = await fetchUserData(session.user) - setUser(userData) - } else { - setUser(null) - } - setLoading(false) - } - ) - - return () => subscription.unsubscribe() - }, []) - - const value: AuthContextType = { - user, - loading, - signIn, - signUp, - signOut, - hasRole, - isAdmin, - refreshUserData - } - - return ( - - {children} - - ) -} diff --git a/src/index.css b/src/index.css index a461c50..08a3ac9 100644 --- a/src/index.css +++ b/src/index.css @@ -1 +1,68 @@ -@import "tailwindcss"; \ No newline at end of file +:root { + font-family: system-ui, Avenir, Helvetica, Arial, sans-serif; + line-height: 1.5; + font-weight: 400; + + color-scheme: light dark; + color: rgba(255, 255, 255, 0.87); + background-color: #242424; + + font-synthesis: none; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +a { + font-weight: 500; + color: #646cff; + text-decoration: inherit; +} +a:hover { + color: #535bf2; +} + +body { + margin: 0; + display: flex; + place-items: center; + min-width: 320px; + min-height: 100vh; +} + +h1 { + font-size: 3.2em; + line-height: 1.1; +} + +button { + border-radius: 8px; + border: 1px solid transparent; + padding: 0.6em 1.2em; + font-size: 1em; + font-weight: 500; + font-family: inherit; + background-color: #1a1a1a; + cursor: pointer; + transition: border-color 0.25s; +} +button:hover { + border-color: #646cff; +} +button:focus, +button:focus-visible { + outline: 4px auto -webkit-focus-ring-color; +} + +@media (prefers-color-scheme: light) { + :root { + color: #213547; + background-color: #ffffff; + } + a:hover { + color: #747bff; + } + button { + background-color: #f9f9f9; + } +} diff --git a/src/lib/supabase.ts b/src/lib/supabase.ts deleted file mode 100644 index de3fd35..0000000 --- a/src/lib/supabase.ts +++ /dev/null @@ -1,107 +0,0 @@ -import { createClient } from '@supabase/supabase-js' - -// Local Supabase instance configuration -const supabaseUrl = 'http://127.0.0.1:54321' -const supabaseAnonKey = '[REDACTED]' - -export const supabase = createClient(supabaseUrl, supabaseAnonKey) - -// Database types for TypeScript -export interface Database { - public: { - Tables: { - roles: { - Row: { - id: number - name: string - description: string | null - created_at: string - updated_at: string - } - Insert: { - id?: number - name: string - description?: string | null - created_at?: string - updated_at?: string - } - Update: { - id?: number - name?: string - description?: string | null - created_at?: string - updated_at?: string - } - } - user_profiles: { - Row: { - id: string - first_name: string | null - last_name: string | null - created_at: string - updated_at: string - } - Insert: { - id: string - first_name?: string | null - last_name?: string | null - created_at?: string - updated_at?: string - } - Update: { - id?: string - first_name?: string | null - last_name?: string | null - created_at?: string - updated_at?: string - } - } - user_roles: { - Row: { - id: number - user_id: string | null - role_id: number | null - created_at: string - updated_at: string - } - Insert: { - id?: number - user_id?: string | null - role_id?: number | null - created_at?: string - updated_at?: string - } - Update: { - id?: number - user_id?: string | null - role_id?: number | null - created_at?: string - updated_at?: string - } - } - } - Views: { - [_ in never]: never - } - Functions: { - get_user_roles: { - Args: { - user_uuid: string - } - Returns: { - role_name: string - }[] - } - user_has_role: { - Args: { - user_uuid: string - role_name: string - } - Returns: boolean - } - } - Enums: { - [_ in never]: never - } - } -} diff --git a/src/types/auth.ts b/src/types/auth.ts deleted file mode 100644 index 9d06821..0000000 --- a/src/types/auth.ts +++ /dev/null @@ -1,43 +0,0 @@ -import type { User } from '@supabase/supabase-js' - -export interface UserProfile { - id: string - first_name: string | null - last_name: string | null - created_at: string - updated_at: string -} - -export interface Role { - id: number - name: string - description: string | null - created_at: string - updated_at: string -} - -export interface UserRole { - id: number - user_id: string | null - role_id: number | null - created_at: string - updated_at: string -} - -export interface AuthUser extends User { - profile?: UserProfile - roles?: string[] -} - -export interface AuthContextType { - user: AuthUser | null - loading: boolean - signIn: (email: string, password: string) => Promise<{ error: any }> - signUp: (email: string, password: string, userData?: { first_name?: string; last_name?: string }) => Promise<{ error: any }> - signOut: () => Promise - hasRole: (roleName: string) => boolean - isAdmin: () => boolean - refreshUserData: () => Promise -} - -export type RoleName = 'admin' | 'user' | 'moderator' | 'coordinator' | 'conductor' | 'analyst' diff --git a/supabase/.gitignore b/supabase/.gitignore deleted file mode 100644 index ad9264f..0000000 --- a/supabase/.gitignore +++ /dev/null @@ -1,8 +0,0 @@ -# Supabase -.branches -.temp - -# dotenvx -.env.keys -.env.local -.env.*.local diff --git a/supabase/config.toml b/supabase/config.toml deleted file mode 100644 index 04d9c30..0000000 --- a/supabase/config.toml +++ /dev/null @@ -1,322 +0,0 @@ -# For detailed configuration reference documentation, visit: -# https://supabase.com/docs/guides/local-development/cli/config -# A string used to distinguish different Supabase projects on the same host. Defaults to the -# working directory name when running `supabase init`. -project_id = "pecan_experiments" - -[api] -enabled = true -# Port to use for the API URL. -port = 54321 -# Schemas to expose in your API. Tables, views and stored procedures in this schema will get API -# endpoints. `public` and `graphql_public` schemas are included by default. -schemas = ["public", "graphql_public"] -# Extra schemas to add to the search_path of every request. -extra_search_path = ["public", "extensions"] -# The maximum number of rows returns from a view, table, or stored procedure. Limits payload size -# for accidental or malicious requests. -max_rows = 1000 - -[api.tls] -# Enable HTTPS endpoints locally using a self-signed certificate. -enabled = false - -[db] -# Port to use for the local database URL. -port = 54322 -# Port used by db diff command to initialize the shadow database. -shadow_port = 54320 -# The database major version to use. This has to be the same as your remote database's. Run `SHOW -# server_version;` on the remote database to check. -major_version = 17 - -[db.pooler] -enabled = false -# Port to use for the local connection pooler. -port = 54329 -# Specifies when a server connection can be reused by other clients. -# Configure one of the supported pooler modes: `transaction`, `session`. -pool_mode = "transaction" -# How many server connections to allow per user/database pair. -default_pool_size = 20 -# Maximum number of client connections allowed. -max_client_conn = 100 - -# [db.vault] -# secret_key = "env(SECRET_VALUE)" - -[db.migrations] -# If disabled, migrations will be skipped during a db push or reset. -enabled = true -# Specifies an ordered list of schema files that describe your database. -# Supports glob patterns relative to supabase directory: "./schemas/*.sql" -schema_paths = [] - -[db.seed] -# If enabled, seeds the database after migrations during a db reset. -enabled = true -# Specifies an ordered list of seed files to load during db reset. -# Supports glob patterns relative to supabase directory: "./seeds/*.sql" -sql_paths = ["./seed.sql"] - -[realtime] -enabled = true -# Bind realtime via either IPv4 or IPv6. (default: IPv4) -# ip_version = "IPv6" -# The maximum length in bytes of HTTP request headers. (default: 4096) -# max_header_length = 4096 - -[studio] -enabled = true -# Port to use for Supabase Studio. -port = 54323 -# External URL of the API server that frontend connects to. -api_url = "http://127.0.0.1" -# OpenAI API Key to use for Supabase AI in the Supabase Studio. -openai_api_key = "env(OPENAI_API_KEY)" - -# Email testing server. Emails sent with the local dev setup are not actually sent - rather, they -# are monitored, and you can view the emails that would have been sent from the web interface. -[inbucket] -enabled = true -# Port to use for the email testing server web interface. -port = 54324 -# Uncomment to expose additional ports for testing user applications that send emails. -# smtp_port = 54325 -# pop3_port = 54326 -# admin_email = "admin@email.com" -# sender_name = "Admin" - -[storage] -enabled = true -# The maximum file size allowed (e.g. "5MB", "500KB"). -file_size_limit = "50MiB" - -# Image transformation API is available to Supabase Pro plan. -# [storage.image_transformation] -# enabled = true - -# Uncomment to configure local storage buckets -# [storage.buckets.images] -# public = false -# file_size_limit = "50MiB" -# allowed_mime_types = ["image/png", "image/jpeg"] -# objects_path = "./images" - -[auth] -enabled = true -# The base URL of your website. Used as an allow-list for redirects and for constructing URLs used -# in emails. -site_url = "http://127.0.0.1:3000" -# A list of *exact* URLs that auth providers are permitted to redirect to post authentication. -additional_redirect_urls = ["https://127.0.0.1:3000"] -# How long tokens are valid for, in seconds. Defaults to 3600 (1 hour), maximum 604,800 (1 week). -jwt_expiry = 3600 -# If disabled, the refresh token will never expire. -enable_refresh_token_rotation = true -# Allows refresh tokens to be reused after expiry, up to the specified interval in seconds. -# Requires enable_refresh_token_rotation = true. -refresh_token_reuse_interval = 10 -# Allow/disallow new user signups to your project. -enable_signup = true -# Allow/disallow anonymous sign-ins to your project. -enable_anonymous_sign_ins = false -# Allow/disallow testing manual linking of accounts -enable_manual_linking = false -# Passwords shorter than this value will be rejected as weak. Minimum 6, recommended 8 or more. -minimum_password_length = 6 -# Passwords that do not meet the following requirements will be rejected as weak. Supported values -# are: `letters_digits`, `lower_upper_letters_digits`, `lower_upper_letters_digits_symbols` -password_requirements = "" - -[auth.rate_limit] -# Number of emails that can be sent per hour. Requires auth.email.smtp to be enabled. -email_sent = 2 -# Number of SMS messages that can be sent per hour. Requires auth.sms to be enabled. -sms_sent = 30 -# Number of anonymous sign-ins that can be made per hour per IP address. Requires enable_anonymous_sign_ins = true. -anonymous_users = 30 -# Number of sessions that can be refreshed in a 5 minute interval per IP address. -token_refresh = 150 -# Number of sign up and sign-in requests that can be made in a 5 minute interval per IP address (excludes anonymous users). -sign_in_sign_ups = 30 -# Number of OTP / Magic link verifications that can be made in a 5 minute interval per IP address. -token_verifications = 30 -# Number of Web3 logins that can be made in a 5 minute interval per IP address. -web3 = 30 - -# Configure one of the supported captcha providers: `hcaptcha`, `turnstile`. -# [auth.captcha] -# enabled = true -# provider = "hcaptcha" -# secret = "" - -[auth.email] -# Allow/disallow new user signups via email to your project. -enable_signup = true -# If enabled, a user will be required to confirm any email change on both the old, and new email -# addresses. If disabled, only the new email is required to confirm. -double_confirm_changes = true -# If enabled, users need to confirm their email address before signing in. -enable_confirmations = false -# If enabled, users will need to reauthenticate or have logged in recently to change their password. -secure_password_change = false -# Controls the minimum amount of time that must pass before sending another signup confirmation or password reset email. -max_frequency = "1s" -# Number of characters used in the email OTP. -otp_length = 6 -# Number of seconds before the email OTP expires (defaults to 1 hour). -otp_expiry = 3600 - -# Use a production-ready SMTP server -# [auth.email.smtp] -# enabled = true -# host = "smtp.sendgrid.net" -# port = 587 -# user = "apikey" -# pass = "env(SENDGRID_API_KEY)" -# admin_email = "admin@email.com" -# sender_name = "Admin" - -# Uncomment to customize email template -# [auth.email.template.invite] -# subject = "You have been invited" -# content_path = "./supabase/templates/invite.html" - -[auth.sms] -# Allow/disallow new user signups via SMS to your project. -enable_signup = false -# If enabled, users need to confirm their phone number before signing in. -enable_confirmations = false -# Template for sending OTP to users -template = "Your code is {{ .Code }}" -# Controls the minimum amount of time that must pass before sending another sms otp. -max_frequency = "5s" - -# Use pre-defined map of phone number to OTP for testing. -# [auth.sms.test_otp] -# 4152127777 = "123456" - -# Configure logged in session timeouts. -# [auth.sessions] -# Force log out after the specified duration. -# timebox = "24h" -# Force log out if the user has been inactive longer than the specified duration. -# inactivity_timeout = "8h" - -# This hook runs before a new user is created and allows developers to reject the request based on the incoming user object. -# [auth.hook.before_user_created] -# enabled = true -# uri = "pg-functions://postgres/auth/before-user-created-hook" - -# This hook runs before a token is issued and allows you to add additional claims based on the authentication method used. -# [auth.hook.custom_access_token] -# enabled = true -# uri = "pg-functions:////" - -# Configure one of the supported SMS providers: `twilio`, `twilio_verify`, `messagebird`, `textlocal`, `vonage`. -[auth.sms.twilio] -enabled = false -account_sid = "" -message_service_sid = "" -# DO NOT commit your Twilio auth token to git. Use environment variable substitution instead: -auth_token = "env(SUPABASE_AUTH_SMS_TWILIO_AUTH_TOKEN)" - -# Multi-factor-authentication is available to Supabase Pro plan. -[auth.mfa] -# Control how many MFA factors can be enrolled at once per user. -max_enrolled_factors = 10 - -# Control MFA via App Authenticator (TOTP) -[auth.mfa.totp] -enroll_enabled = false -verify_enabled = false - -# Configure MFA via Phone Messaging -[auth.mfa.phone] -enroll_enabled = false -verify_enabled = false -otp_length = 6 -template = "Your code is {{ .Code }}" -max_frequency = "5s" - -# Configure MFA via WebAuthn -# [auth.mfa.web_authn] -# enroll_enabled = true -# verify_enabled = true - -# Use an external OAuth provider. The full list of providers are: `apple`, `azure`, `bitbucket`, -# `discord`, `facebook`, `github`, `gitlab`, `google`, `keycloak`, `linkedin_oidc`, `notion`, `twitch`, -# `twitter`, `slack`, `spotify`, `workos`, `zoom`. -[auth.external.apple] -enabled = false -client_id = "" -# DO NOT commit your OAuth provider secret to git. Use environment variable substitution instead: -secret = "env(SUPABASE_AUTH_EXTERNAL_APPLE_SECRET)" -# Overrides the default auth redirectUrl. -redirect_uri = "" -# Overrides the default auth provider URL. Used to support self-hosted gitlab, single-tenant Azure, -# or any other third-party OIDC providers. -url = "" -# If enabled, the nonce check will be skipped. Required for local sign in with Google auth. -skip_nonce_check = false - -# Allow Solana wallet holders to sign in to your project via the Sign in with Solana (SIWS, EIP-4361) standard. -# You can configure "web3" rate limit in the [auth.rate_limit] section and set up [auth.captcha] if self-hosting. -[auth.web3.solana] -enabled = false - -# Use Firebase Auth as a third-party provider alongside Supabase Auth. -[auth.third_party.firebase] -enabled = false -# project_id = "my-firebase-project" - -# Use Auth0 as a third-party provider alongside Supabase Auth. -[auth.third_party.auth0] -enabled = false -# tenant = "my-auth0-tenant" -# tenant_region = "us" - -# Use AWS Cognito (Amplify) as a third-party provider alongside Supabase Auth. -[auth.third_party.aws_cognito] -enabled = false -# user_pool_id = "my-user-pool-id" -# user_pool_region = "us-east-1" - -# Use Clerk as a third-party provider alongside Supabase Auth. -[auth.third_party.clerk] -enabled = false -# Obtain from https://clerk.com/setup/supabase -# domain = "example.clerk.accounts.dev" - -[edge_runtime] -enabled = true -# Configure one of the supported request policies: `oneshot`, `per_worker`. -# Use `oneshot` for hot reload, or `per_worker` for load testing. -policy = "oneshot" -# Port to attach the Chrome inspector for debugging edge functions. -inspector_port = 8083 -# The Deno major version to use. -deno_version = 1 - -# [edge_runtime.secrets] -# secret_key = "env(SECRET_VALUE)" - -[analytics] -enabled = true -port = 54327 -# Configure one of the supported backends: `postgres`, `bigquery`. -backend = "postgres" - -# Experimental features may be deprecated any time -[experimental] -# Configures Postgres storage engine to use OrioleDB (S3) -orioledb_version = "" -# Configures S3 bucket URL, eg. .s3-.amazonaws.com -s3_host = "env(S3_HOST)" -# Configures S3 bucket region, eg. us-east-1 -s3_region = "env(S3_REGION)" -# Configures AWS_ACCESS_KEY_ID for S3 bucket -s3_access_key = "env(S3_ACCESS_KEY)" -# Configures AWS_SECRET_ACCESS_KEY for S3 bucket -s3_secret_key = "env(S3_SECRET_KEY)" diff --git a/supabase/migrations/20250717153538_setup_rbac.sql b/supabase/migrations/20250717153538_setup_rbac.sql deleted file mode 100644 index 690e4e3..0000000 --- a/supabase/migrations/20250717153538_setup_rbac.sql +++ /dev/null @@ -1,102 +0,0 @@ --- Create roles table -CREATE TABLE IF NOT EXISTS public.roles ( - id UUID DEFAULT gen_random_uuid() PRIMARY KEY, - name VARCHAR(50) UNIQUE NOT NULL, - description TEXT, - created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), - updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() -); - --- Create user profiles table -CREATE TABLE IF NOT EXISTS public.user_profiles ( - id UUID REFERENCES auth.users(id) ON DELETE CASCADE PRIMARY KEY, - first_name VARCHAR(100), - last_name VARCHAR(100), - created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), - updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() -); - --- Create user roles junction table -CREATE TABLE IF NOT EXISTS public.user_roles ( - id UUID DEFAULT gen_random_uuid() PRIMARY KEY, - user_id UUID REFERENCES auth.users(id) ON DELETE CASCADE, - role_id UUID REFERENCES public.roles(id) ON DELETE CASCADE, - assigned_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), - assigned_by UUID REFERENCES auth.users(id), - UNIQUE(user_id, role_id) -); - --- Insert default roles -INSERT INTO public.roles (name, description) VALUES - ('admin', 'Administrator with full system access'), - ('user', 'Regular user with basic access'), - ('moderator', 'Moderator with limited administrative access') -ON CONFLICT (name) DO NOTHING; - --- Enable RLS on all tables -ALTER TABLE public.roles ENABLE ROW LEVEL SECURITY; -ALTER TABLE public.user_profiles ENABLE ROW LEVEL SECURITY; -ALTER TABLE public.user_roles ENABLE ROW LEVEL SECURITY; - --- Roles table policies -CREATE POLICY "Anyone can view roles" ON public.roles FOR SELECT USING (true); - -CREATE POLICY "Only admins can manage roles" ON public.roles FOR ALL USING ( - EXISTS ( - SELECT 1 FROM public.user_roles ur - JOIN public.roles r ON ur.role_id = r.id - WHERE ur.user_id = auth.uid() AND r.name = 'admin' - ) -); - --- User profiles policies -CREATE POLICY "Users can view their own profile" ON public.user_profiles FOR SELECT USING (auth.uid() = id); - -CREATE POLICY "Users can update their own profile" ON public.user_profiles FOR UPDATE USING (auth.uid() = id); - -CREATE POLICY "Users can insert their own profile" ON public.user_profiles FOR INSERT WITH CHECK (auth.uid() = id); - -CREATE POLICY "Admins can view all profiles" ON public.user_profiles FOR SELECT USING ( - EXISTS ( - SELECT 1 FROM public.user_roles ur - JOIN public.roles r ON ur.role_id = r.id - WHERE ur.user_id = auth.uid() AND r.name = 'admin' - ) -); - --- User roles policies -CREATE POLICY "Users can view their own roles" ON public.user_roles FOR SELECT USING (auth.uid() = user_id); - -CREATE POLICY "Admins can manage all user roles" ON public.user_roles FOR ALL USING ( - EXISTS ( - SELECT 1 FROM public.user_roles ur - JOIN public.roles r ON ur.role_id = r.id - WHERE ur.user_id = auth.uid() AND r.name = 'admin' - ) -); - --- Function to get user roles -CREATE OR REPLACE FUNCTION get_user_roles(user_uuid UUID) -RETURNS TABLE(role_name VARCHAR(50)) -LANGUAGE sql -SECURITY DEFINER -AS $$ - SELECT r.name - FROM public.user_roles ur - JOIN public.roles r ON ur.role_id = r.id - WHERE ur.user_id = user_uuid; -$$; - --- Function to check if user has specific role -CREATE OR REPLACE FUNCTION user_has_role(user_uuid UUID, role_name VARCHAR(50)) -RETURNS BOOLEAN -LANGUAGE sql -SECURITY DEFINER -AS $$ - SELECT EXISTS ( - SELECT 1 - FROM public.user_roles ur - JOIN public.roles r ON ur.role_id = r.id - WHERE ur.user_id = user_uuid AND r.name = role_name - ); -$$; \ No newline at end of file