{"record":{"id":"46b7c214aae75642","repo":"toeverything/AFFiNE","slug":"email-verification-required","errorCode":"email_verification_required","errorMessage":"You must verify your email before accessing this resource.","messagePattern":"You must verify your email before accessing this resource\\.","errorType":"exception","errorClass":"EmailVerificationRequired","httpStatus":403,"severity":"error","filePath":"packages/backend/server/src/core/auth/resolver.ts","lineNumber":170,"sourceCode":"    await this.auth.sendNotificationChangeEmail(email);\n\n    return user;\n  }\n\n  @Mutation(() => Boolean)\n  async sendChangePasswordEmail(\n    @CurrentUser() user: CurrentUser,\n    @Args('callbackUrl') callbackUrl: string,\n    @Args('email', {\n      type: () => String,\n      nullable: true,\n      deprecationReason: 'fetched from signed in user',\n    })\n    _email: string | undefined,\n    @Context() context: GraphqlContext\n  ) {\n    if (!user.emailVerified) {\n      throw new EmailVerificationRequired();\n    }\n\n    const { token, expiresAt } =\n      await this.models.verificationToken.createWithExpiresAt(\n        TokenType.ChangePassword,\n        user.id\n      );\n\n    const url = this.url.safeLink(callbackUrl, { userId: user.id, token });\n\n    return await this.auth.sendChangePasswordEmail(\n      user.email,\n      url,\n      this.mailMetadata(context, expiresAt)\n    );\n  }\n\n  @Mutation(() => Boolean)","sourceCodeStart":152,"sourceCodeEnd":188,"githubUrl":"https://github.com/toeverything/AFFiNE/blob/b4c8548c09da21b2898443559a5b846f0ccf5dd8/packages/backend/server/src/core/auth/resolver.ts#L152-L188","documentation":"Thrown by sendChangePasswordEmail when the signed-in user has user.emailVerified === false. The server refuses to issue a ChangePassword token for an unverified identity, since the reset flow assumes control of the address was already proven.","triggerScenarios":"Calling the sendChangePasswordEmail mutation while authenticated as a user whose emailVerified flag is false (never completed verifyEmail).","commonSituations":"User registered but never clicked the verification link; dev/test seeded users created without the verified flag; the original verification email failed to send or landed in spam.","solutions":["Complete email verification first: call sendVerifyEmail and then verifyEmail with the token from the link","When seeding dev users, set emailVerified/emailVerifiedAt directly so test flows skip this gate","Check earlier steps actually delivered the verification email (mail provider config, SMTP credentials)"],"exampleFix":"// before\nawait client.request(sendChangePasswordEmailMutation, { callbackUrl });\n\n// after\nconst me = await client.request(currentUserQuery);\nif (!me.me.emailVerified) {\n  throw new Error('Verify your email address first');\n}\nawait client.request(sendChangePasswordEmailMutation, { callbackUrl });","handlingStrategy":"validation","validationCode":"const { me } = await client.request(currentUserQuery);\nif (!me.emailVerified) {\n  router.push('/verify-email');\n} else {\n  await client.request(sendChangePasswordEmailMutation, { callbackUrl });\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Gate password-change UI on currentUser.emailVerified","Send the verification email immediately after sign-up so the flag gets set early","Seed dev/test users with emailVerified true unless the test targets this error"],"tags":["auth","email-verification","graphql"],"backgroundTag":"email-verification-required","analyzedSha":"b4c8548c09da21b2898443559a5b846f0ccf5dd8","analyzedAt":"2026-08-18T21:16:52.546Z","contentChangedAt":"2026-08-18T21:16:52.546Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}