feat(api): add API key authentication to backend endpoints

Add API key security using fastapi.security.APIKeyHeader to protect /v1/completions and /v1/ocr endpoints. Updated frontend to include X-API-Key header in API requests. Also changed default API base URL from http://149.104.29.239:8001 to https://api.learnteach.tech:8002.
This commit is contained in:
2026-02-19 18:18:47 +08:00
parent 4fe4becdd5
commit 4a979ba7c3
4 changed files with 24 additions and 6 deletions
+15 -3
View File
@@ -1,4 +1,5 @@
from fastapi import FastAPI, Request from fastapi import FastAPI, Request, HTTPException, Security
from fastapi.security import APIKeyHeader
from fastapi.middleware.cors import CORSMiddleware from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import StreamingResponse, JSONResponse from fastapi.responses import StreamingResponse, JSONResponse
from pydantic import BaseModel from pydantic import BaseModel
@@ -27,6 +28,17 @@ app.add_middleware(
allow_headers=["*"], allow_headers=["*"],
) )
API_KEY = "your-secret-key-here" # 建议从环境变量读取
api_key_header = APIKeyHeader(name="X-API-Key")
async def get_api_key(api_key: str = Security(api_key_header)):
if api_key != API_KEY:
raise HTTPException(
status_code=403,
detail="Could not validate credentials"
)
return api_key
from typing import Optional from typing import Optional
class UserPreferences(BaseModel): class UserPreferences(BaseModel):
@@ -58,7 +70,7 @@ def get_client_ip(request: Request) -> str:
return request.headers.get("X-Client-IP") or request.client.host if request.client else "unknown" return request.headers.get("X-Client-IP") or request.client.host if request.client else "unknown"
@app.post("/v1/completions") @app.post("/v1/completions")
async def create_completion(request: Request, req: CompletionRequest): async def create_completion(request: Request, req: CompletionRequest, api_key: str = Security(get_api_key)):
request_id = str(uuid.uuid4())[:8] request_id = str(uuid.uuid4())[:8]
client_ip = "hidden" client_ip = "hidden"
@@ -122,7 +134,7 @@ async def create_completion(request: Request, req: CompletionRequest):
return JSONResponse(content={"error": str(e)}, status_code=500) return JSONResponse(content={"error": str(e)}, status_code=500)
@app.post("/v1/ocr") @app.post("/v1/ocr")
async def ocr_image(request: OCRRequest): async def ocr_image(request: OCRRequest, api_key: str = Security(get_api_key)):
request_id = str(uuid.uuid4())[:8] request_id = str(uuid.uuid4())[:8]
try: try:
logger.info( logger.info(
+4 -1
View File
@@ -233,7 +233,10 @@ const performOCR = async (file, cacheKey, imageHash = '') => {
try { try {
const res = await fetch(OCR_URL, { const res = await fetch(OCR_URL, {
method: 'POST', method: 'POST',
headers: { 'Content-Type': 'application/json' }, headers: {
'Content-Type': 'application/json',
'X-API-Key': 'your-secret-key-here'
},
body: JSON.stringify({ body: JSON.stringify({
image: base64, image: base64,
filename: file.name, filename: file.name,
+4 -1
View File
@@ -22,7 +22,10 @@ export async function fetchSuggestion(prefix, suffix, signal, apiUrl = API_URL)
try { try {
const settings = useSettingsStore() const settings = useSettingsStore()
const clientIP = await getClientIP() const clientIP = await getClientIP()
const headers = { 'Content-Type': 'application/json' } const headers = {
'Content-Type': 'application/json',
'X-API-Key': 'your-secret-key-here'
}
// Only send IP if privacy mode is OFF // Only send IP if privacy mode is OFF
if (clientIP && !settings.privacyMode) { if (clientIP && !settings.privacyMode) {
+1 -1
View File
@@ -1,6 +1,6 @@
export const DEBUG = import.meta.env.DEV export const DEBUG = import.meta.env.DEV
const API_BASE_URL = import.meta.env.VITE_API_BASE_URL || 'http://149.104.29.239:8001' const API_BASE_URL = import.meta.env.VITE_API_BASE_URL || 'https://api.learnteach.tech:8002'
export const API_URL = import.meta.env.VITE_API_URL || `${API_BASE_URL}/v1/completions` export const API_URL = import.meta.env.VITE_API_URL || `${API_BASE_URL}/v1/completions`
export const OCR_URL = import.meta.env.VITE_OCR_URL || `${API_BASE_URL}/v1/ocr` export const OCR_URL = import.meta.env.VITE_OCR_URL || `${API_BASE_URL}/v1/ocr`