From 6db3cca1ea134998e64abda2a0ffb39dbbea8d38 Mon Sep 17 00:00:00 2001 From: iammukeshm Date: Wed, 24 Jun 2026 14:47:49 +0530 Subject: [PATCH] fix(admin): correct identity register & change-password routes (405) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The admin app posted to /api/v1/identity/users/register and /api/v1/identity/users/change-password, but the API serves these as bare /api/v1/identity/register and /api/v1/identity/change-password (the /users prefix is only for user-collection sub-resources). The mismatch returned 405/404, so "Add new user" and "Change password" failed in the admin UI. The dashboard app already calls the correct bare routes, and the identity integration tests exercise them — only admin's users.ts was wrong, and it was internally inconsistent (it correctly used the IDENTITY base for forgot-password/reset-password/profile but the /users BASE for these two). Point both calls at the IDENTITY base and update the route-mocked Playwright specs to match. Fixes #1309 Co-Authored-By: Claude Opus 4.8 (1M context) --- clients/admin/src/api/users.ts | 4 ++-- clients/admin/tests/settings/security.spec.ts | 8 ++++---- clients/admin/tests/users/users.spec.ts | 8 ++++---- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/clients/admin/src/api/users.ts b/clients/admin/src/api/users.ts index ff0706f22c..e082822225 100644 --- a/clients/admin/src/api/users.ts +++ b/clients/admin/src/api/users.ts @@ -81,7 +81,7 @@ export async function changePassword(input: { newPassword: string; confirmNewPassword: string; }): Promise { - return apiFetch(`${BASE}/change-password`, { + return apiFetch(`${IDENTITY}/change-password`, { method: "POST", body: JSON.stringify(input), }); @@ -110,7 +110,7 @@ export async function getUserRoles(id: string): Promise { } export async function registerUser(input: RegisterUserInput): Promise { - return apiFetch(`${BASE}/register`, { + return apiFetch(`${IDENTITY}/register`, { method: "POST", body: JSON.stringify(input), }); diff --git a/clients/admin/tests/settings/security.spec.ts b/clients/admin/tests/settings/security.spec.ts index 37ca2b11ac..9cc1ce2555 100644 --- a/clients/admin/tests/settings/security.spec.ts +++ b/clients/admin/tests/settings/security.spec.ts @@ -5,7 +5,7 @@ import { installAdminShellMocks, ADMIN_PERMS } from "../helpers/shell-mocks"; // SecuritySettings = password change + 2FA. The 2FA branch is driven by the // profile's twoFactorEnabled flag (GET /api/v1/identity/profile). Password -// change POSTs to /api/v1/identity/users/change-password with +// change POSTs to /api/v1/identity/change-password with // { password, newPassword, confirmNewPassword }. const PROFILE_2FA_OFF = { @@ -55,7 +55,7 @@ test.describe("settings · security", () => { await mockJsonResponse(page, "**/api/v1/identity/profile", PROFILE_2FA_OFF); await mockJsonResponse( page, - "**/api/v1/identity/users/change-password", + "**/api/v1/identity/change-password", '"Password changed."', { method: "POST" }, ); @@ -75,7 +75,7 @@ test.describe("settings · security", () => { const reqPromise = page.waitForRequest( (r) => - r.url().includes("/api/v1/identity/users/change-password") && r.method() === "POST", + r.url().includes("/api/v1/identity/change-password") && r.method() === "POST", { timeout: 5_000 }, ); await dialog.getByRole("button", { name: /update password/i }).click(); @@ -135,7 +135,7 @@ test.describe("settings · security", () => { await mockJsonResponse(page, "**/api/v1/identity/profile", PROFILE_2FA_OFF); await mockProblemDetails( page, - "**/api/v1/identity/users/change-password", + "**/api/v1/identity/change-password", 400, { title: "Bad Request", detail: "Current password is incorrect." }, ); diff --git a/clients/admin/tests/users/users.spec.ts b/clients/admin/tests/users/users.spec.ts index 1c393ab0c9..3ee323e1d0 100644 --- a/clients/admin/tests/users/users.spec.ts +++ b/clients/admin/tests/users/users.spec.ts @@ -138,8 +138,8 @@ test.describe("users create form", () => { ).toBeVisible(); }); - test("filling the form and submitting POSTs to /users/register", async ({ page }) => { - await page.route("**/api/v1/identity/users/register", async (route) => { + test("filling the form and submitting POSTs to /register", async ({ page }) => { + await page.route("**/api/v1/identity/register", async (route) => { if (route.request().method() !== "POST") { await route.fallback(); return; @@ -179,7 +179,7 @@ test.describe("users create form", () => { await dialog.getByLabel(/^Confirm password/).fill("Sup3rSecret!"); const reqPromise = page.waitForRequest( - (r) => r.url().endsWith("/api/v1/identity/users/register") && r.method() === "POST", + (r) => r.url().endsWith("/api/v1/identity/register") && r.method() === "POST", { timeout: 5_000 }, ); await dialog.getByRole("button", { name: "Create account", exact: true }).click(); @@ -198,7 +198,7 @@ test.describe("users create form", () => { test("client-side validation blocks submit on an invalid username", async ({ page }) => { let posted = false; - await page.route("**/api/v1/identity/users/register", async (route) => { + await page.route("**/api/v1/identity/register", async (route) => { if (route.request().method() === "POST") posted = true; await route.fulfill({ status: 200,