diff --git a/app/api/v1/auth.py b/app/api/v1/auth.py index ac56ae8..7fd136e 100644 --- a/app/api/v1/auth.py +++ b/app/api/v1/auth.py @@ -1,6 +1,7 @@ import uuid import time import threading +import socket from fastapi import APIRouter, HTTPException, Header, Depends, Request, Response from fastapi.responses import JSONResponse from pydantic import BaseModel, EmailStr @@ -60,6 +61,33 @@ def _is_loopback(host: Optional[str]) -> bool: host = host.lower() 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: """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).""" @@ -94,7 +122,7 @@ def get_current_user(request: Request, authorization: Optional[str] = Header(Non if not token: # 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 - if _is_loopback(host): + if _is_local_client(host): 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ệ") 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). 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 - 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") u = _loopback_admin_user() token = create_token(u["user_id"], u["username"], u["role"], False)