langgenius/dify · error · AccountNotFound

account_not_found

account_not_found

Error message

Account not found.

What it means

Raised by POST /console/api/forgot-password/resets when the reset token is valid and consumed, but no Account row matches the email stored in the token. The controller re-derives the account from reset_data['email'] via AccountService.get_account_by_email_with_case_fallback and, if it returns None, throws AccountNotFound (HTTP 400, code account_not_found). This indicates the account was deleted or its email changed between sending the reset email and submitting the new password.

Source

Thrown at api/controllers/console/auth/forgot_password.py:189

        if reset_data.get("phase", "") != "reset":
            raise InvalidTokenError()

        # Revoke token to prevent reuse
        AccountService.revoke_reset_password_token(req_data.token)

        # Generate secure salt and hash password
        salt = secrets.token_bytes(16)
        password_hashed = hash_password(req_data.new_password, salt)

        email = reset_data.get("email", "")
        account = AccountService.get_account_by_email_with_case_fallback(email, session=db.session())

        if account:
            account = db.session.merge(account)
            self._update_existing_account(account, password_hashed, salt)
            db.session.commit()
        else:
            raise AccountNotFound()

        return {"result": "success"}

    def _update_existing_account(self, account, password_hashed, salt):
        # Update existing account credentials
        account.password = base64.b64encode(password_hashed).decode()
        account.password_salt = base64.b64encode(salt).decode()

        # Create workspace if needed
        if (
            not TenantService.get_join_tenants(account, session=db.session())
            and FeatureService.is_workspace_creation_allowed()
        ):
            TenantService.create_owner_tenant(account, session=db.session())

View on GitHub (pinned to ef8544b173)

Solutions

  1. Verify the account still exists: query Account by the email the user is attempting to reset, and confirm the account was not deleted or had its email changed.
  2. Tell the user to request a fresh forgot-password email; the token they hold is bound to a now-absent account.
  3. If the account was deleted within the 30-day freeze window, the user must wait for the freeze to expire before re-registering; instruct them accordingly.
  4. Check Redis token store vs. relational DB consistency if this appears in bulk after a migration or backup restore.

Example fix

// This is server-side; no client fix possible for a missing account.
// Guard on the client by surfacing the error code:
// before
fetch('/console/api/forgot-password/resets', {...})
  .catch(e => alert('reset failed'))
// after
fetch('/console/api/forgot-password/resets', {...})
  .then(r => r.json()).then(body => {
    if (body.code === 'account_not_found') {
      showInfo('This account no longer exists. Please sign up again or contact your admin.')
    }
  })
Defensive patterns

Strategy: try-catch

Validate before calling

# Before calling the reset endpoint, confirm the account still exists
# (requires an authenticated admin/owner context; the public endpoint intentionally
# does not leak account existence). For an admin client:
resp = session.get(f'/console/api/workspaces/me/members', params={'email': email})
exists = any(m['email'] == email for m in resp.json().get('data', []))
if not exists:
    skip_reset()  # do not POST /forgot-password/resets

Type guard

null

Try / catch

try:
    resp = client.post('/console/api/forgot-password/resets', json=payload)
    resp.raise_for_status()
except HTTPError as e:
    body = e.response.json()
    if body.get('code') == 'account_not_found':
        # token email no longer maps to an account; ask user to re-register
        prompt_user_to_register()
    else:
        raise

Prevention

When it happens

Trigger: POST /console/api/forgot-password/resets with a valid phase='reset' token whose stored email no longer maps to any Account row. Happens when the user deletes their account after requesting a reset, an admin purges the account, the account email was changed, or the reset token was issued for an email that only ever existed in a different case-sensitivity state that the case-fallback lookup cannot recover.

Common situations: Account deleted during the reset flow window; email changed after requesting reset; testing against a stale DB with token data in Redis but no matching account; multi-instance setups where Redis token data outlives the relational DB row; race condition where a GDPR/CCPA deletion job runs between token issuance and reset.

Related errors


AI-assisted analysis of langgenius/dify@ef8544b173 (2026-08-12). Data as JSON: /api/errors/c66ad97684499bd7. Report an issue: GitHub.