Switch from HTTP basic auth to MCIAS
Replace HTTP basic auth with MCIAS session cookies. Adds mcdsl auth,
csrf, and web packages. Chi router for route-level auth middleware.
Short code redirects (GET /{short}) remain public; all management
pages require MCIAS login. Nord dark theme, layout+page template
pattern matching other platform services.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
55
main.go
55
main.go
@@ -2,17 +2,27 @@ package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
"log/slog"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
|
||||
"git.wntrmute.dev/mc/mcdsl/auth"
|
||||
"git.wntrmute.dev/mc/mcdsl/csrf"
|
||||
"git.wntrmute.dev/mc/mcdsl/web"
|
||||
"git.wntrmute.dev/kyle/goutils/config"
|
||||
"git.wntrmute.dev/kyle/goutils/die"
|
||||
"git.wntrmute.dev/kyle/kls/links"
|
||||
)
|
||||
|
||||
const cookieName = "kls_session"
|
||||
|
||||
var defaultConfigFile = config.DefaultConfigPath("kls", "kls.conf")
|
||||
|
||||
func main() {
|
||||
@@ -23,12 +33,51 @@ func main() {
|
||||
err := config.LoadFile(*cfgFile)
|
||||
die.If(err)
|
||||
|
||||
logger := slog.New(slog.NewTextHandler(os.Stderr, nil))
|
||||
|
||||
ctx := context.Background()
|
||||
db, err := links.Connect(ctx)
|
||||
die.If(err)
|
||||
|
||||
srv := &server{db: db}
|
||||
http.Handle("/", srv)
|
||||
// MCIAS authenticator.
|
||||
authenticator, err := auth.New(auth.Config{
|
||||
ServerURL: config.Get("MCIAS_SERVER_URL"),
|
||||
CACert: config.Get("MCIAS_CA_CERT"),
|
||||
ServiceName: config.GetDefault("MCIAS_SERVICE_NAME", "kls"),
|
||||
}, logger)
|
||||
die.If(err)
|
||||
|
||||
// CSRF protection.
|
||||
csrfSecret := make([]byte, 32)
|
||||
if _, err := rand.Read(csrfSecret); err != nil {
|
||||
die.If(fmt.Errorf("generate CSRF secret: %w", err))
|
||||
}
|
||||
csrfProtect := csrf.New(csrfSecret, "_csrf", "csrf_token")
|
||||
|
||||
srv := &server{db: db, auth: authenticator, csrf: csrfProtect}
|
||||
|
||||
r := chi.NewRouter()
|
||||
|
||||
// Public: short code redirects (no auth).
|
||||
r.Get("/{short:[a-zA-Z0-9]+}", srv.redirect)
|
||||
|
||||
// Public: login page.
|
||||
r.Get("/login", srv.handleLoginPage)
|
||||
r.Post("/login", csrfProtect.Middleware(http.HandlerFunc(srv.handleLogin)).ServeHTTP)
|
||||
|
||||
// Static files.
|
||||
r.Get("/static/*", http.FileServer(http.FS(staticFiles)).ServeHTTP)
|
||||
|
||||
// Authenticated routes.
|
||||
r.Group(func(r chi.Router) {
|
||||
r.Use(web.RequireAuth(authenticator, cookieName, "/login"))
|
||||
r.Use(csrfProtect.Middleware)
|
||||
|
||||
r.Get("/", srv.handleIndex)
|
||||
r.Post("/", srv.postURL)
|
||||
r.Get("/list", srv.listAll)
|
||||
r.Post("/logout", srv.handleLogout)
|
||||
})
|
||||
|
||||
// $PORT is set by the MCP agent for containers with route declarations.
|
||||
// It takes precedence over config, matching the mcdsl convention.
|
||||
@@ -38,5 +87,5 @@ func main() {
|
||||
}
|
||||
addr := net.JoinHostPort(config.Get("HTTP_ADDR"), port)
|
||||
log.Print("listening on ", addr)
|
||||
log.Fatal(http.ListenAndServe(addr, nil))
|
||||
log.Fatal(http.ListenAndServe(addr, r))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user