การยืนยันตัวตนและความปลอดภัย
Libre WebUI ใช้บัญชีผู้ใช้ในเครื่องกับเซสชัน JWT การติดตั้งใหม่จะอนุญาตให้สร้างผู้ดูแลระบบในเครื่องคนแรกเสมอ และปิดการลงทะเบียนสาธารณะสำหรับบัญชีในเครื่องหรือ OAuth ที่ตามมาโดยค่าเริ่มต้น
การตั้งค่าครั้งแรก
เมื่อฐานข้อมูลยังไม่มีผู้ใช้:
- Libre WebUI แสดงขั้นตอนการตั้งค่าครั้งแรก
- ผู้ใช้สร้างบัญชีในเครื่องบัญชีแรก
- บัญชีได้รับบทบาท
admin - การลงทะเบียนสาธารณะครั้งต่อไปยังปิดอยู่ เว้นแต่เปิดอย่างชัดเจน
ฐานข้อมูลเดิมจะคงผู้ใช้และบทบาทปัจจุบันไว้
บัญชีในเครื่อง
การสมัครในเครื่องต้องมี:
- ชื่อผู้ใช้
- รหัสผ่านระหว่าง 12 อักขระถึง 72 ไบต์ UTF-8 มีตัวพิมพ์ใหญ่ ตัวพิมพ์เล็ก และตัวเลข
- อีเมลที่ไม่บังคับ
รหัสผ่านจะ hash ด้วย bcrypt ก่อนเก็บ เส้นทางเข้าสู่ระบบและสมัครถูกจำกัดอัตรา
การอนุมัติการลงทะเบียน
การลงทะเบียนสาธารณะไม่ได้ให้สิทธิ์เอง ทุกบัญชีที่สร้างผ่านแบบฟอร์มสาธารณะหรือผู้ให้บริการ OAuth เริ่มในสถานะ pending และต้องได้รับอนุมัติจากผู้ดูแลก่อนเข้าสู่ระบบ
ข้อยกเว้นคือบัญชีเริ่มต้น บัญชีจริงบัญชีแรกในฐานข้อมูลว่างจะสร้างแบบ atomic เป็น active พร้อมบทบาท admin เพื่อให้การติดตั้งใหม่มีผู้ดูแลที่ใช้งานได้ การลงทะเบียนหลังจากนั้นรอการตรวจ
สิ่งที่ผู้ใช้รออนุมัติเห็น:
- สมัครสำเร็จแต่ไม่ส่ง token เซสชัน API ตอบ
202พร้อมapprovalRequired: trueและ UI อธิบายว่าผู้ดูแลต้องอนุมัติบัญชี - การเข้าสู่ระบบด้วยรหัสผ่านที่ถูกต้องถูกปฏิเสธด้วย
403และรหัสACCOUNT_PENDING("บัญชีของคุณกำลังรอผู้ดูแลอนุมัติ") การเข้าสู่ระบบ OAuth เปลี่ยนกลับหน้าล็อกอินด้วย?approval=pending - ระบบอ่านสถานะบัญชีใหม่จากฐานข้อมูลทุกคำขอที่ยืนยันตัวตน เซสชันจึงอยู่เกินสถานะ
activeของบัญชีไม่ได้
สิ่งที่ผู้ดูแลเห็น:
- การจัดการผู้ใช้แสดงการ์ด การอนุมัติที่รอ พร้อมบัญชีรอ แต่ละบัญชีมี เปิดใช้บัญชี และการปฏิเสธ การปฏิเสธคือการลบ ไม่มีสถานะพักแยก
- ผู้ดูแลรับแจ้งในแอปขณะเข้าสู่ระบบ ด้วยป้ายที่รายการผู้ใช้และ toast เมื่อมีการสมัครใหม่ สรุปการอนุมัติรอถูกตรวจประมาณนาทีละครั้ง (
GET /api/users/pending-approvals, เฉพาะผู้ดูแล) - การอนุมัติ (
PATCH /api/users/:id/approve, เฉพาะผู้ดูแล) บันทึกผู้อนุมัติและเวลา ไม่เปลี่ยนบทบาท บัญชีที่อนุมัติยังเป็นuserจนผู้ดูแลเลื่อนขั้น มีผลในการเข้าสู่ระบบครั้งถัดไปโดยไม่ต้องสร้างใหม่
บัญชีเดิมไม่กระทบจากการอัปเกรด เฉพาะบัญชีที่สร้างผ่านการลงทะเบียนสาธารณะหลังมีฟีเจอร์เท่านั้นที่เริ่มรออนุมัติ บัญชีที่ผู้ดูแลสร้างจากการจัดการผู้ใช้จะเปิดทันที
เปิดการลงทะเบียนสาธารณะอย่างตั้งใจ
การลงทะเบียนปิดโดยค่าเริ่มต้น กำหนดตัวแปร backend ด้านล่างเฉพาะช่วงที่ต้องรับบัญชีในเครื่องหรือ OAuth ใหม่:
ENABLE_SIGNUP=true
เปลี่ยนกลับเป็น false หลังช่วงลงทะเบียนที่วางแผน ผู้ใช้เดิมยังเข้าสู่ระบบได้และผู้ดูแลยังสร้างบัญชีจากการจัดการผู้ใช้ได้ขณะปิดสาธารณะ
ฐานข้อมูลว่างอนุญาตผู้ดูแลในเครื่องหนึ่งรายเสมอ แม้ ENABLE_SIGNUP=false; OAuth ใช้ช่องเริ่มต้นไม่ได้ สำหรับการติดตั้งระยะไกลส่วนตัว ให้ใส่ชื่อโฮสต์หลังรายการอนุญาตตัวตน เช่น Cloudflare Access ก่อนเริ่มแอป แล้วสร้างผู้ดูแลแรกผ่านเส้นทางที่ปกป้อง
บทบาท
| บทบาท | วัตถุประสงค์ |
|---|---|
admin | จัดการอินสแตนซ์ ผู้ใช้ การตั้งค่าระบบ และการดำเนิน runtime Work ที่เชื่อถือได้ |
user | ขั้นตอนแชต โมเดล persona เอกสาร และการตั้งค่าปกติ |
การติดตั้ง ลบ คัดลอก push และยกเลิกโหลดโมเดลจำกัดให้ผู้ดูแล เพราะเปลี่ยนทรัพยากรโฮสต์
สิทธิ์ Work
Work จำกัดให้ผู้ดูแลโดยค่าเริ่มต้น เพราะให้โมเดลที่เลือกดำเนินคำสั่งใด ๆ ในคอนเทนเนอร์ที่จัดการ ผู้ดูแลเปิด Work ให้ผู้ใช้ active ทั้งหมดจากแท็บจัดการผู้ใช้ในการตั้งค่าได้ การตั้งค่าคงผ่านการเริ่มใหม่และมีผลทันทีรวมถึงเซสชัน terminal ที่เปิด พื้นที่ทำงานโฟลเดอร์โฮสต์ยังเฉพาะผู้ดูแลทุกโหมดเพราะ bind-mount เส้นทางเซิร์ฟเวอร์ ให้ถือผู้มีสิทธิ์ Work เป็นผู้ปฏิบัติ runtime ที่เชื่อถือ ไม่ใช่แค่ผู้ใช้ WebUI
สิทธิ์ผู้ดูแลตรวจจากบทบาทฐานข้อมูลปัจจุบัน ไม่ใช่เฉพาะบทบาท cache ใน JWT การลดบทบาทจึงถอน Work ทันที backend พยายามยกเลิกงาน active และหยุดคอนเทนเนอร์ Work กับตัวอย่างของผู้ใช้ โดยเก็บระเบียนงานและ volume แบบมีชื่อ หากล้าง Docker ล้มเหลว สิทธิ์ยังถูกถอน การเปลี่ยนบทบาทรายงานข้อผิดพลาด และผู้ปฏิบัติต้องคืนสิทธิ์ Docker แล้วลองล้างใหม่
การลบผู้ใช้ทำลายข้อมูล Work ของผู้ใช้นั้น Libre WebUI หยุดคอนเทนเนอร์ที่จัดการและลบ volume Work ก่อน แล้วจึงลบบัญชีกับระเบียนฐานข้อมูล หาก Docker ยืนยันการล้างสำเร็จไม่ได้ การลบบัญชีจะล้มเหลวเพื่อให้ผู้ดูแลแก้ runtime แล้วลองใหม่
กลุ่มและการให้สิทธิ์ทรัพยากร
ผู้ดูแลสร้างกลุ่มและจัดสมาชิกจากแท็บจัดการผู้ใช้ในการตั้งค่า กลุ่มเป็นผู้รับสิทธิ์ทรัพยากร เจ้าของแชต บันทึก เอกสาร คอลเลกชัน โฟลเดอร์ persona, prompt, ทักษะ หรือปฏิทิน ให้ read, write หรือ admin แก่ผู้ใช้หรือกลุ่มผ่าน API สิทธิ์ได้ ทุกพื้นผิวแชร์ใช้กล่องเดียวกัน (ดู การแชร์) และผู้ดูแลจำกัดเซิร์ฟเวอร์เครื่องมือให้ผู้ใช้หรือกลุ่มแบบเดียวกัน ทรัพยากรส่วนตัวโดยค่าเริ่มต้น บทบาท admin ส่วนกลางไม่ให้สิทธิ์เนื้อหาผู้อื่น ประเมินสมาชิกตอนคำขอ การนำสมาชิกออกจึงถอนสิทธิ์กลุ่มทันที มุมมอง "สิทธิ์ที่มีผล" ตอบว่าทำไมผู้ใช้นี้เข้าถึงได้โดยแสดงบทบาท กลุ่ม สิทธิ์ฟีเจอร์ และสิทธิ์ทั้งหมด
บันทึกการตรวจสอบความปลอดภัย
การกระทำด้านความปลอดภัย เช่น การเข้าสู่ระบบและล้มเหลว ออกจากระบบ ถอนเซสชัน/token เปลี่ยนผู้ใช้ กลุ่ม สิทธิ์ และ token จะบันทึกในบันทึกตรวจสอบแบบเพิ่มอย่างเดียว แยกจากการวิเคราะห์การใช้ รายละเอียดถูกปกปิดก่อนเก็บ คีย์คล้ายข้อมูลลับถูกทิ้งและจำกัด payload รหัสผ่าน token และ prompt จึงไม่เข้าบันทึก การเปลี่ยนกลุ่มและสิทธิ์เขียนเหตุการณ์ในธุรกรรมฐานข้อมูลเดียวกัน การเปลี่ยนจึงไม่มีได้หากไม่มีร่องรอย ผู้ดูแลค้นบันทึกจากแท็บจัดการผู้ใช้ในการตั้งค่า การเก็บเริ่มต้น 180 วัน (AUDIT_RETENTION_DAYS)
เซสชัน
backend ลงนาม JWT ด้วย JWT_SECRET ให้กำหนดข้อมูลลับที่เสถียรใน production:
JWT_SECRET=replace-with-a-long-random-secret
การเปลี่ยน JWT_SECRET ทำให้เซสชันเดิมใช้ไม่ได้ token เข้าสู่ระบบในเครื่องและ OAuth ใช้ JWT_EXPIRES_IN ค่าเริ่มต้น 7d; การเปลี่ยนกระทบเซสชันใหม่ การเชื่อม WebSocket แลก token ถาวรเป็นตั๋วใช้ครั้งเดียวอายุสั้นและปิดเมื่อเซสชันพื้นฐานหมดอายุ
ทุกการเข้าสู่ระบบสร้างระเบียนเซสชันฝั่งเซิร์ฟเวอร์ที่ผูกกับ JWT การตั้งค่า → เซสชัน แสดงอุปกรณ์พร้อมวิธีเข้าสู่ระบบ กิจกรรมแรก/ล่าสุด และหมดอายุ การถอนเซสชัน (หรือ "ออกจากเซสชันอื่น") ทำให้ token ใช้ไม่ได้ทันทีทุก replica และปิด WebSocket สด การออกจากระบบถอนเซสชันปัจจุบันแบบเดียวกัน token ก่อนฟีเจอร์ไม่มี ID เซสชันและใช้ได้ถึงหมดอายุ แต่ "ออกจากเซสชันอื่น" จากการเข้าสู่ระบบใหม่จะตั้งจุดตัดต่อบัญชีเพื่อปฏิเสธด้วย
การยืนยันตัวตนสองปัจจัยและ passkey
การตั้งค่า → เซสชัน จัดการทั้งปัจจัยที่สองและการเข้าสู่ระบบไม่ใช้รหัสผ่าน:
- แอปยืนยัน (TOTP) การลงทะเบียนแสดงข้อมูลลับ base32 และลิงก์
otpauth://สำหรับแอปใด ๆ การยืนยันรหัส 6 หลักแรกจะเปิดและแสดงรหัสกู้คืนใช้ครั้งเดียวสิบรายการ จากนั้นการเข้าสู่ระบบด้วยรหัสผ่านจะส่ง challenge อายุสั้นแทนเซสชัน และPOST /api/auth/mfa/verifyทำการเข้าสู่ระบบด้วย TOTP หรือรหัสกู้คืน ระบบบันทึก timestep ของรหัสที่รับเพื่อไม่ให้เล่นรหัสที่ดักซ้ำ รหัสกู้คืนเก็บเฉพาะ token ค้นหาทางเดียวแบบมีคีย์และใช้ได้ครั้งเดียว การปิดหรือสร้างรหัสใหม่ต้องพิสูจน์ปัจจัยอีกครั้ง - Passkey (WebAuthn) "เข้าสู่ระบบด้วย passkey" ทำการเข้าสู่ระบบไม่ใช้รหัสผ่านด้วยข้อมูลประจำตัวที่ค้นพบได้ ต้องยืนยันผู้ใช้ (ล็อกหน้าจอ ชีวมิติ หรือ PIN) ตอนลงทะเบียนและเข้าสู่ระบบ รับ attestation เป็น
none, รองรับ ES256 และ EdDSA และเข้ารหัสวัสดุข้อมูลประจำตัวขณะเก็บโดย ID เป็น token ค้นหาแบบมีคีย์ challenge ใช้ครั้งเดียวหมดอายุห้านาที ตัวนับลายเซ็นที่ไม่ใช่ศูนย์แต่ไม่เพิ่มจะถูกปฏิเสธเป็นสัญญาณโคลน passkey ต้องมี origin HTTPS ที่ปลอดภัย หรือlocalhostในการพัฒนา ตั้งWEBAUTHN_RP_IDเมื่อเข้าถึงอินสแตนซ์ผ่านหลายชื่อโฮสต์
token challenge MFA หลังรหัสผ่านถูกต้องจะลงนามด้วยข้อมูลลับที่มาจากแต่ต่างจาก JWT_SECRET ไม่สามารถยืนยันคำขอ API ผูกกับบัญชีและวัตถุประสงค์เดียว และถูกใช้หมดเมื่อสำเร็จ
ผู้ดูแลบังคับปัจจัยที่สองทุกบัญชีได้ (ผู้ใช้ → การ์ดนโยบายสองปัจจัย หรือ MFA_REQUIRED_MODE=required) ผู้ที่ไม่มีจะเข้าสู่ขั้นตอนลงทะเบียนในการเข้าสู่ระบบครั้งถัดไปก่อนออกเซสชัน ผู้ดูแลรีเซ็ต TOTP จากรายชื่อเพื่อกู้บัญชีได้ ส่วน passkey คงไว้เพราะผู้ใช้จัดการเอง การลงทะเบียน เปิดใช้ ล้มเหลวตรวจ ปิด เปลี่ยนนโยบาย เพิ่ม/ลบ passkey และรีเซ็ตโดยผู้ดูแลถูกบันทึกทั้งหมด
MFA ใช้กับการเข้าสู่ระบบด้วยรหัสผ่าน OAuth และ OIDC พึ่งปัจจัยที่สองของผู้ให้บริการตัวตนและไม่ถูกถามซ้ำ token API ไม่กระทบเพราะไม่ใช้การยืนยันเซสชัน
token API
การตั้งค่า → คีย์ API สร้าง token เข้าถึงส่วนตัว (คำนำหน้า lwk_) สำหรับโปรแกรม ข้อมูลลับแสดงครั้งเดียวและเก็บเป็น hash เท่านั้น แต่ละ token มีรายการขอบเขต (chat, models, documents, notes, personas, media, work, admin) backend แมปแต่ละครอบครัวเส้นทางกับขอบเขตที่ต้องใช้ token เฉพาะบันทึกจึงเข้าถึงแชตหรือการจัดการไม่ได้ และ token เข้าถึงการจัดการเซสชันไม่ได้ รองรับวันหมดอายุที่เลือกได้ ติดตามการใช้ล่าสุด ถอนเมื่อใดก็ได้ และจำกัดอัตราต่อ token ทุก replica token ขอบเขต admin สร้างได้เฉพาะผู้ดูแลและบัญชียังต้องมีบทบาท admin ขณะใช้ token ขอบเขต chat ยังเป็นคีย์ของ API /v1 สาธารณะ ที่เข้ากันได้กับ OpenAI
Cloudflare Turnstile
Turnstile ปกป้องการเข้าสู่ระบบและสมัครด้วยรหัสผ่านเมื่อกำหนดทั้งสองคีย์:
TURNSTILE_SITE_KEY=...
TURNSTILE_SECRET_KEY=...
TURNSTILE_EXPECTED_HOSTNAME=chat.example.com
frontend กำหนดการทำงาน login และ signup แยก backend ตรวจ token กับ Cloudflare และปฏิเสธคำตอบที่ชื่อโฮสต์หรือการทำงานไม่ตรงคำขอ BASE_URL ให้ชื่อโฮสต์ที่คาดเมื่อไม่ตั้ง TURNSTILE_EXPECTED_HOSTNAME
หากขาดคีย์ใด Turnstile จะปิด
GitHub OAuth
กำหนดค่า:
GITHUB_CLIENT_ID=...
GITHUB_CLIENT_SECRET=...
GITHUB_CALLBACK_URL=https://your-domain.example/api/auth/oauth/github/callback
ขั้นตอน GitHub OAuth สร้างผู้ใช้ในเครื่องชื่อคำนำหน้า gh_ และให้บทบาท user โดยค่าเริ่มต้น
Hugging Face OAuth
กำหนดค่า:
HUGGINGFACE_CLIENT_ID=...
HUGGINGFACE_CLIENT_SECRET=...
HUGGINGFACE_CALLBACK_URL=https://your-domain.example/api/auth/oauth/huggingface/callback
ขั้นตอน Hugging Face OAuth สร้างผู้ใช้ในเครื่องชื่อคำนำหน้า hf_ และให้บทบาท user โดยค่าเริ่มต้น
ทั้งสองผู้ให้บริการ OAuth ใช้ค่า state สุ่มทางเข้ารหัสที่ผูกกับ cookie HttpOnly, SameSite อายุสั้น callback ปฏิเสธ state ที่ขาดหรือไม่ตรง หลัง callback สำเร็จ JWT กลับ frontend ใน cookie HttpOnly 60 วินาทีที่แลกและลบทันที bearer token ไม่ถูกใส่ใน URL callback ประวัติเบราว์เซอร์ หรือ header referrer
การเปลี่ยนเส้นทางและ CORS
ตั้ง BASE_URL สำหรับ callback เริ่มต้นและ CORS_ORIGIN สำหรับเบราว์เซอร์:
BASE_URL=https://your-domain.example
CORS_ORIGIN=https://your-domain.example
สำหรับการพัฒนาในเครื่อง ให้รวม origin พัฒนา Vite:
CORS_ORIGIN=http://localhost:5173,http://127.0.0.1:5173
โหมดสาธิต
โหมดสาธิตเป็นโหมดตัวอย่าง frontend กรอกข้อมูลสาธิตที่ปิดไว้ล่วงหน้าและใช้คำตอบ API จำลอง ไม่ใช่โหมดยืนยัน production
รายการตรวจสอบความปลอดภัย
- ตั้ง
JWT_SECRETที่แข็งแรง - เก็บ
DATA_DIRบนพื้นที่ถาวรที่ควบคุมสิทธิ์ - สำรอง
ENCRYPTION_KEYพร้อมฐานข้อมูล - กำหนด Turnstile สำหรับการสมัครสาธารณะ
- ใช้ HTTPS สำหรับการติดตั้งสาธารณะ
- จำกัดคีย์ API ผู้ให้บริการให้แคบที่สุด
- รักษา URL callback OAuth ให้ตรง
- ให้สิทธิ์ Work เฉพาะผู้ที่เชื่อถือให้ดำเนิน runtime คอนเทนเนอร์ backend