toeverything/AFFiNE · warning · SameEmailProvided
same_email_provided
same_email_provided
Error message
You are trying to update your account email to the same as the old one.
What it means
Thrown by the changeEmail GraphQL mutation when the requested new email already belongs to a user record whose id equals the authenticated user's id. Category is 'invalid_input', code 'same_email_provided'. It is intentionally distinct from EmailAlreadyUsed (which fires when a DIFFERENT account owns the email) so the client can show a precise, non-alarming message and keep the form open.
Source
Thrown at packages/backend/server/src/core/auth/resolver.ts:278
const valid = await this.models.verificationToken.verify(
TokenType.ChangeEmail,
token,
{
credential: user.id,
}
);
if (!valid) {
throw new InvalidEmailToken();
}
const hasRegistered = await this.models.user.getUserByEmail(email);
if (hasRegistered) {
if (hasRegistered.id !== user.id) {
throw new EmailAlreadyUsed();
} else {
throw new SameEmailProvided();
}
}
const { token: verifyEmailToken, expiresAt } =
await this.models.verificationToken.createWithExpiresAt(
TokenType.VerifyEmail,
user.id
);
const url = this.url.safeLink(callbackUrl, {
token: verifyEmailToken,
email,
});
return await this.auth.sendVerifyChangeEmail(
email,
url,
this.mailMetadata(context, expiresAt)
);View on GitHub (pinned to 26c515e050)
Solutions
- Compare the new email to the cached current user's email in the client and short-circuit (do not submit) when they are equal.
- Disable the submit/save button until the field value differs from the current email.
- Treat the error as informational on the client: show 'This is already your email' rather than a retryable error.
Example fix
// before: submit unconditionally
await changeEmail({ token, email: form.email, callbackUrl });
// after: guard against no-op
if (form.email.trim().toLowerCase() === currentUser.email.toLowerCase()) {
setNotice('This is already your email.');
return;
}
await changeEmail({ token, email: form.email, callbackUrl }); Defensive patterns
Strategy: validation
Validate before calling
function isSameEmail(newEmail: string, currentEmail: string): boolean {
return newEmail.trim().toLowerCase() === currentEmail.trim().toLowerCase();
}
// before calling changeEmail:
if (isSameEmail(form.email, currentUser.email)) {
setNotice('This is already your email.');
return;
} Type guard
function isEmailChangeValid(newEmail: string, currentEmail: string): newEmail is string {
return !!newEmail && !isSameEmail(newEmail, currentEmail);
} Prevention
- Pre-fill the email field but disable submit until the value differs from the current email.
- Normalize both emails (trim + lowercase) before comparing.
- After a successful change, update the cached current email immediately to prevent stale comparisons.
When it happens
Trigger: Calling changeEmail (resolver.ts:272-279) with an `email` argument that, after getUserByEmail lookup, resolves to a row where hasRegistered.id === user.id. The token must already be valid (ChangeEmail token verified) before this check runs.
Common situations: A profile page that pre-fills the email field with the current address and the user submits without editing; stale form state after the email was already changed in another tab; a copy-paste of the existing address into the 'new email' box.
Related errors
AI-assisted analysis of toeverything/AFFiNE@26c515e050 (2026-08-12).
Data as JSON: /api/errors/24de72fbbc2bfdb7.
Report an issue: GitHub.