// gitfed-web is gitfed's web UI: a public repo browser (README/LICENSE/tags,
// public repos, no login needed) plus, behind a real username/password
// login, self-service (manage your own keys, repos, collaborators) and an
// admin section for instance-admin accounts. This is DESIGN.md §9 Phase 4
// ("UI web plus tard, branchée sur le même noyau identité/ACL") — it drives
// the same admin.Ops interface as gitfed-tui (live socket or offline store,
// via internal/opsconnect), not a second implementation of the repo/ACL
// logic.
//
// SSH keys remain the only way to actually push/pull git — the password
// login here only ever grants access to this web UI.
package main
import (
"flag"
"fmt"
"net/http"
"os"
"time"
"git.neuromancer.ovh/bastien-mrq/gitfed/internal/admin"
"git.neuromancer.ovh/bastien-mrq/gitfed/internal/config"
"git.neuromancer.ovh/bastien-mrq/gitfed/internal/opsconnect"
)
type server struct {
ops admin.Ops
domain string
loginByIP *rateLimiter
loginByUser *rateLimiter
gitHTTPByIP *rateLimiter
}
func main() {
configPath := flag.String("config", "gitfed.json", "path to instance config file")
listen := flag.String("listen", ":8088", "address to serve the web UI on")
flag.Parse()
cfg, err := config.Load(*configPath)
if err != nil {
fmt.Fprintf(os.Stderr, "gitfed-web: load config: %v\n", err)
fmt.Fprintln(os.Stderr, "hint: run gitfed-server -init <domain> first")
os.Exit(1)
}
ops, mode, closeFn, err := opsconnect.Connect(cfg)
if err != nil {
fmt.Fprintf(os.Stderr, "gitfed-web: %v\n", err)
os.Exit(1)
}
defer closeFn()
s := &server{
ops: ops,
domain: cfg.Domain,
// Per-account is the tighter bound (an attacker targeting one login);
// per-IP is looser but catches spraying across many usernames from
// one source. Either tripping blocks the attempt.
loginByUser: newRateLimiter(5, 15*time.Minute),
loginByIP: newRateLimiter(20, 15*time.Minute),
// The anonymous git-clone endpoints (handlers_git_http.go) have no
// login to fail, so this counts every request rather than just
// failures — generous enough for a real clone (which is a handful
// of requests: one info/refs, one or more upload-pack), tight
// enough to blunt someone hammering it for disk/CPU/bandwidth.
gitHTTPByIP: newRateLimiter(60, time.Minute),
}
mux := http.NewServeMux()
s.routes(mux)
// Security middleware wraps the whole mux: same-origin enforcement on
// state-changing requests (CSRF defense-in-depth on top of SameSite
// cookies) and the standard hardening response headers.
handler := securityHeaders(s.sameOriginPOST(mux))
srv := &http.Server{
Addr: *listen,
Handler: handler,
ReadHeaderTimeout: 5 * time.Second,
ReadTimeout: 15 * time.Second,
WriteTimeout: 30 * time.Second,
IdleTimeout: 60 * time.Second,
}
fmt.Printf("gitfed-web serving on %s (mode: %s)\n", *listen, mode)
if err := srv.ListenAndServe(); err != nil {
fmt.Fprintln(os.Stderr, "gitfed-web:", err)
os.Exit(1)
}
}