From dd0ff39890172b4f5592e6126d7ac78b2ea0ad19 Mon Sep 17 00:00:00 2001 From: Gerhard Scheikl Date: Sun, 31 May 2026 13:23:43 +0200 Subject: [PATCH] fix(admin): return 404 (not 500) when deleting an already-removed user GoTrue returns an empty body for a missing-user delete, surfacing as an opaque JSON-parse error; pre-check existence via getUserById for a clean 404. --- app/api/admin/users/[id]/route.ts | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/app/api/admin/users/[id]/route.ts b/app/api/admin/users/[id]/route.ts index ece7dc5..5f3637c 100644 --- a/app/api/admin/users/[id]/route.ts +++ b/app/api/admin/users/[id]/route.ts @@ -100,16 +100,21 @@ export async function DELETE( const admin = getSupabaseAdmin(); + // Confirm the user exists up front. GoTrue replies with an empty body when + // deleting a non-existent user, which supabase-js surfaces as an opaque + // JSON-parse error (no status / "not found" text); a positive existence + // check lets us return a clean 404 instead of a misleading 500. + const { data: existing, error: lookupErr } = + await admin.auth.admin.getUserById(id); + if (lookupErr || !existing.user) { + return jsonNoStore({ error: 'user not found' }, { status: 404 }); + } + // Delete the AUTH USER first. Only if that succeeds do we remove the tunnel // row, so a mid-failure never leaves an orphaned auth user with a dangling // tunnel (or half-deletes the tunnel of an already-gone user). const { error: delErr } = await admin.auth.admin.deleteUser(id); if (delErr) { - const httpStatus = (delErr as { status?: number }).status; - const message = (delErr.message ?? '').toLowerCase(); - if (httpStatus === 404 || message.includes('not found')) { - return jsonNoStore({ error: 'user not found' }, { status: 404 }); - } console.error('admin user.delete: deleteUser failed', delErr); return jsonNoStore({ error: 'internal error' }, { status: 500 }); }