Youngger9765 Claude commited on
Commit ·
3f45635
1
Parent(s): 78052ea
Add password protection system
Browse files- Implement password protection page with session persistence
- Add API authentication middleware with password verification
- Create /api/verify-password endpoint
- Configure APP_PASSWORD environment variable (3030)
- Fix CORS preflight requests being blocked by auth middleware
- Update frontend to handle password verification flow
- Add X-Password-Verified header to authenticated API requests
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
- backend/app/config/settings.py +7 -1
- backend/app/models/grading.py +8 -1
- backend/app/routers/grading.py +28 -1
- backend/main.py +4 -0
- frontend/src/App.tsx +18 -1
- frontend/src/api/grading.ts +14 -0
backend/app/config/settings.py
CHANGED
|
@@ -24,8 +24,14 @@ class Settings(BaseSettings):
|
|
| 24 |
# Text Limits
|
| 25 |
max_text_length: int = 10000
|
| 26 |
|
|
|
|
|
|
|
|
|
|
| 27 |
class Config:
|
| 28 |
env_file = ".env"
|
| 29 |
case_sensitive = False
|
| 30 |
|
| 31 |
-
settings = Settings()
|
|
|
|
|
|
|
|
|
|
|
|
| 24 |
# Text Limits
|
| 25 |
max_text_length: int = 10000
|
| 26 |
|
| 27 |
+
# Security
|
| 28 |
+
app_password: str = ""
|
| 29 |
+
|
| 30 |
class Config:
|
| 31 |
env_file = ".env"
|
| 32 |
case_sensitive = False
|
| 33 |
|
| 34 |
+
settings = Settings()
|
| 35 |
+
|
| 36 |
+
def get_settings():
|
| 37 |
+
return settings
|
backend/app/models/grading.py
CHANGED
|
@@ -54,4 +54,11 @@ class AgentInfo(BaseModel):
|
|
| 54 |
|
| 55 |
class AgentsListResponse(BaseModel):
|
| 56 |
version: int
|
| 57 |
-
agents: List[AgentInfo]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 54 |
|
| 55 |
class AgentsListResponse(BaseModel):
|
| 56 |
version: int
|
| 57 |
+
agents: List[AgentInfo]
|
| 58 |
+
|
| 59 |
+
class PasswordVerifyRequest(BaseModel):
|
| 60 |
+
password: str = Field(..., description="Password to verify")
|
| 61 |
+
|
| 62 |
+
class PasswordVerifyResponse(BaseModel):
|
| 63 |
+
success: bool = Field(..., description="Whether password is correct")
|
| 64 |
+
message: Optional[str] = None
|
backend/app/routers/grading.py
CHANGED
|
@@ -5,10 +5,13 @@ from app.models.grading import (
|
|
| 5 |
GradeRequest,
|
| 6 |
GradeResponse,
|
| 7 |
AgentsListResponse,
|
| 8 |
-
AgentInfo
|
|
|
|
|
|
|
| 9 |
)
|
| 10 |
from app.config.agents import get_agents_config
|
| 11 |
from app.services.openai_service import get_openai_service
|
|
|
|
| 12 |
|
| 13 |
logger = logging.getLogger(__name__)
|
| 14 |
router = APIRouter(prefix="/api", tags=["grading"])
|
|
@@ -98,6 +101,30 @@ async def grade_text(request: GradeRequest):
|
|
| 98 |
}
|
| 99 |
)
|
| 100 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 101 |
@router.get("/health")
|
| 102 |
async def health_check():
|
| 103 |
"""Health check endpoint"""
|
|
|
|
| 5 |
GradeRequest,
|
| 6 |
GradeResponse,
|
| 7 |
AgentsListResponse,
|
| 8 |
+
AgentInfo,
|
| 9 |
+
PasswordVerifyRequest,
|
| 10 |
+
PasswordVerifyResponse
|
| 11 |
)
|
| 12 |
from app.config.agents import get_agents_config
|
| 13 |
from app.services.openai_service import get_openai_service
|
| 14 |
+
from app.config.settings import get_settings
|
| 15 |
|
| 16 |
logger = logging.getLogger(__name__)
|
| 17 |
router = APIRouter(prefix="/api", tags=["grading"])
|
|
|
|
| 101 |
}
|
| 102 |
)
|
| 103 |
|
| 104 |
+
@router.post("/verify-password", response_model=PasswordVerifyResponse)
|
| 105 |
+
async def verify_password(request: PasswordVerifyRequest):
|
| 106 |
+
"""Verify password for platform access"""
|
| 107 |
+
try:
|
| 108 |
+
settings = get_settings()
|
| 109 |
+
correct_password = settings.app_password or "3030"
|
| 110 |
+
|
| 111 |
+
if request.password == correct_password:
|
| 112 |
+
return PasswordVerifyResponse(
|
| 113 |
+
success=True,
|
| 114 |
+
message="Password verified successfully"
|
| 115 |
+
)
|
| 116 |
+
else:
|
| 117 |
+
return PasswordVerifyResponse(
|
| 118 |
+
success=False,
|
| 119 |
+
message="Incorrect password"
|
| 120 |
+
)
|
| 121 |
+
except Exception as e:
|
| 122 |
+
logger.error(f"Error verifying password: {str(e)}")
|
| 123 |
+
return PasswordVerifyResponse(
|
| 124 |
+
success=False,
|
| 125 |
+
message="Password verification failed"
|
| 126 |
+
)
|
| 127 |
+
|
| 128 |
@router.get("/health")
|
| 129 |
async def health_check():
|
| 130 |
"""Health check endpoint"""
|
backend/main.py
CHANGED
|
@@ -11,6 +11,7 @@ load_dotenv(".env")
|
|
| 11 |
|
| 12 |
from app.config.settings import settings
|
| 13 |
from app.routers import grading
|
|
|
|
| 14 |
|
| 15 |
# Configure logging
|
| 16 |
logging.basicConfig(
|
|
@@ -44,6 +45,9 @@ app.add_middleware(
|
|
| 44 |
allow_headers=["*"],
|
| 45 |
)
|
| 46 |
|
|
|
|
|
|
|
|
|
|
| 47 |
# Include routers
|
| 48 |
app.include_router(grading.router)
|
| 49 |
|
|
|
|
| 11 |
|
| 12 |
from app.config.settings import settings
|
| 13 |
from app.routers import grading
|
| 14 |
+
from app.middleware.password_auth import PasswordProtectionMiddleware
|
| 15 |
|
| 16 |
# Configure logging
|
| 17 |
logging.basicConfig(
|
|
|
|
| 45 |
allow_headers=["*"],
|
| 46 |
)
|
| 47 |
|
| 48 |
+
# Add password protection middleware
|
| 49 |
+
app.add_middleware(PasswordProtectionMiddleware)
|
| 50 |
+
|
| 51 |
# Include routers
|
| 52 |
app.include_router(grading.router)
|
| 53 |
|
frontend/src/App.tsx
CHANGED
|
@@ -3,8 +3,10 @@ import './App.css';
|
|
| 3 |
import type { Agent, GradeResult } from './types/index';
|
| 4 |
import { gradingApi } from './api/grading';
|
| 5 |
import AssistantGuide from './AssistantGuide';
|
|
|
|
| 6 |
|
| 7 |
function App() {
|
|
|
|
| 8 |
const [currentView, setCurrentView] = useState<'grading' | 'guide'>('grading');
|
| 9 |
const [agents, setAgents] = useState<Agent[]>([]);
|
| 10 |
const [selectedAgent, setSelectedAgent] = useState<string>('');
|
|
@@ -106,9 +108,19 @@ function App() {
|
|
| 106 |
const currentLabels = labels[selectedLanguage as 'zh' | 'en'] || labels.zh;
|
| 107 |
|
| 108 |
useEffect(() => {
|
| 109 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 110 |
}, []);
|
| 111 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 112 |
const loadAgents = async () => {
|
| 113 |
try {
|
| 114 |
const agentsList = await gradingApi.getAgents();
|
|
@@ -164,6 +176,11 @@ function App() {
|
|
| 164 |
// 取得當前選中的 agent
|
| 165 |
const currentAgent = agents.find(a => a.key === selectedAgent);
|
| 166 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 167 |
return (
|
| 168 |
<div className="App">
|
| 169 |
<header className="app-header">
|
|
|
|
| 3 |
import type { Agent, GradeResult } from './types/index';
|
| 4 |
import { gradingApi } from './api/grading';
|
| 5 |
import AssistantGuide from './AssistantGuide';
|
| 6 |
+
import PasswordProtection from './components/PasswordProtection';
|
| 7 |
|
| 8 |
function App() {
|
| 9 |
+
const [isPasswordVerified, setIsPasswordVerified] = useState<boolean>(false);
|
| 10 |
const [currentView, setCurrentView] = useState<'grading' | 'guide'>('grading');
|
| 11 |
const [agents, setAgents] = useState<Agent[]>([]);
|
| 12 |
const [selectedAgent, setSelectedAgent] = useState<string>('');
|
|
|
|
| 108 |
const currentLabels = labels[selectedLanguage as 'zh' | 'en'] || labels.zh;
|
| 109 |
|
| 110 |
useEffect(() => {
|
| 111 |
+
// Check if password is already verified
|
| 112 |
+
const savedPassword = sessionStorage.getItem('app_password_verified');
|
| 113 |
+
if (savedPassword === 'true') {
|
| 114 |
+
setIsPasswordVerified(true);
|
| 115 |
+
loadAgents();
|
| 116 |
+
}
|
| 117 |
}, []);
|
| 118 |
|
| 119 |
+
const handlePasswordCorrect = () => {
|
| 120 |
+
setIsPasswordVerified(true);
|
| 121 |
+
loadAgents();
|
| 122 |
+
};
|
| 123 |
+
|
| 124 |
const loadAgents = async () => {
|
| 125 |
try {
|
| 126 |
const agentsList = await gradingApi.getAgents();
|
|
|
|
| 176 |
// 取得當前選中的 agent
|
| 177 |
const currentAgent = agents.find(a => a.key === selectedAgent);
|
| 178 |
|
| 179 |
+
// Show password protection if not verified
|
| 180 |
+
if (!isPasswordVerified) {
|
| 181 |
+
return <PasswordProtection onPasswordCorrect={handlePasswordCorrect} />;
|
| 182 |
+
}
|
| 183 |
+
|
| 184 |
return (
|
| 185 |
<div className="App">
|
| 186 |
<header className="app-header">
|
frontend/src/api/grading.ts
CHANGED
|
@@ -10,6 +10,15 @@ const api = axios.create({
|
|
| 10 |
},
|
| 11 |
});
|
| 12 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 13 |
export const gradingApi = {
|
| 14 |
async getAgents(): Promise<Agent[]> {
|
| 15 |
const response = await api.get<AgentsResponse>('/agents');
|
|
@@ -20,4 +29,9 @@ export const gradingApi = {
|
|
| 20 |
const response = await api.post<GradeResponse>('/grade', request);
|
| 21 |
return response.data;
|
| 22 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 23 |
};
|
|
|
|
| 10 |
},
|
| 11 |
});
|
| 12 |
|
| 13 |
+
// Add password verification interceptor
|
| 14 |
+
api.interceptors.request.use((config) => {
|
| 15 |
+
const passwordVerified = sessionStorage.getItem('app_password_verified');
|
| 16 |
+
if (passwordVerified === 'true') {
|
| 17 |
+
config.headers['X-Password-Verified'] = 'true';
|
| 18 |
+
}
|
| 19 |
+
return config;
|
| 20 |
+
});
|
| 21 |
+
|
| 22 |
export const gradingApi = {
|
| 23 |
async getAgents(): Promise<Agent[]> {
|
| 24 |
const response = await api.get<AgentsResponse>('/agents');
|
|
|
|
| 29 |
const response = await api.post<GradeResponse>('/grade', request);
|
| 30 |
return response.data;
|
| 31 |
},
|
| 32 |
+
|
| 33 |
+
async verifyPassword(password: string): Promise<{success: boolean, message?: string}> {
|
| 34 |
+
const response = await axios.post(`${API_BASE_URL}/verify-password`, { password });
|
| 35 |
+
return response.data;
|
| 36 |
+
},
|
| 37 |
};
|