diff --git a/backend/app/config.py b/backend/app/config.py index 4bba2f9..adefc33 100644 --- a/backend/app/config.py +++ b/backend/app/config.py @@ -11,6 +11,7 @@ LEGACY_DEFAULT_SECRET = "change-me-to-a-long-random-string" class Settings(BaseSettings): # ---------- 运行环境 ---------- environment: Literal["development", "test", "production"] = "development" + auth_enabled: bool = False # ---------- 数据库 ---------- DATABASE_URL: str = "sqlite+aiosqlite:///./pingwatch.db" diff --git a/backend/app/core/auth.py b/backend/app/core/auth.py index c59caff..4773598 100644 --- a/backend/app/core/auth.py +++ b/backend/app/core/auth.py @@ -27,7 +27,7 @@ from app.models.user import User, UserRoleEnum from app.core.deps import get_db logger = logging.getLogger("pingwatch.auth") -security = HTTPBearer() +security = HTTPBearer(auto_error=False) def create_access_token(data: dict, expires_delta: Optional[timedelta] = None) -> str: @@ -99,10 +99,22 @@ async def exchange_code_for_user(code: str) -> Optional[dict]: async def get_current_user( - credentials: HTTPAuthorizationCredentials = Depends(security), + credentials: Optional[HTTPAuthorizationCredentials] = Depends(security), db: AsyncSession = Depends(get_db), ) -> User: """从 PingWatch JWT 中解析当前登录用户""" + if not settings.auth_enabled: + return User( + id=0, + casdoor_uid="local-anonymous-admin", + username="local-admin", + display_name="本地管理员", + role=UserRoleEnum.admin, + is_active=True, + ) + + if credentials is None: + raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="缺少认证信息") token = credentials.credentials try: payload = jwt.decode(token, settings.SECRET_KEY, algorithms=["HS256"]) diff --git a/backend/tests/test_auth_disabled.py b/backend/tests/test_auth_disabled.py new file mode 100644 index 0000000..80a3021 --- /dev/null +++ b/backend/tests/test_auth_disabled.py @@ -0,0 +1,29 @@ +"""Authentication switch behavior.""" + +import pytest +from fastapi import HTTPException + +from app.config import settings +from app.core.auth import get_current_user +from app.models.user import UserRoleEnum + + +@pytest.mark.asyncio +async def test_auth_disabled_provides_local_admin_without_token(monkeypatch): + monkeypatch.setattr(settings, "auth_enabled", False) + + user = await get_current_user(credentials=None, db=None) + + assert user.id == 0 + assert user.username == "local-admin" + assert user.role == UserRoleEnum.admin + + +@pytest.mark.asyncio +async def test_auth_enabled_still_requires_a_token(monkeypatch): + monkeypatch.setattr(settings, "auth_enabled", True) + + with pytest.raises(HTTPException) as exc_info: + await get_current_user(credentials=None, db=None) + + assert exc_info.value.status_code == 401 diff --git a/frontend/src/router/index.js b/frontend/src/router/index.js index 5d0a61c..59aa740 100644 --- a/frontend/src/router/index.js +++ b/frontend/src/router/index.js @@ -50,8 +50,19 @@ const router = createRouter({ routes, }) -// 路由守卫:检查登录 +// 默认运行在内网免登录模式;需要 Casdoor 时构建时设置 VITE_AUTH_ENABLED=true。 +const authEnabled = import.meta.env.VITE_AUTH_ENABLED === 'true' + router.beforeEach((to, from, next) => { + if (!authEnabled) { + if (to.name === 'Login') { + next({ name: 'Dashboard' }) + } else { + next() + } + return + } + const token = localStorage.getItem('token') if (to.name !== 'Login' && !token) { next({ name: 'Login' }) diff --git a/frontend/src/stores/app.js b/frontend/src/stores/app.js index 3c25fb6..a5f708c 100644 --- a/frontend/src/stores/app.js +++ b/frontend/src/stores/app.js @@ -3,12 +3,19 @@ import { ref, computed } from 'vue' import { authApi, statsApi } from '@/api' export const useAppStore = defineStore('app', () => { + const authEnabled = import.meta.env.VITE_AUTH_ENABLED === 'true' + const localAdmin = { + id: 0, + username: 'local-admin', + display_name: '本地管理员', + role: 'admin', + } // 用户状态 - const user = ref(JSON.parse(localStorage.getItem('user') || 'null')) + const user = ref(authEnabled ? JSON.parse(localStorage.getItem('user') || 'null') : localAdmin) const token = ref(localStorage.getItem('token') || '') - const isLoggedIn = computed(() => !!token.value) - const isAdmin = computed(() => user.value?.role === 'admin') + const isLoggedIn = computed(() => !authEnabled || !!token.value) + const isAdmin = computed(() => !authEnabled || user.value?.role === 'admin') function setUser(userData, tokenStr) { user.value = userData @@ -18,6 +25,7 @@ export const useAppStore = defineStore('app', () => { } function logout() { + if (!authEnabled) return user.value = null token.value = '' localStorage.removeItem('user') @@ -77,7 +85,7 @@ export const useAppStore = defineStore('app', () => { } return { - user, token, isLoggedIn, isAdmin, + user, token, authEnabled, isLoggedIn, isAdmin, setUser, logout, dashboardData, loading, fetchDashboard, connectWebSocket, diff --git a/frontend/src/views/Layout.vue b/frontend/src/views/Layout.vue index 5c16e3e..55ec875 100644 --- a/frontend/src/views/Layout.vue +++ b/frontend/src/views/Layout.vue @@ -58,7 +58,7 @@
- + +