feat(auth): add wechat miniapp customer phone login

Implement Wechat Mini Program phone number login flow for end customers:
- add PhoneLoginIn Pydantic request schema
- create wechat.py module for Wechat API interactions and phone code exchange
- add customer JWT utilities and require_customer authentication dependency
- add new public API endpoints: /api/public/auth/phone-login and /api/public/auth/me
- add required environment config variables and update example .env
- add comprehensive test cases for the new auth flow and endpoints
This commit is contained in:
duanshuwen
2026-07-08 16:11:34 +08:00
parent fe85197e68
commit bac68842ef
8 changed files with 242 additions and 5 deletions

View File

@@ -6,7 +6,7 @@ from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
from sqlalchemy.orm import Session
from .config import get_settings
from .database import get_db
from .models import AdminUser
from .models import AdminUser, Customer
bearer = HTTPBearer(auto_error=False)
@@ -24,6 +24,7 @@ def create_token(user: AdminUser) -> str:
settings = get_settings()
now = datetime.now(timezone.utc)
payload = {
"typ": "admin",
"sub": user.id,
"email": user.email,
"role": user.role,
@@ -33,6 +34,18 @@ def create_token(user: AdminUser) -> str:
return jwt.encode(payload, settings.jwt_secret, algorithm="HS256")
def create_customer_token(customer: Customer) -> str:
settings = get_settings()
now = datetime.now(timezone.utc)
payload = {
"typ": "customer",
"sub": customer.id,
"iat": now,
"exp": now + timedelta(hours=settings.customer_jwt_expires_hours),
}
return jwt.encode(payload, settings.jwt_secret, algorithm="HS256")
def require_admin(
request: Request,
credentials: HTTPAuthorizationCredentials | None = Depends(bearer),
@@ -44,6 +57,8 @@ def require_admin(
payload = jwt.decode(credentials.credentials, get_settings().jwt_secret, algorithms=["HS256"])
except jwt.PyJWTError as exc:
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="请先登录后台") from exc
if payload.get("typ", "admin") != "admin":
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="请先登录后台")
user_id = payload.get("sub")
user = db.get(AdminUser, user_id) if user_id else None
if not user or not user.isActive:
@@ -52,5 +67,26 @@ def require_admin(
return user
def require_customer(
request: Request,
credentials: HTTPAuthorizationCredentials | None = Depends(bearer),
db: Session = Depends(get_db),
) -> Customer:
if credentials is None:
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="请先登录")
try:
payload = jwt.decode(credentials.credentials, get_settings().jwt_secret, algorithms=["HS256"])
except jwt.PyJWTError as exc:
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="请先登录") from exc
if payload.get("typ") != "customer":
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="请先登录")
customer_id = payload.get("sub")
customer = db.get(Customer, customer_id) if customer_id else None
if not customer:
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="请先登录")
request.state.actor_id = customer.id
return customer
def get_actor_id(request: Request) -> str | None:
return getattr(request.state, "actor_id", None)