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
+55
View File
@@ -0,0 +1,55 @@
import { cookies } from "next/headers";
import { NextResponse } from "next/server";
import { gatewayFetch } from "@/lib/api/gateway";
import { ACCESS_TOKEN_COOKIE } from "@/lib/auth/constants";
export const dynamic = "force-dynamic";
/**
* Change the signed-in user's password via Identity (`/v1/auth/password/change`).
* Identity re-validates the current password server-side, so no client-side
* re-authentication round-trip is needed.
*/
export async function POST(req: Request) {
const token = (await cookies()).get(ACCESS_TOKEN_COOKIE)?.value;
if (!token) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
const body = await req.json().catch(() => null);
const currentPassword = body?.current_password;
const newPassword = body?.new_password;
if (!currentPassword || !newPassword) {
return NextResponse.json(
{ error: "Current and new password are required." },
{ status: 400 }
);
}
if (typeof newPassword !== "string" || newPassword.length < 8) {
return NextResponse.json(
{ error: "New password must be at least 8 characters." },
{ status: 400 }
);
}
const res = await gatewayFetch("/v1/auth/password/change", {
method: "POST",
headers: { Authorization: `Bearer ${token}` },
body: JSON.stringify({
current_password: currentPassword,
new_password: newPassword,
}),
});
if (!res.ok) {
const data = await res.json().catch(() => null);
const message =
res.status === 400 || res.status === 401
? (data?.message ?? "Current password is incorrect.")
: (data?.message ?? "Could not change password.");
return NextResponse.json({ error: message }, { status: res.status });
}
return NextResponse.json({ ok: true });
}
+42
View File
@@ -0,0 +1,42 @@
import { cookies } from "next/headers";
import { NextResponse } from "next/server";
import { gatewayFetch } from "@/lib/api/gateway";
import { ACCESS_TOKEN_COOKIE } from "@/lib/auth/constants";
export const dynamic = "force-dynamic";
/**
* Update the signed-in user's profile via Identity (`PATCH /v1/users/me`).
* Currently surfaces the display name (full_name); the Identity DTO accepts more
* fields that can be added here as the settings UI grows.
*/
export async function PATCH(req: Request) {
const token = (await cookies()).get(ACCESS_TOKEN_COOKIE)?.value;
if (!token) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
const body = await req.json().catch(() => null);
const fullName = typeof body?.full_name === "string" ? body.full_name.trim() : undefined;
if (fullName === undefined) {
return NextResponse.json({ error: "Nothing to update" }, { status: 400 });
}
const res = await gatewayFetch("/v1/users/me", {
method: "PATCH",
headers: { Authorization: `Bearer ${token}` },
body: JSON.stringify({ full_name: fullName }),
});
if (!res.ok) {
const data = await res.json().catch(() => null);
return NextResponse.json(
{ error: data?.message ?? "Could not update profile." },
{ status: res.status }
);
}
const user = await res.json().catch(() => null);
return NextResponse.json({ user });
}