feat: add DEV_MODE configuration and implement dev-token endpoint for authentication

This commit is contained in:
2026-04-07 10:39:07 +08:00
parent d12c1223f9
commit d170ba3362
4 changed files with 36 additions and 6 deletions

2
dev.sh
View File

@@ -1,7 +1,7 @@
#!/bin/bash #!/bin/bash
trap 'kill 0' EXIT trap 'kill 0' EXIT
(cd qwen3-tts-backend && /home/bdim/miniconda3/envs/qwen3-tts/bin/uvicorn main:app --host 0.0.0.0 --port 8000 --reload 2>&1 | sed 's/^/[backend] /') & (cd qwen3-tts-backend && DEV_MODE=true LOG_LEVEL=debug /home/bdim/miniconda3/envs/qwen3-tts/bin/uvicorn main:app --host 0.0.0.0 --port 8000 --reload --log-level debug 2>&1 | sed 's/^/[backend] /') &
(cd qwen3-tts-frontend && npm run dev 2>&1 | sed 's/^/[frontend] /') & (cd qwen3-tts-frontend && npm run dev 2>&1 | sed 's/^/[frontend] /') &
wait wait

View File

@@ -1,5 +1,5 @@
from datetime import timedelta from datetime import timedelta
from typing import Annotated from typing import Annotated, Optional
from fastapi import APIRouter, Depends, HTTPException, status, Request from fastapi import APIRouter, Depends, HTTPException, status, Request
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
@@ -20,20 +20,28 @@ from schemas.audiobook import LLMConfigResponse
router = APIRouter(prefix="/auth", tags=["authentication"]) router = APIRouter(prefix="/auth", tags=["authentication"])
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/auth/token") oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/auth/token", auto_error=not settings.DEV_MODE)
limiter = Limiter(key_func=get_remote_address) limiter = Limiter(key_func=get_remote_address)
async def get_current_user( async def get_current_user(
token: Annotated[str, Depends(oauth2_scheme)], token: Annotated[Optional[str], Depends(oauth2_scheme)],
db: Session = Depends(get_db) db: Session = Depends(get_db)
) -> User: ) -> User:
if settings.DEV_MODE and not token:
user = get_user_by_username(db, username="admin")
if user:
return user
credentials_exception = HTTPException( credentials_exception = HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED, status_code=status.HTTP_401_UNAUTHORIZED,
detail="Could not validate credentials", detail="Could not validate credentials",
headers={"WWW-Authenticate": "Bearer"}, headers={"WWW-Authenticate": "Bearer"},
) )
if token is None:
raise credentials_exception
username = decode_access_token(token) username = decode_access_token(token)
if username is None: if username is None:
raise credentials_exception raise credentials_exception
@@ -99,6 +107,16 @@ async def login(
return {"access_token": access_token, "token_type": "bearer"} return {"access_token": access_token, "token_type": "bearer"}
@router.get("/dev-token", response_model=Token)
async def dev_token(db: Session = Depends(get_db)):
if not settings.DEV_MODE:
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Not available outside DEV_MODE")
user = get_user_by_username(db, username="admin")
if not user:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Admin user not found")
access_token = create_access_token(data={"sub": user.username})
return {"access_token": access_token, "token_type": "bearer"}
@router.get("/me", response_model=User) @router.get("/me", response_model=User)
@limiter.limit("30/minute") @limiter.limit("30/minute")
async def get_current_user_info( async def get_current_user_info(

View File

@@ -25,6 +25,7 @@ class Settings(BaseSettings):
WORKERS: int = Field(default=1) WORKERS: int = Field(default=1)
LOG_LEVEL: str = Field(default="info") LOG_LEVEL: str = Field(default="info")
LOG_FILE: str = Field(default="./app.log") LOG_FILE: str = Field(default="./app.log")
DEV_MODE: bool = Field(default=False)
RATE_LIMIT_PER_MINUTE: int = Field(default=50) RATE_LIMIT_PER_MINUTE: int = Field(default=50)
RATE_LIMIT_PER_HOUR: int = Field(default=1000) RATE_LIMIT_PER_HOUR: int = Field(default=1000)
@@ -60,7 +61,10 @@ class Settings(BaseSettings):
return v return v
def validate(self): def validate(self):
if self.SECRET_KEY == "your-secret-key-change-this-in-production": if self.DEV_MODE:
import warnings
warnings.warn("DEV_MODE is enabled — authentication is bypassed. Do NOT use in production.")
elif self.SECRET_KEY == "your-secret-key-change-this-in-production":
raise ValueError("Insecure default SECRET_KEY is not allowed. Please set a strong SECRET_KEY in environment.") raise ValueError("Insecure default SECRET_KEY is not allowed. Please set a strong SECRET_KEY in environment.")
Path(self.CACHE_DIR).mkdir(parents=True, exist_ok=True) Path(self.CACHE_DIR).mkdir(parents=True, exist_ok=True)

View File

@@ -22,7 +22,15 @@ export function AuthProvider({ children }: { children: ReactNode }) {
useEffect(() => { useEffect(() => {
const initAuth = async () => { const initAuth = async () => {
try { try {
const storedToken = localStorage.getItem('token') let storedToken = localStorage.getItem('token')
if (!storedToken && import.meta.env.DEV) {
const res = await fetch('/api/auth/dev-token')
if (res.ok) {
const data = await res.json()
storedToken = data.access_token
localStorage.setItem('token', storedToken!)
}
}
if (storedToken) { if (storedToken) {
setToken(storedToken) setToken(storedToken)
const currentUser = await authApi.getCurrentUser() const currentUser = await authApi.getCurrentUser()