{"record":{"id":"6946934a9e263ce1","repo":"toeverything/AFFiNE","slug":"email-already-used","errorCode":"email_already_used","errorMessage":"This email has already been registered.","messagePattern":"This email has already been registered\\.","errorType":"exception","errorClass":"EmailAlreadyUsed","httpStatus":400,"severity":"error","filePath":"packages/backend/server/src/core/auth/resolver.ts","lineNumber":276,"sourceCode":"\n    validators.assertValidEmail(email);\n    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,","sourceCodeStart":258,"sourceCodeEnd":294,"githubUrl":"https://github.com/toeverything/AFFiNE/blob/26c515e050211269e911f7d9cfe162a26c83ed98/packages/backend/server/src/core/auth/resolver.ts#L258-L294","documentation":"In sendVerifyChangeEmail, after the ChangeEmail token verifies, the server checks whether the requested new email is already registered via models.user.getUserByEmail(email). If a user exists and its id differs from the current user.id, EmailAlreadyUsed is thrown — the new email belongs to someone else and cannot be claimed. (If it belongs to the same user, SameEmailProvided is thrown instead.) This prevents hijacking or merging onto an existing account.","triggerScenarios":"The user attempts to change their email to an address that is already the primary email of a DIFFERENT user account. The lookup returns a user whose id !== currentUser.id.","commonSituations":"User mistyped the new email as one already in use by another account. Two accounts with overlapping intended addresses. The new email was previously registered and abandoned but not deleted.","solutions":["Choose a new email address that is not registered to any other account.","If the user owns the other account, sign into it instead, or have an admin delete/merge accounts out-of-band.","Suggest the user verify the new email spelling before submitting.","If the existing row is a stale duplicate, an admin can remove it to free the address."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"// client pre-check: warn if the new email looks taken (requires a lookup API)\n// otherwise validate format and uniqueness intent before submit\nif (newEmail === currentUser.email) {\n  showUser('This is already your email.');\n  return;\n}","typeGuard":"function isEmailAlreadyUsed(err: unknown): boolean {\n  return (\n    !!err &&\n    typeof err === 'object' &&\n    (err as { code?: string }).code === 'email_already_used'\n  );\n}","tryCatchPattern":"try {\n  await sendVerifyChangeEmail({ token, email: newEmail, callbackUrl });\n} catch (err) {\n  if (isEmailAlreadyUsed(err)) {\n    showUser('That email is already registered to another account.');\n    return;\n  }\n  throw err;\n}","preventionTips":["Prompt the user to pick a new email that is not already in use.","If the user owns the other account, sign into it instead.","Have admins remove stale duplicate rows to free addresses.","Validate email format before submitting the change."],"tags":["auth","email-change","uniqueness","conflict","graphql"],"backgroundTag":null,"analyzedSha":"26c515e050211269e911f7d9cfe162a26c83ed98","analyzedAt":"2026-08-12T13:15:16.447Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}