Add projects
This commit is contained in:
parent
2d3a9ad623
commit
8b607dd700
1802 changed files with 503346 additions and 2 deletions
|
|
@ -0,0 +1,220 @@
|
|||
package management
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
const (
|
||||
anthropicCallbackPort = 54545
|
||||
codexCallbackPort = 1455
|
||||
)
|
||||
|
||||
type callbackForwarder struct {
|
||||
provider string
|
||||
server *http.Server
|
||||
done chan struct{}
|
||||
}
|
||||
|
||||
func isWebUIRequest(c *gin.Context) bool {
|
||||
raw := strings.TrimSpace(c.Query("is_webui"))
|
||||
if raw == "" {
|
||||
return false
|
||||
}
|
||||
switch strings.ToLower(raw) {
|
||||
case "1", "true", "yes", "on":
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func startCallbackForwarder(port int, provider, targetBase string) (*callbackForwarder, error) {
|
||||
callbackForwardersMu.Lock()
|
||||
prev := callbackForwarders[port]
|
||||
if prev != nil {
|
||||
delete(callbackForwarders, port)
|
||||
}
|
||||
callbackForwardersMu.Unlock()
|
||||
|
||||
if prev != nil {
|
||||
stopForwarderInstance(port, prev)
|
||||
}
|
||||
|
||||
addr := fmt.Sprintf("0.0.0.0:%d", port)
|
||||
ln, err := net.Listen("tcp", addr)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to listen on %s: %w", addr, err)
|
||||
}
|
||||
|
||||
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
target := targetBase
|
||||
if raw := r.URL.RawQuery; raw != "" {
|
||||
if strings.Contains(target, "?") {
|
||||
target = target + "&" + raw
|
||||
} else {
|
||||
target = target + "?" + raw
|
||||
}
|
||||
}
|
||||
w.Header().Set("Cache-Control", "no-store")
|
||||
http.Redirect(w, r, target, http.StatusFound)
|
||||
})
|
||||
|
||||
srv := &http.Server{
|
||||
Handler: handler,
|
||||
ReadHeaderTimeout: 5 * time.Second,
|
||||
WriteTimeout: 5 * time.Second,
|
||||
}
|
||||
done := make(chan struct{})
|
||||
|
||||
go func() {
|
||||
if errServe := srv.Serve(ln); errServe != nil && !errors.Is(errServe, http.ErrServerClosed) {
|
||||
log.WithError(errServe).Warnf("callback forwarder for %s stopped unexpectedly", provider)
|
||||
}
|
||||
close(done)
|
||||
}()
|
||||
|
||||
forwarder := &callbackForwarder{
|
||||
provider: provider,
|
||||
server: srv,
|
||||
done: done,
|
||||
}
|
||||
|
||||
callbackForwardersMu.Lock()
|
||||
callbackForwarders[port] = forwarder
|
||||
callbackForwardersMu.Unlock()
|
||||
|
||||
log.Infof("callback forwarder for %s listening on %s", provider, addr)
|
||||
|
||||
return forwarder, nil
|
||||
}
|
||||
|
||||
func stopCallbackForwarderInstance(port int, forwarder *callbackForwarder) {
|
||||
if forwarder == nil {
|
||||
return
|
||||
}
|
||||
callbackForwardersMu.Lock()
|
||||
if current := callbackForwarders[port]; current == forwarder {
|
||||
delete(callbackForwarders, port)
|
||||
}
|
||||
callbackForwardersMu.Unlock()
|
||||
|
||||
stopForwarderInstance(port, forwarder)
|
||||
}
|
||||
|
||||
func stopForwarderInstance(port int, forwarder *callbackForwarder) {
|
||||
if forwarder == nil || forwarder.server == nil {
|
||||
return
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
|
||||
defer cancel()
|
||||
|
||||
if err := forwarder.server.Shutdown(ctx); err != nil && !errors.Is(err, http.ErrServerClosed) {
|
||||
log.WithError(err).Warnf("failed to shut down callback forwarder on port %d", port)
|
||||
}
|
||||
|
||||
select {
|
||||
case <-forwarder.done:
|
||||
case <-time.After(2 * time.Second):
|
||||
}
|
||||
|
||||
log.Infof("callback forwarder on port %d stopped", port)
|
||||
}
|
||||
|
||||
func (h *Handler) managementCallbackURL(path string) (string, error) {
|
||||
if h == nil || h.cfg == nil || h.cfg.Port <= 0 {
|
||||
return "", fmt.Errorf("server port is not configured")
|
||||
}
|
||||
if !strings.HasPrefix(path, "/") {
|
||||
path = "/" + path
|
||||
}
|
||||
scheme := "http"
|
||||
if h.cfg.TLS.Enable {
|
||||
scheme = "https"
|
||||
}
|
||||
return fmt.Sprintf("%s://127.0.0.1:%d%s", scheme, h.cfg.Port, path), nil
|
||||
}
|
||||
|
||||
func pluginAuthProviderFromPath(path string) (string, bool) {
|
||||
path = strings.TrimSpace(path)
|
||||
const prefix = "/v0/management/"
|
||||
const suffix = "-auth-url"
|
||||
if !strings.HasPrefix(path, prefix) || !strings.HasSuffix(path, suffix) {
|
||||
return "", false
|
||||
}
|
||||
provider := strings.TrimSuffix(strings.TrimPrefix(path, prefix), suffix)
|
||||
provider = strings.ToLower(strings.TrimSpace(provider))
|
||||
if provider == "" {
|
||||
return "", false
|
||||
}
|
||||
for _, r := range provider {
|
||||
switch {
|
||||
case r >= 'a' && r <= 'z':
|
||||
case r >= '0' && r <= '9':
|
||||
case r == '-':
|
||||
default:
|
||||
return "", false
|
||||
}
|
||||
}
|
||||
return provider, true
|
||||
}
|
||||
|
||||
func (h *Handler) ServePluginAuthURL(c *gin.Context) bool {
|
||||
if h == nil || c == nil || c.Request == nil || c.Request.URL == nil {
|
||||
return false
|
||||
}
|
||||
h.mu.Lock()
|
||||
host := h.pluginHost
|
||||
h.mu.Unlock()
|
||||
if host == nil {
|
||||
return false
|
||||
}
|
||||
provider, ok := pluginAuthProviderFromPath(c.Request.URL.Path)
|
||||
if !ok || !host.HasAuthProvider(provider) {
|
||||
return false
|
||||
}
|
||||
|
||||
ctx := PopulateAuthContext(context.Background(), c)
|
||||
baseURL, errBaseURL := h.managementCallbackURL("/v0/management/oauth-callback")
|
||||
if errBaseURL != nil {
|
||||
log.WithError(errBaseURL).Error("failed to compute plugin auth callback URL")
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to generate authorization url"})
|
||||
return true
|
||||
}
|
||||
resp, handled, errStart := host.StartLogin(ctx, provider, baseURL)
|
||||
if !handled {
|
||||
return false
|
||||
}
|
||||
if errStart != nil {
|
||||
log.WithError(errStart).Error("failed to start plugin auth login")
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to generate authorization url"})
|
||||
return true
|
||||
}
|
||||
state := strings.TrimSpace(resp.State)
|
||||
if state == "" {
|
||||
log.WithField("provider", provider).Error("plugin auth provider returned empty state")
|
||||
c.JSON(http.StatusBadGateway, gin.H{"error": "invalid oauth state"})
|
||||
return true
|
||||
}
|
||||
if errState := ValidateOAuthState(state); errState != nil {
|
||||
log.WithError(errState).WithField("provider", provider).Error("plugin auth provider returned invalid state")
|
||||
c.JSON(http.StatusBadGateway, gin.H{"error": "invalid oauth state"})
|
||||
return true
|
||||
}
|
||||
if errRegister := RegisterPluginOAuthSession(state, provider, resp.Metadata); errRegister != nil {
|
||||
log.WithError(errRegister).WithField("provider", provider).Error("failed to register plugin oauth session")
|
||||
c.JSON(http.StatusBadGateway, gin.H{"error": "failed to generate authorization url"})
|
||||
return true
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{"status": "ok", "url": resp.URL, "state": state})
|
||||
return true
|
||||
}
|
||||
Loading…
Reference in a new issue