feat(frontend): move settings profile + password off Supabase to V2 Identity

Adds two authenticated gateway proxies and rewires the settings UI to them:
- POST /api/auth/password → Identity /v1/auth/password/change (server-side
  re-validates current password; drops the client-side re-auth round-trip)
- PATCH /api/profile → Identity PATCH /v1/users/me (full_name)

SettingsProfile and SettingsSecurity now fetch these routes instead of the
Supabase browser client.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
soroush.asadi
2026-05-30 06:06:47 +03:30
parent 6d2a296c38
commit 2adaf57f10
4 changed files with 131 additions and 34 deletions
@@ -2,7 +2,6 @@
import { useState } from "react";
import { User } from "lucide-react";
import { createClient } from "@/lib/supabase/client";
interface SettingsProfileProps {
email: string;
@@ -20,13 +19,22 @@ export function SettingsProfile({ email, displayName }: SettingsProfileProps) {
e.preventDefault();
setSaving(true);
setMessage(null);
const supabase = createClient();
const { error } = await supabase.auth.updateUser({ data: { full_name: name.trim() } });
setSaving(false);
if (error) {
setMessage({ type: "error", text: error.message });
} else {
setMessage({ type: "success", text: "Profile updated successfully." });
try {
const res = await fetch("/api/profile", {
method: "PATCH",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ full_name: name.trim() }),
});
const data = (await res.json().catch(() => null)) as { error?: string } | null;
if (!res.ok) {
setMessage({ type: "error", text: data?.error ?? "Could not update profile." });
} else {
setMessage({ type: "success", text: "Profile updated successfully." });
}
} catch {
setMessage({ type: "error", text: "Network error. Please try again." });
} finally {
setSaving(false);
}
}
@@ -2,7 +2,6 @@
import { useState } from "react";
import { Eye, EyeOff } from "lucide-react";
import { createClient } from "@/lib/supabase/client";
export function SettingsSecurity() {
const [current, setCurrent] = useState("");
@@ -26,32 +25,25 @@ export function SettingsSecurity() {
}
setSaving(true);
const supabase = createClient();
// Re-authenticate with current password first
const { data: session } = await supabase.auth.getSession();
const email = session.session?.user?.email;
if (!email) {
setMessage({ type: "error", text: "Session expired. Please sign in again." });
try {
// Identity re-validates the current password server-side, so no separate
// client-side re-authentication round-trip is needed.
const res = await fetch("/api/auth/password", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ current_password: current, new_password: next }),
});
const data = (await res.json().catch(() => null)) as { error?: string } | null;
if (!res.ok) {
setMessage({ type: "error", text: data?.error ?? "Could not change password." });
} else {
setMessage({ type: "success", text: "Password changed successfully." });
setCurrent(""); setNext(""); setConfirm("");
}
} catch {
setMessage({ type: "error", text: "Network error. Please try again." });
} finally {
setSaving(false);
return;
}
const { error: signInError } = await supabase.auth.signInWithPassword({ email, password: current });
if (signInError) {
setSaving(false);
setMessage({ type: "error", text: "Current password is incorrect." });
return;
}
const { error } = await supabase.auth.updateUser({ password: next });
setSaving(false);
if (error) {
setMessage({ type: "error", text: error.message });
} else {
setMessage({ type: "success", text: "Password changed successfully." });
setCurrent(""); setNext(""); setConfirm("");
}
}