From d9ea716c1633304a32701bf1610a85bf5c5e50d3 Mon Sep 17 00:00:00 2001 From: Torben Nehmer Date: Sun, 12 Jul 2026 08:39:21 +0200 Subject: [PATCH] fix gitignore and add missing files --- .gitea/workflows/build.yaml | 4 +- .gitignore | 2 +- cmd/authproxy/main.go | 111 ++++++++++++++++++++++++++++++++++++ 3 files changed, 114 insertions(+), 3 deletions(-) create mode 100644 cmd/authproxy/main.go diff --git a/.gitea/workflows/build.yaml b/.gitea/workflows/build.yaml index d8eef9a..952e095 100644 --- a/.gitea/workflows/build.yaml +++ b/.gitea/workflows/build.yaml @@ -20,10 +20,10 @@ jobs: apt-get install -y --no-install-recommends git nodejs curl unzip ca-certificates - name: Checkout Code - uses: actions/checkout@v4 + uses: actions/checkout@v7 - name: Setup Go - uses: actions/setup-go@v5 + uses: actions/setup-go@v6 with: go-version: '1.26.x' diff --git a/.gitignore b/.gitignore index 55e9bf2..2aa7baf 100644 --- a/.gitignore +++ b/.gitignore @@ -8,7 +8,7 @@ *.dll *.so *.dylib -authproxy +/authproxy # Test binary, built with `go test -c` *.test diff --git a/cmd/authproxy/main.go b/cmd/authproxy/main.go new file mode 100644 index 0000000..941e020 --- /dev/null +++ b/cmd/authproxy/main.go @@ -0,0 +1,111 @@ +package main + +import ( + "context" + "errors" + "fmt" + "log/slog" + "net/http" + "os" + "os/signal" + "strings" + "syscall" + "time" + + "gitea.nehmer.net/torben/gitea-mcp-forward-auth/internal/auth" +) + +const ( + defaultListenAddr = ":8080" + defaultLogLevel = "info" + shutdownTimeout = 10 * time.Second +) + +func main() { + os.Exit(run()) +} + +func run() int { + listenAddr := envOrDefault("AUTH_PROXY_LISTEN_ADDR", defaultListenAddr) + hashesDir := os.Getenv("AUTH_PROXY_TOKEN_HASHES_DIR") + hashesCSV := os.Getenv(auth.EnvTokenHashes) + logLevel := envOrDefault("AUTH_PROXY_LOG_LEVEL", defaultLogLevel) + + logger, err := newLogger(logLevel) + if err != nil { + fmt.Fprintf(os.Stderr, "failed to initialize logger: %v\n", err) + return 1 + } + + hashes, err := auth.LoadTokenHashes(hashesDir, hashesCSV) + if err != nil { + logger.Error("failed to load token hashes", "error", err) + logger.Error("startup aborted: configure at least one hash via AUTH_PROXY_TOKEN_HASHES_DIR and/or AUTH_PROXY_TOKEN_HASHES") + return 1 + } + + validator, err := auth.NewValidator(hashes) + if err != nil { + logger.Error("failed to initialize validator", "error", err) + return 1 + } + + logger.Info("loaded token set", "token_count", validator.TokenCount()) + + handler := auth.NewHandler(validator, logger) + server := &http.Server{ + Addr: listenAddr, + Handler: handler, + ReadHeaderTimeout: 5 * time.Second, + } + + errCh := make(chan error, 1) + go func() { + errCh <- server.ListenAndServe() + }() + + logger.Info("auth proxy started", "listen_addr", listenAddr) + + sigCh := make(chan os.Signal, 1) + signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM) + + select { + case sig := <-sigCh: + logger.Info("shutdown signal received", "signal", sig.String()) + case err := <-errCh: + if err != nil && !errors.Is(err, http.ErrServerClosed) { + logger.Error("server failed", "error", err) + return 1 + } + return 0 + } + + ctx, cancel := context.WithTimeout(context.Background(), shutdownTimeout) + defer cancel() + + if err := server.Shutdown(ctx); err != nil { + logger.Error("graceful shutdown failed", "error", err) + return 1 + } + + logger.Info("server stopped") + return 0 +} + +func newLogger(levelRaw string) (*slog.Logger, error) { + level := new(slog.LevelVar) + if err := level.UnmarshalText([]byte(strings.ToUpper(strings.TrimSpace(levelRaw)))); err != nil { + return nil, fmt.Errorf("parse log level %q: %w", levelRaw, err) + } + + handler := slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{Level: level}) + return slog.New(handler), nil +} + +func envOrDefault(name, fallback string) string { + v := strings.TrimSpace(os.Getenv(name)) + if v == "" { + return fallback + } + return v +}