mirror of
https://github.com/open-webui/open-webui.git
synced 2026-08-28 05:27:35 +00:00
perf(auth): make password hashing non-blocking and batch CSV user import (#25804)
Co-authored-by: Tim Baek <tim@openwebui.com>
This commit is contained in:
parent
18e6a10778
commit
6fdf9b4340
5 changed files with 33 additions and 17 deletions
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -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++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue