Files
localmodelroutex/backend/internal/api/api.go
LovroG05 9d807a6cf1 feat: OIDC server-side login flow with confidential client
Add /auth/login and /auth/callback handlers. The backend holds the
client secret and exchanges the authorization code server-side;
the browser never sees the secret. Redirect URI and post-login
fragment destination are derived solely from GATEWAY_SERVER_PUBLIC_URL
(required when OIDC is set) to prevent host-header injection.
2026-06-28 23:46:30 +02:00

84 lines
2.8 KiB
Go

// Package api implements the gateway HTTP surface: the OpenAI-compatible proxy
// (/v1/*) and the admin API (/api/admin/*).
package api
import (
"context"
"github.com/go-chi/chi/v5"
"git.govekar.net/LovroG05/localmodelroutex/internal/catalog"
"git.govekar.net/LovroG05/localmodelroutex/internal/crypto"
"git.govekar.net/LovroG05/localmodelroutex/internal/providermgr"
"git.govekar.net/LovroG05/localmodelroutex/internal/ratelimit"
"git.govekar.net/LovroG05/localmodelroutex/internal/store"
)
// oidcVerifier validates OIDC tokens for admin access.
// Satisfied by *oidcauth.Verifier; nil means OIDC is not configured.
type oidcVerifier interface {
VerifyAdmin(ctx context.Context, rawToken string) error
}
// Deps are the dependencies the API handlers need.
type Deps struct {
Store *store.Store
Cache *catalog.Cache
Manager *providermgr.Manager
Cipher *crypto.Cipher
AdminToken string
OIDCVerifier oidcVerifier // nil when OIDC is not configured
OIDCClientID string // empty when OIDC is not configured
OIDCClientSecret string
OIDCAuthURL string // authorization_endpoint from discovery
OIDCTokenURL string // token_endpoint from discovery
// PublicBaseURL is the canonical external URL of this server (e.g.
// "https://lmr.example.com"). Required when OIDC is enabled — used to
// build the redirect_uri and the post-login fragment redirect so neither
// can be overridden by a forged Host/X-Forwarded-Host header.
PublicBaseURL string
Limiter *ratelimit.Limiter
// SyncNow triggers an immediate provider reload + catalog sync (used after
// admin changes). May be nil.
SyncNow func(context.Context) error
}
// API holds handler dependencies.
type API struct {
Deps
}
// New returns an API.
func New(d Deps) *API { return &API{Deps: d} }
// Mount registers the proxy and admin routes on r.
func (a *API) Mount(r chi.Router) {
r.Route("/v1", func(r chi.Router) {
r.Use(a.virtualKeyAuth)
r.Post("/chat/completions", a.chatCompletions)
r.Post("/embeddings", a.embeddings)
r.Get("/models", a.listModels)
})
// Public auth endpoints — no auth middleware.
r.Get("/api/auth/config", a.authConfig)
r.Get("/auth/login", a.oidcLogin)
r.Get("/auth/callback", a.oidcCallback)
r.Route("/api/admin", func(r chi.Router) {
r.Use(a.adminAuth)
r.Get("/providers", a.adminListProviders)
r.Post("/providers", a.adminCreateProvider)
r.Delete("/providers/{id}", a.adminDeleteProvider)
r.Get("/keys", a.adminListKeys)
r.Post("/keys", a.adminCreateKey)
r.Delete("/keys/{id}", a.adminRevokeKey)
r.Get("/logs", a.adminListLogs)
r.Get("/models", a.adminListModels)
r.Patch("/models/{id}", a.adminUpdateModel)
r.Get("/settings", a.adminGetSettings)
r.Put("/settings/{key}", a.adminPutSetting)
r.Post("/catalog/refresh", a.adminRefreshCatalog)
})
}