mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-28 22:06:58 -05:00
Compare commits
5 Commits
de3d80764c
...
5a8c899ae6
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5a8c899ae6 | ||
|
|
f7053997d2 | ||
|
|
2e632caea3 | ||
|
|
5a2e250337 | ||
|
|
9529dd340e |
@@ -74,9 +74,11 @@ export function AdminSidebar() {
|
|||||||
<SidebarHeader className="border-b border-border/40 px-4 py-4">
|
<SidebarHeader className="border-b border-border/40 px-4 py-4">
|
||||||
{!collapsed && (
|
{!collapsed && (
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<div className="w-8 h-8 rounded-lg bg-primary/10 flex items-center justify-center">
|
<img
|
||||||
<span className="text-lg">🎢</span>
|
src="https://cdn.thrillwiki.com/images/5d06b122-a3a3-47bc-6176-f93ad8f0ce00/favicon32"
|
||||||
</div>
|
alt="ThrillWiki"
|
||||||
|
className="w-8 h-8"
|
||||||
|
/>
|
||||||
<div className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
<span className="text-sm font-semibold">ThrillWiki</span>
|
<span className="text-sm font-semibold">ThrillWiki</span>
|
||||||
<span className="text-xs text-muted-foreground">Admin Panel</span>
|
<span className="text-xs text-muted-foreground">Admin Panel</span>
|
||||||
@@ -85,7 +87,11 @@ export function AdminSidebar() {
|
|||||||
)}
|
)}
|
||||||
{collapsed && (
|
{collapsed && (
|
||||||
<div className="flex items-center justify-center">
|
<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>
|
</div>
|
||||||
)}
|
)}
|
||||||
</SidebarHeader>
|
</SidebarHeader>
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { useState, useEffect, useRef } from 'react';
|
import { useState, useEffect, useRef } from 'react';
|
||||||
import { X, ChevronLeft, ChevronRight } from 'lucide-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 { Button } from '@/components/ui/button';
|
||||||
import { useIsMobile } from '@/hooks/use-mobile';
|
import { useIsMobile } from '@/hooks/use-mobile';
|
||||||
|
|
||||||
@@ -83,7 +84,10 @@ export function PhotoModal({ photos, initialIndex, isOpen, onClose }: PhotoModal
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog open={isOpen} onOpenChange={onClose}>
|
<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
|
<div
|
||||||
className="relative bg-black rounded-lg overflow-hidden touch-none"
|
className="relative bg-black rounded-lg overflow-hidden touch-none"
|
||||||
onKeyDown={handleKeyDown}
|
onKeyDown={handleKeyDown}
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
project_id = "ydvtmnrszybqnbcqbdcy"
|
project_id = "ydvtmnrszybqnbcqbdcy"
|
||||||
|
|
||||||
|
[functions.admin-delete-user]
|
||||||
|
verify_jwt = true
|
||||||
|
|
||||||
[functions.send-password-added-email]
|
[functions.send-password-added-email]
|
||||||
verify_jwt = true
|
verify_jwt = true
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
// Create admin client for privileged operations
|
||||||
const supabaseAdmin = createClient(supabaseUrl, supabaseServiceKey);
|
const supabaseAdmin = createClient(supabaseUrl, supabaseServiceKey);
|
||||||
|
|
||||||
// Get current user
|
// Get current user - extract token and verify
|
||||||
const { data: { user }, error: userError } = await supabase.auth.getUser();
|
const token = authHeader.replace('Bearer ', '');
|
||||||
|
const { data: { user }, error: userError } = await supabaseAdmin.auth.getUser(token);
|
||||||
if (userError || !user) {
|
if (userError || !user) {
|
||||||
edgeLogger.warn('Failed to get user', {
|
edgeLogger.warn('Failed to get user', {
|
||||||
requestId: tracking.requestId,
|
requestId: tracking.requestId,
|
||||||
@@ -69,6 +65,11 @@ Deno.serve(async (req) => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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;
|
const adminUserId = user.id;
|
||||||
|
|
||||||
// Parse request
|
// Parse request
|
||||||
|
|||||||
@@ -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())
|
||||||
|
);
|
||||||
@@ -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())
|
||||||
|
);
|
||||||
Reference in New Issue
Block a user