feat: Implement first admin setup guard to lock registration after initial account creation

- Updated the registration flow to check if the first admin account has been created, preventing further public registrations.
- Introduced `is_first_admin_setup_available` function to determine registration availability.
- Modified the `/register` and `/login` pages to redirect based on the setup state.
- Enhanced the user creation process to handle internal admin accounts correctly.
- Added migration script to enforce the new registration rules in the database.
- Refactored chat API to improve message handling and context building.
- Updated dashboard and settings components for better state management.
- Improved error handling and user feedback across various components.
This commit is contained in:
Poyraz Avsever
2026-06-08 16:22:26 +03:00
parent cf8492db9e
commit 950522d467
15 changed files with 570 additions and 445 deletions
+96 -45
View File
@@ -1,6 +1,12 @@
import { createClient } from "@supabase/supabase-js";
import { NextResponse } from "next/server";
type SupabaseAdminUserResponse = {
id?: string;
email?: string;
message?: string;
error_description?: string;
};
export async function POST(request: Request) {
try {
const { email, password, client_id } = await request.json();
@@ -8,64 +14,109 @@ export async function POST(request: Request) {
if (!email || !password || !client_id) {
return NextResponse.json(
{ error: "Email, şifre ve müşteri ID gereklidir." },
{ status: 400 }
{ status: 400 },
);
}
// Initialize Supabase Admin client with service role key
const supabaseAdmin = createClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.SUPABASE_SERVICE_ROLE_KEY!
);
const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL;
const serviceRoleKey = process.env.SUPABASE_SERVICE_ROLE_KEY;
// 1. Create the user in auth.users
const { data: authData, error: authError } = await supabaseAdmin.auth.admin.createUser({
email,
password,
email_confirm: true,
if (!supabaseUrl || !serviceRoleKey) {
return NextResponse.json(
{ error: "Supabase service role ayarı eksik." },
{ status: 500 },
);
}
const userResponse = await fetch(`${supabaseUrl}/auth/v1/admin/users`, {
method: "POST",
headers: getServiceHeaders(serviceRoleKey),
body: JSON.stringify({
email,
password,
email_confirm: true,
app_metadata: {
internal_created: true,
role: "client",
},
}),
});
const userPayload = (await userResponse.json()) as SupabaseAdminUserResponse;
if (!userResponse.ok || !userPayload.id) {
return NextResponse.json(
{
error:
userPayload.message ||
userPayload.error_description ||
"Kullanıcı oluşturulamadı.",
},
{ status: 400 },
);
}
const userId = userPayload.id;
await patchRestRow({
supabaseUrl,
serviceRoleKey,
table: "profiles",
filter: `id=eq.${encodeURIComponent(userId)}`,
payload: { role: "client" },
});
if (authError || !authData.user) {
return NextResponse.json(
{ error: authError?.message || "Kullanıcı oluşturulamadı." },
{ status: 400 }
);
}
const clientResponse = await patchRestRow({
supabaseUrl,
serviceRoleKey,
table: "clients",
filter: `id=eq.${encodeURIComponent(client_id)}`,
payload: { client_auth_id: userId },
});
const userId = authData.user.id;
// 2. Wait for trigger to create the profile (it might take a fraction of a second, but usually synchronous in Postgres)
// We update the profile to set the role to 'client'
const { error: profileError } = await supabaseAdmin
.from("profiles")
.update({ role: "client" })
.eq("id", userId);
if (profileError) {
console.error("Profile update error:", profileError);
// Optional: Handle partial failure
}
// 3. Link the user to the client record
const { error: clientError } = await supabaseAdmin
.from("clients")
.update({ client_auth_id: userId })
.eq("id", client_id);
if (clientError) {
console.error("Client link error:", clientError);
if (!clientResponse.ok) {
return NextResponse.json(
{ error: "Kullanıcı oluşturuldu fakat müşteri kaydıyla ilişkilendirilemedi." },
{ status: 500 }
{ status: 500 },
);
}
return NextResponse.json({ success: true, user: authData.user });
} catch (err: any) {
console.error("Create client user error:", err);
return NextResponse.json({ success: true, user: userPayload });
} catch (error) {
console.error("Create client user error:", error);
return NextResponse.json(
{ error: "Sunucu tarafında beklenmeyen bir hata oluştu." },
{ status: 500 }
{ status: 500 },
);
}
}
function getServiceHeaders(serviceRoleKey: string) {
return {
apikey: serviceRoleKey,
authorization: `Bearer ${serviceRoleKey}`,
"content-type": "application/json",
};
}
function patchRestRow({
supabaseUrl,
serviceRoleKey,
table,
filter,
payload,
}: {
supabaseUrl: string;
serviceRoleKey: string;
table: string;
filter: string;
payload: Record<string, unknown>;
}) {
return fetch(`${supabaseUrl}/rest/v1/${table}?${filter}`, {
method: "PATCH",
headers: {
...getServiceHeaders(serviceRoleKey),
prefer: "return=minimal",
},
body: JSON.stringify(payload),
});
}