fix: enable per-user RLS via SupabaseAnonClient.for_user() and StorageUser(access_token=)
This commit is contained in:
@@ -14,19 +14,31 @@ class CreateBucketOptions(TypedDict, total=False):
|
||||
allowed_mime_types: List[str]
|
||||
name: str
|
||||
|
||||
def _create_base_client(url: str, key: str, options: Optional[Dict[str, Any]] = None) -> Client:
|
||||
"""Create a base Supabase client with given configuration."""
|
||||
def _create_base_client(url: str, key: str, access_token: Optional[str] = None, options: Optional[Dict[str, Any]] = None) -> Client:
|
||||
"""Create a base Supabase client with given configuration.
|
||||
|
||||
If access_token is provided, it is used as the Authorization header (for per-user RLS).
|
||||
Otherwise, the API key is used (service role bypasses RLS, anon key does not).
|
||||
"""
|
||||
# If an access token is provided, use it for Authorization (enables per-user RLS)
|
||||
# Otherwise fall back to the API key
|
||||
auth_header = f"Bearer {access_token}" if access_token else f"Bearer {key}"
|
||||
|
||||
client_options = SyncClientOptions(
|
||||
schema="public",
|
||||
storage=SyncMemoryStorage(),
|
||||
headers={
|
||||
"Authorization": f"Bearer {key}"
|
||||
}
|
||||
headers={{
|
||||
"Authorization": auth_header
|
||||
}}
|
||||
)
|
||||
return create_client(url, key, options=client_options)
|
||||
|
||||
class SupabaseServiceRoleClient:
|
||||
"""Supabase client for making authenticated requests using the service role key"""
|
||||
"""Supabase client for making authenticated requests using the service role key.
|
||||
|
||||
NOTE: Service role bypasses all RLS policies. Use only for admin operations
|
||||
where you need to read/write any row regardless of ownership.
|
||||
"""
|
||||
|
||||
def __init__(self, url: Optional[str] = None, service_role_key: Optional[str] = None):
|
||||
"""Initialize the Supabase client with URL and service role key"""
|
||||
@@ -36,7 +48,7 @@ class SupabaseServiceRoleClient:
|
||||
if not self.url or not self.service_role_key:
|
||||
raise ValueError("SUPABASE_URL and SERVICE_ROLE_KEY must be provided")
|
||||
|
||||
# Initialize Supabase client with service role key and optional access token
|
||||
# Initialize Supabase client with service role key (bypasses RLS)
|
||||
self.supabase = _create_base_client(self.url, self.service_role_key)
|
||||
|
||||
def create_bucket(self, id: str, options: Optional[CreateBucketOptions] = None) -> Dict[str, Any]:
|
||||
@@ -48,17 +60,29 @@ class SupabaseServiceRoleClient:
|
||||
return self.supabase.storage.create_bucket(id, options=options)
|
||||
|
||||
class SupabaseAnonClient:
|
||||
"""Supabase client for making authenticated requests using the anon key"""
|
||||
"""Supabase client for making authenticated requests using the anon key.
|
||||
|
||||
When initialized with an access_token, per-user RLS policies are enforced
|
||||
via auth.uid() in the JWT. Without an access_token, requests use the anon key
|
||||
which does NOT enforce per-user RLS (only bucket-level storage rules apply).
|
||||
"""
|
||||
|
||||
def __init__(self, url: Optional[str] = None, anon_key: Optional[str] = None, access_token: Optional[str] = None):
|
||||
"""Initialize the Supabase client with URL and anon key"""
|
||||
"""Initialize the Supabase client with URL and anon key.
|
||||
|
||||
Args:
|
||||
url: Supabase URL
|
||||
anon_key: Anon API key (fallback if no access_token)
|
||||
access_token: User's JWT access token for per-user RLS enforcement
|
||||
"""
|
||||
self.url = url or os.environ.get("SUPABASE_URL", "http://localhost:8000")
|
||||
self.anon_key = anon_key or os.environ.get("ANON_KEY")
|
||||
self.access_token = access_token
|
||||
|
||||
if not self.url or not self.anon_key:
|
||||
raise ValueError("SUPABASE_URL and ANON_KEY must be provided")
|
||||
|
||||
# Initialize Supabase client with anon key and optional access token
|
||||
# Initialize Supabase client with anon key and optional access token for RLS
|
||||
self.supabase = _create_base_client(self.url, self.anon_key, access_token=access_token)
|
||||
|
||||
def create_bucket(self, id: str, options: Optional[CreateBucketOptions] = None) -> Dict[str, Any]:
|
||||
@@ -67,5 +91,8 @@ class SupabaseAnonClient:
|
||||
|
||||
@classmethod
|
||||
def for_user(cls, access_token: str) -> 'SupabaseAnonClient':
|
||||
"""Create a client instance for a specific user using their access token"""
|
||||
"""Create a client instance for a specific user using their access token.
|
||||
|
||||
This enables per-user RLS enforcement via auth.uid() in the JWT.
|
||||
"""
|
||||
return cls(access_token=access_token)
|
||||
|
||||
Reference in New Issue
Block a user