fix: clearer error when changing email to one already used for auth (#1569)

This commit is contained in:
Konsti Wohlwend 2026-06-15 13:55:26 -07:00 committed by GitHub
parent 72456d3748
commit 5be2160021
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 59 additions and 8 deletions

View File

@ -254,7 +254,7 @@ async function checkAuthData(
});
if (existingChannelUsedForAuth) {
throw new KnownErrors.UserWithEmailAlreadyExists(data.primaryEmail);
throw new KnownErrors.ContactChannelAlreadyUsedForAuthBySomeoneElse("email", data.primaryEmail);
}
}
}

View File

@ -2,7 +2,7 @@ import { useAdminApp } from "@/app/(main)/(protected)/projects/[projectId]/use-a
import { ServerUser } from "@hexclave/next";
import { KnownErrors } from "@hexclave/shared";
import { countryCodeSchema, emailSchema, jsonStringOrEmptySchema, passwordSchema } from "@hexclave/shared/dist/schema-fields";
import { Accordion, AccordionContent, AccordionItem, AccordionTrigger, Button, Typography, useToast } from "@/components/ui";
import { Accordion, AccordionContent, AccordionItem, AccordionTrigger, Button, Typography } from "@/components/ui";
import * as yup from "yup";
import { FormDialog } from "./form-dialog";
import { CountryCodeField } from "./country-code-select";
@ -22,7 +22,6 @@ export function UserDialog(props: {
type: 'edit',
user: ServerUser,
})) {
const { toast } = useToast();
const adminApp = useAdminApp();
const project = adminApp.useProject();
@ -128,13 +127,14 @@ export function UserDialog(props: {
}
} catch (error) {
if (KnownErrors.UserWithEmailAlreadyExists.isInstance(error)) {
toast({
title: "Email already exists",
description: "Please choose a different email address",
variant: "destructive",
});
alert("Email already exists. Please choose a different email address.");
return 'prevent-close';
}
if (KnownErrors.ContactChannelAlreadyUsedForAuthBySomeoneElse.isInstance(error)) {
alert("Email already used for authentication. This email is already used for sign-in by another account. Please choose a different email address.");
return 'prevent-close';
}
throw error;
}
}

View File

@ -433,6 +433,57 @@ describe("updating primary_email via users/me endpoint", () => {
expect(updateResponse.body.primary_email).toBe(newMailbox.emailAddress);
expect(updateResponse.body.primary_email_auth_enabled).toBe(true);
});
it("should return a clear error when changing primary_email to an email already used for auth by another user", async ({ expect }) => {
// Create first user with email used for auth (via OTP sign-in)
await Auth.Otp.signIn();
const firstUserEmail = (await niceBackendFetch("/api/v1/users/me", {
accessType: "client",
})).body.primary_email;
// Create second user via server
const secondMailbox = createMailbox();
const createResponse = await niceBackendFetch("/api/v1/users", {
accessType: "server",
method: "POST",
body: {
primary_email: secondMailbox.emailAddress,
primary_email_auth_enabled: true,
},
});
expect(createResponse.status).toBe(201);
const secondUserId = createResponse.body.id;
// Try to change second user's primary_email to first user's email (with auth enabled)
const updateResponse = await niceBackendFetch(`/api/v1/users/${secondUserId}`, {
accessType: "server",
method: "PATCH",
body: {
primary_email: firstUserEmail,
primary_email_auth_enabled: true,
},
});
// Should get a clear error about the email being used for auth by another account
expect(updateResponse).toMatchInlineSnapshot(`
NiceResponse {
"status": 409,
"body": {
"code": "CONTACT_CHANNEL_ALREADY_USED_FOR_AUTH_BY_SOMEONE_ELSE",
"details": {
"contact_channel_value": "default-mailbox--<stripped UUID>@stack-generated.example.com",
"type": "email",
"would_work_if_email_was_verified": false,
},
"error": "This email \\"(default-mailbox--<stripped UUID>@stack-generated.example.com)\\" is already used for authentication by another account.",
},
"headers": Headers {
"x-stack-known-error": "CONTACT_CHANNEL_ALREADY_USED_FOR_AUTH_BY_SOMEONE_ELSE",
<some fields may have been hidden>,
},
}
`);
});
});
describe("edge cases", () => {