From 2b33713c3e53d225da97234dc6d2336668065e62 Mon Sep 17 00:00:00 2001 From: Ryan Salazar Date: Fri, 8 May 2026 20:21:08 +0000 Subject: [PATCH] wp-login: use service_role admin client for createUser; /login redirects to /client-login - Add src/lib/supabase/admin.ts: server-only Supabase client constructed with SUPABASE_SERVICE_ROLE_KEY. Uses persistSession=false. Throws if the env var is missing. - src/app/api/auth/wp-login/route.ts: switch the auth.admin.createUser call (step 5, transparent migration on first login) to use the new admin client. The anon-key client cannot call admin.createUser, which would have made first-login attempts for legacy WP users fail at step 5 with a permission error. - src/app/login/page.tsx: replaced the old WP-style login form with a server-side redirect to /client-login so the new client-login is the canonical PR-firm/contributor entry point. Existing bookmarks for /login keep working. Coolify app env: SUPABASE_SERVICE_ROLE_KEY added (set out-of-band; not NEXT_PUBLIC_-prefixed so it never ships to the client bundle). --- src/app/api/auth/wp-login/route.ts | 7 +- src/app/login/page.tsx | 177 +---------------------------- src/lib/supabase/admin.ts | 23 ++++ 3 files changed, 32 insertions(+), 175 deletions(-) create mode 100644 src/lib/supabase/admin.ts diff --git a/src/app/api/auth/wp-login/route.ts b/src/app/api/auth/wp-login/route.ts index efb3c8a..ea0e1b9 100644 --- a/src/app/api/auth/wp-login/route.ts +++ b/src/app/api/auth/wp-login/route.ts @@ -1,5 +1,6 @@ import { NextRequest, NextResponse } from 'next/server'; import { createClient } from '@/lib/supabase/server'; +import { createAdminClient } from '@/lib/supabase/admin'; import { validatePhpassPassword } from '@/lib/auth/phpass'; export async function POST(request: NextRequest) { @@ -54,8 +55,10 @@ export async function POST(request: NextRequest) { } } - // 5. Create a new Supabase auth account for this legacy user (transparent migration) - const { data: signUpData, error: signUpError } = await supabase.auth.admin.createUser({ + // 5. Create a new Supabase auth account for this legacy user (transparent migration). + // admin.createUser requires the service_role key — use the admin client. + const admin = createAdminClient(); + const { data: signUpData, error: signUpError } = await admin.auth.admin.createUser({ email: email.toLowerCase().trim(), password, email_confirm: true, diff --git a/src/app/login/page.tsx b/src/app/login/page.tsx index 300a86b..740e45b 100644 --- a/src/app/login/page.tsx +++ b/src/app/login/page.tsx @@ -1,176 +1,7 @@ -"use client"; -import React, { useState, useEffect } from "react"; -import Link from "next/link"; -import { useRouter } from "next/navigation"; -import Header from "@/components/Header"; -import Footer from "@/components/Footer"; -import { createClient } from "@/lib/supabase/client"; +import { redirect } from "next/navigation"; +// /login is an alias for the canonical PR Firm / contributor login at +// /client-login. Server-side redirect so any existing bookmarks update. export default function LoginPage() { - const router = useRouter(); - const supabase = createClient(); - - const [email, setEmail] = useState(""); - const [password, setPassword] = useState(""); - const [loading, setLoading] = useState(false); - const [error, setError] = useState(null); - const [checkingAuth, setCheckingAuth] = useState(true); - - useEffect(() => { - supabase.auth.getUser().then(({ data }) => { - if (data?.user) { - router.replace("/account"); - } else { - setCheckingAuth(false); - } - }); - }, []); - - const handleLogin = async (e: React.FormEvent) => { - e.preventDefault(); - if (!email || !password) { - setError("Please enter your email and password."); - return; - } - setLoading(true); - setError(null); - try { - const { error: signInError } = await supabase.auth.signInWithPassword({ email, password }); - if (signInError) { - setError(signInError.message); - } else { - router.push("/account"); - router.refresh(); - } - } catch { - setError("An unexpected error occurred. Please try again."); - } finally { - setLoading(false); - } - }; - - if (checkingAuth) { - return ( -
-
-
-
-
-
-
- ); - } - - return ( -
-
- - {/* Breadcrumb */} -
-
- -
-
- -
-
- {/* Header */} -
-

- Sign In -

-

- Access your bookmarks, reading history, and personalized content. -

-
- - {/* Form Card */} -
-
- {/* Error */} - {error && ( -
- - {error} -
- )} - - {/* Email */} -
- - setEmail(e.target.value)} - placeholder="you@example.com" - disabled={loading} - className="w-full h-10 px-3 bg-[#0d0d0d] border border-[#2a2a2a] text-[#e0e0e0] font-body text-sm rounded-sm placeholder-[#444] focus:outline-none focus:border-[#3b82f6] focus-visible:ring-1 focus-visible:ring-[#3b82f6] transition-colors disabled:opacity-50" - /> -
- - {/* Password */} -
-
- -
- setPassword(e.target.value)} - placeholder="••••••••" - disabled={loading} - className="w-full h-10 px-3 bg-[#0d0d0d] border border-[#2a2a2a] text-[#e0e0e0] font-body text-sm rounded-sm placeholder-[#444] focus:outline-none focus:border-[#3b82f6] focus-visible:ring-1 focus-visible:ring-[#3b82f6] transition-colors disabled:opacity-50" - /> -
- - {/* Submit */} - -
- - {/* Divider */} -
-
- New to BroadcastBeat? -
-
- - {/* Register Link */} - - Create an Account - -
-
-
- -
- ); + redirect("/client-login"); } diff --git a/src/lib/supabase/admin.ts b/src/lib/supabase/admin.ts new file mode 100644 index 0000000..5c61b38 --- /dev/null +++ b/src/lib/supabase/admin.ts @@ -0,0 +1,23 @@ +import { createClient } from "@supabase/supabase-js"; + +/** + * Server-only Supabase client authenticated as `service_role`. Used for the + * admin auth API (createUser, deleteUser, listUsers). Bypasses RLS — never + * import this from client components. + * + * Needs SUPABASE_SERVICE_ROLE_KEY in the env (set as a Coolify app env var, + * NOT prefixed with NEXT_PUBLIC_ so it never ships to the client bundle). + */ +export function createAdminClient() { + const url = process.env.NEXT_PUBLIC_SUPABASE_URL; + const key = process.env.SUPABASE_SERVICE_ROLE_KEY; + if (!url || !key) { + throw new Error( + "createAdminClient: missing NEXT_PUBLIC_SUPABASE_URL or SUPABASE_SERVICE_ROLE_KEY", + ); + } + return createClient(url, key, { + auth: { persistSession: false, autoRefreshToken: false }, + db: { schema: process.env.NEXT_PUBLIC_SUPABASE_SCHEMA || "bb" }, + }); +}