diff --git a/backend/open_webui/routers/auths.py b/backend/open_webui/routers/auths.py index c52d1d279c..608128e0c6 100644 --- a/backend/open_webui/routers/auths.py +++ b/backend/open_webui/routers/auths.py @@ -379,7 +379,7 @@ async def update_password( validate_password(form_data.new_password) except Exception as e: raise HTTPException(400, detail=str(e)) - hashed = get_password_hash(form_data.new_password) + hashed = await get_password_hash(form_data.new_password) success = await Auths.update_user_password_by_id(user.id, hashed, db=db) if success: await publish_event( @@ -786,7 +786,7 @@ async def signup_handler( # 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) + hashed = await get_password_hash(password) user = await Auths.insert_new_auth( email=email.lower(), @@ -1040,7 +1040,7 @@ async def add_user( except Exception as e: raise HTTPException(400, detail=str(e)) - hashed = get_password_hash(form_data.password) + hashed = await get_password_hash(form_data.password) user = await Auths.insert_new_auth( form_data.email.lower(), hashed, diff --git a/backend/open_webui/routers/users.py b/backend/open_webui/routers/users.py index c98eb2d6da..f93a11fc4c 100644 --- a/backend/open_webui/routers/users.py +++ b/backend/open_webui/routers/users.py @@ -642,7 +642,7 @@ async def update_user_by_id( except Exception as e: raise HTTPException(400, detail=str(e)) - hashed = get_password_hash(form_data.password) + hashed = await get_password_hash(form_data.password) await Auths.update_user_password_by_id(user_id, hashed, db=db) # Build update dict from only the provided fields diff --git a/backend/open_webui/utils/auth.py b/backend/open_webui/utils/auth.py index e7a9a0ad1b..b379328db1 100644 --- a/backend/open_webui/utils/auth.py +++ b/backend/open_webui/utils/auth.py @@ -158,9 +158,11 @@ def get_license_data(app, key): bearer_security = HTTPBearer(auto_error=False) -def get_password_hash(password: str) -> str: - """Hash a password using bcrypt""" - return bcrypt.hashpw(password.encode('utf-8'), bcrypt.gensalt()).decode('utf-8') +async def get_password_hash(password: str) -> str: + """Hash a password using bcrypt in a thread pool (non-blocking).""" + import asyncio + + return (await asyncio.to_thread(bcrypt.hashpw, password.encode('utf-8'), bcrypt.gensalt())).decode('utf-8') def validate_password(password: str) -> bool: @@ -486,7 +488,7 @@ async def create_admin_user(email: str, password: str, name: str = 'Admin'): log.info(f'Creating admin account from environment variables: {email}') try: - hashed = get_password_hash(password) + hashed = await get_password_hash(password) user = await Auths.insert_new_auth( email=email.lower(), password=hashed, diff --git a/backend/open_webui/utils/oauth.py b/backend/open_webui/utils/oauth.py index 2ffb209689..8e2b5e1825 100644 --- a/backend/open_webui/utils/oauth.py +++ b/backend/open_webui/utils/oauth.py @@ -1871,7 +1871,7 @@ class OAuthManager: user = await Auths.insert_new_auth( email=email, - password=get_password_hash(str(uuid.uuid4())), # Random password, not used + password=await get_password_hash(str(uuid.uuid4())), # Random password, not used name=name, profile_image_url=picture_url, role=await self.get_user_role(None, user_data), diff --git a/src/lib/components/admin/Users/UserList/AddUserModal.svelte b/src/lib/components/admin/Users/UserList/AddUserModal.svelte index 84cfd4ffb6..19454e4607 100644 --- a/src/lib/components/admin/Users/UserList/AddUserModal.svelte +++ b/src/lib/components/admin/Users/UserList/AddUserModal.svelte @@ -72,7 +72,7 @@ const csv = e.target.result; const rows = csv.split('\n'); - let userCount = 0; + const validRows = []; for (const [idx, row] of rows.entries()) { const columns = row.split(',').map((col) => col.trim()); @@ -83,7 +83,21 @@ columns.length === 4 && ['admin', 'user', 'pending'].includes(columns[3].toLowerCase()) ) { - const res = await addUser( + validRows.push({ idx, columns }); + } else { + toast.error(`Row ${idx + 1}: invalid format.`); + } + } + } + + let userCount = 0; + const BATCH_SIZE = 10; + + for (let i = 0; i < validRows.length; i += BATCH_SIZE) { + const batch = validRows.slice(i, i + BATCH_SIZE); + const results = await Promise.allSettled( + batch.map(({ idx, columns }) => + addUser( localStorage.token, columns[0], columns[1], @@ -93,13 +107,13 @@ ).catch((error) => { toast.error(`Row ${idx + 1}: ${error}`); return null; - }); + }) + ) + ); - if (res) { - userCount = userCount + 1; - } - } else { - toast.error(`Row ${idx + 1}: invalid format.`); + for (const result of results) { + if (result.status === 'fulfilled' && result.value) { + userCount++; } } }