Add SSO login support
MCAT can now redirect users to MCIAS for SSO login (including passkey support) instead of showing its own login form. SSO is opt-in via the [sso] config section. - Add SSO landing page with "Sign in with MCIAS" button - Add /sso/redirect and /sso/callback routes - Update mcdsl to v1.5.0 (sso package) - Fix .gitignore: /mcat ignores only the root binary, not cmd/mcat/ - Track cmd/mcat/ source files (previously gitignored by accident) Security: - State cookie uses SameSite=Lax for cross-site redirect compatibility - Session cookie remains SameSite=Strict after login Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
35
cmd/mcat/main.go
Normal file
35
cmd/mcat/main.go
Normal file
@@ -0,0 +1,35 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var version = "dev"
|
||||
|
||||
func main() {
|
||||
root := &cobra.Command{
|
||||
Use: "mcat",
|
||||
Short: "MCIAS login policy tester",
|
||||
}
|
||||
|
||||
root.AddCommand(serverCmd())
|
||||
root.AddCommand(versionCmd())
|
||||
|
||||
if err := root.Execute(); err != nil {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
func versionCmd() *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "version",
|
||||
Short: "Print mcat version",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
fmt.Println(version)
|
||||
},
|
||||
}
|
||||
}
|
||||
116
cmd/mcat/server.go
Normal file
116
cmd/mcat/server.go
Normal file
@@ -0,0 +1,116 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log/slog"
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"git.wntrmute.dev/mc/mcat/internal/webserver"
|
||||
"git.wntrmute.dev/mc/mcdsl/auth"
|
||||
"git.wntrmute.dev/mc/mcdsl/config"
|
||||
"git.wntrmute.dev/mc/mcdsl/httpserver"
|
||||
"git.wntrmute.dev/mc/mcdsl/sso"
|
||||
)
|
||||
|
||||
// mcatConfig is the mcat-specific configuration. It embeds config.Base
|
||||
// for the standard sections (server, mcias, log). mcat has no database
|
||||
// or additional sections.
|
||||
type mcatConfig struct {
|
||||
config.Base
|
||||
SSO ssoConfig `toml:"sso"`
|
||||
}
|
||||
|
||||
type ssoConfig struct {
|
||||
RedirectURI string `toml:"redirect_uri"`
|
||||
}
|
||||
|
||||
func serverCmd() *cobra.Command {
|
||||
var configPath string
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "server",
|
||||
Short: "Start the mcat web server",
|
||||
RunE: func(_ *cobra.Command, _ []string) error {
|
||||
return runServer(configPath)
|
||||
},
|
||||
}
|
||||
|
||||
cmd.Flags().StringVarP(&configPath, "config", "c", "mcat.toml", "path to config file")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func runServer(configPath string) error {
|
||||
cfg, err := config.Load[mcatConfig](configPath, "MCAT")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var logLevel slog.Level
|
||||
switch cfg.Log.Level {
|
||||
case "debug":
|
||||
logLevel = slog.LevelDebug
|
||||
case "warn":
|
||||
logLevel = slog.LevelWarn
|
||||
case "error":
|
||||
logLevel = slog.LevelError
|
||||
default:
|
||||
logLevel = slog.LevelInfo
|
||||
}
|
||||
logger := slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{Level: logLevel}))
|
||||
|
||||
authenticator, err := auth.New(cfg.MCIAS, logger)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Create SSO client if configured.
|
||||
var ssoClient *sso.Client
|
||||
if cfg.SSO.RedirectURI != "" {
|
||||
ssoClient, err = sso.New(sso.Config{
|
||||
MciasURL: cfg.MCIAS.ServerURL,
|
||||
ClientID: cfg.MCIAS.ServiceName,
|
||||
RedirectURI: cfg.SSO.RedirectURI,
|
||||
CACert: cfg.MCIAS.CACert,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
logger.Info("SSO enabled", "mcias", cfg.MCIAS.ServerURL)
|
||||
}
|
||||
|
||||
wsCfg := webserver.Config{
|
||||
ServiceName: cfg.MCIAS.ServiceName,
|
||||
Tags: cfg.MCIAS.Tags,
|
||||
}
|
||||
srv, err := webserver.New(wsCfg, authenticator, logger, ssoClient)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
httpSrv := httpserver.New(cfg.Server, logger)
|
||||
httpSrv.Router.Mount("/", srv.Handler())
|
||||
|
||||
ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
|
||||
defer stop()
|
||||
|
||||
go func() {
|
||||
logger.Info("starting mcat", "addr", cfg.Server.ListenAddr, "version", version)
|
||||
if err := httpSrv.ListenAndServeTLS(); err != nil {
|
||||
logger.Error("server error", "error", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}()
|
||||
|
||||
<-ctx.Done()
|
||||
logger.Info("shutting down")
|
||||
|
||||
shutdownCtx, cancel := context.WithTimeout(context.Background(), cfg.Server.ShutdownTimeout.Duration)
|
||||
defer cancel()
|
||||
|
||||
return httpSrv.Shutdown(shutdownCtx)
|
||||
}
|
||||
Reference in New Issue
Block a user