{"record":{"id":"24de72fbbc2bfdb7","repo":"toeverything/AFFiNE","slug":"same-email-provided","errorCode":"same_email_provided","errorMessage":"You are trying to update your account email to the same as the old one.","messagePattern":"You are trying to update your account email to the same as the old one\\.","errorType":"exception","errorClass":"SameEmailProvided","httpStatus":400,"severity":"warning","filePath":"packages/backend/server/src/core/auth/resolver.ts","lineNumber":278,"sourceCode":"    const valid = await this.models.verificationToken.verify(\n      TokenType.ChangeEmail,\n      token,\n      {\n        credential: user.id,\n      }\n    );\n\n    if (!valid) {\n      throw new InvalidEmailToken();\n    }\n\n    const hasRegistered = await this.models.user.getUserByEmail(email);\n\n    if (hasRegistered) {\n      if (hasRegistered.id !== user.id) {\n        throw new EmailAlreadyUsed();\n      } else {\n        throw new SameEmailProvided();\n      }\n    }\n\n    const { token: verifyEmailToken, expiresAt } =\n      await this.models.verificationToken.createWithExpiresAt(\n        TokenType.VerifyEmail,\n        user.id\n      );\n\n    const url = this.url.safeLink(callbackUrl, {\n      token: verifyEmailToken,\n      email,\n    });\n    return await this.auth.sendVerifyChangeEmail(\n      email,\n      url,\n      this.mailMetadata(context, expiresAt)\n    );","sourceCodeStart":260,"sourceCodeEnd":296,"githubUrl":"https://github.com/toeverything/AFFiNE/blob/26c515e050211269e911f7d9cfe162a26c83ed98/packages/backend/server/src/core/auth/resolver.ts#L260-L296","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before: submit unconditionally\nawait changeEmail({ token, email: form.email, callbackUrl });\n\n// after: guard against no-op\nif (form.email.trim().toLowerCase() === currentUser.email.toLowerCase()) {\n  setNotice('This is already your email.');\n  return;\n}\nawait changeEmail({ token, email: form.email, callbackUrl });","handlingStrategy":"validation","validationCode":"function isSameEmail(newEmail: string, currentEmail: string): boolean {\n  return newEmail.trim().toLowerCase() === currentEmail.trim().toLowerCase();\n}\n// before calling changeEmail:\nif (isSameEmail(form.email, currentUser.email)) {\n  setNotice('This is already your email.');\n  return;\n}","typeGuard":"function isEmailChangeValid(newEmail: string, currentEmail: string): newEmail is string {\n  return !!newEmail && !isSameEmail(newEmail, currentEmail);\n}","tryCatchPattern":null,"preventionTips":["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."],"tags":["auth","email","validation","graphql"],"backgroundTag":null,"analyzedSha":"26c515e050211269e911f7d9cfe162a26c83ed98","analyzedAt":"2026-08-12T13:15:16.447Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}