diff --git a/core/http/react-ui/src/components/RequireAuth.jsx b/core/http/react-ui/src/components/RequireAuth.jsx index 57268961a..b4a6b2b56 100644 --- a/core/http/react-ui/src/components/RequireAuth.jsx +++ b/core/http/react-ui/src/components/RequireAuth.jsx @@ -2,8 +2,8 @@ import { Navigate } from 'react-router-dom' import { useAuth } from '../context/AuthContext' export default function RequireAuth({ children }) { - const { authEnabled, user, loading } = useAuth() + const { authEnabled, staticApiKeyRequired, user, loading } = useAuth() if (loading) return null - if (authEnabled && !user) return + if ((authEnabled || staticApiKeyRequired) && !user) return return children } diff --git a/core/http/react-ui/src/context/AuthContext.jsx b/core/http/react-ui/src/context/AuthContext.jsx index 5fd33f0ad..9e3de7f29 100644 --- a/core/http/react-ui/src/context/AuthContext.jsx +++ b/core/http/react-ui/src/context/AuthContext.jsx @@ -7,6 +7,7 @@ export function AuthProvider({ children }) { const [state, setState] = useState({ loading: true, authEnabled: false, + staticApiKeyRequired: false, user: null, permissions: {}, }) @@ -20,12 +21,13 @@ export function AuthProvider({ children }) { setState({ loading: false, authEnabled: data.authEnabled || false, + staticApiKeyRequired: data.staticApiKeyRequired || false, user, permissions, }) }) .catch(() => { - setState({ loading: false, authEnabled: false, user: null, permissions: {} }) + setState({ loading: false, authEnabled: false, staticApiKeyRequired: false, user: null, permissions: {} }) }) } @@ -45,17 +47,20 @@ export function AuthProvider({ children }) { const refresh = () => fetchStatus() + const noAuthRequired = !state.authEnabled && !state.staticApiKeyRequired + const hasFeature = (name) => { - if (state.user?.role === 'admin' || !state.authEnabled) return true + if (state.user?.role === 'admin' || noAuthRequired) return true return !!state.permissions[name] } const value = { loading: state.loading, authEnabled: state.authEnabled, + staticApiKeyRequired: state.staticApiKeyRequired, user: state.user, permissions: state.permissions, - isAdmin: state.user?.role === 'admin' || !state.authEnabled, + isAdmin: state.user?.role === 'admin' || noAuthRequired, hasFeature, logout, refresh, diff --git a/core/http/react-ui/src/pages/Login.jsx b/core/http/react-ui/src/pages/Login.jsx index 34d02c8f4..d8aa19c98 100644 --- a/core/http/react-ui/src/pages/Login.jsx +++ b/core/http/react-ui/src/pages/Login.jsx @@ -8,7 +8,7 @@ export default function Login() { const navigate = useNavigate() const { code: urlInviteCode } = useParams() const [searchParams] = useSearchParams() - const { authEnabled, user, loading: authLoading, refresh } = useAuth() + const { authEnabled, staticApiKeyRequired, user, loading: authLoading, refresh } = useAuth() const [providers, setProviders] = useState([]) const [hasUsers, setHasUsers] = useState(true) const [registrationMode, setRegistrationMode] = useState('open') @@ -66,7 +66,7 @@ export default function Login() { // Redirect if auth is disabled or user is already logged in useEffect(() => { - if (!authLoading && (!authEnabled || user)) { + if (!authLoading && ((!authEnabled && !staticApiKeyRequired) || user)) { navigate('/app', { replace: true }) } }, [authLoading, authEnabled, user, navigate]) @@ -176,6 +176,40 @@ export default function Login() { if (authLoading || statusLoading) return null + // Legacy API key-only mode: show a simplified login with just the token input + if (staticApiKeyRequired && !authEnabled) { + return ( +
+
+
+ LocalAI +

Enter your API key to continue

+
+ + {error && ( +
{error}
+ )} + +
+
+ { setToken(e.target.value); setError('') }} + placeholder="Enter API key..." + autoFocus + /> +
+ +
+
+
+ ) + } + const hasGitHub = providers.includes('github') const hasOIDC = providers.includes('oidc') const hasLocal = providers.includes('local') diff --git a/core/http/routes/auth.go b/core/http/routes/auth.go index f44c569e9..e66b3c69e 100644 --- a/core/http/routes/auth.go +++ b/core/http/routes/auth.go @@ -157,10 +157,11 @@ func RegisterAuthRoutes(e *echo.Echo, app *application.Application) { } resp := map[string]any{ - "authEnabled": authEnabled, - "providers": providers, - "hasUsers": hasUsers, - "registrationMode": registrationMode, + "authEnabled": authEnabled, + "staticApiKeyRequired": !authEnabled && len(appConfig.ApiKeys) > 0, + "providers": providers, + "hasUsers": hasUsers, + "registrationMode": registrationMode, } // Include current user if authenticated diff --git a/core/http/routes/auth_test.go b/core/http/routes/auth_test.go index a6a6e3f8a..e9575e59f 100644 --- a/core/http/routes/auth_test.go +++ b/core/http/routes/auth_test.go @@ -45,9 +45,10 @@ func newTestAuthApp(db *gorm.DB, appConfig *config.ApplicationConfig) *echo.Echo } resp := map[string]any{ - "authEnabled": authEnabled, - "providers": providers, - "hasUsers": hasUsers, + "authEnabled": authEnabled, + "staticApiKeyRequired": !authEnabled && len(appConfig.ApiKeys) > 0, + "providers": providers, + "hasUsers": hasUsers, } user := auth.GetUser(c) @@ -407,6 +408,29 @@ var _ = Describe("Auth Routes", Label("auth"), func() { json.Unmarshal(rec.Body.Bytes(), &resp) Expect(resp["hasUsers"]).To(BeFalse()) }) + + It("returns staticApiKeyRequired=true when no DB but API keys configured", func() { + cfg := config.NewApplicationConfig() + config.WithApiKeys([]string{"test-key-123"})(cfg) + app := newTestAuthApp(nil, cfg) + rec := doAuthRequest(app, "GET", "/api/auth/status", nil) + Expect(rec.Code).To(Equal(http.StatusOK)) + + var resp map[string]any + json.Unmarshal(rec.Body.Bytes(), &resp) + Expect(resp["authEnabled"]).To(BeFalse()) + Expect(resp["staticApiKeyRequired"]).To(BeTrue()) + }) + + It("returns staticApiKeyRequired=false when no DB and no API keys", func() { + app := newTestAuthApp(nil, config.NewApplicationConfig()) + rec := doAuthRequest(app, "GET", "/api/auth/status", nil) + Expect(rec.Code).To(Equal(http.StatusOK)) + + var resp map[string]any + json.Unmarshal(rec.Body.Bytes(), &resp) + Expect(resp["staticApiKeyRequired"]).To(BeFalse()) + }) }) Context("POST /api/auth/logout", func() {