diff --git a/backend/open_webui/routers/auths.py b/backend/open_webui/routers/auths.py index 8ada514cc2..9b246b297b 100644 --- a/backend/open_webui/routers/auths.py +++ b/backend/open_webui/routers/auths.py @@ -700,8 +700,9 @@ async def signup_handler( Returns the newly created UserModel. Raises HTTPException on failure. """ - has_users = Users.has_users(db=db) - role = "admin" if not has_users else request.app.state.config.DEFAULT_USER_ROLE + # Insert with default role first to avoid TOCTOU race on first signup. + # If has_users() is checked before insert, concurrent requests during + # first-user registration can all see an empty table and each get admin. hashed = get_password_hash(password) user = Auths.insert_new_auth( @@ -709,12 +710,19 @@ async def signup_handler( password=hashed, name=name, profile_image_url=profile_image_url, - role=role, + role=request.app.state.config.DEFAULT_USER_ROLE, db=db, ) if not user: raise HTTPException(500, detail=ERROR_MESSAGES.CREATE_USER_ERROR) + # Atomically check if this is the only user *after* the insert. + # Only the single user present at this point should become admin. + if Users.get_num_users(db=db) == 1: + Users.update_user_role_by_id(user.id, "admin", db=db) + user = Users.get_user_by_id(user.id, db=db) + request.app.state.config.ENABLE_SIGNUP = False + if request.app.state.config.WEBHOOK_URL: await post_webhook( request.app.state.WEBUI_NAME, @@ -727,10 +735,6 @@ async def signup_handler( }, ) - if not has_users: - # Disable signup after the first user is created - request.app.state.config.ENABLE_SIGNUP = False - apply_default_group_assignment( request.app.state.config.DEFAULT_GROUP_ID, user.id,