mirror of
https://github.com/mudler/LocalAI.git
synced 2026-04-17 13:28:31 -04:00
fix(ui): pass by staticApiKeyRequired to show login when only api key is configured (#9220)
This fixes #9213 Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
This commit is contained in:
committed by
GitHub
parent
7962dd16f7
commit
84e51b68ef
@@ -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 <Navigate to="/login" replace />
|
||||
if ((authEnabled || staticApiKeyRequired) && !user) return <Navigate to="/login" replace />
|
||||
return children
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 (
|
||||
<div className="login-page">
|
||||
<div className="card login-card">
|
||||
<div className="login-header">
|
||||
<img src={apiUrl('/static/logo.png')} alt="LocalAI" className="login-logo" />
|
||||
<p className="login-subtitle">Enter your API key to continue</p>
|
||||
</div>
|
||||
|
||||
{error && (
|
||||
<div className="login-alert login-alert-error">{error}</div>
|
||||
)}
|
||||
|
||||
<form onSubmit={handleTokenLogin}>
|
||||
<div className="form-group">
|
||||
<input
|
||||
className="input"
|
||||
type="password"
|
||||
value={token}
|
||||
onChange={(e) => { setToken(e.target.value); setError('') }}
|
||||
placeholder="Enter API key..."
|
||||
autoFocus
|
||||
/>
|
||||
</div>
|
||||
<button type="submit" className="btn btn-primary login-btn-full" disabled={submitting}>
|
||||
{submitting ? 'Signing in...' : 'Sign In'}
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const hasGitHub = providers.includes('github')
|
||||
const hasOIDC = providers.includes('oidc')
|
||||
const hasLocal = providers.includes('local')
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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() {
|
||||
|
||||
Reference in New Issue
Block a user