{"record":{"id":"6bc1924aeebc6270","repo":"infiniflow/ragflow","slug":"same-email-user-info-email-exists","errorCode":null,"errorMessage":"Same email: {user_info.email} exists!","messagePattern":"Same email: (.+?) exists!","errorType":"exception","errorClass":"Exception","httpStatus":302,"severity":"error","filePath":"api/apps/restful_apis/user_api.py","lineNumber":248,"sourceCode":"                    avatar = \"\"\n\n                users = user_register(\n                    user_id,\n                    {\n                        \"access_token\": get_uuid(),\n                        \"email\": user_info.email,\n                        \"avatar\": avatar,\n                        \"nickname\": user_info.nickname,\n                        \"login_channel\": channel,\n                        \"last_login_time\": get_format_time(),\n                        \"is_superuser\": False,\n                    },\n                )\n\n                if not users:\n                    raise Exception(f\"Failed to register {user_info.email}\")\n                if len(users) > 1:\n                    raise Exception(f\"Same email: {user_info.email} exists!\")\n\n                # Try to log in\n                user = users[0]\n                login_user(user)\n                return redirect(f\"/?auth={user.get_id()}\")\n\n            except Exception as e:\n                rollback_user_registration(user_id)\n                logging.exception(e)\n                return redirect(f\"/?error={str(e)}\")\n\n        # User exists, try to log in\n        user = users[0]\n        user.access_token = get_uuid()\n        if user and hasattr(user, \"is_active\") and user.is_active == \"0\":\n            return redirect(\"/?error=user_inactive\")\n\n        login_user(user)","sourceCodeStart":230,"sourceCodeEnd":266,"githubUrl":"https://github.com/infiniflow/ragflow/blob/554fb1133ac3861732235ad9c377eb5e0a770665/api/apps/restful_apis/user_api.py#L230-L266","documentation":"Raised during OAuth registration when the query after user_register() returns more than one row for the same email — the user table contains duplicates, violating the single-account-per-email assumption. Registration is rolled back and the browser is redirected to /?error=Same email ... exists!.","triggerScenarios":"OAuth callback for a new email where the users lookup returns len>1: pre-existing duplicate rows in the user table (from historical bugs or manual inserts) for that email.","commonSituations":"Databases migrated from older versions that allowed duplicates, concurrent OAuth callbacks for the same new email racing the insert, or test fixtures creating users with the same email.","solutions":["Deduplicate the user table: SELECT email, COUNT(*) FROM user GROUP BY email HAVING COUNT(*)>1, then merge/remove the extra rows.","Add/restore a unique constraint on user.email to prevent recurrence.","If caused by concurrent callbacks, retry login after the first transaction commits — the existing-user path will then log in.","Verify rollback_user_registration cleaned the half-inserted row."],"exampleFix":"-- find duplicates\nSELECT email, COUNT(*) FROM user GROUP BY email HAVING COUNT(*) > 1;\n-- keep one row per email, delete the others, then:\nALTER TABLE user ADD UNIQUE KEY uq_user_email (email);","handlingStrategy":"validation","validationCode":"def email_unique(email):\n    users = UserService.query(email=email)\n    return len(users) <= 1\n\nassert email_unique(user_info.email), f\"duplicate rows for {user_info.email} - dedupe user table\"","typeGuard":null,"tryCatchPattern":"try:\n    user = complete_oauth_registration(user_info)\nexcept Exception as e:\n    if \"Same email\" in str(e):\n        users = UserService.query(email=user_info.email)\n        if users:\n            login_user(users[0])  # recover by logging into the existing account\n        else:\n            raise","preventionTips":["Enforce a unique index on user.email in every environment.","After migrations, run a duplicate-email audit query.","Serialize concurrent first-login attempts for the same email (lock or single-flight)."],"tags":["auth","oauth","registration","duplicate-data"],"backgroundTag":null,"analyzedSha":"554fb1133ac3861732235ad9c377eb5e0a770665","analyzedAt":"2026-08-15T09:20:16.380Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}