From 9f558fb41c351e7a7b8449c6035809f413c13708 Mon Sep 17 00:00:00 2001 From: "gpt-engineer-app[bot]" <159125892+gpt-engineer-app[bot]@users.noreply.github.com> Date: Sat, 18 Apr 2026 16:01:01 +0000 Subject: [PATCH] Changes Co-authored-by: renee-png <262607627+renee-png@users.noreply.github.com> --- src/routeTree.gen.ts | 9 --- src/routes/admin.users.tsx | 51 ++++++------- .../functions/admin-set-password/index.ts | 71 +++++++++++++++++++ 3 files changed, 98 insertions(+), 33 deletions(-) create mode 100644 supabase/functions/admin-set-password/index.ts diff --git a/src/routeTree.gen.ts b/src/routeTree.gen.ts index 2ce160e..f44751d 100644 --- a/src/routeTree.gen.ts +++ b/src/routeTree.gen.ts @@ -847,12 +847,3 @@ const rootRouteChildren: RootRouteChildren = { export const routeTree = rootRouteImport ._addFileChildren(rootRouteChildren) ._addFileTypes() - -import type { getRouter } from './router.tsx' -import type { createStart } from '@tanstack/react-start' -declare module '@tanstack/react-start' { - interface Register { - ssr: true - router: Awaited> - } -} diff --git a/src/routes/admin.users.tsx b/src/routes/admin.users.tsx index e866344..af07c44 100644 --- a/src/routes/admin.users.tsx +++ b/src/routes/admin.users.tsx @@ -50,7 +50,7 @@ import { TableRow, } from "@/components/ui/table"; import { Badge } from "@/components/ui/badge"; -import { Loader2, UserPlus, Trash2 } from "lucide-react"; +import { Loader2, UserPlus, Trash2, KeyRound } from "lucide-react"; import { toast } from "sonner"; export const Route = createFileRoute("/admin/users")({ @@ -231,29 +231,32 @@ function UsersPage() { /> - {!isSelf && ( - - - - - - - Delete user? - - This will permanently remove {u.email} and revoke their access. - - - - Cancel - handleDelete(u.id)}> - Delete - - - - - )} +
+ + {!isSelf && ( + + + + + + + Delete user? + + This will permanently remove {u.email} and revoke their access. + + + + Cancel + handleDelete(u.id)}> + Delete + + + + + )} +
); diff --git a/supabase/functions/admin-set-password/index.ts b/supabase/functions/admin-set-password/index.ts new file mode 100644 index 0000000..1edd33d --- /dev/null +++ b/supabase/functions/admin-set-password/index.ts @@ -0,0 +1,71 @@ +// Admin-only edge function to set a user's password. +import { createClient } from "https://esm.sh/@supabase/supabase-js@2.49.4"; + +const corsHeaders = { + "Access-Control-Allow-Origin": "*", + "Access-Control-Allow-Headers": "authorization, x-client-info, apikey, content-type", + "Access-Control-Allow-Methods": "POST, OPTIONS", +}; + +Deno.serve(async (req) => { + if (req.method === "OPTIONS") return new Response(null, { headers: corsHeaders }); + + try { + const SUPABASE_URL = Deno.env.get("SUPABASE_URL")!; + const SERVICE_ROLE = Deno.env.get("SUPABASE_SERVICE_ROLE_KEY")!; + const ANON_KEY = Deno.env.get("SUPABASE_PUBLISHABLE_KEY") ?? Deno.env.get("SUPABASE_ANON_KEY")!; + + const token = (req.headers.get("Authorization") ?? "").replace("Bearer ", ""); + if (!token) { + return new Response(JSON.stringify({ error: "Missing auth" }), { + status: 401, + headers: { ...corsHeaders, "Content-Type": "application/json" }, + }); + } + + const userClient = createClient(SUPABASE_URL, ANON_KEY, { + global: { headers: { Authorization: `Bearer ${token}` } }, + }); + const { data: userData } = await userClient.auth.getUser(); + if (!userData.user) { + return new Response(JSON.stringify({ error: "Unauthorized" }), { + status: 401, + headers: { ...corsHeaders, "Content-Type": "application/json" }, + }); + } + + const admin = createClient(SUPABASE_URL, SERVICE_ROLE); + const { data: isAdminData } = await admin.rpc("is_admin", { _user_id: userData.user.id }); + if (!isAdminData) { + return new Response(JSON.stringify({ error: "Forbidden" }), { + status: 403, + headers: { ...corsHeaders, "Content-Type": "application/json" }, + }); + } + + const { user_id, password } = (await req.json()) as { user_id: string; password: string }; + if (!user_id || !password || typeof password !== "string" || password.length < 10) { + return new Response( + JSON.stringify({ error: "Password must be at least 10 characters" }), + { status: 400, headers: { ...corsHeaders, "Content-Type": "application/json" } }, + ); + } + + const { error } = await admin.auth.admin.updateUserById(user_id, { password }); + if (error) { + return new Response(JSON.stringify({ error: error.message }), { + status: 400, + headers: { ...corsHeaders, "Content-Type": "application/json" }, + }); + } + + return new Response(JSON.stringify({ ok: true }), { + headers: { ...corsHeaders, "Content-Type": "application/json" }, + }); + } catch (e) { + return new Response(JSON.stringify({ error: (e as Error).message }), { + status: 500, + headers: { ...corsHeaders, "Content-Type": "application/json" }, + }); + } +});