Compare commits

...

5 Commits

Author SHA1 Message Date
gpt-engineer-app[bot]
5a8c899ae6 Refactor: Replace emoji with favicon 2025-11-02 02:54:08 +00:00
gpt-engineer-app[bot]
f7053997d2 Fix blog posts RLS policy 2025-11-02 02:49:12 +00:00
gpt-engineer-app[bot]
2e632caea3 Fix audit log RLS policy 2025-11-02 02:43:09 +00:00
gpt-engineer-app[bot]
5a2e250337 Fix PhotoModal accessibility and audit log RLS 2025-11-02 02:42:45 +00:00
gpt-engineer-app[bot]
9529dd340e Fix admin delete user unauthorized error 2025-11-02 02:39:31 +00:00
6 changed files with 98 additions and 14 deletions

View File

@@ -74,9 +74,11 @@ export function AdminSidebar() {
<SidebarHeader className="border-b border-border/40 px-4 py-4">
{!collapsed && (
<div className="flex items-center gap-2">
<div className="w-8 h-8 rounded-lg bg-primary/10 flex items-center justify-center">
<span className="text-lg">🎢</span>
</div>
<img
src="https://cdn.thrillwiki.com/images/5d06b122-a3a3-47bc-6176-f93ad8f0ce00/favicon32"
alt="ThrillWiki"
className="w-8 h-8"
/>
<div className="flex flex-col">
<span className="text-sm font-semibold">ThrillWiki</span>
<span className="text-xs text-muted-foreground">Admin Panel</span>
@@ -85,7 +87,11 @@ export function AdminSidebar() {
)}
{collapsed && (
<div className="flex items-center justify-center">
<span className="text-lg">🎢</span>
<img
src="https://cdn.thrillwiki.com/images/5d06b122-a3a3-47bc-6176-f93ad8f0ce00/favicon32"
alt="ThrillWiki"
className="w-6 h-6"
/>
</div>
)}
</SidebarHeader>

View File

@@ -1,6 +1,7 @@
import { useState, useEffect, useRef } from 'react';
import { X, ChevronLeft, ChevronRight } from 'lucide-react';
import { Dialog, DialogContent } from '@/components/ui/dialog';
import { Dialog, DialogContent, DialogTitle } from '@/components/ui/dialog';
import { VisuallyHidden } from '@radix-ui/react-visually-hidden';
import { Button } from '@/components/ui/button';
import { useIsMobile } from '@/hooks/use-mobile';
@@ -83,9 +84,12 @@ export function PhotoModal({ photos, initialIndex, isOpen, onClose }: PhotoModal
return (
<Dialog open={isOpen} onOpenChange={onClose}>
<DialogContent className={`max-w-7xl w-full p-0 [&>button]:hidden ${isMobile ? 'max-h-screen h-screen' : 'max-h-[90vh]'}`}>
<DialogContent className={`max-w-7xl w-full p-0 [&>button]:hidden ${isMobile ? 'max-h-screen h-screen' : 'max-h-[90vh]'}`} aria-describedby={undefined}>
<VisuallyHidden>
<DialogTitle>Photo Viewer</DialogTitle>
</VisuallyHidden>
<div
className="relative bg-black rounded-lg overflow-hidden touch-none"
className="relative bg-black rounded-lg overflow-hidden touch-none"
onKeyDown={handleKeyDown}
onTouchStart={onTouchStart}
onTouchMove={onTouchMove}

View File

@@ -1,5 +1,8 @@
project_id = "ydvtmnrszybqnbcqbdcy"
[functions.admin-delete-user]
verify_jwt = true
[functions.send-password-added-email]
verify_jwt = true

View File

@@ -43,16 +43,12 @@ Deno.serve(async (req) => {
);
}
// Create client with user's JWT for permission checks
const supabase = createClient(supabaseUrl, Deno.env.get('SUPABASE_ANON_KEY')!, {
global: { headers: { authorization: authHeader } }
});
// Create admin client for privileged operations
const supabaseAdmin = createClient(supabaseUrl, supabaseServiceKey);
// Get current user
const { data: { user }, error: userError } = await supabase.auth.getUser();
// Get current user - extract token and verify
const token = authHeader.replace('Bearer ', '');
const { data: { user }, error: userError } = await supabaseAdmin.auth.getUser(token);
if (userError || !user) {
edgeLogger.warn('Failed to get user', {
requestId: tracking.requestId,
@@ -68,6 +64,11 @@ Deno.serve(async (req) => {
{ status: 401, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
);
}
// Create client with user's JWT for MFA checks
const supabase = createClient(supabaseUrl, Deno.env.get('SUPABASE_ANON_KEY')!, {
global: { headers: { Authorization: authHeader } }
});
const adminUserId = user.id;

View File

@@ -0,0 +1,15 @@
-- Relax admin_audit_log SELECT policy to not require AAL2
-- This allows admins to view audit logs without constant MFA step-up
-- Write operations still require AAL2 for security
-- Drop the existing SELECT policy
DROP POLICY IF EXISTS "Admins can view audit log" ON public.admin_audit_log;
-- Create new SELECT policy without AAL2 requirement for reads
CREATE POLICY "Admins can view audit log"
ON public.admin_audit_log
FOR SELECT
TO authenticated
USING (
is_moderator(auth.uid())
);

View File

@@ -0,0 +1,55 @@
-- Relax blog_posts RLS policies to remove AAL2 requirement for SELECT
-- This allows admins to view blog posts without constant MFA step-up
-- Write operations still require AAL2 for security
-- Drop the existing combined admin policy
DROP POLICY IF EXISTS "Admins and superusers can manage blog posts" ON public.blog_posts;
-- Create separate SELECT policy without AAL2 requirement
CREATE POLICY "Admins can view all blog posts"
ON public.blog_posts
FOR SELECT
TO authenticated
USING (
has_role(auth.uid(), 'admin'::app_role) OR
has_role(auth.uid(), 'superuser'::app_role)
);
-- Create INSERT policy with AAL2 requirement for write operations
CREATE POLICY "Admins can create blog posts with AAL2"
ON public.blog_posts
FOR INSERT
TO authenticated
WITH CHECK (
(has_role(auth.uid(), 'admin'::app_role) OR has_role(auth.uid(), 'superuser'::app_role))
AND ((NOT EXISTS (
SELECT 1 FROM auth.mfa_factors
WHERE user_id = auth.uid() AND status = 'verified'::auth.factor_status
)) OR has_aal2())
);
-- Create UPDATE policy with AAL2 requirement for write operations
CREATE POLICY "Admins can update blog posts with AAL2"
ON public.blog_posts
FOR UPDATE
TO authenticated
USING (
(has_role(auth.uid(), 'admin'::app_role) OR has_role(auth.uid(), 'superuser'::app_role))
AND ((NOT EXISTS (
SELECT 1 FROM auth.mfa_factors
WHERE user_id = auth.uid() AND status = 'verified'::auth.factor_status
)) OR has_aal2())
);
-- Create DELETE policy with AAL2 requirement for write operations
CREATE POLICY "Admins can delete blog posts with AAL2"
ON public.blog_posts
FOR DELETE
TO authenticated
USING (
(has_role(auth.uid(), 'admin'::app_role) OR has_role(auth.uid(), 'superuser'::app_role))
AND ((NOT EXISTS (
SELECT 1 FROM auth.mfa_factors
WHERE user_id = auth.uid() AND status = 'verified'::auth.factor_status
)) OR has_aal2())
);