toeverything/AFFiNE · error · EmailVerificationRequired

email_verification_required

email_verification_required

Error message

You must verify your email before accessing this resource.

What it means

The sendChangePasswordEmail mutation requires the authenticated user to have emailVerified=true; if user.emailVerified is false, EmailVerificationRequired is thrown before any token is minted or email sent. This prevents unverified accounts from triggering password-change flows, ensuring the account's email ownership is established first.

Source

Thrown at packages/backend/server/src/core/auth/resolver.ts:170

    await this.auth.sendNotificationChangeEmail(email);

    return user;
  }

  @Mutation(() => Boolean)
  async sendChangePasswordEmail(
    @CurrentUser() user: CurrentUser,
    @Args('callbackUrl') callbackUrl: string,
    @Args('email', {
      type: () => String,
      nullable: true,
      deprecationReason: 'fetched from signed in user',
    })
    _email: string | undefined,
    @Context() context: GraphqlContext
  ) {
    if (!user.emailVerified) {
      throw new EmailVerificationRequired();
    }

    const { token, expiresAt } =
      await this.models.verificationToken.createWithExpiresAt(
        TokenType.ChangePassword,
        user.id
      );

    const url = this.url.safeLink(callbackUrl, { userId: user.id, token });

    return await this.auth.sendChangePasswordEmail(
      user.email,
      url,
      this.mailMetadata(context, expiresAt)
    );
  }

  @Mutation(() => Boolean)

View on GitHub (pinned to 26c515e050)

Solutions

  1. Complete email verification first: call sendVerifyEmail and click the link to set emailVerified=true.
  2. If the user cannot verify (no access to inbox), an admin can mark the email verified out-of-band.
  3. Re-check that the user record's emailVerified column is set after verification completes.
  4. Guide the user through the verify-email flow before exposing the change-password UI.
Defensive patterns

Strategy: validation

Validate before calling

// guard the UI: only show 'change password' after email verification
if (!currentUser.emailVerified) {
  showVerifyEmailFirst();
  return;
}

Type guard

function isEmailVerificationRequired(err: unknown): boolean {
  return (
    !!err &&
    typeof err === 'object' &&
    (err as { code?: string }).code === 'email_verification_required'
  );
}

Try / catch

try {
  await sendChangePasswordEmail({ callbackUrl });
} catch (err) {
  if (isEmailVerificationRequired(err)) {
    redirectToVerifyEmail();
    return;
  }
  throw err;
}

Prevention

When it happens

Trigger: An authenticated but email-unverified user calls sendChangePasswordEmail. Common for accounts created via a method that skips verification, or that never completed the verify-email step.

Common situations: User signed up via a provider/admin path that didn't mark emailVerified. User hasn't clicked the verification email yet. Imported accounts where emailVerified wasn't set.

Related errors


AI-assisted analysis of toeverything/AFFiNE@26c515e050 (2026-08-12). Data as JSON: /api/errors/46b7c214aae75642. Report an issue: GitHub.