添加日志
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import hashlib
|
||||
import logging
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, status
|
||||
from fastapi import APIRouter, Depends, HTTPException, Request, status
|
||||
from sqlalchemy import text
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
@@ -17,10 +18,22 @@ except ModuleNotFoundError:
|
||||
|
||||
|
||||
router = APIRouter(prefix="/auth", tags=["auth"])
|
||||
logger = logging.getLogger("app.auth")
|
||||
|
||||
|
||||
def _mask_username(username: str) -> str:
|
||||
if len(username) <= 2:
|
||||
return "*" * len(username)
|
||||
return f"{username[:2]}***"
|
||||
|
||||
|
||||
@router.post("/login", response_model=LoginResponse)
|
||||
def login(payload: LoginRequest, db: Session = Depends(get_db)) -> LoginResponse:
|
||||
def login(
|
||||
payload: LoginRequest,
|
||||
request: Request,
|
||||
db: Session = Depends(get_db),
|
||||
) -> LoginResponse:
|
||||
username_masked = _mask_username(payload.username)
|
||||
with db.begin():
|
||||
row = (
|
||||
db.execute(
|
||||
@@ -45,12 +58,31 @@ def login(payload: LoginRequest, db: Session = Depends(get_db)) -> LoginResponse
|
||||
.first()
|
||||
)
|
||||
if not row:
|
||||
logger.warning(
|
||||
"login failed",
|
||||
extra={
|
||||
"event": "login",
|
||||
"request_id": getattr(request.state, "request_id", None),
|
||||
"username": username_masked,
|
||||
"reason": "invalid_username_or_password",
|
||||
},
|
||||
)
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail="invalid username or password",
|
||||
)
|
||||
|
||||
if int(row["auth_status"]) != 1 or int(row["user_status"]) != 1:
|
||||
logger.warning(
|
||||
"login failed",
|
||||
extra={
|
||||
"event": "login",
|
||||
"request_id": getattr(request.state, "request_id", None),
|
||||
"username": username_masked,
|
||||
"user_id": int(row["user_id"]),
|
||||
"reason": "account_disabled",
|
||||
},
|
||||
)
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail="account is disabled",
|
||||
@@ -59,6 +91,15 @@ def login(payload: LoginRequest, db: Session = Depends(get_db)) -> LoginResponse
|
||||
# Seed data currently stores sha256; migrate to bcrypt/argon2 in production.
|
||||
password_hash = hashlib.sha256(payload.password.encode("utf-8")).hexdigest()
|
||||
if not row["password_hash"] or password_hash != row["password_hash"]:
|
||||
logger.warning(
|
||||
"login failed",
|
||||
extra={
|
||||
"event": "login",
|
||||
"request_id": getattr(request.state, "request_id", None),
|
||||
"username": username_masked,
|
||||
"reason": "invalid_username_or_password",
|
||||
},
|
||||
)
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail="invalid username or password",
|
||||
@@ -88,6 +129,15 @@ def login(payload: LoginRequest, db: Session = Depends(get_db)) -> LoginResponse
|
||||
)
|
||||
|
||||
access_token, expires_in = create_access_token(user_id=int(row["user_id"]))
|
||||
logger.info(
|
||||
"login succeeded",
|
||||
extra={
|
||||
"event": "login",
|
||||
"request_id": getattr(request.state, "request_id", None),
|
||||
"user_id": int(row["user_id"]),
|
||||
"username": username_masked,
|
||||
},
|
||||
)
|
||||
return LoginResponse(
|
||||
access_token=access_token,
|
||||
expires_in=expires_in,
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import json
|
||||
import logging
|
||||
from collections.abc import Mapping
|
||||
from typing import Any
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query, Response, status
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query, Request, Response, status
|
||||
from sqlalchemy import text
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
@@ -29,6 +30,7 @@ except ModuleNotFoundError:
|
||||
|
||||
|
||||
router = APIRouter(prefix="/messages", tags=["messages"])
|
||||
logger = logging.getLogger("app.messages")
|
||||
|
||||
|
||||
def _build_preview(content_type: int, content_text: str | None) -> str:
|
||||
@@ -141,6 +143,7 @@ def _assert_conversation_access(
|
||||
@router.post("", response_model=MessageCreateResponse, status_code=status.HTTP_201_CREATED)
|
||||
def create_message(
|
||||
payload: MessageCreateRequest,
|
||||
request: Request,
|
||||
response: Response,
|
||||
current_user_id: int = Depends(get_current_user_id),
|
||||
db: Session = Depends(get_db),
|
||||
@@ -223,6 +226,19 @@ def create_message(
|
||||
|
||||
existing = _get_existing_message(db, conversation_id, payload.client_msg_id)
|
||||
if existing:
|
||||
logger.info(
|
||||
"message idempotent hit",
|
||||
extra={
|
||||
"event": "message_create",
|
||||
"request_id": getattr(request.state, "request_id", None),
|
||||
"user_id": sender_user_id,
|
||||
"conversation_id": conversation_id,
|
||||
"message_id": int(existing["id"]),
|
||||
"seq": int(existing["seq"]),
|
||||
"client_msg_id": payload.client_msg_id,
|
||||
"idempotent": True,
|
||||
},
|
||||
)
|
||||
response.status_code = status.HTTP_200_OK
|
||||
return MessageCreateResponse(
|
||||
idempotent=True,
|
||||
@@ -328,6 +344,19 @@ def create_message(
|
||||
if not created:
|
||||
raise HTTPException(status_code=500, detail="failed to load created message")
|
||||
|
||||
logger.info(
|
||||
"message created",
|
||||
extra={
|
||||
"event": "message_create",
|
||||
"request_id": getattr(request.state, "request_id", None),
|
||||
"user_id": sender_user_id,
|
||||
"conversation_id": conversation_id,
|
||||
"message_id": int(created["id"]),
|
||||
"seq": int(created["seq"]),
|
||||
"client_msg_id": payload.client_msg_id,
|
||||
"idempotent": False,
|
||||
},
|
||||
)
|
||||
return MessageCreateResponse(
|
||||
idempotent=False,
|
||||
message=_row_to_message_item(created),
|
||||
@@ -336,6 +365,7 @@ def create_message(
|
||||
|
||||
@router.get("", response_model=MessageListResponse)
|
||||
def list_messages(
|
||||
request: Request,
|
||||
conversation_id: int = Query(gt=0),
|
||||
cursor_seq: int | None = Query(default=None, ge=1),
|
||||
limit: int = Query(default=20, ge=1, le=100),
|
||||
@@ -382,6 +412,19 @@ def list_messages(
|
||||
items = [_row_to_message_item(row) for row in rows]
|
||||
|
||||
next_cursor_seq = items[0].seq if has_more and items else None
|
||||
logger.info(
|
||||
"messages listed",
|
||||
extra={
|
||||
"event": "message_list",
|
||||
"request_id": getattr(request.state, "request_id", None),
|
||||
"user_id": current_user_id,
|
||||
"conversation_id": conversation_id,
|
||||
"cursor_seq": cursor_seq,
|
||||
"limit": limit,
|
||||
"count": len(items),
|
||||
"has_more": has_more,
|
||||
},
|
||||
)
|
||||
return MessageListResponse(
|
||||
conversation_id=conversation_id,
|
||||
has_more=has_more,
|
||||
|
||||
Reference in New Issue
Block a user