{"record":{"id":"c66ad97684499bd7","repo":"langgenius/dify","slug":"account-not-found","errorCode":"account_not_found","errorMessage":"Account not found.","messagePattern":"Account not found\\.","errorType":"error_code","errorClass":"AccountNotFound","httpStatus":400,"severity":"error","filePath":"api/controllers/console/auth/forgot_password.py","lineNumber":189,"sourceCode":"        if reset_data.get(\"phase\", \"\") != \"reset\":\n            raise InvalidTokenError()\n\n        # Revoke token to prevent reuse\n        AccountService.revoke_reset_password_token(req_data.token)\n\n        # Generate secure salt and hash password\n        salt = secrets.token_bytes(16)\n        password_hashed = hash_password(req_data.new_password, salt)\n\n        email = reset_data.get(\"email\", \"\")\n        account = AccountService.get_account_by_email_with_case_fallback(email, session=db.session())\n\n        if account:\n            account = db.session.merge(account)\n            self._update_existing_account(account, password_hashed, salt)\n            db.session.commit()\n        else:\n            raise AccountNotFound()\n\n        return {\"result\": \"success\"}\n\n    def _update_existing_account(self, account, password_hashed, salt):\n        # Update existing account credentials\n        account.password = base64.b64encode(password_hashed).decode()\n        account.password_salt = base64.b64encode(salt).decode()\n\n        # Create workspace if needed\n        if (\n            not TenantService.get_join_tenants(account, session=db.session())\n            and FeatureService.is_workspace_creation_allowed()\n        ):\n            TenantService.create_owner_tenant(account, session=db.session())\n","sourceCodeStart":171,"sourceCodeEnd":204,"githubUrl":"https://github.com/langgenius/dify/blob/ef8544b173fd6cd7a8e71df2cab576e52bebbfbc/api/controllers/console/auth/forgot_password.py#L171-L204","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["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.","Tell the user to request a fresh forgot-password email; the token they hold is bound to a now-absent account.","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.","Check Redis token store vs. relational DB consistency if this appears in bulk after a migration or backup restore."],"exampleFix":"// This is server-side; no client fix possible for a missing account.\n// Guard on the client by surfacing the error code:\n// before\nfetch('/console/api/forgot-password/resets', {...})\n  .catch(e => alert('reset failed'))\n// after\nfetch('/console/api/forgot-password/resets', {...})\n  .then(r => r.json()).then(body => {\n    if (body.code === 'account_not_found') {\n      showInfo('This account no longer exists. Please sign up again or contact your admin.')\n    }\n  })","handlingStrategy":"try-catch","validationCode":"# Before calling the reset endpoint, confirm the account still exists\n# (requires an authenticated admin/owner context; the public endpoint intentionally\n# does not leak account existence). For an admin client:\nresp = session.get(f'/console/api/workspaces/me/members', params={'email': email})\nexists = any(m['email'] == email for m in resp.json().get('data', []))\nif not exists:\n    skip_reset()  # do not POST /forgot-password/resets","typeGuard":"null","tryCatchPattern":"try:\n    resp = client.post('/console/api/forgot-password/resets', json=payload)\n    resp.raise_for_status()\nexcept HTTPError as e:\n    body = e.response.json()\n    if body.get('code') == 'account_not_found':\n        # token email no longer maps to an account; ask user to re-register\n        prompt_user_to_register()\n    else:\n        raise","preventionTips":["Treat a forgotten-password request as ephemeral; if the user delayed completing it, re-request a fresh token.","When deleting accounts, ensure any in-flight reset tokens in Redis are also revoked.","Do not persist reset tokens client-side beyond the immediate session."],"tags":["auth","account","forgot-password","token","data-consistency"],"backgroundTag":null,"analyzedSha":"ef8544b173fd6cd7a8e71df2cab576e52bebbfbc","analyzedAt":"2026-08-12T05:15:17.394Z","schemaVersion":2},"datasetVersion":"2026-08-12T13:17:24.610Z"}