fix(auth): standalone auto-login qua IP LAN cua chinh may — _is_local_client (own interface IPs)

This commit is contained in:
2026-08-23 15:40:49 +07:00
parent 6edfe1db32
commit 6176cf3f8e
+30 -2
View File
@@ -1,6 +1,7 @@
import uuid import uuid
import time import time
import threading import threading
import socket
from fastapi import APIRouter, HTTPException, Header, Depends, Request, Response from fastapi import APIRouter, HTTPException, Header, Depends, Request, Response
from fastapi.responses import JSONResponse from fastapi.responses import JSONResponse
from pydantic import BaseModel, EmailStr from pydantic import BaseModel, EmailStr
@@ -60,6 +61,33 @@ def _is_loopback(host: Optional[str]) -> bool:
host = host.lower() host = host.lower()
return host in ("127.0.0.1", "::1", "localhost") or host.startswith("127.") return host in ("127.0.0.1", "::1", "localhost") or host.startswith("127.")
_LOCAL_IPS_CACHE = {"at": 0.0, "ips": set()}
def _own_interface_ips(max_age=60.0) -> set:
"""Các IP interface cua CHINH may (standalone): truy cap qua IP LAN
cua may (vd http://192.168.1.12:8000) van la may nay -> auto-login.
Client LAN may khac co source IP khac -> khong match -> van can mat khau."""
now = time.time()
c = _LOCAL_IPS_CACHE
if now - c["at"] > max_age:
ips = set()
try:
ips = set(socket.gethostbyname_ex(socket.gethostname())[2])
except Exception:
pass
c["ips"] = {i for i in ips if not _is_loopback(i)}
c["at"] = now
return c["ips"]
def _is_local_client(host: Optional[str]) -> bool:
"""May chay engine (loopback) hoac IP interface cua chinh may -> local.
Dung chung cho get_current_user + /auth/local."""
if _is_loopback(host):
return True
if not host:
return False
return host in _own_interface_ips()
def _loopback_admin_user() -> dict: def _loopback_admin_user() -> dict:
"""Identity admin cho loopback — real user_id từ DB; không bắt buộc đổi """Identity admin cho loopback — real user_id từ DB; không bắt buộc đổi
mật khẩu (standalone tự vào thẳng DAW).""" mật khẩu (standalone tự vào thẳng DAW)."""
@@ -94,7 +122,7 @@ def get_current_user(request: Request, authorization: Optional[str] = Header(Non
if not token: if not token:
# Standalone (loopback): không cần mật khẩu — auto admin. LAN vẫn 401. # Standalone (loopback): không cần mật khẩu — auto admin. LAN vẫn 401.
host = request.client.host if request.client else None host = request.client.host if request.client else None
if _is_loopback(host): if _is_local_client(host):
return _loopback_admin_user() return _loopback_admin_user()
raise HTTPException(status_code=401, detail="Thiếu Token xác thực hoặc Token không hợp lệ") raise HTTPException(status_code=401, detail="Thiếu Token xác thực hoặc Token không hợp lệ")
payload = decode_token(token) payload = decode_token(token)
@@ -168,7 +196,7 @@ async def local_login(request: Request, response: Response):
"""Auto-login standalone: chỉ cho phép từ loopback (máy chạy engine). """Auto-login standalone: chỉ cho phép từ loopback (máy chạy engine).
Client LAN nhận 403 → buộc đăng nhập mật khẩu qua /login.""" Client LAN nhận 403 → buộc đăng nhập mật khẩu qua /login."""
host = request.client.host if request.client else None host = request.client.host if request.client else None
if not _is_loopback(host): if not _is_local_client(host):
raise HTTPException(status_code=403, detail="Truy cập từ xa — cần đăng nhập") raise HTTPException(status_code=403, detail="Truy cập từ xa — cần đăng nhập")
u = _loopback_admin_user() u = _loopback_admin_user()
token = create_token(u["user_id"], u["username"], u["role"], False) token = create_token(u["user_id"], u["username"], u["role"], False)