diff --git a/.envrc b/.envrc index cb5e78d..03de5e9 100644 --- a/.envrc +++ b/.envrc @@ -8,7 +8,7 @@ export POLYCULECONNECT_SERVER_PORT="5000" # POLYCULECONNECT_SERVER_SOCK_PATH = "" export POLYCULECONNECT_STORAGE_TYPE="sqlite" -export POLYCULECONNECT_STORAGE_FILEPATH="./build/polyculeconnect.db" +export POLYCULECONNECT_STORAGE_PATH="./build/polyculeconnect.db" # POLYCULECONNECT_STORAGE_HOST = "127.0.0.1" # POLYCULECONNECT_STORAGE_PORT = "5432" # POLYCULECONNECT_STORAGE_DB = "polyculeconnect" diff --git a/Dockerfile b/Dockerfile index c128031..b1d21d9 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM --platform=$TARGETPLATFORM golang:1.20 AS builder +FROM --platform=$TARGETPLATFORM golang:1.21 AS builder ARG TARGETPLATFORM ARG BUILDPLATFORM WORKDIR /go/src/git.faercol.me/polyculeconnect diff --git a/polyculeconnect.db b/polyculeconnect.db new file mode 100644 index 0000000..5b1a7bd Binary files /dev/null and b/polyculeconnect.db differ diff --git a/polyculeconnect/cmd/app/add.go b/polyculeconnect/cmd/app/add.go index 6a020df..1ad636c 100644 --- a/polyculeconnect/cmd/app/add.go +++ b/polyculeconnect/cmd/app/add.go @@ -1,12 +1,14 @@ package cmd import ( + "context" "fmt" "git.faercol.me/faercol/polyculeconnect/polyculeconnect/cmd/utils" + "git.faercol.me/faercol/polyculeconnect/polyculeconnect/internal/db" + "git.faercol.me/faercol/polyculeconnect/polyculeconnect/internal/model" + "git.faercol.me/faercol/polyculeconnect/polyculeconnect/logger" "git.faercol.me/faercol/polyculeconnect/polyculeconnect/services" - "git.faercol.me/faercol/polyculeconnect/polyculeconnect/services/app" - "github.com/dexidp/dex/storage" "github.com/spf13/cobra" ) @@ -60,7 +62,12 @@ func generateSecret(interactive bool, currentValue, valueName string) (string, e func addNewApp() { c := utils.InitConfig("") - s := utils.InitStorage(c) + logger.Init(c.LogLevel) + + s, err := db.New(*c) + if err != nil { + utils.Failf("failed to init storage: %s", err.Error()) + } clientID, err := generateSecret(appInteractive, appClientID, "client ID") if err != nil { @@ -71,14 +78,18 @@ func addNewApp() { utils.Fail(err.Error()) } - appConf := storage.Client{ + appConf := model.ClientConfig{ ID: clientID, Secret: clientSecret, Name: appName, RedirectURIs: appRedirectURIs, } - if err := app.New(s).AddApp(appConf); err != nil { - utils.Failf("Failed to add new app to storage: %s", err.Error()) + clt := model.Client{ + ClientConfig: appConf, + } + + if err := s.ClientStorage().AddClient(context.Background(), &clt); err != nil { + utils.Failf("failed to create app: %s", err) } fmt.Printf("New app %s added.\n", appName) diff --git a/polyculeconnect/cmd/app/remove.go b/polyculeconnect/cmd/app/remove.go index 3d29ed6..0036112 100644 --- a/polyculeconnect/cmd/app/remove.go +++ b/polyculeconnect/cmd/app/remove.go @@ -1,12 +1,12 @@ package cmd import ( - "errors" + "context" "fmt" "git.faercol.me/faercol/polyculeconnect/polyculeconnect/cmd/utils" - "git.faercol.me/faercol/polyculeconnect/polyculeconnect/services/app" - "github.com/dexidp/dex/storage" + "git.faercol.me/faercol/polyculeconnect/polyculeconnect/internal/db" + "git.faercol.me/faercol/polyculeconnect/polyculeconnect/logger" "github.com/spf13/cobra" ) @@ -21,12 +21,16 @@ var appRemoveCmd = &cobra.Command{ } func removeApp(appID string) { - s := utils.InitStorage(utils.InitConfig("")) + c := utils.InitConfig("") + logger.Init(c.LogLevel) - if err := app.New(s).RemoveApp(appID); err != nil { - if !errors.Is(err, storage.ErrNotFound) { - utils.Failf("Failed to remove app: %s", err.Error()) - } + s, err := db.New(*c) + if err != nil { + utils.Failf("failed to init storage: %s", err.Error()) + } + + if err := s.ClientStorage().DeleteClient(context.Background(), appID); err != nil { + utils.Failf("Failed to remove app: %s", err.Error()) } fmt.Println("App deleted") } diff --git a/polyculeconnect/cmd/app/show.go b/polyculeconnect/cmd/app/show.go index 3c1e79e..b25355f 100644 --- a/polyculeconnect/cmd/app/show.go +++ b/polyculeconnect/cmd/app/show.go @@ -1,12 +1,14 @@ package cmd import ( + "context" "errors" "fmt" "git.faercol.me/faercol/polyculeconnect/polyculeconnect/cmd/utils" - "git.faercol.me/faercol/polyculeconnect/polyculeconnect/services/app" - "github.com/dexidp/dex/storage" + "git.faercol.me/faercol/polyculeconnect/polyculeconnect/internal/db" + "git.faercol.me/faercol/polyculeconnect/polyculeconnect/internal/db/client" + "git.faercol.me/faercol/polyculeconnect/polyculeconnect/logger" "github.com/spf13/cobra" ) @@ -19,20 +21,26 @@ Optional parameters: - app-id: id of the application to display. If empty, display the list of available apps instead`, Args: cobra.MaximumNArgs(1), Run: func(cmd *cobra.Command, args []string) { - s := utils.InitStorage(utils.InitConfig("")) + c := utils.InitConfig("") + logger.Init(c.LogLevel) + + s, err := db.New(*c) + if err != nil { + utils.Failf("failed to init storage: %s", err.Error()) + } if len(args) > 0 { - showApp(args[0], app.New(s)) + showApp(args[0], s.ClientStorage()) } else { - listApps(app.New(s)) + listApps(s.ClientStorage()) } }, } -func showApp(appID string, appService app.Service) { - appConfig, err := appService.GetApp(appID) +func showApp(appID string, st client.ClientDB) { + appConfig, err := st.GetClientByID(context.Background(), appID) if err != nil { - if errors.Is(err, storage.ErrNotFound) { + if errors.Is(err, client.ErrNotFound) { utils.Failf("App with ID %s does not exist\n", appID) } utils.Failf("Failed to get config for app %s: %q\n", appID, err.Error()) @@ -43,13 +51,13 @@ func showApp(appID string, appService app.Service) { printProperty("ID", appConfig.ID, 1) printProperty("Client secret", appConfig.Secret, 1) printProperty("Redirect URIs", "", 1) - for _, uri := range appConfig.RedirectURIs { + for _, uri := range appConfig.RedirectURIs() { printProperty("", uri, 2) } } -func listApps(appService app.Service) { - apps, err := appService.ListApps() +func listApps(st client.ClientDB) { + apps, err := st.GetAllClients(context.Background()) if err != nil { utils.Failf("Failed to list apps: %q\n", err.Error()) } diff --git a/polyculeconnect/cmd/backend/add.go b/polyculeconnect/cmd/backend/add.go index ba70a18..4cd1f6d 100644 --- a/polyculeconnect/cmd/backend/add.go +++ b/polyculeconnect/cmd/backend/add.go @@ -1,11 +1,15 @@ package cmd import ( + "context" "fmt" + "strings" "git.faercol.me/faercol/polyculeconnect/polyculeconnect/cmd/utils" + "git.faercol.me/faercol/polyculeconnect/polyculeconnect/internal/db" + "git.faercol.me/faercol/polyculeconnect/polyculeconnect/internal/model" "git.faercol.me/faercol/polyculeconnect/polyculeconnect/logger" - "git.faercol.me/faercol/polyculeconnect/polyculeconnect/services/backend" + "github.com/google/uuid" "github.com/spf13/cobra" ) @@ -15,6 +19,7 @@ var ( backendIssuer string backendClientID string backendClientSecret string + backendScopes []string ) var backendAddCmd = &cobra.Command{ @@ -35,10 +40,23 @@ Parameters to provide: }, } +func scopesValid(scopes []string) bool { + for _, s := range scopes { + if s == "openid" { + return true + } + } + return false +} + func addNewBackend() { c := utils.InitConfig("") logger.Init(c.LogLevel) - s := utils.InitStorage(c) + + s, err := db.New(*c) + if err != nil { + utils.Failf("failed to init storage: %s", err.Error()) + } if backendClientID == "" { utils.Fail("Empty client ID") @@ -47,15 +65,24 @@ func addNewBackend() { utils.Fail("Empty client secret") } - backendConf := backend.BackendConfig{ - Issuer: backendIssuer, - ClientID: backendClientID, - ClientSecret: backendClientSecret, - RedirectURI: c.RedirectURI(), - ID: backendID, - Name: backendName, + if !scopesValid(backendScopes) { + utils.Failf("Invalid list of scopes %s", strings.Join(backendScopes, ", ")) } - if err := backend.New(s).AddBackend(backendConf); err != nil { + + backendIDUUID := uuid.New() + + backendConf := model.Backend{ + ID: backendIDUUID, + Name: backendName, + Config: model.BackendOIDCConfig{ + ClientID: backendClientID, + ClientSecret: backendClientSecret, + Issuer: backendIssuer, + RedirectURI: c.RedirectURI(), + Scopes: backendScopes, + }, + } + if err := s.BackendStorage().AddBackend(context.Background(), &backendConf); err != nil { utils.Failf("Failed to add new backend to storage: %s", err.Error()) } @@ -70,4 +97,5 @@ func init() { backendAddCmd.Flags().StringVarP(&backendIssuer, "issuer", "d", "", "Full hostname of the backend") backendAddCmd.Flags().StringVarP(&backendClientID, "client-id", "", "", "OIDC Client ID for the backend") backendAddCmd.Flags().StringVarP(&backendClientSecret, "client-secret", "", "", "OIDC Client secret for the backend") + backendAddCmd.Flags().StringArrayVarP(&backendScopes, "scopes", "s", []string{"openid", "profile", "email"}, "OIDC Scopes asked to the backend") } diff --git a/polyculeconnect/cmd/backend/backend.go b/polyculeconnect/cmd/backend/backend.go index b971f35..b5b01c5 100644 --- a/polyculeconnect/cmd/backend/backend.go +++ b/polyculeconnect/cmd/backend/backend.go @@ -11,9 +11,6 @@ var backendCmd = &cobra.Command{ Use: "backend", Short: "Handle authentication backends", Long: `Add, Remove or Show currently installed authentication backends`, - Run: func(cmd *cobra.Command, args []string) { - fmt.Println("backend called") - }, } func printProperty(key, value string) { diff --git a/polyculeconnect/cmd/backend/remove.go b/polyculeconnect/cmd/backend/remove.go index de52874..c39c7a5 100644 --- a/polyculeconnect/cmd/backend/remove.go +++ b/polyculeconnect/cmd/backend/remove.go @@ -1,12 +1,12 @@ package cmd import ( - "errors" + "context" "fmt" "git.faercol.me/faercol/polyculeconnect/polyculeconnect/cmd/utils" - "git.faercol.me/faercol/polyculeconnect/polyculeconnect/services/backend" - "github.com/dexidp/dex/storage" + "git.faercol.me/faercol/polyculeconnect/polyculeconnect/internal/db" + "github.com/google/uuid" "github.com/spf13/cobra" ) @@ -20,13 +20,19 @@ var backendRemoveCmd = &cobra.Command{ }, } -func removeBackend(backendID string) { - s := utils.InitStorage(utils.InitConfig("")) +func removeBackend(backendIDStr string) { + backendID, err := uuid.Parse(backendIDStr) + if err != nil { + utils.Failf("Invalid UUID format: %s", err.Error()) + } - if err := backend.New(s).RemoveBackend(backendID); err != nil { - if !errors.Is(err, storage.ErrNotFound) { - utils.Failf("Failed to remove backend: %s", err.Error()) - } + s, err := db.New(*utils.InitConfig("")) + if err != nil { + utils.Failf("Failed to init storage: %s", err.Error()) + } + + if err := s.BackendStorage().DeleteBackend(context.Background(), backendID); err != nil { + utils.Failf("Failed to remove backend: %s", err.Error()) } fmt.Println("Backend deleted") } diff --git a/polyculeconnect/cmd/backend/show.go b/polyculeconnect/cmd/backend/show.go index 1fc8253..d677049 100644 --- a/polyculeconnect/cmd/backend/show.go +++ b/polyculeconnect/cmd/backend/show.go @@ -1,12 +1,15 @@ package cmd import ( + "context" "errors" "fmt" + "strings" "git.faercol.me/faercol/polyculeconnect/polyculeconnect/cmd/utils" - "git.faercol.me/faercol/polyculeconnect/polyculeconnect/services/backend" - "github.com/dexidp/dex/storage" + "git.faercol.me/faercol/polyculeconnect/polyculeconnect/internal/db" + "git.faercol.me/faercol/polyculeconnect/polyculeconnect/internal/db/backend" + "git.faercol.me/faercol/polyculeconnect/polyculeconnect/logger" "github.com/spf13/cobra" ) @@ -19,36 +22,42 @@ Optional parameters: - app-id: id of the backend to display. If empty, display the list of available backends instead`, Args: cobra.MaximumNArgs(1), Run: func(cmd *cobra.Command, args []string) { - s := utils.InitStorage(utils.InitConfig("")) + conf := utils.InitConfig("") + s, err := db.New(*conf) + if err != nil { + utils.Failf("Failed to init storage: %s", err.Error()) + } + logger.Init(conf.LogLevel) if len(args) > 0 { - showBackend(args[0], backend.New(s)) + showBackend(args[0], s.BackendStorage()) } else { - listBackends(backend.New(s)) + listBackends(s.BackendStorage()) } }, } -func showBackend(backendId string, backendService backend.Service) { - backendConfig, err := backendService.GetBackend(backendId) +func showBackend(backendName string, st backend.BackendDB) { + backendConfig, err := st.GetBackendByName(context.Background(), backendName) if err != nil { - if errors.Is(err, storage.ErrNotFound) { - utils.Failf("Backend with ID %s does not exist\n", backendId) + if errors.Is(err, backend.ErrNotFound) { + utils.Failf("Backend with name %s does not exist\n", backendName) } - utils.Failf("Failed to get config for backend %s: %q\n", backendId, err.Error()) + utils.Failf("Failed to get config for backend %s: %q\n", backendName, err.Error()) } fmt.Println("Backend config:") - printProperty("ID", backendConfig.ID) + printProperty("ID", backendConfig.ID.String()) printProperty("Name", backendConfig.Name) - printProperty("Issuer", backendConfig.Issuer) - printProperty("Client ID", backendConfig.ClientID) - printProperty("Client secret", backendConfig.ClientSecret) - printProperty("Redirect URI", backendConfig.RedirectURI) + printProperty("Issuer", backendConfig.Config.Issuer) + printProperty("Client ID", backendConfig.Config.ClientID) + printProperty("Client secret", backendConfig.Config.ClientSecret) + printProperty("Redirect URI", backendConfig.Config.RedirectURI) + printProperty("Scopes", strings.Join(backendConfig.Config.Scopes, ", ")) } -func listBackends(backendService backend.Service) { - backends, err := backendService.ListBackends() +func listBackends(backendStorage backend.BackendDB) { + backends, err := backendStorage.GetAllBackends(context.Background()) if err != nil { utils.Failf("Failed to list backends: %q\n", err.Error()) } @@ -58,7 +67,7 @@ func listBackends(backendService backend.Service) { return } for _, b := range backends { - fmt.Printf("\t - %s: (%s) - %s\n", b.ID, b.Name, b.Issuer) + fmt.Printf("\t - %s: (%s) - %s\n", b.ID, b.Name, b.Config.Issuer) } } diff --git a/polyculeconnect/cmd/db/connect.go b/polyculeconnect/cmd/db/connect.go index bdacdbe..a5cd153 100644 --- a/polyculeconnect/cmd/db/connect.go +++ b/polyculeconnect/cmd/db/connect.go @@ -24,7 +24,7 @@ var connectCmd = &cobra.Command{ }, } -func connectSQLite(conf *config.StorageConfig) error { +func connectSQLite(conf config.StorageConfig) error { path, err := exec.LookPath("sqlite3") if err != nil { if errors.Is(err, exec.ErrNotFound) { diff --git a/polyculeconnect/cmd/db/migrate.go b/polyculeconnect/cmd/db/migrate.go new file mode 100644 index 0000000..bf5749f --- /dev/null +++ b/polyculeconnect/cmd/db/migrate.go @@ -0,0 +1,96 @@ +package db + +import ( + "fmt" + "strconv" + + "git.faercol.me/faercol/polyculeconnect/polyculeconnect/cmd/utils" + "git.faercol.me/faercol/polyculeconnect/polyculeconnect/config" + "git.faercol.me/faercol/polyculeconnect/polyculeconnect/internal/db" + "github.com/golang-migrate/migrate/v4" + "github.com/golang-migrate/migrate/v4/database/sqlite3" + _ "github.com/golang-migrate/migrate/v4/source/file" + "github.com/spf13/cobra" +) + +// migrateCmd represents the db migrate command +var migrateCmd = &cobra.Command{ + Use: "migrate", + Short: "Run the database migrations", + Long: `Run the database migrations.`, + Run: func(cmd *cobra.Command, args []string) { + conf := utils.InitConfig("") + up, nbSteps := parseArgs(args) + if err := runMigrations(conf, up, nbSteps); err != nil { + utils.Failf("Failed to run migrations: %s", err.Error()) + } + }, +} + +func parseArgs(args []string) (bool, int) { + if len(args) == 0 { + return true, 0 + } + var actionUp bool + switch args[0] { + case "up": + actionUp = true + case "down": + actionUp = false + default: + actionUp = true + } + + nbSteps := 0 + if len(args) > 1 { + var err error + nbSteps, err = strconv.Atoi(args[1]) + if err != nil { + return actionUp, 0 + } + } + + return actionUp, nbSteps +} + +func runMigrations(conf *config.AppConfig, up bool, nbSteps int) error { + storage, err := db.New(*conf) + if err != nil { + return fmt.Errorf("failed to connect to db: %w", err) + } + driver, err := sqlite3.WithInstance(storage.DB(), &sqlite3.Config{}) + if err != nil { + return fmt.Errorf("failed to open sqlite3 driver: %w", err) + } + + m, err := migrate.NewWithDatabaseInstance("file://migrations", "", driver) + if err != nil { + return fmt.Errorf("failed to init migrator: %w", err) + } + + if nbSteps > 0 { + multiplier := 1 + if !up { + multiplier = -1 + } + return m.Steps(multiplier * nbSteps) + } + if up { + return m.Up() + } + return m.Down() +} + +func init() { + dbCmd.AddCommand(migrateCmd) + + // Here you will define your flags and configuration settings. + + // Cobra supports Persistent Flags which will work for this command + // and all subcommands, e.g.: + // dbCmd.PersistentFlags().String("foo", "", "A help for foo") + + // Cobra supports local flags which will only run when this command + // is called directly, e.g.: + // dbCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") +} diff --git a/polyculeconnect/cmd/serve/serve.go b/polyculeconnect/cmd/serve/serve.go index 1c4a98f..946c3cf 100644 --- a/polyculeconnect/cmd/serve/serve.go +++ b/polyculeconnect/cmd/serve/serve.go @@ -2,19 +2,28 @@ package serve import ( "context" + "crypto/rand" + "crypto/rsa" + "crypto/sha256" + "log/slog" "os" "os/signal" "time" "git.faercol.me/faercol/polyculeconnect/polyculeconnect/cmd" "git.faercol.me/faercol/polyculeconnect/polyculeconnect/cmd/utils" - "git.faercol.me/faercol/polyculeconnect/polyculeconnect/connector" + "git.faercol.me/faercol/polyculeconnect/polyculeconnect/internal/client" + "git.faercol.me/faercol/polyculeconnect/polyculeconnect/internal/db" + "git.faercol.me/faercol/polyculeconnect/polyculeconnect/internal/middlewares" + "git.faercol.me/faercol/polyculeconnect/polyculeconnect/internal/model" + "git.faercol.me/faercol/polyculeconnect/polyculeconnect/internal/storage" "git.faercol.me/faercol/polyculeconnect/polyculeconnect/logger" "git.faercol.me/faercol/polyculeconnect/polyculeconnect/server" - "git.faercol.me/faercol/polyculeconnect/polyculeconnect/services" - dex_server "github.com/dexidp/dex/server" - "github.com/prometheus/client_golang/prometheus" + "github.com/go-jose/go-jose/v4" + "github.com/google/uuid" "github.com/spf13/cobra" + "github.com/zitadel/oidc/v3/pkg/op" + "go.uber.org/zap/exp/zapslog" ) var configPath string @@ -39,45 +48,45 @@ func serve() { logger.Init(conf.LogLevel) logger.L.Infof("Initialized logger with level %v", conf.LogLevel) - storageType := utils.InitStorage(conf) - logger.L.Infof("Initialized storage backend %q", conf.StorageType) - dexConf := dex_server.Config{ - Web: dex_server.WebConfig{ - Dir: conf.StaticDir, - Theme: "default", - }, - Storage: storageType, - Issuer: conf.Issuer, - SupportedResponseTypes: []string{"code"}, - SkipApprovalScreen: false, - AllowedOrigins: []string{"*"}, - Logger: logger.L, - PrometheusRegistry: prometheus.NewRegistry(), - } - - logger.L.Info("Initializing authentication backends") - - dex_server.ConnectorsConfig[connector.TypeRefuseAll] = func() dex_server.ConnectorConfig { return new(connector.RefuseAllConfig) } - connectors, err := dexConf.Storage.ListConnectors() + userDB, err := db.New(*conf) if err != nil { - logger.L.Fatalf("Failed to get existing connectors: %s", err.Error()) - } - var connectorIDs []string - for _, conn := range connectors { - connectorIDs = append(connectorIDs, conn.ID) + utils.Failf("failed to init user DB: %s", err.Error()) } - if err := services.AddDefaultBackend(storageType); err != nil { - logger.L.Errorf("Failed to add connector for backend RefuseAll to stage: %s", err.Error()) - } + backends := map[uuid.UUID]*client.OIDCClient{} + key := sha256.Sum256([]byte("test")) - dexSrv, err := dex_server.NewServer(mainCtx, dexConf) + privateKey, err := rsa.GenerateKey(rand.Reader, 2048) if err != nil { - logger.L.Fatalf("Failed to init dex server: %s", err.Error()) + utils.Failf("Failed to generate private key: %s", err) + } + signingKey := model.Key{ + PrivateKey: privateKey, + KeyID: uuid.New(), + SigningAlg: jose.RS256, + } + + st := storage.Storage{LocalStorage: userDB, InitializedBackends: backends, Key: &signingKey} + opConf := op.Config{ + CryptoKey: key, + CodeMethodS256: false, + GrantTypeRefreshToken: true, + } + slogger := slog.New(zapslog.NewHandler(logger.L.Desugar().Core(), nil)) + // slogger := + options := []op.Option{ + op.WithAllowInsecure(), + op.WithLogger(slogger), + op.WithHttpInterceptors(middlewares.WithBackendFromRequestMiddleware), + } + + provider, err := op.NewProvider(&opConf, &st, op.StaticIssuer(conf.Issuer), options...) + if err != nil { + utils.Failf("failed to init OIDC provider: %s", err.Error()) } logger.L.Info("Initializing server") - s, err := server.New(conf, dexSrv, logger.L) + s, err := server.New(conf, provider, &st, logger.L) if err != nil { logger.L.Fatalf("Failed to initialize server: %s", err.Error()) } diff --git a/polyculeconnect/cmd/utils/utils.go b/polyculeconnect/cmd/utils/utils.go index 137d768..bf71d54 100644 --- a/polyculeconnect/cmd/utils/utils.go +++ b/polyculeconnect/cmd/utils/utils.go @@ -5,8 +5,6 @@ import ( "os" "git.faercol.me/faercol/polyculeconnect/polyculeconnect/config" - "git.faercol.me/faercol/polyculeconnect/polyculeconnect/services" - "github.com/dexidp/dex/storage" ) // Fail displays the given error to stderr and exits the program with a returncode 1 @@ -29,12 +27,3 @@ func InitConfig(configPath string) *config.AppConfig { } return conf } - -// Initstorage inits the storage, and fails the program if an error occurs -func InitStorage(config *config.AppConfig) storage.Storage { - s, err := services.InitStorage(config) - if err != nil { - Failf("Failed to init the storage: %s", err.Error()) - } - return s -} diff --git a/polyculeconnect/config/config.go b/polyculeconnect/config/config.go index 2db5424..42c35a4 100644 --- a/polyculeconnect/config/config.go +++ b/polyculeconnect/config/config.go @@ -7,9 +7,8 @@ import ( "io/fs" "os" - "github.com/dexidp/dex/connector/oidc" "github.com/kelseyhightower/envconfig" - "github.com/sirupsen/logrus" + "go.uber.org/zap" ) const ( @@ -32,7 +31,7 @@ const ( ) const ( - defaultLogLevel = logrus.InfoLevel + defaultLogLevel = zap.InfoLevel defaultServerMode = ModeNet defaultServerHost = "0.0.0.0" @@ -55,11 +54,9 @@ const ( // Deprecated: remove when we finally drop the JSON config type BackendConfig struct { - Config *oidc.Config `json:"config"` - Name string `json:"name"` - ID string `json:"ID"` - Type BackendConfigType `json:"type"` - Local bool `json:"local"` + Name string `json:"name"` + ID string `json:"ID"` + Local bool `json:"local"` } type SSLStorageConfig struct { @@ -126,26 +123,26 @@ func (c *jsonConf) initValues(ac *AppConfig) { } type AppConfig struct { - LogLevel logrus.Level `envconfig:"LOG_LEVEL"` - ServerMode ListeningMode `envconfig:"SERVER_MODE"` - Host string `envconfig:"SERVER_HOST"` - Port int `envconfig:"SERVER_PORT"` - SockPath string `envconfig:"SERVER_SOCK"` - StorageType string `envconfig:"STORAGE_TYPE"` - StorageConfig *StorageConfig + LogLevel zap.AtomicLevel `envconfig:"LOG_LEVEL"` + ServerMode ListeningMode `envconfig:"SERVER_MODE"` + Host string `envconfig:"SERVER_HOST"` + Port int `envconfig:"SERVER_PORT"` + SockPath string `envconfig:"SERVER_SOCK"` + StorageType string `envconfig:"STORAGE_TYPE"` + StorageConfig StorageConfig Issuer string StaticDir string } func defaultConfig() AppConfig { return AppConfig{ - LogLevel: defaultLogLevel, + LogLevel: zap.NewAtomicLevelAt(defaultLogLevel), ServerMode: defaultServerMode, Host: defaultServerHost, Port: defaultServerPort, SockPath: defaultServerSocket, StorageType: string(defaultStorageType), - StorageConfig: &StorageConfig{ + StorageConfig: StorageConfig{ File: defaultStorageFile, Host: defaultStorageHost, Port: defaultServerPort, @@ -162,13 +159,12 @@ func defaultConfig() AppConfig { } } -func parseLevel(lvlStr string) logrus.Level { - for _, lvl := range logrus.AllLevels { - if lvl.String() == lvlStr { - return lvl - } +func parseLevel(lvlStr string) zap.AtomicLevel { + var res zap.AtomicLevel + if err := res.UnmarshalText([]byte(lvlStr)); err != nil { + return zap.NewAtomicLevelAt(zap.InfoLevel) } - return logrus.InfoLevel + return res } func (ac *AppConfig) UnmarshalJSON(data []byte) error { @@ -216,11 +212,12 @@ func New(filepath string) (*AppConfig, error) { if err := envconfig.Process(envConfigPrefix, &conf); err != nil { return nil, err } - if err := envconfig.Process(envConfigPrefix, conf.StorageConfig); err != nil { + if err := envconfig.Process(envConfigPrefix, &conf.StorageConfig); err != nil { return nil, err } if err := envconfig.Process(envConfigPrefix, &conf.StorageConfig.SSL); err != nil { return nil, err } + return &conf, nil } diff --git a/polyculeconnect/config/config_test.go b/polyculeconnect/config/config_test.go index adae3e8..52dbca7 100644 --- a/polyculeconnect/config/config_test.go +++ b/polyculeconnect/config/config_test.go @@ -3,11 +3,12 @@ package config import ( "os" "path" + "strings" "testing" - "github.com/sirupsen/logrus" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "go.uber.org/zap" ) func initJson(t *testing.T, content string) string { @@ -24,7 +25,23 @@ func setEnv(t *testing.T, envVars map[string]string) { } } +// remove all polyculeconnect environment variables from the environment and put them back after the test +func clearEnv(t *testing.T) { + for _, envvar := range os.Environ() { + if strings.HasPrefix(envvar, "POLYCULECONNECT") { + splitVar := strings.SplitN(envvar, "=", 2) + require.True(t, len(splitVar) >= 2, "invalid format for envvar %q", envvar) + require.NoError(t, os.Unsetenv(splitVar[0]), "failed to unset var %q", splitVar[0]) + t.Cleanup(func() { + os.Setenv(splitVar[0], splitVar[1]) + }) + } + } +} + func TestDefault(t *testing.T) { + clearEnv(t) + t.Run("no file", func(t *testing.T) { conf, err := New("/this/path/does/not/exist") require.NoError(t, err) @@ -42,6 +59,8 @@ func TestDefault(t *testing.T) { // Since we still use a JSON conf for the OIDC config, we still need to check this for now // But as soon as the config file is not necessary, this will probably disappear func TestInvalidJSON(t *testing.T) { + clearEnv(t) + confPath := initJson(t, "toto") errMsg := "failed to parse config file: invalid character 'o' in literal true (expecting 'r')" _, err := New(confPath) @@ -49,6 +68,8 @@ func TestInvalidJSON(t *testing.T) { } func TestJSONConfig(t *testing.T) { + clearEnv(t) + confPath := initJson(t, `{ "log": {"level":"info"}, "server": { @@ -67,6 +88,8 @@ func TestJSONConfig(t *testing.T) { } func TestJSONConfigOverriden(t *testing.T) { + clearEnv(t) + confPath := initJson(t, `{ "log": {"level":"info"}, "server": { @@ -92,6 +115,8 @@ func TestJSONConfigOverriden(t *testing.T) { } func TestHostNetMode(t *testing.T) { + clearEnv(t) + envVars := map[string]string{ string("POLYCULECONNECT_SERVER_MODE"): string(ModeNet), string("POLYCULECONNECT_SERVER_HOST"): "127.0.0.1", @@ -108,6 +133,8 @@ func TestHostNetMode(t *testing.T) { } func TestHostSocketMode(t *testing.T) { + clearEnv(t) + envVars := map[string]string{ string("POLYCULECONNECT_SERVER_MODE"): string(ModeUnix), string("POLYCULECONNECT_SERVER_SOCK"): "/run/polyculeconnect.sock", @@ -122,6 +149,8 @@ func TestHostSocketMode(t *testing.T) { } func TestLogLevel(t *testing.T) { + clearEnv(t) + envVars := map[string]string{ string("POLYCULECONNECT_LOG_LEVEL"): "error", } @@ -130,10 +159,12 @@ func TestLogLevel(t *testing.T) { conf, err := New("") require.NoError(t, err) - assert.Equal(t, logrus.ErrorLevel, conf.LogLevel) + assert.Equal(t, zap.NewAtomicLevelAt(zap.ErrorLevel), conf.LogLevel) } func TestSqliteConfig(t *testing.T) { + clearEnv(t) + envVars := map[string]string{ string("POLYCULECONNECT_STORAGE_TYPE"): "sqlite", string("POLYCULECONNECT_STORAGE_PATH"): "/data/polyculeconnect.db", @@ -148,6 +179,8 @@ func TestSqliteConfig(t *testing.T) { } func TestSqliteConfigJSON(t *testing.T) { + clearEnv(t) + confPath := initJson(t, `{ "log": {"level":"info"}, "storage": { @@ -164,6 +197,8 @@ func TestSqliteConfigJSON(t *testing.T) { } func TestSqliteConfigJSONOverriden(t *testing.T) { + clearEnv(t) + confPath := initJson(t, `{ "log": {"level":"info"}, "storage": { diff --git a/polyculeconnect/connector/refuse_all.go b/polyculeconnect/connector/refuse_all.go deleted file mode 100644 index 5e13970..0000000 --- a/polyculeconnect/connector/refuse_all.go +++ /dev/null @@ -1,35 +0,0 @@ -package connector - -import ( - "fmt" - "net/http" - - "github.com/dexidp/dex/connector" - "github.com/dexidp/dex/pkg/log" - "github.com/dexidp/dex/storage" -) - -const TypeRefuseAll = "refuseAll" - -var RefuseAllConnectorConfig storage.Connector = storage.Connector{ - ID: "null", - Name: "RefuseAll", - Type: TypeRefuseAll, - Config: nil, -} - -type RefuseAllConfig struct{} - -func (c *RefuseAllConfig) Open(id string, logger log.Logger) (connector.Connector, error) { - return &RefuseAllConnector{}, nil -} - -type RefuseAllConnector struct{} - -func (m *RefuseAllConnector) LoginURL(s connector.Scopes, callbackURL, state string) (string, error) { - return "", fmt.Errorf("you shouldn't use this function") -} - -func (m *RefuseAllConnector) HandleCallback(s connector.Scopes, r *http.Request) (identity connector.Identity, err error) { - return connector.Identity{}, fmt.Errorf("you shouldn't use this function") -} diff --git a/polyculeconnect/controller/auth/approval.go b/polyculeconnect/controller/auth/approval.go new file mode 100644 index 0000000..097c348 --- /dev/null +++ b/polyculeconnect/controller/auth/approval.go @@ -0,0 +1,144 @@ +package auth + +import ( + "bytes" + "fmt" + "html/template" + "io" + "net/http" + "path/filepath" + + "git.faercol.me/faercol/polyculeconnect/polyculeconnect/helpers" + "git.faercol.me/faercol/polyculeconnect/polyculeconnect/internal/db" + "github.com/google/uuid" + "go.uber.org/zap" +) + +const ApprovalRoute = "/approval" + +var scopeDescriptions = map[string]string{ + "offline_access": "Have offline access", + "profile": "View basic profile information", + "email": "View your email address", + "groups": "View your groups", +} + +func scopeDescription(rawScope string) string { + if desc, ok := scopeDescriptions[rawScope]; ok { + return desc + } + return rawScope +} + +type approvalData struct { + Scopes []string + Client string + AuthReqID string +} + +type ApprovalController struct { + l *zap.SugaredLogger + st db.Storage + baseDir string +} + +func NewApprovalController(l *zap.SugaredLogger, st db.Storage, baseDir string) *ApprovalController { + return &ApprovalController{ + l: l, + st: st, + baseDir: baseDir, + } +} + +func (c *ApprovalController) handleFormResponse(w http.ResponseWriter, r *http.Request) { + reqID, err := uuid.Parse(r.Form.Get("req")) + if err != nil { + c.l.Errorf("Invalid request ID: %s", err) + helpers.HandleResponse(w, r, http.StatusBadRequest, []byte("invalid query format"), c.l) + return + } + + if r.Form.Get("approval") != "approve" { + c.l.Debug("Approval rejected") + helpers.HandleResponse(w, r, http.StatusBadRequest, []byte("approval rejected"), c.l) + return + } + + if err := c.st.AuthRequestStorage().GiveConsent(r.Context(), reqID); err != nil { + c.l.Errorf("Failed to approve request: %s", err) + helpers.HandleResponse(w, r, http.StatusInternalServerError, nil, c.l) + return + } + + http.Redirect(w, r, fmt.Sprintf("/callback?code=%s&state=%s", r.Form.Get("code"), reqID.String()), http.StatusSeeOther) +} + +func (c *ApprovalController) ServeHTTP(w http.ResponseWriter, r *http.Request) { + if err := r.ParseForm(); err != nil { + c.l.Errorf("Failed to parse query: %s", err) + helpers.HandleResponse(w, r, http.StatusBadRequest, []byte("invalid query format"), c.l) + return + } + + if r.Method == http.MethodPost { + c.handleFormResponse(w, r) + return + } + + state := r.Form.Get("state") + reqID, err := uuid.Parse(state) + if err != nil { + c.l.Errorf("Invalid state %q: %s", state, err) + helpers.HandleResponse(w, r, http.StatusBadRequest, []byte("unexpected state"), c.l) + return + } + + req, err := c.st.AuthRequestStorage().GetAuthRequestByID(r.Context(), reqID) + if err != nil { + c.l.Errorf("Failed to get auth request from DB: %s", err) + helpers.HandleResponse(w, r, http.StatusInternalServerError, nil, c.l) + return + } + + app, err := c.st.ClientStorage().GetClientByID(r.Context(), req.ClientID) + if err != nil { + c.l.Errorf("Failed to get client details from DB: %s", err) + helpers.HandleResponse(w, r, http.StatusInternalServerError, nil, c.l) + return + } + + data := approvalData{ + Scopes: []string{}, + Client: app.Name, + AuthReqID: reqID.String(), + } + for _, s := range req.Scopes { + if s == "openid" { // it's implied we want that, no consent is really important there + continue + } + data.Scopes = append(data.Scopes, scopeDescription(s)) + } + + lp := filepath.Join(c.baseDir, "templates", "approval.html") + hdrTpl := filepath.Join(c.baseDir, "templates", "header.html") + footTpl := filepath.Join(c.baseDir, "templates", "footer.html") + tmpl, err := template.New("approval.html").ParseFiles(hdrTpl, footTpl, lp) + if err != nil { + c.l.Errorf("Failed to parse templates: %s", err) + helpers.HandleResponse(w, r, http.StatusInternalServerError, nil, c.l) + return + } + buf := new(bytes.Buffer) + + if err := tmpl.Execute(buf, data); err != nil { + c.l.Errorf("Failed to execute template: %s", err) + helpers.HandleResponse(w, r, http.StatusInternalServerError, nil, c.l) + return + } + _, err = io.Copy(w, buf) + if err != nil { + c.l.Errorf("Failed to write response: %s", err) + helpers.HandleResponse(w, r, http.StatusInternalServerError, nil, c.l) + return + } +} diff --git a/polyculeconnect/controller/auth/authcallback.go b/polyculeconnect/controller/auth/authcallback.go new file mode 100644 index 0000000..cd32116 --- /dev/null +++ b/polyculeconnect/controller/auth/authcallback.go @@ -0,0 +1,116 @@ +package auth + +import ( + "fmt" + "net/http" + + "git.faercol.me/faercol/polyculeconnect/polyculeconnect/helpers" + "git.faercol.me/faercol/polyculeconnect/polyculeconnect/internal/model" + "git.faercol.me/faercol/polyculeconnect/polyculeconnect/internal/storage" + "github.com/google/uuid" + "github.com/zitadel/oidc/v3/pkg/client/rp" + "github.com/zitadel/oidc/v3/pkg/oidc" + "go.uber.org/zap" +) + +const AuthCallbackRoute = "/callback" + +type AuthCallbackController struct { + l *zap.SugaredLogger + st *storage.Storage +} + +func NewAuthCallbackController(l *zap.SugaredLogger, st *storage.Storage) *AuthCallbackController { + return &AuthCallbackController{ + l: l, + st: st, + } +} + +func (c *AuthCallbackController) HandleUserInfoCallback(w http.ResponseWriter, r *http.Request, tokens *oidc.Tokens[*oidc.IDTokenClaims], state string, rp rp.RelyingParty, info *oidc.UserInfo) { + requestID, err := uuid.Parse(state) + if err != nil { + c.l.Errorf("Invalid state, should be a request UUID, but got %s: %s", state, err) + helpers.HandleResponse(w, r, http.StatusInternalServerError, []byte("failed to perform authentication"), c.l) + return + } + + c.l.Infof("Successful login from %s", info.Email) + user := model.User{ + Subject: info.Subject, + Name: info.Name, + FamilyName: info.FamilyName, + GivenName: info.GivenName, + Picture: info.Picture, + UpdatedAt: info.UpdatedAt.AsTime(), + Email: info.Email, + EmailVerified: bool(info.EmailVerified), + } + + err = c.st.LocalStorage.AuthRequestStorage().ValidateAuthRequest(r.Context(), requestID, user.Subject) + if err != nil { + c.l.Errorf("Failed to validate auth request from storage: %s", err) + helpers.HandleResponse(w, r, http.StatusInternalServerError, []byte("failed to perform authentication"), c.l) + return + } + if err := c.st.LocalStorage.UserStorage().AddUser(r.Context(), &user); err != nil { + c.l.Errorf("Failed to add related user to storageL %w", err) + helpers.HandleResponse(w, r, http.StatusInternalServerError, []byte("failed to perform authentication"), c.l) + return + } + + http.Redirect(w, r, "/authorize/callback?id="+state, http.StatusFound) +} + +type CallbackDispatchController struct { + l *zap.SugaredLogger + st *storage.Storage + callbackHandlers map[uuid.UUID]http.Handler +} + +func NewCallbackDispatchController(l *zap.SugaredLogger, st *storage.Storage, handlers map[uuid.UUID]http.Handler) *CallbackDispatchController { + return &CallbackDispatchController{ + l: l, + st: st, + callbackHandlers: handlers, + } +} + +func (c *CallbackDispatchController) ServeHTTP(w http.ResponseWriter, r *http.Request) { + errMsg := r.URL.Query().Get("error") + if errMsg != "" { + errorDesc := r.URL.Query().Get("error_description") + c.l.Errorf("Failed to perform authentication: %s (%s)", errMsg, errorDesc) + helpers.HandleResponse(w, r, http.StatusInternalServerError, []byte("failed to perform authentication"), c.l) + return + } + + state := r.URL.Query().Get("state") + requestID, err := uuid.Parse(state) + if err != nil { + c.l.Errorf("Invalid state, should be a request UUID, but got %s: %s", state, err) + helpers.HandleResponse(w, r, http.StatusInternalServerError, []byte("failed to perform authentication"), c.l) + return + } + + req, err := c.st.LocalStorage.AuthRequestStorage().GetAuthRequestByID(r.Context(), requestID) + if err != nil { + c.l.Errorf("Failed to get auth request from DB: %s", err) + helpers.HandleResponse(w, r, http.StatusBadRequest, []byte("unknown request id"), c.l) + return + } + + if !req.Consent { + c.l.Debug("Redirecting to consent endpoint") + http.Redirect(w, r, fmt.Sprintf("/approval?state=%s&code=%s", state, r.URL.Query().Get("code")), http.StatusSeeOther) + return + } + + callbackHandler, ok := c.callbackHandlers[req.BackendID] + if !ok { + c.l.Errorf("Backend %s does not exist for request %s", req.ID, req.BackendID) + helpers.HandleResponse(w, r, http.StatusNotFound, []byte("unknown backend"), c.l) + return + } + callbackHandler.ServeHTTP(w, r) +} diff --git a/polyculeconnect/controller/auth/authdispatch.go b/polyculeconnect/controller/auth/authdispatch.go new file mode 100644 index 0000000..cf36b12 --- /dev/null +++ b/polyculeconnect/controller/auth/authdispatch.go @@ -0,0 +1,54 @@ +package auth + +import ( + "net/http" + + "git.faercol.me/faercol/polyculeconnect/polyculeconnect/helpers" + "git.faercol.me/faercol/polyculeconnect/polyculeconnect/internal/storage" + "github.com/google/uuid" + "go.uber.org/zap" +) + +type AuthDispatchController struct { + l *zap.SugaredLogger + st *storage.Storage + redirectHandlers map[uuid.UUID]http.Handler +} + +func NewAuthDispatchController(l *zap.SugaredLogger, storage *storage.Storage, redirectHandlers map[uuid.UUID]http.Handler) *AuthDispatchController { + return &AuthDispatchController{ + l: l, + st: storage, + redirectHandlers: redirectHandlers, + } +} + +func (c *AuthDispatchController) ServeHTTP(w http.ResponseWriter, r *http.Request) { + requestIDStr := r.URL.Query().Get("request_id") + if requestIDStr == "" { + helpers.HandleResponse(w, r, http.StatusBadRequest, []byte("no request ID in request"), c.l) + return + } + + requestID, err := uuid.Parse(requestIDStr) + if err != nil { + c.l.Errorf("Invalid UUID format for request ID: %s", err) + helpers.HandleResponse(w, r, http.StatusBadRequest, []byte("invalid request id"), c.l) + return + } + + req, err := c.st.LocalStorage.AuthRequestStorage().GetAuthRequestByID(r.Context(), requestID) + if err != nil { + c.l.Errorf("Failed to get auth request from DB: %s", err) + helpers.HandleResponse(w, r, http.StatusBadRequest, []byte("unknown request id"), c.l) + return + } + + loginHandler, ok := c.redirectHandlers[req.BackendID] + if !ok { + c.l.Errorf("Backend %s does not exist for request %s", req.ID, req.BackendID) + helpers.HandleResponse(w, r, http.StatusNotFound, []byte("unknown backend"), c.l) + return + } + loginHandler.ServeHTTP(w, r) +} diff --git a/polyculeconnect/controller/auth/authredirect.go b/polyculeconnect/controller/auth/authredirect.go new file mode 100644 index 0000000..d611090 --- /dev/null +++ b/polyculeconnect/controller/auth/authredirect.go @@ -0,0 +1,50 @@ +package auth + +import ( + "net/http" + + "git.faercol.me/faercol/polyculeconnect/polyculeconnect/helpers" + "git.faercol.me/faercol/polyculeconnect/polyculeconnect/internal/storage" + "github.com/google/uuid" + "github.com/zitadel/oidc/v3/pkg/client/rp" + "go.uber.org/zap" +) + +const AuthRedirectRoute = "/perform_auth" + +type AuthRedirectController struct { + provider rp.RelyingParty + l *zap.SugaredLogger + st *storage.Storage +} + +func NewAuthRedirectController(l *zap.SugaredLogger, provider rp.RelyingParty, storage *storage.Storage) *AuthRedirectController { + return &AuthRedirectController{ + l: l, + st: storage, + provider: provider, + } +} + +func (c *AuthRedirectController) ServeHTTP(w http.ResponseWriter, r *http.Request) { + requestIDStr := r.URL.Query().Get("request_id") + if requestIDStr == "" { + helpers.HandleResponse(w, r, http.StatusBadRequest, []byte("no request ID in request"), c.l) + return + } + + requestID, err := uuid.Parse(requestIDStr) + if err != nil { + c.l.Errorf("Invalid UUID format for request ID: %s", err) + helpers.HandleResponse(w, r, http.StatusBadRequest, []byte("invalid request id"), c.l) + return + } + + _, err = c.st.LocalStorage.AuthRequestStorage().GetAuthRequestByID(r.Context(), requestID) + if err != nil { + c.l.Errorf("Failed to get auth request from DB: %s", err) + helpers.HandleResponse(w, r, http.StatusBadRequest, []byte("unknown request id"), c.l) + return + } + rp.AuthURLHandler(func() string { return requestIDStr }, c.provider).ServeHTTP(w, r) +} diff --git a/polyculeconnect/controller/ui/static.go b/polyculeconnect/controller/ui/static.go index 1145194..66cbd35 100644 --- a/polyculeconnect/controller/ui/static.go +++ b/polyculeconnect/controller/ui/static.go @@ -9,7 +9,7 @@ import ( "path/filepath" "git.faercol.me/faercol/polyculeconnect/polyculeconnect/helpers" - "github.com/sirupsen/logrus" + "go.uber.org/zap" ) const StaticRoute = "/static/" @@ -30,12 +30,12 @@ func (sc *StaticController) ServeHTTP(w http.ResponseWriter, r *http.Request) { } type IndexController struct { - l *logrus.Logger + l *zap.SugaredLogger downstreamConstroller http.Handler baseDir string } -func NewIndexController(l *logrus.Logger, downstream http.Handler, baseDir string) *IndexController { +func NewIndexController(l *zap.SugaredLogger, downstream http.Handler, baseDir string) *IndexController { return &IndexController{ l: l, downstreamConstroller: downstream, @@ -70,7 +70,7 @@ func (ic IndexController) serveUI(w http.ResponseWriter, r *http.Request) (int, func (ic *IndexController) ServeHTTP(w http.ResponseWriter, r *http.Request) { if r.RequestURI != "/" { - ic.l.Debugf("Serving URI %q to dex handler", r.RequestURI) + ic.l.Debugf("Serving URI %q to oidc handler", r.RequestURI) ic.downstreamConstroller.ServeHTTP(w, r) return } diff --git a/polyculeconnect/go.mod b/polyculeconnect/go.mod index 4214398..5bd35ca 100644 --- a/polyculeconnect/go.mod +++ b/polyculeconnect/go.mod @@ -1,74 +1,51 @@ module git.faercol.me/faercol/polyculeconnect/polyculeconnect -go 1.20 +go 1.21 + +toolchain go1.22.6 require ( - github.com/dexidp/dex v0.0.0-20231014000322-089f374d4f3e + github.com/go-jose/go-jose/v4 v4.0.4 + github.com/golang-migrate/migrate/v4 v4.17.1 + github.com/google/uuid v1.6.0 github.com/kelseyhightower/envconfig v1.4.0 - github.com/prometheus/client_golang v1.17.0 + github.com/mattn/go-sqlite3 v1.14.17 github.com/sirupsen/logrus v1.9.3 github.com/spf13/cobra v1.7.0 - github.com/stretchr/testify v1.8.4 + github.com/stretchr/testify v1.9.0 + github.com/zitadel/oidc/v3 v3.30.1 + go.uber.org/zap v1.24.0 + go.uber.org/zap/exp v0.2.0 ) require ( - cloud.google.com/go/compute v1.23.0 // indirect - cloud.google.com/go/compute/metadata v0.2.3 // indirect - github.com/AppsFlyer/go-sundheit v0.5.0 // indirect - github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 // indirect - github.com/Masterminds/goutils v1.1.1 // indirect - github.com/Masterminds/semver/v3 v3.2.0 // indirect - github.com/Masterminds/sprig/v3 v3.2.3 // indirect - github.com/beevik/etree v1.2.0 // indirect - github.com/beorn7/perks v1.0.1 // indirect - github.com/cespare/xxhash/v2 v2.2.0 // indirect - github.com/coreos/go-oidc/v3 v3.6.0 // indirect + github.com/bmatcuk/doublestar/v4 v4.6.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect - github.com/dexidp/dex/api/v2 v2.1.1-0.20231014000322-089f374d4f3e // indirect - github.com/felixge/httpsnoop v1.0.3 // indirect - github.com/go-asn1-ber/asn1-ber v1.5.5 // indirect - github.com/go-jose/go-jose/v3 v3.0.0 // indirect - github.com/go-ldap/ldap/v3 v3.4.6 // indirect - github.com/go-sql-driver/mysql v1.7.1 // indirect - github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect - github.com/golang/protobuf v1.5.3 // indirect - github.com/google/s2a-go v0.1.7 // indirect - github.com/google/uuid v1.3.1 // indirect - github.com/googleapis/enterprise-certificate-proxy v0.3.1 // indirect - github.com/googleapis/gax-go/v2 v2.12.0 // indirect - github.com/gorilla/handlers v1.5.1 // indirect - github.com/gorilla/mux v1.8.0 // indirect - github.com/huandu/xstrings v1.3.3 // indirect - github.com/imdario/mergo v0.3.11 // indirect + github.com/go-chi/chi/v5 v5.1.0 // indirect + github.com/go-logr/logr v1.4.2 // indirect + github.com/go-logr/stdr v1.2.2 // indirect + github.com/gorilla/securecookie v1.1.2 // indirect + github.com/hashicorp/errwrap v1.1.0 // indirect + github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect - github.com/jonboulle/clockwork v0.2.2 // indirect - github.com/lib/pq v1.10.9 // indirect - github.com/mattermost/xml-roundtrip-validator v0.1.0 // indirect - github.com/mattn/go-sqlite3 v1.14.17 // indirect - github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect - github.com/mitchellh/copystructure v1.0.0 // indirect - github.com/mitchellh/reflectwalk v1.0.0 // indirect - github.com/pkg/errors v0.9.1 // indirect + github.com/kr/pretty v0.3.1 // indirect + github.com/muhlemmer/gu v0.3.1 // indirect + github.com/muhlemmer/httpforwarded v0.1.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/prometheus/client_model v0.4.1-0.20230718164431-9a2bf3000d16 // indirect - github.com/prometheus/common v0.44.0 // indirect - github.com/prometheus/procfs v0.11.1 // indirect - github.com/russellhaering/goxmldsig v1.4.0 // indirect - github.com/shopspring/decimal v1.2.0 // indirect - github.com/spf13/cast v1.4.1 // indirect + github.com/rogpeppe/go-internal v1.10.0 // indirect + github.com/rs/cors v1.11.1 // indirect github.com/spf13/pflag v1.0.5 // indirect - go.opencensus.io v0.24.0 // indirect - golang.org/x/crypto v0.14.0 // indirect - golang.org/x/exp v0.0.0-20221004215720-b9f4876ce741 // indirect - golang.org/x/net v0.17.0 // indirect - golang.org/x/oauth2 v0.13.0 // indirect - golang.org/x/sys v0.13.0 // indirect - golang.org/x/text v0.13.0 // indirect - google.golang.org/api v0.147.0 // indirect - google.golang.org/appengine v1.6.7 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20231009173412-8bfb1ae86b6c // indirect - google.golang.org/grpc v1.58.3 // indirect - google.golang.org/protobuf v1.31.0 // indirect - gopkg.in/square/go-jose.v2 v2.6.0 // indirect + github.com/zitadel/logging v0.6.1 // indirect + github.com/zitadel/schema v1.3.0 // indirect + go.opentelemetry.io/otel v1.29.0 // indirect + go.opentelemetry.io/otel/metric v1.29.0 // indirect + go.opentelemetry.io/otel/trace v1.29.0 // indirect + go.uber.org/atomic v1.10.0 // indirect + go.uber.org/multierr v1.10.0 // indirect + golang.org/x/crypto v0.25.0 // indirect + golang.org/x/oauth2 v0.23.0 // indirect + golang.org/x/sys v0.22.0 // indirect + golang.org/x/text v0.18.0 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/polyculeconnect/go.sum b/polyculeconnect/go.sum index c60f175..4de9eab 100644 --- a/polyculeconnect/go.sum +++ b/polyculeconnect/go.sum @@ -1,307 +1,115 @@ -cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go/compute v1.23.0 h1:tP41Zoavr8ptEqaW6j+LQOnyBBhO7OkOMAGrgLopTwY= -cloud.google.com/go/compute v1.23.0/go.mod h1:4tCnrn48xsqlwSAiLf1HXMQk8CONslYbdiEZc9FEIbM= -cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY= -cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= -github.com/AppsFlyer/go-sundheit v0.5.0 h1:/VxpyigCfJrq1r97mn9HPiAB2qrhcTFHwNIIDr15CZM= -github.com/AppsFlyer/go-sundheit v0.5.0/go.mod h1:2ZM0BnfqT/mljBQO224VbL5XH06TgWuQ6Cn+cTtCpTY= -github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 h1:mFRzDkZVAjdal+s7s0MwaRv9igoPqLRdzOLzw/8Xvq8= -github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU= -github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= -github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= -github.com/Masterminds/semver/v3 v3.2.0 h1:3MEsd0SM6jqZojhjLWWeBY+Kcjy9i6MQAeY7YgDP83g= -github.com/Masterminds/semver/v3 v3.2.0/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ= -github.com/Masterminds/sprig/v3 v3.2.3 h1:eL2fZNezLomi0uOLqjQoN6BfsDD+fyLtgbJMAj9n6YA= -github.com/Masterminds/sprig/v3 v3.2.3/go.mod h1:rXcFaZ2zZbLRJv/xSysmlgIM1u11eBaRMhvYXJNkGuM= -github.com/alexbrainman/sspi v0.0.0-20210105120005-909beea2cc74 h1:Kk6a4nehpJ3UuJRqlA3JxYxBZEqCeOmATOvrbT4p9RA= -github.com/alexbrainman/sspi v0.0.0-20210105120005-909beea2cc74/go.mod h1:cEWa1LVoE5KvSD9ONXsZrj0z6KqySlCCNKHlLzbqAt4= -github.com/beevik/etree v1.1.0/go.mod h1:r8Aw8JqVegEf0w2fDnATrX9VpkMcyFeM0FhwO62wh+A= -github.com/beevik/etree v1.2.0 h1:l7WETslUG/T+xOPs47dtd6jov2Ii/8/OjCldk5fYfQw= -github.com/beevik/etree v1.2.0/go.mod h1:aiPf89g/1k3AShMVAzriilpcE4R/Vuor90y83zVZWFc= -github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= -github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= -github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= -github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= -github.com/coreos/go-oidc/v3 v3.6.0 h1:AKVxfYw1Gmkn/w96z0DbT/B/xFnzTd3MkZvWLjF4n/o= -github.com/coreos/go-oidc/v3 v3.6.0/go.mod h1:ZpHUsHBucTUj6WOkrP4E20UPynbLZzhTQ1XKCXkxyPc= +github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8= +github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= +github.com/bmatcuk/doublestar/v4 v4.6.1 h1:FH9SifrbvJhnlQpztAx++wlkk70QBf0iBWDwNy7PA4I= +github.com/bmatcuk/doublestar/v4 v4.6.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/dexidp/dex v0.0.0-20231014000322-089f374d4f3e h1:d/wCtR05IxnKZN9cg8YsLXPzYeDZoQsJpGoBBw2oQn4= -github.com/dexidp/dex v0.0.0-20231014000322-089f374d4f3e/go.mod h1:xFYDBaDJhmCE6xrovgHeUaFyslqhfocm3/xIJRkUYdk= -github.com/dexidp/dex/api/v2 v2.1.1-0.20231014000322-089f374d4f3e h1:46E2eP+vALmVQQz68L21EusFbwjJDCZVZT5hI1al4lE= -github.com/dexidp/dex/api/v2 v2.1.1-0.20231014000322-089f374d4f3e/go.mod h1:MwZ7k1lmdibhSptV8z+eKgZDOyAamm3M332EyQHycxA= -github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= -github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= -github.com/felixge/httpsnoop v1.0.3 h1:s/nj+GCswXYzN5v2DpNMuMQYe+0DDwt5WVCU6CWBdXk= -github.com/felixge/httpsnoop v1.0.3/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= -github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw= -github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= -github.com/go-asn1-ber/asn1-ber v1.5.5 h1:MNHlNMBDgEKD4TcKr36vQN68BA00aDfjIt3/bD50WnA= -github.com/go-asn1-ber/asn1-ber v1.5.5/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0= -github.com/go-jose/go-jose/v3 v3.0.0 h1:s6rrhirfEP/CGIoc6p+PZAeogN2SxKav6Wp7+dyMWVo= -github.com/go-jose/go-jose/v3 v3.0.0/go.mod h1:RNkWWRld676jZEYoV3+XK8L2ZnNSvIsxFMht0mSX+u8= -github.com/go-ldap/ldap/v3 v3.4.6 h1:ert95MdbiG7aWo/oPYp9btL3KJlMPKnP58r09rI8T+A= -github.com/go-ldap/ldap/v3 v3.4.6/go.mod h1:IGMQANNtxpsOzj7uUAMjpGBaOVTC4DYyIy8VsTdxmtc= -github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI= -github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= -github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= -github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= -github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= -github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= -github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= -github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= -github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= -github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= -github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= -github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= -github.com/google/s2a-go v0.1.7 h1:60BLSyTrOV4/haCDW4zb1guZItoSq8foHCXrAnjBo/o= -github.com/google/s2a-go v0.1.7/go.mod h1:50CgR4k1jNlWBu4UfS4AcfhVe1r6pdZPygJ3R8F0Qdw= -github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4= -github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/googleapis/enterprise-certificate-proxy v0.3.1 h1:SBWmZhjUDRorQxrN0nwzf+AHBxnbFjViHQS4P0yVpmQ= -github.com/googleapis/enterprise-certificate-proxy v0.3.1/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0= -github.com/googleapis/gax-go/v2 v2.12.0 h1:A+gCJKdRfqXkr+BIRGtZLibNXf0m1f9E4HG56etFpas= -github.com/googleapis/gax-go/v2 v2.12.0/go.mod h1:y+aIqrI5eb1YGMVJfuV3185Ts/D7qKpsEkdD5+I6QGU= -github.com/gorilla/handlers v1.5.1 h1:9lRY6j8DEeeBT10CvO9hGW0gmky0BprnvDI5vfhUHH4= -github.com/gorilla/handlers v1.5.1/go.mod h1:t8XrUpc4KVXb7HGyJ4/cEnwQiaxrX/hz1Zv/4g96P1Q= -github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= -github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= -github.com/huandu/xstrings v1.3.3 h1:/Gcsuc1x8JVbJ9/rlye4xZnVAbEkGauT8lbebqcQws4= -github.com/huandu/xstrings v1.3.3/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= -github.com/imdario/mergo v0.3.11 h1:3tnifQM4i+fbajXKBHXWEH+KvNHqojZ778UH75j3bGA= -github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= +github.com/go-chi/chi/v5 v5.1.0 h1:acVI1TYaD+hhedDJ3r54HyA6sExp3HfXq7QWEEY/xMw= +github.com/go-chi/chi/v5 v5.1.0/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= +github.com/go-jose/go-jose/v4 v4.0.4 h1:VsjPI33J0SB9vQM6PLmNjoHqMQNGPiZ0rHL7Ni7Q6/E= +github.com/go-jose/go-jose/v4 v4.0.4/go.mod h1:NKb5HO1EZccyMpiZNbdUw/14tiXNyUJh188dfnMCAfc= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= +github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/golang-migrate/migrate/v4 v4.17.1 h1:4zQ6iqL6t6AiItphxJctQb3cFqWiSpMnX7wLTPnnYO4= +github.com/golang-migrate/migrate/v4 v4.17.1/go.mod h1:m8hinFyWBn0SA4QKHuKh175Pm9wjmxj3S2Mia7dbXzM= +github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= +github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= +github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA= +github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= +github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= +github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= -github.com/jonboulle/clockwork v0.2.2 h1:UOGuzwb1PwsrDAObMuhUnj0p5ULPj8V/xJ7Kx9qUBdQ= -github.com/jonboulle/clockwork v0.2.2/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8= +github.com/jeremija/gosubmit v0.2.7 h1:At0OhGCFGPXyjPYAsCchoBUhE099pcBXmsb4iZqROIc= +github.com/jeremija/gosubmit v0.2.7/go.mod h1:Ui+HS073lCFREXBbdfrJzMB57OI/bdxTiLtrDHHhFPI= github.com/kelseyhightower/envconfig v1.4.0 h1:Im6hONhd3pLkfDFsbRgu68RDNkGF1r3dvMUtDTo2cv8= github.com/kelseyhightower/envconfig v1.4.0/go.mod h1:cccZRl6mQpaq41TPp5QxidR+Sa3axMbJDNb//FQX6Gg= -github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= -github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= -github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= -github.com/mattermost/xml-roundtrip-validator v0.1.0 h1:RXbVD2UAl7A7nOTR4u7E3ILa4IbtvKBHw64LDsmu9hU= -github.com/mattermost/xml-roundtrip-validator v0.1.0/go.mod h1:qccnGMcpgwcNaBnxqpJpWWUiPNr5H3O8eDgGV9gT5To= github.com/mattn/go-sqlite3 v1.14.17 h1:mCRHCLDUBXgpKAqIKsaAaAsrAlbkeomtRFKXh2L6YIM= github.com/mattn/go-sqlite3 v1.14.17/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= -github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= -github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= -github.com/mitchellh/copystructure v1.0.0 h1:Laisrj+bAB6b/yJwB5Bt3ITZhGJdqmxquMKeZ+mmkFQ= -github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= -github.com/mitchellh/reflectwalk v1.0.0 h1:9D+8oIskB4VJBN5SFlmc27fSlIBZaov1Wpk/IfikLNY= -github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= +github.com/muhlemmer/gu v0.3.1 h1:7EAqmFrW7n3hETvuAdmFmn4hS8W+z3LgKtrnow+YzNM= +github.com/muhlemmer/gu v0.3.1/go.mod h1:YHtHR+gxM+bKEIIs7Hmi9sPT3ZDUvTN/i88wQpZkrdM= +github.com/muhlemmer/httpforwarded v0.1.0 h1:x4DLrzXdliq8mprgUMR0olDvHGkou5BJsK/vWUetyzY= +github.com/muhlemmer/httpforwarded v0.1.0/go.mod h1:yo9czKedo2pdZhoXe+yDkGVbU0TJ0q9oQ90BVoDEtw0= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= -github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/prometheus/client_golang v1.17.0 h1:rl2sfwZMtSthVU752MqfjQozy7blglC+1SOtjMAMh+Q= -github.com/prometheus/client_golang v1.17.0/go.mod h1:VeL+gMmOAxkS2IqfCq0ZmHSL+LjWfWDUmp1mBz9JgUY= -github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.4.1-0.20230718164431-9a2bf3000d16 h1:v7DLqVdK4VrYkVD5diGdl4sxJurKJEMnODWRJlxV9oM= -github.com/prometheus/client_model v0.4.1-0.20230718164431-9a2bf3000d16/go.mod h1:oMQmHW1/JoDwqLtg57MGgP/Fb1CJEYF2imWWhWtMkYU= -github.com/prometheus/common v0.44.0 h1:+5BrQJwiBB9xsMygAB3TNvpQKOwlkc25LbISbrdOOfY= -github.com/prometheus/common v0.44.0/go.mod h1:ofAIvZbQ1e/nugmZGz4/qCb9Ap1VoSTIO7x0VV9VvuY= -github.com/prometheus/procfs v0.11.1 h1:xRC8Iq1yyca5ypa9n1EZnWZkt7dwcoRPQwX/5gwaUuI= -github.com/prometheus/procfs v0.11.1/go.mod h1:eesXgaPo1q7lBpVMoMy0ZOFTth9hBn4W/y0/p/ScXhY= -github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= -github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= +github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= -github.com/russellhaering/goxmldsig v1.4.0 h1:8UcDh/xGyQiyrW+Fq5t8f+l2DLB1+zlhYzkPUJ7Qhys= -github.com/russellhaering/goxmldsig v1.4.0/go.mod h1:gM4MDENBQf7M+V824SGfyIUVFWydB7n0KkEubVJl+Tw= +github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= +github.com/rs/cors v1.11.1 h1:eU3gRzXLRK57F5rKMGMZURNdIG4EoAmX8k94r9wXWHA= +github.com/rs/cors v1.11.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ= -github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= -github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= -github.com/spf13/cast v1.4.1 h1:s0hze+J0196ZfEMTs80N7UlFt0BDuQ7Q+JDnHiMWKdA= -github.com/spf13/cast v1.4.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I= github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= -github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= -github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= -github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= -github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= -github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= -github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= -go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= -golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= -golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc= -golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= -golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20221004215720-b9f4876ce741 h1:fGZugkZk2UgYBxtpKmvub51Yno1LJDeEsRp2xGD+0gY= -golang.org/x/exp v0.0.0-20221004215720-b9f4876ce741/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE= -golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= -golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= -golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= -golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= -golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= -golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.13.0 h1:jDDenyj+WgFtmV3zYVoi8aE2BwtXFLWOA67ZfNWftiY= -golang.org/x/oauth2 v0.13.0/go.mod h1:/JMhi4ZRXAf4HG9LiNmxvk+45+96RUlVThiH8FzNBn0= -golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.4.0 h1:zxkM55ReGkDlKSM+Fu41A+zmbZuaPVbGMzvvdUPznYQ= -golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/zitadel/logging v0.6.1 h1:Vyzk1rl9Kq9RCevcpX6ujUaTYFX43aa4LkvV1TvUk+Y= +github.com/zitadel/logging v0.6.1/go.mod h1:Y4CyAXHpl3Mig6JOszcV5Rqqsojj+3n7y2F591Mp/ow= +github.com/zitadel/oidc/v3 v3.30.1 h1:CCi9qDjleuRYbECfUoVKrrN97KdheNCHAs33X8XnIRg= +github.com/zitadel/oidc/v3 v3.30.1/go.mod h1:N5p02vx+mLUwf+WFNpDsNp+8DS8+jlgFBwpz7NIQjrg= +github.com/zitadel/schema v1.3.0 h1:kQ9W9tvIwZICCKWcMvCEweXET1OcOyGEuFbHs4o5kg0= +github.com/zitadel/schema v1.3.0/go.mod h1:NptN6mkBDFvERUCvZHlvWmmME+gmZ44xzwRXwhzsbtc= +go.opentelemetry.io/otel v1.29.0 h1:PdomN/Al4q/lN6iBJEN3AwPvUiHPMlt93c8bqTG5Llw= +go.opentelemetry.io/otel v1.29.0/go.mod h1:N/WtXPs1CNCUEx+Agz5uouwCba+i+bJGFicT8SR4NP8= +go.opentelemetry.io/otel/metric v1.29.0 h1:vPf/HFWTNkPu1aYeIsc98l4ktOQaL6LeSoeV2g+8YLc= +go.opentelemetry.io/otel/metric v1.29.0/go.mod h1:auu/QWieFVWx+DmQOUMgj0F8LHWdgalxXqvp7BII/W8= +go.opentelemetry.io/otel/trace v1.29.0 h1:J/8ZNK4XgR7a21DZUAsbF8pZ5Jcw1VhACmnYt39JTi4= +go.opentelemetry.io/otel/trace v1.29.0/go.mod h1:eHl3w0sp3paPkYstJOmAimxhiFXPg+MMTlEh3nsQgWQ= +go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ= +go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= +go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI= +go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= +go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ= +go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60= +go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg= +go.uber.org/zap/exp v0.2.0 h1:FtGenNNeCATRB3CmB/yEUnjEFeJWpB/pMcy7e2bKPYs= +go.uber.org/zap/exp v0.2.0/go.mod h1:t0gqAIdh1MfKv9EwN/dLwfZnJxe9ITAZN78HEWPFWDQ= +golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30= +golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M= +golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ= +golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= +golang.org/x/oauth2 v0.23.0 h1:PbgcYx2W7i4LvjJWEbf0ngHV6qJYr86PkAV3bXdLEbs= +golang.org/x/oauth2 v0.23.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= -golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= -golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= -golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= -golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= -golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= -golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= -golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= -golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/api v0.147.0 h1:Can3FaQo9LlVqxJCodNmeZW/ib3/qKAY3rFeXiHo5gc= -google.golang.org/api v0.147.0/go.mod h1:pQ/9j83DcmPd/5C9e2nFOdjjNkDZ1G+zkbK2uvdkJMs= -google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= -google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= -google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto v0.0.0-20231002182017-d307bd883b97 h1:SeZZZx0cP0fqUyA+oRzP9k7cSwJlvDFiROO72uwD6i0= -google.golang.org/genproto/googleapis/api v0.0.0-20231002182017-d307bd883b97 h1:W18sezcAYs+3tDZX4F80yctqa12jcP1PUS2gQu1zTPU= -google.golang.org/genproto/googleapis/rpc v0.0.0-20231009173412-8bfb1ae86b6c h1:jHkCUWkseRf+W+edG5hMzr/Uh1xkDREY4caybAq4dpY= -google.golang.org/genproto/googleapis/rpc v0.0.0-20231009173412-8bfb1ae86b6c/go.mod h1:4cYg8o5yUbm77w8ZX00LhMVNl/YVBFJRYWDc0uYWMs0= -google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= -google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= -google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= -google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= -google.golang.org/grpc v1.58.3 h1:BjnpXut1btbtgN/6sp+brB2Kbm2LjNXnidYujAVbSoQ= -google.golang.org/grpc v1.58.3/go.mod h1:tgX3ZQDlNJGU96V6yHh1T/JeoBQ2TXdr43YbYSsCJk0= -google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= -google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= -google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= -google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= -google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= -google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= -google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= -google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= -google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= +golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224= +golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= -gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= -gopkg.in/square/go-jose.v2 v2.6.0 h1:NGk74WTnPKBNUhNzQX7PYcTLUjoq7mzKk2OKbvwk2iI= -gopkg.in/square/go-jose.v2 v2.6.0/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= -gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/polyculeconnect/helpers/helpers.go b/polyculeconnect/helpers/helpers.go index 5a6e659..fce58ab 100644 --- a/polyculeconnect/helpers/helpers.go +++ b/polyculeconnect/helpers/helpers.go @@ -4,7 +4,7 @@ import ( "context" "net/http" - "github.com/sirupsen/logrus" + "go.uber.org/zap" ) type ContextKey string @@ -16,7 +16,7 @@ type ResponseInfo struct { ContentLength int } -func HandleResponse(w http.ResponseWriter, r *http.Request, returncode int, content []byte, l *logrus.Logger) { +func HandleResponse(w http.ResponseWriter, r *http.Request, returncode int, content []byte, l *zap.SugaredLogger) { w.WriteHeader(returncode) n, err := w.Write(content) if err != nil { diff --git a/polyculeconnect/internal/client/client.go b/polyculeconnect/internal/client/client.go new file mode 100644 index 0000000..6fa123d --- /dev/null +++ b/polyculeconnect/internal/client/client.go @@ -0,0 +1,52 @@ +package client + +import ( + "context" + "fmt" + "log/slog" + + "git.faercol.me/faercol/polyculeconnect/polyculeconnect/logger" + "github.com/google/uuid" + "github.com/zitadel/oidc/v3/pkg/client/rp" + "go.uber.org/zap" + "go.uber.org/zap/exp/zapslog" +) + +type BackendOIDCConfig struct { + Issuer string + ClientID string + ClientSecret string + RedirectURI string +} + +type Backend struct { + ID uuid.UUID + Name string + Config BackendOIDCConfig +} + +// OIDCClient is an OIDC client which is the client used to access a registered backend +type OIDCClient struct { + backend *Backend + + provider rp.RelyingParty + ctx context.Context + l *zap.SugaredLogger +} + +func New(ctx context.Context, conf *Backend, l *zap.SugaredLogger) (*OIDCClient, error) { + options := []rp.Option{ + rp.WithLogger(slog.New(zapslog.NewHandler(logger.L.Desugar().Core(), nil))), + } + pr, err := rp.NewRelyingPartyOIDC(ctx, conf.Config.Issuer, conf.Config.ClientID, conf.Config.ClientSecret, conf.Config.RedirectURI, []string{}, options...) + if err != nil { + return nil, fmt.Errorf("failed to init relying party provider: %w", err) + } + + return &OIDCClient{ctx: ctx, backend: conf, provider: pr, l: l}, nil +} + +func (c *OIDCClient) AuthorizationEndpoint() string { + url := rp.AuthURL(uuid.NewString(), c.provider) + return url +} diff --git a/polyculeconnect/internal/db/authcode/authcode.go b/polyculeconnect/internal/db/authcode/authcode.go new file mode 100644 index 0000000..f005abc --- /dev/null +++ b/polyculeconnect/internal/db/authcode/authcode.go @@ -0,0 +1,62 @@ +package authcode + +import ( + "context" + "database/sql" + "errors" + "fmt" + + "git.faercol.me/faercol/polyculeconnect/polyculeconnect/internal/model" + "git.faercol.me/faercol/polyculeconnect/polyculeconnect/logger" +) + +var ErrNotFound = errors.New("auth code not found") + +type AuthCodeDB interface { + GetAuthCodeByCode(ctx context.Context, code string) (*model.AuthCode, error) + CreateAuthCode(ctx context.Context, code model.AuthCode) error +} + +type sqlAuthCodeDB struct { + db *sql.DB +} + +func (db *sqlAuthCodeDB) CreateAuthCode(ctx context.Context, code model.AuthCode) error { + logger.L.Debugf("Creating auth code for request %s", code.RequestID) + tx, err := db.db.BeginTx(ctx, nil) + if err != nil { + return fmt.Errorf("failed to start transaction: %w", err) + } + defer func() { _ = tx.Rollback() }() + + query := `INSERT INTO "auth_code" ("id", "auth_request_id", "code") VALUES ($1, $2, $3)` + _, err = tx.ExecContext(ctx, query, code.CodeID, code.RequestID, code.Code) + if err != nil { + return fmt.Errorf("failed to insert in DB: %w", err) + } + + if err := tx.Commit(); err != nil { + return fmt.Errorf("failed to commit transaction: %w", err) + } + return nil +} + +func (db *sqlAuthCodeDB) GetAuthCodeByCode(ctx context.Context, code string) (*model.AuthCode, error) { + logger.L.Debugf("Getting auth code %s from DB", code) + query := `SELECT "id", "auth_request_id", "code" FROM "auth_code" WHERE "code" = ?` + row := db.db.QueryRowContext(ctx, query, code) + + var res model.AuthCode + if err := row.Scan(&res.CodeID, &res.RequestID, &res.Code); err != nil { + if errors.Is(err, sql.ErrNoRows) { + return nil, ErrNotFound + } + return nil, fmt.Errorf("failed to read row from DB: %w", err) + } + + return &res, nil +} + +func New(db *sql.DB) AuthCodeDB { + return &sqlAuthCodeDB{db: db} +} diff --git a/polyculeconnect/internal/db/authrequest/authrequest.go b/polyculeconnect/internal/db/authrequest/authrequest.go new file mode 100644 index 0000000..b923954 --- /dev/null +++ b/polyculeconnect/internal/db/authrequest/authrequest.go @@ -0,0 +1,159 @@ +package authrequest + +import ( + "context" + "database/sql" + "encoding/json" + "errors" + "fmt" + "time" + + "git.faercol.me/faercol/polyculeconnect/polyculeconnect/internal/model" + "git.faercol.me/faercol/polyculeconnect/polyculeconnect/logger" + "github.com/google/uuid" +) + +var ErrNotFound = errors.New("backend not found") + +const authRequestRows = `"id", "client_id", "backend_id", "scopes", "redirect_uri", "state", "nonce", "response_type", "creation_time", "done", "code_challenge", "code_challenge_method", "auth_time", "user_id", "consent"` + +type AuthRequestDB interface { + GetAuthRequestByID(ctx context.Context, id uuid.UUID) (*model.AuthRequest, error) + CreateAuthRequest(ctx context.Context, req model.AuthRequest) error + ValidateAuthRequest(ctx context.Context, reqID uuid.UUID, userID string) error + DeleteAuthRequest(ctx context.Context, reqID uuid.UUID) error + GiveConsent(ctx context.Context, reqID uuid.UUID) error +} + +type sqlAuthRequestDB struct { + db *sql.DB +} + +func (db *sqlAuthRequestDB) GetAuthRequestByID(ctx context.Context, id uuid.UUID) (*model.AuthRequest, error) { + logger.L.Debugf("Getting auth request with id %s", id) + query := fmt.Sprintf(`SELECT %s FROM "auth_request" WHERE "id" = ?`, authRequestRows) + row := db.db.QueryRowContext(ctx, query, id) + + var res model.AuthRequest + var scopesStr []byte + + var timestamp *time.Time + + if err := row.Scan(&res.ID, &res.ClientID, &res.BackendID, &scopesStr, &res.RedirectURI, &res.State, &res.Nonce, &res.ResponseType, &res.CreationDate, &res.DoneVal, &res.CodeChallenge, &res.CodeChallengeMethod, ×tamp, &res.UserID, &res.Consent); err != nil { + return nil, fmt.Errorf("failed to get auth request from DB: %w", err) + } + if timestamp != nil { + res.AuthTime = *timestamp + } + if err := json.Unmarshal(scopesStr, &res.Scopes); err != nil { + return nil, fmt.Errorf("invalid format for scopes: %w", err) + } + + return &res, nil +} + +func (db *sqlAuthRequestDB) CreateAuthRequest(ctx context.Context, req model.AuthRequest) error { + logger.L.Debugf("Creating a new auth request between client app %s and backend %s", req.ClientID, req.BackendID) + tx, err := db.db.BeginTx(ctx, nil) + if err != nil { + return fmt.Errorf("failed to start transaction: %w", err) + } + defer func() { _ = tx.Rollback() }() + + scopesStr, err := json.Marshal(req.Scopes) + if err != nil { + return fmt.Errorf("failed to serialize scopes: %w", err) + } + + query := fmt.Sprintf(`INSERT INTO "auth_request" (%s) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, NULL, '', 0)`, authRequestRows) + _, err = tx.ExecContext(ctx, query, + req.ID, req.ClientID, req.BackendID, + scopesStr, req.RedirectURI, req.State, + req.Nonce, req.ResponseType, req.CreationDate, false, + req.CodeChallenge, req.CodeChallengeMethod, + ) + if err != nil { + return fmt.Errorf("failed to insert in DB: %w", err) + } + + if err := tx.Commit(); err != nil { + return fmt.Errorf("failed to commit transaction: %w", err) + } + + return nil +} + +func (db *sqlAuthRequestDB) ValidateAuthRequest(ctx context.Context, reqID uuid.UUID, userID string) error { + logger.L.Debugf("Validating auth request %s", reqID) + tx, err := db.db.BeginTx(ctx, nil) + if err != nil { + return fmt.Errorf("failed to start transaction: %w", err) + } + defer func() { _ = tx.Rollback() }() + + res, err := tx.ExecContext(ctx, `UPDATE "auth_request" SET done = true, auth_time = $1, user_id = $2 WHERE id = $3`, time.Now().UTC(), userID, reqID) + if err != nil { + return fmt.Errorf("failed to update in DB: %w", err) + } + affectedRows, err := res.RowsAffected() + if err != nil { + return fmt.Errorf("failed to check number of affected rows: %w", err) + } + if affectedRows != 1 { + return ErrNotFound + } + + if err := tx.Commit(); err != nil { + return fmt.Errorf("failed to commit transaction: %w", err) + } + + return nil +} + +func (db *sqlAuthRequestDB) GiveConsent(ctx context.Context, reqID uuid.UUID) error { + tx, err := db.db.BeginTx(ctx, nil) + if err != nil { + return fmt.Errorf("failed to start transaction: %w", err) + } + defer func() { _ = tx.Rollback() }() + + res, err := tx.ExecContext(ctx, `UPDATE "auth_request" SET consent = true WHERE id = $1`, reqID) + if err != nil { + return fmt.Errorf("failed to update in DB: %w", err) + } + affectedRows, err := res.RowsAffected() + if err != nil { + return fmt.Errorf("failed to check number of affected rows: %w", err) + } + if affectedRows != 1 { + return ErrNotFound + } + + if err := tx.Commit(); err != nil { + return fmt.Errorf("failed to commit transaction: %w", err) + } + + return nil +} + +func (db *sqlAuthRequestDB) DeleteAuthRequest(ctx context.Context, reqID uuid.UUID) error { + logger.L.Debugf("Deleting auth request: %s", reqID) + tx, err := db.db.BeginTx(ctx, nil) + if err != nil { + return fmt.Errorf("failed to start transaction: %w", err) + } + defer func() { _ = tx.Rollback() }() + + _, err = tx.ExecContext(ctx, `DELETE FROM "auth_request" WHERE id = $1`, reqID.String()) + if err != nil { + return fmt.Errorf("failed to delete auth request: %w", err) + } + if err := tx.Commit(); err != nil { + return fmt.Errorf("failed to commit transaction: %w", err) + } + return nil +} + +func New(db *sql.DB) *sqlAuthRequestDB { + return &sqlAuthRequestDB{db: db} +} diff --git a/polyculeconnect/internal/db/backend/backend.go b/polyculeconnect/internal/db/backend/backend.go new file mode 100644 index 0000000..cfac9fe --- /dev/null +++ b/polyculeconnect/internal/db/backend/backend.go @@ -0,0 +1,137 @@ +package backend + +import ( + "context" + "database/sql" + "encoding/json" + "errors" + "fmt" + + "git.faercol.me/faercol/polyculeconnect/polyculeconnect/internal/model" + "git.faercol.me/faercol/polyculeconnect/polyculeconnect/logger" + "github.com/google/uuid" +) + +var ErrNotFound = errors.New("backend not found") + +const backendRows = `"id", "name", "oidc_issuer", "oidc_client_id", "oidc_client_secret", "oidc_redirect_uri", "oidc_scopes"` + +type scannable interface { + Scan(dest ...any) error +} + +type BackendDB interface { + GetAllBackends(ctx context.Context) ([]*model.Backend, error) + + GetBackendByID(ctx context.Context, id uuid.UUID) (*model.Backend, error) + GetBackendByName(ctx context.Context, name string) (*model.Backend, error) + + AddBackend(ctx context.Context, newBackend *model.Backend) error + + DeleteBackend(ctx context.Context, id uuid.UUID) error +} + +type sqlBackendDB struct { + db *sql.DB +} + +func backendFromRow(row scannable) (*model.Backend, error) { + var res model.Backend + var scopesStr []byte + + fmt.Println(string(scopesStr)) + + if err := row.Scan(&res.ID, &res.Name, &res.Config.Issuer, &res.Config.ClientID, &res.Config.ClientSecret, &res.Config.RedirectURI, &scopesStr); err != nil { + if errors.Is(err, sql.ErrNoRows) { + return nil, ErrNotFound + } + return nil, fmt.Errorf("invalid format for backend: %w", err) + } + if err := json.Unmarshal(scopesStr, &res.Config.Scopes); err != nil { + return nil, fmt.Errorf("invalid value for scopes: %w", err) + } + return &res, nil +} + +func (db *sqlBackendDB) GetBackendByName(ctx context.Context, name string) (*model.Backend, error) { + logger.L.Debugf("Getting backend with name %s from DB", name) + query := fmt.Sprintf(`SELECT %s FROM "backend" WHERE "name" = ?`, backendRows) + row := db.db.QueryRowContext(ctx, query, name) + return backendFromRow(row) +} + +func (db *sqlBackendDB) GetBackendByID(ctx context.Context, id uuid.UUID) (*model.Backend, error) { + logger.L.Debugf("Getting backend with ID %s from DB", id) + query := fmt.Sprintf(`SELECT %s FROM "backend" WHERE "id" = ?`, backendRows) + row := db.db.QueryRowContext(ctx, query, id) + return backendFromRow(row) +} + +func (db *sqlBackendDB) GetAllBackends(ctx context.Context) ([]*model.Backend, error) { + logger.L.Debug("Getting all backends from DB") + rows, err := db.db.QueryContext(ctx, fmt.Sprintf(`SELECT %s FROM "backend"`, backendRows)) + if err != nil { + return nil, err + } + + var res []*model.Backend + for rows.Next() { + b, err := backendFromRow(rows) + if err != nil { + return nil, err + } + res = append(res, b) + } + return res, rows.Err() +} + +func (db *sqlBackendDB) AddBackend(ctx context.Context, newBackend *model.Backend) error { + tx, err := db.db.BeginTx(ctx, nil) + if err != nil { + return fmt.Errorf("failed to start transaction: %w", err) + } + defer func() { _ = tx.Rollback() }() + + scopesStr, err := json.Marshal(newBackend.Config.Scopes) + if err != nil { + return fmt.Errorf("failed to serialize scopes: %w", err) + } + + query := fmt.Sprintf(`INSERT INTO "backend" (%s) VALUES ($1, $2, $3, $4, $5, $6, $7)`, backendRows) + _, err = tx.ExecContext( + ctx, query, + newBackend.ID, newBackend.Name, + newBackend.Config.Issuer, newBackend.Config.ClientID, + newBackend.Config.ClientSecret, newBackend.Config.RedirectURI, + scopesStr, + ) + if err != nil { + return fmt.Errorf("failed to insert in DB: %w", err) + } + + if err := tx.Commit(); err != nil { + return fmt.Errorf("failed to commit transaction: %w", err) + } + return nil +} + +func (db *sqlBackendDB) DeleteBackend(ctx context.Context, id uuid.UUID) error { + tx, err := db.db.BeginTx(ctx, nil) + if err != nil { + return fmt.Errorf("failed to start transaction: %w", err) + } + defer func() { _ = tx.Rollback() }() + + if _, err := tx.ExecContext(ctx, `DELETE FROM "backend" WHERE id = $1`, id.String()); err != nil { + return fmt.Errorf("failed to run query: %w", err) + } + + if err := tx.Commit(); err != nil { + return fmt.Errorf("failed to commit transaction: %w", err) + } + return nil +} + +func New(db *sql.DB) *sqlBackendDB { + return &sqlBackendDB{db: db} +} diff --git a/polyculeconnect/internal/db/base.go b/polyculeconnect/internal/db/base.go new file mode 100644 index 0000000..0a61e13 --- /dev/null +++ b/polyculeconnect/internal/db/base.go @@ -0,0 +1,64 @@ +package db + +import ( + "database/sql" + "fmt" + + "git.faercol.me/faercol/polyculeconnect/polyculeconnect/config" + "git.faercol.me/faercol/polyculeconnect/polyculeconnect/internal/db/authcode" + "git.faercol.me/faercol/polyculeconnect/polyculeconnect/internal/db/authrequest" + "git.faercol.me/faercol/polyculeconnect/polyculeconnect/internal/db/backend" + "git.faercol.me/faercol/polyculeconnect/polyculeconnect/internal/db/client" + "git.faercol.me/faercol/polyculeconnect/polyculeconnect/internal/db/token" + "git.faercol.me/faercol/polyculeconnect/polyculeconnect/internal/db/user" +) + +type Storage interface { + DB() *sql.DB + ClientStorage() client.ClientDB + BackendStorage() backend.BackendDB + AuthRequestStorage() authrequest.AuthRequestDB + AuthCodeStorage() authcode.AuthCodeDB + UserStorage() user.UserDB + TokenStorage() token.TokenDB +} + +type sqlStorage struct { + db *sql.DB +} + +func (s *sqlStorage) DB() *sql.DB { + return s.db +} + +func (s *sqlStorage) ClientStorage() client.ClientDB { + return client.New(s.db) +} + +func (s *sqlStorage) BackendStorage() backend.BackendDB { + return backend.New(s.db) +} + +func (s *sqlStorage) AuthRequestStorage() authrequest.AuthRequestDB { + return authrequest.New(s.db) +} + +func (s *sqlStorage) AuthCodeStorage() authcode.AuthCodeDB { + return authcode.New(s.db) +} + +func (s *sqlStorage) UserStorage() user.UserDB { + return user.New(s.db) +} + +func (s *sqlStorage) TokenStorage() token.TokenDB { + return token.New(s.db) +} + +func New(conf config.AppConfig) (Storage, error) { + db, err := sql.Open("sqlite3", conf.StorageConfig.File) + if err != nil { + return nil, fmt.Errorf("failed to open DB: %w", err) + } + return &sqlStorage{db: db}, nil +} diff --git a/polyculeconnect/internal/db/client/client.go b/polyculeconnect/internal/db/client/client.go new file mode 100644 index 0000000..b9d9859 --- /dev/null +++ b/polyculeconnect/internal/db/client/client.go @@ -0,0 +1,135 @@ +package client + +import ( + "context" + "database/sql" + "encoding/json" + "errors" + "fmt" + + "git.faercol.me/faercol/polyculeconnect/polyculeconnect/internal/model" + "git.faercol.me/faercol/polyculeconnect/polyculeconnect/logger" + _ "github.com/mattn/go-sqlite3" +) + +var ErrNotFound = errors.New("not found") + +const clientRows = `"client"."id", "client"."secret", "client"."redirect_uris", "client"."trusted_peers", "client"."name"` + +type ClientDB interface { + GetClientByID(ctx context.Context, id string) (*model.Client, error) + GetAllClients(ctx context.Context) ([]*model.Client, error) + AddClient(ctx context.Context, client *model.Client) error + DeleteClient(ctx context.Context, id string) error +} + +type sqlClientDB struct { + db *sql.DB +} + +func strArrayToSlice(rawVal string) []string { + var res []string + if err := json.Unmarshal([]byte(rawVal), &res); err != nil { + return nil + } + return res +} + +func sliceToStrArray(rawVal []string) string { + res, err := json.Marshal(rawVal) + if err != nil { + return "[]" + } + return string(res) +} + +type scannable interface { + Scan(dest ...any) error +} + +func clientFromRow(row scannable) (*model.Client, error) { + var res model.Client + redirectURIsStr := "" + trustedPeersStr := "" + + if err := row.Scan(&res.ID, &res.Secret, &redirectURIsStr, &trustedPeersStr, &res.Name); err != nil { + if errors.Is(err, sql.ErrNoRows) { + return nil, ErrNotFound + } + return nil, fmt.Errorf("invalid format for client: %w", err) + } + + res.ClientConfig.RedirectURIs = strArrayToSlice(redirectURIsStr) + res.ClientConfig.TrustedPeers = strArrayToSlice(trustedPeersStr) + + return &res, nil +} + +func (db *sqlClientDB) GetClientByID(ctx context.Context, id string) (*model.Client, error) { + logger.L.Debugf("Getting client app with ID %s from DB", id) + query := fmt.Sprintf(`SELECT %s FROM "client" WHERE "id" = ?`, clientRows) + row := db.db.QueryRowContext(ctx, query, id) + return clientFromRow(row) +} + +func (db *sqlClientDB) GetAllClients(ctx context.Context) ([]*model.Client, error) { + rows, err := db.db.QueryContext(ctx, fmt.Sprintf(`SELECT %s FROM "client"`, clientRows)) + if err != nil { + return nil, fmt.Errorf("failed to query clients from DB: %w", err) + } + + var res []*model.Client + for rows.Next() { + clt, err := clientFromRow(rows) + if err != nil { + return nil, fmt.Errorf("failed to scan row: %w", err) + } + res = append(res, clt) + } + if err := rows.Err(); err != nil { + return nil, fmt.Errorf("failed to read all rows %w", err) + } + return res, nil +} + +func (db *sqlClientDB) AddClient(ctx context.Context, client *model.Client) error { + logger.L.Debugf("Creating client %s", client.Name) + query := `INSERT INTO "client" ("id", "secret", "redirect_uris", "trusted_peers", "name") VALUES ($1, $2, $3, $4, $5)` + + tx, err := db.db.BeginTx(ctx, nil) + if err != nil { + return fmt.Errorf("failed to start transaction: %w", err) + } + defer func() { _ = tx.Rollback() }() + if affectedRows, err := tx.ExecContext(ctx, query, client.ID, client.Secret, sliceToStrArray(client.RedirectURIs()), sliceToStrArray(client.TrustedPeers), client.Name); err != nil { + return fmt.Errorf("failed to insert in DB: %w", err) + } else if nbAffected, err := affectedRows.RowsAffected(); err != nil { + return fmt.Errorf("failed to check number of affected rows: %w", err) + } else if nbAffected != 1 { + return fmt.Errorf("unexpected number of affected rows: %d", nbAffected) + } + if err := tx.Commit(); err != nil { + return fmt.Errorf("failed to commit transaction: %w", err) + } + return nil +} + +func (db *sqlClientDB) DeleteClient(ctx context.Context, id string) error { + tx, err := db.db.BeginTx(ctx, nil) + if err != nil { + return fmt.Errorf("failed to start transaction: %w", err) + } + + if _, err := tx.ExecContext(ctx, `DELETE FROM "client" WHERE "id" = ?`, id); err != nil { + return fmt.Errorf("failed to exec query: %w", err) + } + + if err := tx.Commit(); err != nil { + return fmt.Errorf("failed to commit transaction: %w", err) + } + return nil +} + +func New(db *sql.DB) *sqlClientDB { + return &sqlClientDB{db: db} +} diff --git a/polyculeconnect/internal/db/token/token.go b/polyculeconnect/internal/db/token/token.go new file mode 100644 index 0000000..0ba9142 --- /dev/null +++ b/polyculeconnect/internal/db/token/token.go @@ -0,0 +1,68 @@ +package token + +import ( + "context" + "database/sql" + "encoding/json" + "fmt" + + "git.faercol.me/faercol/polyculeconnect/polyculeconnect/internal/model" + "github.com/google/uuid" +) + +func strArrayToSlice(rawVal string) []string { + var res []string + if err := json.Unmarshal([]byte(rawVal), &res); err != nil { + return nil + } + return res +} + +func sliceToStrArray(rawVal []string) string { + res, err := json.Marshal(rawVal) + if err != nil { + return "[]" + } + return string(res) +} + +type TokenDB interface { + AddRefreshToken(ctx context.Context, refreshToken *model.RefreshToken) error + GetRefreshTokenByID(ctx context.Context, id uuid.UUID) (*model.RefreshToken, error) +} + +type sqlTokenDB struct { + db *sql.DB +} + +func (db *sqlTokenDB) AddRefreshToken(ctx context.Context, refreshToken *model.RefreshToken) error { + tx, err := db.db.BeginTx(ctx, nil) + if err != nil { + return fmt.Errorf("failed to start transaction: %w", err) + } + defer func() { _ = tx.Rollback() }() + + if _, err := tx.ExecContext(ctx, `INSERT INTO "refresh_token" ("id", "client_id", "user_id", "scopes", "auth_time") VALUES ($1, $2, $3, $4, $5)`, refreshToken.ID, refreshToken.ClientID, refreshToken.UserID, sliceToStrArray(refreshToken.Scopes), refreshToken.AuthTime); err != nil { + return fmt.Errorf("failed to exec query: %w", err) + } + + if err := tx.Commit(); err != nil { + return fmt.Errorf("failed to commit transaction: %w", err) + } + return nil +} + +func (db *sqlTokenDB) GetRefreshTokenByID(ctx context.Context, id uuid.UUID) (*model.RefreshToken, error) { + row := db.db.QueryRowContext(ctx, `SELECT "id", "client_id", "user_id", "scopes", "auth_time" FROM "refresh_token" WHERE "id" = ?`, id) + var res model.RefreshToken + var strScopes string + if err := row.Scan(&res.ID, &res.ClientID, &res.UserID, &strScopes, &res.AuthTime); err != nil { + return nil, fmt.Errorf("failed to query DB: %w", err) + } + res.Scopes = strArrayToSlice(strScopes) + return &res, nil +} + +func New(db *sql.DB) TokenDB { + return &sqlTokenDB{db: db} +} diff --git a/polyculeconnect/internal/db/user/user.go b/polyculeconnect/internal/db/user/user.go new file mode 100644 index 0000000..7c874e9 --- /dev/null +++ b/polyculeconnect/internal/db/user/user.go @@ -0,0 +1,63 @@ +package user + +import ( + "context" + "database/sql" + "errors" + "fmt" + + "git.faercol.me/faercol/polyculeconnect/polyculeconnect/internal/model" +) + +type UserDB interface { + AddUser(ctx context.Context, user *model.User) error + GetUserBySubject(ctx context.Context, subject string) (*model.User, error) +} + +var ErrNotFound = errors.New("not found") + +const getUserQuery = ` + SELECT id, name, family_name, given_name, nickname, picture, updated_at, email, email_verified + FROM user + WHERE id = ? +` +const insertUserQuery = ` + INSERT OR REPLACE INTO user (id, name, family_name, given_name, nickname, picture, updated_at, email, email_verified) + VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9) +` + +type sqlUserDB struct { + db *sql.DB +} + +func (db *sqlUserDB) GetUserBySubject(ctx context.Context, subject string) (*model.User, error) { + row := db.db.QueryRowContext(ctx, getUserQuery, subject) + var res model.User + if err := row.Scan(&res.Subject, &res.Name, &res.FamilyName, &res.GivenName, &res.Nickname, &res.Picture, &res.UpdatedAt, &res.Email, &res.EmailVerified); err != nil { + if errors.Is(err, sql.ErrNoRows) { + return nil, ErrNotFound + } + return nil, fmt.Errorf("failed to read result from DB: %w", err) + } + return &res, nil +} + +func (db *sqlUserDB) AddUser(ctx context.Context, user *model.User) error { + tx, err := db.db.BeginTx(ctx, nil) + if err != nil { + return fmt.Errorf("failed to start transaction: %w", err) + } + defer func() { _ = tx.Rollback() }() + + if _, err := tx.ExecContext(ctx, insertUserQuery, user.Subject, user.Name, user.FamilyName, user.GivenName, user.Nickname, user.Picture, user.UpdatedAt, user.Email, user.EmailVerified); err != nil { + return fmt.Errorf("failed to insert in DB: %w", err) + } + if err := tx.Commit(); err != nil { + return fmt.Errorf("failed to commit transaction: %w", err) + } + return nil +} + +func New(db *sql.DB) *sqlUserDB { + return &sqlUserDB{db: db} +} diff --git a/polyculeconnect/internal/middlewares/connectordispatcher.go b/polyculeconnect/internal/middlewares/connectordispatcher.go new file mode 100644 index 0000000..a6c5408 --- /dev/null +++ b/polyculeconnect/internal/middlewares/connectordispatcher.go @@ -0,0 +1,75 @@ +package middlewares + +import ( + "bytes" + "context" + "fmt" + "html/template" + "io" + "net/http" + "path/filepath" + + "go.uber.org/zap" +) + +const ( + backendNameQueryParam = "connector_id" + backendCtxKeyName = "backendName" +) + +type BackendFromRequestMiddleware struct { + l *zap.SugaredLogger + h http.Handler + baseDir string +} + +func (m *BackendFromRequestMiddleware) serveBackendSelector(w http.ResponseWriter, r *http.Request) (int, error) { + lp := filepath.Join(m.baseDir, "templates", "login.html") + hdrTpl := filepath.Join(m.baseDir, "templates", "header.html") + footTpl := filepath.Join(m.baseDir, "templates", "footer.html") + tmpl, err := template.New("login.html").ParseFiles(hdrTpl, footTpl, lp) + if err != nil { + return http.StatusInternalServerError, fmt.Errorf("failed to init template: %w", err) + } + buf := new(bytes.Buffer) + + if err := tmpl.Execute(buf, nil); err != nil { + return http.StatusInternalServerError, fmt.Errorf("failed to execute template: %w", err) + } + _, err = io.Copy(w, buf) + if err != nil { + return http.StatusInternalServerError, fmt.Errorf("failed to write response; %w", err) + } + + return http.StatusOK, nil +} + +func (m *BackendFromRequestMiddleware) ServeHTTP(w http.ResponseWriter, r *http.Request) { + if r.URL.Path != "/authorize" { + m.h.ServeHTTP(w, r) + return + } + + if err := r.ParseForm(); err != nil { + // TODO: handle this better + w.WriteHeader(http.StatusBadRequest) + return + } + + backendName := r.Form.Get(backendNameQueryParam) + if backendName == "" { + statusCode, err := m.serveBackendSelector(w, r) + if err != nil { + m.l.Errorf("Failed to serve backend selector page: %s", err) + } + w.WriteHeader(statusCode) + return + } + + ctx := context.WithValue(r.Context(), backendCtxKeyName, backendName) + m.h.ServeHTTP(w, r.WithContext(ctx)) +} + +func WithBackendFromRequestMiddleware(input http.Handler) http.Handler { + return &BackendFromRequestMiddleware{h: input} +} diff --git a/polyculeconnect/middlewares/logger.go b/polyculeconnect/internal/middlewares/logger.go similarity index 96% rename from polyculeconnect/middlewares/logger.go rename to polyculeconnect/internal/middlewares/logger.go index 0808010..43c1d10 100644 --- a/polyculeconnect/middlewares/logger.go +++ b/polyculeconnect/internal/middlewares/logger.go @@ -5,7 +5,7 @@ import ( "net/http" "time" - "github.com/sirupsen/logrus" + "go.uber.org/zap" ) type loggedResponseWriter struct { @@ -33,7 +33,7 @@ func (lr *loggedResponseWriter) WriteHeader(statusCode int) { } type LoggerMiddleware struct { - l *logrus.Logger + l *zap.SugaredLogger h http.Handler } diff --git a/polyculeconnect/internal/middlewares/middlewarechain.go b/polyculeconnect/internal/middlewares/middlewarechain.go new file mode 100644 index 0000000..ba89469 --- /dev/null +++ b/polyculeconnect/internal/middlewares/middlewarechain.go @@ -0,0 +1,14 @@ +package middlewares + +import ( + "net/http" + + "go.uber.org/zap" +) + +func WithLogger(handler http.Handler, l *zap.SugaredLogger) http.Handler { + return &LoggerMiddleware{ + l: l, + h: handler, + } +} diff --git a/polyculeconnect/internal/model/authcode.go b/polyculeconnect/internal/model/authcode.go new file mode 100644 index 0000000..4d896c6 --- /dev/null +++ b/polyculeconnect/internal/model/authcode.go @@ -0,0 +1,9 @@ +package model + +import "github.com/google/uuid" + +type AuthCode struct { + CodeID uuid.UUID + RequestID uuid.UUID + Code string +} diff --git a/polyculeconnect/internal/model/authrequest.go b/polyculeconnect/internal/model/authrequest.go new file mode 100644 index 0000000..21cb2ee --- /dev/null +++ b/polyculeconnect/internal/model/authrequest.go @@ -0,0 +1,118 @@ +package model + +import ( + "strings" + "time" + + "github.com/google/uuid" + "github.com/zitadel/oidc/v3/pkg/oidc" +) + +// AuthRequest also implements the op.AuthRequest interface +type AuthRequest struct { + ID uuid.UUID + ClientID string + Scopes []string + RedirectURI string + State string + Nonce string + + ResponseType string + + CreationDate time.Time + AuthTime time.Time + + // TODO mapping to claims to be added I guess + + CodeChallenge string + CodeChallengeMethod string + + BackendID uuid.UUID + Backend *Backend + + UserID string + User *User + + DoneVal bool + Consent bool +} + +func (a AuthRequest) GetID() string { + return a.ID.String() +} + +func (a AuthRequest) GetACR() string { + return "" // TODO: the hell is ACR??? +} + +func (a AuthRequest) GetAMR() []string { + return []string{} // TODO: the hell is this??? +} + +func (a AuthRequest) GetAudience() []string { + return []string{a.ID.String()} // TODO: check if we need to return something else +} + +func (a AuthRequest) GetAuthTime() time.Time { + return a.AuthTime +} + +func (a AuthRequest) GetClientID() string { + return a.ClientID +} + +func (a AuthRequest) GetCodeChallenge() *oidc.CodeChallenge { + return &oidc.CodeChallenge{ + Challenge: a.CodeChallenge, + Method: oidc.CodeChallengeMethod(a.CodeChallengeMethod), + } +} + +func (a AuthRequest) GetNonce() string { + return a.Nonce +} + +func (a AuthRequest) GetRedirectURI() string { + return a.RedirectURI +} + +func (a AuthRequest) GetResponseType() oidc.ResponseType { + return oidc.ResponseType(a.ResponseType) +} + +func (a AuthRequest) GetResponseMode() oidc.ResponseMode { + return oidc.ResponseModeQuery // TODO: check if this is good +} + +func (a AuthRequest) GetScopes() []string { + return a.Scopes +} + +func (a AuthRequest) GetState() string { + return a.State +} + +func (a AuthRequest) GetSubject() string { + if a.User == nil { + return "" + } + return a.User.Subject +} + +func (a AuthRequest) Done() bool { + return a.DoneVal +} + +func (a *AuthRequest) FromOIDCAuthRequest(req *oidc.AuthRequest, backendID uuid.UUID) { + a.ID = uuid.New() + a.ClientID = req.ClientID + a.Scopes = strings.Split(req.Scopes.String(), " ") + a.RedirectURI = req.RedirectURI + a.State = req.State + a.Nonce = req.Nonce + a.ResponseType = string(req.ResponseType) + a.CreationDate = time.Now().UTC() + a.CodeChallenge = req.CodeChallenge + a.CodeChallengeMethod = string(req.CodeChallengeMethod) + a.BackendID = backendID +} diff --git a/polyculeconnect/internal/model/backend.go b/polyculeconnect/internal/model/backend.go new file mode 100644 index 0000000..d16b911 --- /dev/null +++ b/polyculeconnect/internal/model/backend.go @@ -0,0 +1,17 @@ +package model + +import "github.com/google/uuid" + +type BackendOIDCConfig struct { + Issuer string + ClientID string + ClientSecret string + RedirectURI string + Scopes []string +} + +type Backend struct { + ID uuid.UUID + Name string + Config BackendOIDCConfig +} diff --git a/polyculeconnect/internal/model/client.go b/polyculeconnect/internal/model/client.go new file mode 100644 index 0000000..a7ae0d5 --- /dev/null +++ b/polyculeconnect/internal/model/client.go @@ -0,0 +1,97 @@ +package model + +import ( + "time" + + "github.com/zitadel/oidc/v3/pkg/oidc" + "github.com/zitadel/oidc/v3/pkg/op" +) + +// ClientConfig represents the configuration for a OIDC client app +type ClientConfig struct { + ID string + Secret string + RedirectURIs []string + TrustedPeers []string + Name string + AuthRequest *AuthRequest +} + +// Client represents an OIDC client app +type Client struct { + ClientConfig +} + +func (c Client) GetID() string { + return c.ClientConfig.ID +} + +func (c Client) RedirectURIs() []string { + return c.ClientConfig.RedirectURIs +} + +func (c Client) PostLogoutRedirectURIs() []string { + return nil +} + +func (c Client) ApplicationType() op.ApplicationType { + return op.ApplicationTypeWeb // TODO: should we support more? +} + +func (c Client) AuthMethod() oidc.AuthMethod { + return oidc.AuthMethodBasic +} + +func (c Client) ResponseTypes() []oidc.ResponseType { + return []oidc.ResponseType{oidc.ResponseTypeCode} +} + +func (c Client) GrantTypes() []oidc.GrantType { + return []oidc.GrantType{oidc.GrantTypeCode, oidc.GrantTypeRefreshToken, oidc.GrantTypeTokenExchange} +} + +// LoginURL returns the login URL for a given client app and auth request. +// This login url should be the authorization URL for the selected OIDC backend +func (c Client) LoginURL(authRequestID string) string { + if authRequestID == "" { + return "" // we don't have a request, let's return nothing + } + + return "/perform_auth?request_id=" + authRequestID +} + +func (c Client) AccessTokenType() op.AccessTokenType { + return op.AccessTokenTypeJWT +} + +func (c Client) IDTokenLifetime() time.Duration { + return 1 * time.Hour +} + +func (c Client) DevMode() bool { + return true +} + +func (c Client) RestrictAdditionalIdTokenScopes() func(scopes []string) []string { + return func(scopes []string) []string { + return scopes + } +} + +func (c Client) RestrictAdditionalAccessTokenScopes() func(scopes []string) []string { + return func(scopes []string) []string { + return scopes + } +} + +func (c Client) IsScopeAllowed(scope string) bool { + return true +} + +func (c Client) IDTokenUserinfoClaimsAssertion() bool { + return true +} + +func (c Client) ClockSkew() time.Duration { + return 0 +} diff --git a/polyculeconnect/internal/model/signingkey.go b/polyculeconnect/internal/model/signingkey.go new file mode 100644 index 0000000..16a8fab --- /dev/null +++ b/polyculeconnect/internal/model/signingkey.go @@ -0,0 +1,57 @@ +package model + +import ( + "crypto/rsa" + "strings" + + "github.com/go-jose/go-jose/v4" + "github.com/google/uuid" +) + +type SigningKey struct { + PrivateKey *rsa.PrivateKey + KeyID uuid.UUID + Algorithm jose.SignatureAlgorithm +} + +func (k *SigningKey) ID() string { + return strings.ReplaceAll(k.KeyID.String(), "-", "") +} + +func (k *SigningKey) SignatureAlgorithm() jose.SignatureAlgorithm { + return k.Algorithm +} + +func (k *SigningKey) Key() any { + return k.PrivateKey +} + +type Key struct { + PrivateKey *rsa.PrivateKey + KeyID uuid.UUID + SigningAlg jose.SignatureAlgorithm +} + +func (k *Key) SigningKey() *SigningKey { + return &SigningKey{ + PrivateKey: k.PrivateKey, + KeyID: k.KeyID, + Algorithm: k.SigningAlg, + } +} + +func (k *Key) ID() string { + return strings.ReplaceAll(k.KeyID.String(), "-", "") +} + +func (k *Key) Algorithm() jose.SignatureAlgorithm { + return k.SigningAlg +} + +func (k *Key) Key() any { + return &k.PrivateKey.PublicKey +} + +func (k *Key) Use() string { + return "sig" +} diff --git a/polyculeconnect/internal/model/token.go b/polyculeconnect/internal/model/token.go new file mode 100644 index 0000000..9ba5688 --- /dev/null +++ b/polyculeconnect/internal/model/token.go @@ -0,0 +1,68 @@ +package model + +import ( + "time" + + "github.com/google/uuid" +) + +type Token struct { + ID uuid.UUID + RefreshTokenID uuid.UUID + Expiration time.Time + Subjet string + Audiences []string + Scopes []string +} + +type RefreshToken struct { + ID uuid.UUID + ClientID string + UserID string + Scopes []string + AuthTime time.Time +} + +func (t RefreshToken) Request() *RefreshTokenRequest { + return &RefreshTokenRequest{ + userID: t.UserID, + clientID: t.ClientID, + scopes: t.Scopes, + authTime: t.AuthTime, + } +} + +type RefreshTokenRequest struct { + clientID string + authTime time.Time + userID string + scopes []string +} + +func (r RefreshTokenRequest) GetAMR() []string { + return []string{} +} + +func (r RefreshTokenRequest) GetAudience() []string { + return []string{} +} + +func (r RefreshTokenRequest) GetAuthTime() time.Time { + return r.authTime +} + +func (r RefreshTokenRequest) GetClientID() string { + return r.clientID +} + +func (r RefreshTokenRequest) GetScopes() []string { + return r.scopes +} + +func (r RefreshTokenRequest) GetSubject() string { + return r.userID +} + +func (r *RefreshTokenRequest) SetCurrentScopes(scopes []string) { + r.scopes = scopes +} diff --git a/polyculeconnect/internal/model/user.go b/polyculeconnect/internal/model/user.go new file mode 100644 index 0000000..1360cb8 --- /dev/null +++ b/polyculeconnect/internal/model/user.go @@ -0,0 +1,22 @@ +package model + +import ( + "time" +) + +type User struct { + // Part of openid scope + Subject string + + // Part of profile scope + Name string + FamilyName string + GivenName string + Nickname string + Picture string + UpdatedAt time.Time + + // part of email scope + Email string + EmailVerified bool +} diff --git a/polyculeconnect/internal/storage/local.go b/polyculeconnect/internal/storage/local.go new file mode 100644 index 0000000..c964712 --- /dev/null +++ b/polyculeconnect/internal/storage/local.go @@ -0,0 +1,4 @@ +package storage + +type LocalStorage struct { +} diff --git a/polyculeconnect/internal/storage/storage.go b/polyculeconnect/internal/storage/storage.go new file mode 100644 index 0000000..0da35a2 --- /dev/null +++ b/polyculeconnect/internal/storage/storage.go @@ -0,0 +1,377 @@ +package storage + +import ( + "context" + "database/sql" + "errors" + "fmt" + "time" + + "git.faercol.me/faercol/polyculeconnect/polyculeconnect/internal/client" + "git.faercol.me/faercol/polyculeconnect/polyculeconnect/internal/db" + "git.faercol.me/faercol/polyculeconnect/polyculeconnect/internal/model" + "git.faercol.me/faercol/polyculeconnect/polyculeconnect/logger" + "github.com/go-jose/go-jose/v4" + "github.com/google/uuid" + "github.com/zitadel/oidc/v3/pkg/oidc" + "github.com/zitadel/oidc/v3/pkg/op" +) + +func ErrNotImplemented(name string) error { + return fmt.Errorf("%s is not implemented", name) +} + +// Storage implements the Storage interface from zitadel/oidc/op +type Storage struct { + LocalStorage db.Storage + InitializedBackends map[uuid.UUID]*client.OIDCClient + Key *model.Key +} + +/* +Auth storage interface +*/ +func (s *Storage) CreateAuthRequest(ctx context.Context, req *oidc.AuthRequest, userID string) (op.AuthRequest, error) { + + // userID should normally be an empty string (to verify), we don't get it in our workflow from what I saw + // TODO: check this is indeed not needed / never present + logger.L.Debugf("Creating a new auth request") + + // validate that the connector is correct + backendName, ok := stringFromCtx(ctx, "backendName") + if !ok { + return nil, errors.New("no backend name provided") + } + selectedBackend, err := s.LocalStorage.BackendStorage().GetBackendByName(ctx, backendName) + if err != nil { + return nil, fmt.Errorf("failed to get backend: %w", err) + } + + var opReq model.AuthRequest + opReq.FromOIDCAuthRequest(req, selectedBackend.ID) + + if err := s.LocalStorage.AuthRequestStorage().CreateAuthRequest(ctx, opReq); err != nil { + return nil, fmt.Errorf("failed to save auth request: %w", err) + } + + logger.L.Debugf("Created a new auth request for backend %s", backendName) + + return opReq, nil +} + +func (s *Storage) AuthRequestByID(ctx context.Context, requestID string) (op.AuthRequest, error) { + logger.L.Debugf("Getting auth request with ID %s", requestID) + + id, err := uuid.Parse(requestID) + if err != nil { + return nil, fmt.Errorf("invalid format for uuid: %w", err) + } + + req, err := s.LocalStorage.AuthRequestStorage().GetAuthRequestByID(ctx, id) + if err != nil { + return nil, fmt.Errorf("failed to get auth request from DB: %w", err) + } + if req.UserID == "" { + return req, nil + } + + user, err := s.LocalStorage.UserStorage().GetUserBySubject(ctx, req.UserID) + if err != nil { + return nil, fmt.Errorf("failed to get user information from DB: %w", err) + } + req.User = user + return req, nil +} + +func (s *Storage) AuthRequestByCode(ctx context.Context, requestCode string) (op.AuthRequest, error) { + logger.L.Debugf("Getting auth request from code %s", requestCode) + + authCode, err := s.LocalStorage.AuthCodeStorage().GetAuthCodeByCode(ctx, requestCode) + if err != nil { + return nil, fmt.Errorf("failed to get auth code from DB: %w", err) + } + + req, err := s.LocalStorage.AuthRequestStorage().GetAuthRequestByID(ctx, authCode.RequestID) + if err != nil { + return nil, fmt.Errorf("failed to get auth request from DB: %w", err) + } + if req.UserID == "" { + return req, nil + } + + user, err := s.LocalStorage.UserStorage().GetUserBySubject(ctx, req.UserID) + if err != nil { + return nil, fmt.Errorf("failed to get user information from DB: %w", err) + } + req.User = user + return req, nil +} + +func (s *Storage) SaveAuthCode(ctx context.Context, id string, code string) error { + logger.L.Debugf("Saving auth code %s for request %s", code, id) + + requestID, err := uuid.Parse(id) + if err != nil { + return fmt.Errorf("invalid requestID %s: %w", requestID, err) + } + + codeID := uuid.New() + + savedCode := model.AuthCode{ + CodeID: codeID, + RequestID: requestID, + Code: code, + } + return s.LocalStorage.AuthCodeStorage().CreateAuthCode(ctx, savedCode) +} + +func (s *Storage) DeleteAuthRequest(ctx context.Context, id string) error { + reqID, err := uuid.Parse(id) + if err != nil { + return fmt.Errorf("invalid id format: %w", err) + } + return s.LocalStorage.AuthRequestStorage().DeleteAuthRequest(ctx, reqID) +} + +func (s *Storage) CreateAccessToken(ctx context.Context, req op.TokenRequest) (accessTokenID string, expiration time.Time, err error) { + accessTokenUUID := uuid.New() + var authTime time.Time + + switch typedReq := req.(type) { + case *model.AuthRequest: + logger.L.Debug("Creating access token for new authentication") + authTime = typedReq.AuthTime + case *model.RefreshTokenRequest: + logger.L.Debug("Handling refresh token request") + authTime = typedReq.GetAuthTime() + default: + logger.L.Errorf("Unexpected type for request %v", err) + return "", time.Time{}, errors.New("failed to parse auth request") + } + + expiration = authTime.Add(5 * time.Minute) + + // token := model.Token{ + // ID: accessTokenUUID, + // RefreshTokenID: refreshTokenUUID, + // Expiration: authTime.Add(5 * time.Minute), + // Subjet: request.GetSubject(), + // Audiences: request.GetAudience(), + // Scopes: request.GetScopes(), + // } + + return accessTokenUUID.String(), expiration, nil +} + +func (s *Storage) CreateAccessAndRefreshTokens(ctx context.Context, request op.TokenRequest, currentRefreshToken string) (accessTokenID string, newRefreshTokenID string, expiration time.Time, err error) { + accessTokenUUID := uuid.New() + refreshTokenUUID := uuid.New() + var authTime time.Time + var clientID string + + switch typedReq := request.(type) { + case *model.AuthRequest: + logger.L.Debug("Creating access token for new authentication") + clientID = typedReq.ClientID + authTime = typedReq.AuthTime + case *model.RefreshTokenRequest: + logger.L.Debug("Handling refresh token request") + clientID = typedReq.GetClientID() + authTime = typedReq.GetAuthTime() + default: + logger.L.Errorf("Unexpected type for request %v", err) + return "", "", time.Time{}, errors.New("failed to parse auth request") + } + + expiration = authTime.Add(5 * time.Minute) + + // token := model.Token{ + // ID: accessTokenUUID, + // RefreshTokenID: refreshTokenUUID, + // Expiration: authTime.Add(5 * time.Minute), + // Subjet: request.GetSubject(), + // Audiences: request.GetAudience(), + // Scopes: request.GetScopes(), + // } + refreshToken := model.RefreshToken{ + ID: refreshTokenUUID, + ClientID: clientID, + UserID: request.GetSubject(), + Scopes: request.GetScopes(), + AuthTime: authTime, + } + if err := s.LocalStorage.TokenStorage().AddRefreshToken(ctx, &refreshToken); err != nil { + return "", "", time.Time{}, fmt.Errorf("failed to insert token in DB: %w", err) + } + + return accessTokenUUID.String(), refreshTokenUUID.String(), expiration, nil +} + +func (s *Storage) TokenRequestByRefreshToken(ctx context.Context, refreshTokenID string) (op.RefreshTokenRequest, error) { + parsedID, err := uuid.Parse(refreshTokenID) + if err != nil { + return nil, fmt.Errorf("invalid format for refresh token id: %w", err) + } + + refreshToken, err := s.LocalStorage.TokenStorage().GetRefreshTokenByID(ctx, parsedID) + if err != nil { + if errors.Is(err, sql.ErrNoRows) { + return nil, op.ErrInvalidRefreshToken + } + return nil, fmt.Errorf("failed to get refresh token: %w", err) + } + return refreshToken.Request(), nil +} + +func (s *Storage) TerminateSession(ctx context.Context, userID string, clientID string) error { + return ErrNotImplemented("TerminateSession") +} + +func (s *Storage) RevokeToken(ctx context.Context, tokenOrTokenID string, userID string, clientID string) *oidc.Error { + return nil +} + +func (s *Storage) GetRefreshTokenInfo(ctx context.Context, clientID string, stoken string) (string, string, error) { + return "", "", ErrNotImplemented("GetRefreshTokenInfo") +} + +func (s *Storage) SigningKey(ctx context.Context) (op.SigningKey, error) { + return s.Key.SigningKey(), nil +} + +func (s *Storage) SignatureAlgorithms(ctx context.Context) ([]jose.SignatureAlgorithm, error) { + return nil, ErrNotImplemented("SignatureAlgorithms") +} + +func (s *Storage) KeySet(ctx context.Context) ([]op.Key, error) { + return []op.Key{s.Key}, nil +} + +/* + OP storage +*/ + +func (s *Storage) getClientWithDetails(ctx context.Context, authRequestID uuid.UUID) (op.Client, error) { + logger.L.Debug("Trying to get client details from auth request") + + authRequest, err := s.LocalStorage.AuthRequestStorage().GetAuthRequestByID(ctx, authRequestID) + if err != nil { + return nil, fmt.Errorf("failed to get authRequest from local storage: %w", err) + } + backend, err := s.LocalStorage.BackendStorage().GetBackendByID(ctx, authRequest.BackendID) + if err != nil { + return nil, fmt.Errorf("failed to get associated backend from local storage: %w", err) + } + client, err := s.LocalStorage.ClientStorage().GetClientByID(ctx, authRequest.ClientID) + if err != nil { + return nil, fmt.Errorf("failed to get associated client from local storage: %w", err) + } + + // oidcClient, ok := s.InitializedBackends[backend.ID] + // if !ok { + // return nil, fmt.Errorf("no initialized backend for ID %s", backend.ID) + // } + + authRequest.Backend = backend + client.AuthRequest = authRequest + + return client, nil +} + +// We're cheating a bit here since we're using the authrequest to get its associated client +// but a request is always associated to a backend, and we really need both, so we have no +// choice here. I'll maybe need to have a more elegant solution later, but not choice for now +func (s *Storage) GetClientByClientID(ctx context.Context, id string) (op.Client, error) { + logger.L.Debugf("Selecting client app with ID %s", id) + + authRequestID, err := uuid.Parse(id) + if err != nil { + // it's not a UUID, it means this was called using client_id, we just return the client without details + client, err := s.LocalStorage.ClientStorage().GetClientByID(ctx, id) + if err != nil { + return nil, fmt.Errorf("failed to get client %s from local storage: %w", id, err) + } + return client, nil + } + + // we have a UUID, it means we got a requestID, so we can get all details here + return s.getClientWithDetails(ctx, authRequestID) +} + +func (s *Storage) AuthorizeClientIDSecret(ctx context.Context, clientID, clientSecret string) error { + logger.L.Debugf("Validating client secret %s for client %s", clientSecret, clientID) + + client, err := s.LocalStorage.ClientStorage().GetClientByID(ctx, clientID) + if err != nil { + return err + } + + if client.Secret != clientSecret { + return errors.New("invalid secret") + } + + return nil +} + +func (s *Storage) SetUserinfoFromScopes(ctx context.Context, userinfo *oidc.UserInfo, userID, clientID string, scopes []string) error { + logger.L.Debugf("Setting user info for user %s", userID) + + user, err := s.LocalStorage.UserStorage().GetUserBySubject(ctx, userID) + if err != nil { + return fmt.Errorf("failed to get user from DB: %w", err) + } + + for _, s := range scopes { + switch s { + case "openid": + userinfo.Subject = user.Subject + case "profile": + userinfo.Name = user.Name + userinfo.FamilyName = user.FamilyName + userinfo.GivenName = user.GivenName + userinfo.Nickname = user.Nickname + userinfo.Picture = user.Picture + userinfo.UpdatedAt = oidc.FromTime(user.UpdatedAt) + case "email": + userinfo.Email = user.Email + userinfo.EmailVerified = oidc.Bool(user.EmailVerified) + } + } + + return nil +} + +func (s *Storage) SetUserinfoFromToken(ctx context.Context, userinfo *oidc.UserInfo, tokenID, subject, origin string) error { + return ErrNotImplemented("SetUserinfoFromToken") +} + +func (s *Storage) SetIntrospectionFromToken(ctx context.Context, userinfo *oidc.IntrospectionResponse, tokenID, subject, clientID string) error { + return ErrNotImplemented("SetIntrospectionFromToken") +} + +func (s *Storage) GetPrivateClaimsFromScopes(ctx context.Context, userID, clientID string, scopes []string) (map[string]interface{}, error) { + // For now, let's just return nothing, we don't want to add any private scope + return nil, nil +} + +func (s *Storage) GetKeyByIDAndClientID(ctx context.Context, keyID, clientID string) (*jose.JSONWebKey, error) { + return nil, ErrNotImplemented("GetKeyByIDAndClientID") +} + +func (s *Storage) ValidateJWTProfileScopes(ctx context.Context, userID string, scopes []string) ([]string, error) { + return nil, ErrNotImplemented("ValidateJWTProfileScopes") +} + +func (s *Storage) Health(ctx context.Context) error { + return ErrNotImplemented("Health") +} + +func stringFromCtx(ctx context.Context, key string) (string, bool) { + rawVal := ctx.Value(key) + if rawVal == nil { + return "", false + } + + val, ok := rawVal.(string) + return val, ok +} diff --git a/polyculeconnect/logger/logger.go b/polyculeconnect/logger/logger.go index 31b8a0e..da777cf 100644 --- a/polyculeconnect/logger/logger.go +++ b/polyculeconnect/logger/logger.go @@ -1,10 +1,28 @@ package logger -import "github.com/sirupsen/logrus" +import ( + "fmt" -var L *logrus.Logger + "go.uber.org/zap" + "go.uber.org/zap/zapcore" +) + +var L *zap.SugaredLogger + +func Init(level zap.AtomicLevel) { + conf := zap.Config{ + Level: level, + Encoding: "console", + OutputPaths: []string{"stdout"}, + ErrorOutputPaths: []string{"stderr"}, + EncoderConfig: zap.NewDevelopmentEncoderConfig(), + } + conf.EncoderConfig.EncodeLevel = zapcore.CapitalColorLevelEncoder + + if l, err := conf.Build(); err != nil { + panic(fmt.Errorf("failed to init logger: %w", err)) + } else { + L = l.Sugar() + } -func Init(level logrus.Level) { - L = logrus.New() - L.SetLevel(level) } diff --git a/polyculeconnect/middlewares/middlewarechain.go b/polyculeconnect/middlewares/middlewarechain.go deleted file mode 100644 index f1eb3ea..0000000 --- a/polyculeconnect/middlewares/middlewarechain.go +++ /dev/null @@ -1,14 +0,0 @@ -package middlewares - -import ( - "net/http" - - "github.com/sirupsen/logrus" -) - -func WithLogger(handler http.Handler, l *logrus.Logger) http.Handler { - return &LoggerMiddleware{ - l: l, - h: handler, - } -} diff --git a/polyculeconnect/migrations/0_initial_schema.down.sql b/polyculeconnect/migrations/0_initial_schema.down.sql new file mode 100644 index 0000000..aa0b196 --- /dev/null +++ b/polyculeconnect/migrations/0_initial_schema.down.sql @@ -0,0 +1,5 @@ +DROP TABLE "auth_code"; +DROP TABLE "auth_request"; +DROP TABLE "user"; +DROP TABLE "backend"; +DROP TABLE "client"; diff --git a/polyculeconnect/migrations/0_initial_schema.up.sql b/polyculeconnect/migrations/0_initial_schema.up.sql new file mode 100644 index 0000000..a699c06 --- /dev/null +++ b/polyculeconnect/migrations/0_initial_schema.up.sql @@ -0,0 +1,58 @@ +CREATE TABLE "backend" ( + id TEXT NOT NULL PRIMARY KEY, + name TEXT NOT NULL UNIQUE, + oidc_issuer TEXT NOT NULL, + oidc_client_id TEXT NOT NULL, + oidc_client_secret TEXT NOT NULL, + oidc_redirect_uri TEXT NOT NULL, + oidc_scopes blob NOT NULL DEFAULT '[]' -- list of strings, json-encoded, +); + +CREATE TABLE "client" ( + id TEXT NOT NULL PRIMARY KEY, + secret TEXT NOT NULL, + redirect_uris blob NOT NULL, + trusted_peers blob NOT NULL, + public integer NOT NULL DEFAULT 0, + name TEXT NOT NULL +); + +CREATE TABLE "user" ( + id TEXT NOT NULL PRIMARY KEY, + name TEXT NOT NULL DEFAULT '', + family_name TEXT NOT NULL DEFAULT '', + given_name TEXT NOT NULL DEFAULT '', + nickname TEXT NOT NULL DEFAULT '', + picture TEXT NOT NULL DEFAULT '', + updated_at timestamp, + email TEXT NOT NULL DEFAULT '', + email_verified INTEGER NOT NULL DEFAULT 0 +); + +CREATE TABLE "auth_request" ( + id TEXT NOT NULL PRIMARY KEY, + client_id TEXT NOT NULL, + backend_id TEXT NOT NULL, + scopes blob NOT NULL, -- list of strings, json-encoded + redirect_uri TEXT NOT NULL, + state TEXT NOT NULL, + nonce TEXT NOT NULL, + response_type TEXT NOT NULL, + creation_time timestamp NOT NULL, + done INTEGER NOT NULL DEFAULT 0, + code_challenge STRING NOT NULL DEFAULT '', + code_challenge_method STRING NOT NULL DEFAULT '', + auth_time timestamp, + user_id TEXT NOT NULL DEFAULT '', + FOREIGN KEY(backend_id) REFERENCES backend(id), + FOREIGN KEY(client_id) REFERENCES client(id), + FOREIGN KEY(user_id) REFERENCES user(id) +); + +CREATE TABLE "auth_code" ( + id TEXT NOT NULL PRIMARY KEY, + code TEXT NOT NULL, + auth_request_id TEXT NOT NULL, + FOREIGN KEY(auth_request_id) REFERENCES auth_request(id) +); + diff --git a/polyculeconnect/migrations/1_tokens.down.sql b/polyculeconnect/migrations/1_tokens.down.sql new file mode 100644 index 0000000..3e12ff9 --- /dev/null +++ b/polyculeconnect/migrations/1_tokens.down.sql @@ -0,0 +1 @@ +DROP TABLE refresh_token; diff --git a/polyculeconnect/migrations/1_tokens.up.sql b/polyculeconnect/migrations/1_tokens.up.sql new file mode 100644 index 0000000..818e48d --- /dev/null +++ b/polyculeconnect/migrations/1_tokens.up.sql @@ -0,0 +1,9 @@ +CREATE TABLE refresh_token ( + id TEXT NOT NULL PRIMARY KEY, + client_id TEXT NOT NULL, + user_id TEXT NOT NULL, + scopes blob NOT NULL, -- list of strings, json-encoded + auth_time timestamp NOT NULL, + FOREIGN KEY(client_id) REFERENCES client(id), + FOREIGN KEY(user_id) REFERENCES user(id) +); diff --git a/polyculeconnect/migrations/2_consent.down.sql b/polyculeconnect/migrations/2_consent.down.sql new file mode 100644 index 0000000..f4fac91 --- /dev/null +++ b/polyculeconnect/migrations/2_consent.down.sql @@ -0,0 +1 @@ +ALTER TABLE "auth_request" DROP COLUMN consent; diff --git a/polyculeconnect/migrations/2_consent.up.sql b/polyculeconnect/migrations/2_consent.up.sql new file mode 100644 index 0000000..ccfe5b2 --- /dev/null +++ b/polyculeconnect/migrations/2_consent.up.sql @@ -0,0 +1 @@ +ALTER TABLE "auth_request" ADD COLUMN consent INTEGER NOT NULL DEFAULT 0; \ No newline at end of file diff --git a/polyculeconnect/server/server.go b/polyculeconnect/server/server.go index 84a8b5e..027203e 100644 --- a/polyculeconnect/server/server.go +++ b/polyculeconnect/server/server.go @@ -9,10 +9,14 @@ import ( "os" "git.faercol.me/faercol/polyculeconnect/polyculeconnect/config" + "git.faercol.me/faercol/polyculeconnect/polyculeconnect/controller/auth" "git.faercol.me/faercol/polyculeconnect/polyculeconnect/controller/ui" - "git.faercol.me/faercol/polyculeconnect/polyculeconnect/middlewares" - dex_server "github.com/dexidp/dex/server" - "github.com/sirupsen/logrus" + "git.faercol.me/faercol/polyculeconnect/polyculeconnect/internal/middlewares" + "git.faercol.me/faercol/polyculeconnect/polyculeconnect/internal/storage" + "github.com/google/uuid" + "github.com/zitadel/oidc/v3/pkg/client/rp" + "github.com/zitadel/oidc/v3/pkg/op" + "go.uber.org/zap" ) type Server struct { @@ -24,7 +28,7 @@ type Server struct { address string handler *http.ServeMux controllers map[string]http.Handler - l *logrus.Logger + l *zap.SugaredLogger } func newUnixListener(sockPath string) (net.Listener, error) { @@ -42,7 +46,7 @@ func newUnixListener(sockPath string) (net.Listener, error) { return sock, nil } -func New(appConf *config.AppConfig, dexSrv *dex_server.Server, logger *logrus.Logger) (*Server, error) { +func New(appConf *config.AppConfig, oidcHandler *op.Provider, st *storage.Storage, logger *zap.SugaredLogger) (*Server, error) { var listener net.Listener var addr string var err error @@ -64,10 +68,32 @@ func New(appConf *config.AppConfig, dexSrv *dex_server.Server, logger *logrus.Lo } controllers := map[string]http.Handler{ - ui.StaticRoute: middlewares.WithLogger(ui.NewStaticController(appConf.StaticDir), logger), - "/": middlewares.WithLogger(ui.NewIndexController(logger, dexSrv, appConf.StaticDir), logger), + ui.StaticRoute: middlewares.WithLogger(ui.NewStaticController(appConf.StaticDir), logger), + auth.ApprovalRoute: middlewares.WithLogger(auth.NewApprovalController(logger, st.LocalStorage, appConf.StaticDir), logger), + "/": middlewares.WithLogger(ui.NewIndexController(logger, oidcHandler, appConf.StaticDir), logger), } + userInfoHandler := auth.NewAuthCallbackController(logger, st) + loginHandlers := map[uuid.UUID]http.Handler{} + callbackHandlers := map[uuid.UUID]http.Handler{} + + backends, err := st.LocalStorage.BackendStorage().GetAllBackends(context.Background()) + if err != nil { + return nil, fmt.Errorf("failed to get list of backends from storage: %w", err) + } + for _, b := range backends { + provider, err := rp.NewRelyingPartyOIDC(context.Background(), b.Config.Issuer, b.Config.ClientID, b.Config.ClientSecret, b.Config.RedirectURI, b.Config.Scopes) + if err != nil { + return nil, fmt.Errorf("failed to create connector for backend %s: %w", b.Name, err) + } + + loginHandlers[b.ID] = middlewares.WithLogger(auth.NewAuthRedirectController(logger, provider, st), logger) + callbackHandlers[b.ID] = middlewares.WithLogger(rp.CodeExchangeHandler(rp.UserinfoCallback(userInfoHandler.HandleUserInfoCallback), provider), logger) + } + + controllers[auth.AuthRedirectRoute] = middlewares.WithLogger(auth.NewAuthDispatchController(logger, st, loginHandlers), logger) + controllers[auth.AuthCallbackRoute] = middlewares.WithLogger(auth.NewCallbackDispatchController(logger, st, callbackHandlers), logger) + m := http.NewServeMux() return &Server{ diff --git a/polyculeconnect/services/app/app.go b/polyculeconnect/services/app/app.go deleted file mode 100644 index 5d9095d..0000000 --- a/polyculeconnect/services/app/app.go +++ /dev/null @@ -1,34 +0,0 @@ -package app - -import "github.com/dexidp/dex/storage" - -type Service interface { - ListApps() ([]storage.Client, error) - GetApp(id string) (storage.Client, error) - AddApp(config storage.Client) error - RemoveApp(id string) error -} - -type concreteAppService struct { - s storage.Storage -} - -func (cas *concreteAppService) ListApps() ([]storage.Client, error) { - return cas.s.ListClients() -} - -func (cas *concreteAppService) GetApp(id string) (storage.Client, error) { - return cas.s.GetClient(id) -} - -func (cas *concreteAppService) AddApp(config storage.Client) error { - return cas.s.CreateClient(config) -} - -func (cas *concreteAppService) RemoveApp(id string) error { - return cas.s.DeleteClient(id) -} - -func New(s storage.Storage) Service { - return &concreteAppService{s} -} diff --git a/polyculeconnect/services/app/app_test.go b/polyculeconnect/services/app/app_test.go deleted file mode 100644 index 9e88d52..0000000 --- a/polyculeconnect/services/app/app_test.go +++ /dev/null @@ -1 +0,0 @@ -package app_test diff --git a/polyculeconnect/services/backend/backend.go b/polyculeconnect/services/backend/backend.go deleted file mode 100644 index 9f9d6f5..0000000 --- a/polyculeconnect/services/backend/backend.go +++ /dev/null @@ -1,123 +0,0 @@ -package backend - -import ( - "encoding/json" - "errors" - "fmt" - - "git.faercol.me/faercol/polyculeconnect/polyculeconnect/connector" - "github.com/dexidp/dex/connector/oidc" - "github.com/dexidp/dex/storage" -) - -var ErrUnsupportedType = errors.New("unsupported connector type") - -type BackendConfig struct { - ID string - Name string - Issuer string - ClientID string - ClientSecret string - RedirectURI string -} - -func (bc *BackendConfig) OIDC() oidc.Config { - return oidc.Config{ - Issuer: bc.Issuer, - ClientID: bc.ClientID, - ClientSecret: bc.ClientSecret, - RedirectURI: bc.RedirectURI, - } -} - -func (bc *BackendConfig) Storage() (storage.Connector, error) { - oidcJSON, err := json.Marshal(bc.OIDC()) - if err != nil { - return storage.Connector{}, fmt.Errorf("failed to serialize oidc config: %w", err) - } - - return storage.Connector{ - ID: bc.ID, - Type: "oidc", - Name: bc.Name, - Config: oidcJSON, - }, nil -} - -func (bc *BackendConfig) FromConnector(connector storage.Connector) error { - var oidc oidc.Config - if connector.Type != "oidc" { - return ErrUnsupportedType - } - if err := json.Unmarshal(connector.Config, &oidc); err != nil { - return fmt.Errorf("invalid OIDC config: %w", err) - } - bc.ID = connector.ID - bc.Name = connector.Name - bc.ClientID = oidc.ClientID - bc.ClientSecret = oidc.ClientSecret - bc.Issuer = oidc.Issuer - bc.RedirectURI = oidc.RedirectURI - return nil -} - -type Service interface { - ListBackends() ([]BackendConfig, error) - GetBackend(id string) (BackendConfig, error) - AddBackend(config BackendConfig) error - RemoveBackend(id string) error -} - -type concreteBackendService struct { - s storage.Storage -} - -func (cbs *concreteBackendService) ListBackends() ([]BackendConfig, error) { - connectors, err := cbs.s.ListConnectors() - if err != nil { - return nil, fmt.Errorf("failed to get connectors from storage: %w", err) - } - var res []BackendConfig - for _, c := range connectors { - - // We know that this type is special, we don't want to use it at all here - if c.Type == connector.TypeRefuseAll { - continue - } - - var b BackendConfig - if err := b.FromConnector(c); err != nil { - return res, err - } - res = append(res, b) - } - return res, nil -} - -func (cbs *concreteBackendService) GetBackend(connectorID string) (BackendConfig, error) { - c, err := cbs.s.GetConnector(connectorID) - if err != nil { - return BackendConfig{}, fmt.Errorf("failed to get connector from storage: %w", err) - } - var res BackendConfig - if err := res.FromConnector(c); err != nil { - return BackendConfig{}, err - } - return res, nil -} - -func (cbs *concreteBackendService) AddBackend(config BackendConfig) error { - storageConf, err := config.Storage() - if err != nil { - return fmt.Errorf("failed to create storage configuration: %w", err) - } - return cbs.s.CreateConnector(storageConf) -} - -func (cbs *concreteBackendService) RemoveBackend(connectorID string) error { - return cbs.s.DeleteConnector(connectorID) -} - -func New(s storage.Storage) Service { - return &concreteBackendService{s} -} diff --git a/polyculeconnect/services/backend/backend_test.go b/polyculeconnect/services/backend/backend_test.go deleted file mode 100644 index 4fde675..0000000 --- a/polyculeconnect/services/backend/backend_test.go +++ /dev/null @@ -1,215 +0,0 @@ -package backend_test - -import ( - "encoding/json" - "fmt" - "testing" - - "git.faercol.me/faercol/polyculeconnect/polyculeconnect/connector" - "git.faercol.me/faercol/polyculeconnect/polyculeconnect/services/backend" - "github.com/dexidp/dex/storage" - "github.com/dexidp/dex/storage/memory" - logt "github.com/sirupsen/logrus/hooks/test" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -const ( - testDomain string = "https://test.domain.com" - testClientID string = "this_is_an_id" - testClientSecret string = "this_is_a_secret" - testRedirectURI string = "http://127.0.0.1:5000/callback" - testConnectorName string = "Test connector" -) - -func generateConnector(id string) storage.Connector { - confJson := fmt.Sprintf(`{ - "issuer": "%s", - "clientID": "%s", - "clientSecret": "%s", - "redirectURI": "%s" -}`, testDomain, testClientID, testClientSecret, testRedirectURI) - storageConfig := storage.Connector{ - ID: id, - Name: testConnectorName, - Type: "oidc", - Config: []byte(confJson), - } - return storageConfig -} - -func generateConfig(id string) backend.BackendConfig { - return backend.BackendConfig{ - ID: id, - Name: testConnectorName, - Issuer: testDomain, - ClientID: testClientID, - ClientSecret: testClientSecret, - RedirectURI: testRedirectURI, - } -} - -func checkStrInMap(t *testing.T, vals map[string]interface{}, key, expected string) { - rawVal, ok := vals[key] - require.Truef(t, ok, "missing key %s", key) - strVal, ok := rawVal.(string) - require.Truef(t, ok, "invalid string format %v", rawVal) - assert.Equal(t, expected, strVal, "unexpected value") -} - -func initStorage(t *testing.T) storage.Storage { - logger, _ := logt.NewNullLogger() - s := memory.New(logger) - - require.NoError(t, s.CreateConnector(connector.RefuseAllConnectorConfig)) - require.NoError(t, s.CreateConnector(generateConnector("test0"))) - require.NoError(t, s.CreateConnector(generateConnector("test1"))) - - return s -} - -func TestBackendConfigFromConnector(t *testing.T) { - connector := generateConnector("test") - var bc backend.BackendConfig - require.NoError(t, bc.FromConnector(connector)) - - assert.Equal(t, testDomain, bc.Issuer) - assert.Equal(t, testClientID, bc.ClientID) - assert.Equal(t, testClientSecret, bc.ClientSecret) - assert.Equal(t, testRedirectURI, bc.RedirectURI) - assert.Equal(t, testConnectorName, bc.Name) - assert.Equal(t, "test", bc.ID) -} - -func TestBackendConfigInvalidType(t *testing.T) { - connector := generateConnector("test") - connector.Type = "test" - var bc backend.BackendConfig - assert.ErrorIs(t, bc.FromConnector(connector), backend.ErrUnsupportedType) -} - -func TestBackendConfigInvalidOIDCConfig(t *testing.T) { - connector := generateConnector("test") - connector.Config = []byte("toto") - var bc backend.BackendConfig - assert.ErrorContains(t, bc.FromConnector(connector), "invalid OIDC config") -} - -func TestOIDCConfigFromBackendConfig(t *testing.T) { - conf := generateConfig("test") - oidcConf := conf.OIDC() - - assert.Equal(t, testDomain, oidcConf.Issuer) - assert.Equal(t, testClientID, oidcConf.ClientID) - assert.Equal(t, testClientSecret, oidcConf.ClientSecret) - assert.Equal(t, testRedirectURI, oidcConf.RedirectURI) -} - -func TestConnectorConfigFromBackendConfig(t *testing.T) { - conf := generateConfig("test") - con, err := conf.Storage() - require.NoError(t, err) - - // The OIDC config is stored as JSON data, we just want the raw keys here - var oidcConf map[string]interface{} - require.NoError(t, json.Unmarshal(con.Config, &oidcConf)) - - assert.Equal(t, "oidc", con.Type) - assert.Equal(t, "test", con.ID) - assert.Equal(t, testConnectorName, con.Name) - checkStrInMap(t, oidcConf, "issuer", testDomain) - checkStrInMap(t, oidcConf, "clientID", testClientID) - checkStrInMap(t, oidcConf, "clientSecret", testClientSecret) - checkStrInMap(t, oidcConf, "redirectURI", testRedirectURI) -} - -func TestListBackendsEmpty(t *testing.T) { - logger, _ := logt.NewNullLogger() - s := memory.New(logger) - - // add the default refuse all connector, it should not be visible in the list - require.NoError(t, s.CreateConnector(connector.RefuseAllConnectorConfig)) - srv := backend.New(s) - - backends, err := srv.ListBackends() // empty list, and no error - require.NoError(t, err) - require.Len(t, backends, 0) -} - -func TestListBackendsNotEmpty(t *testing.T) { - s := initStorage(t) - srv := backend.New(s) - - backends, err := srv.ListBackends() // empty list, and no error - expectedIds := []string{"test0", "test1"} - require.NoError(t, err) - assert.Len(t, backends, 2) - for _, c := range backends { - assert.Contains(t, expectedIds, c.ID) - } -} - -func TestGetBackend(t *testing.T) { - s := initStorage(t) - srv := backend.New(s) - - t.Run("OK", func(t *testing.T) { - conf, err := srv.GetBackend("test0") - require.NoError(t, err) - assert.Equal(t, testDomain, conf.Issuer) - assert.Equal(t, testClientID, conf.ClientID) - assert.Equal(t, testClientSecret, conf.ClientSecret) - assert.Equal(t, testRedirectURI, conf.RedirectURI) - assert.Equal(t, testConnectorName, conf.Name) - assert.Equal(t, "test0", conf.ID) - }) - - t.Run("Not exist", func(t *testing.T) { - _, err := srv.GetBackend("toto") - assert.ErrorIs(t, err, storage.ErrNotFound) - }) - - t.Run("Invalid type", func(t *testing.T) { - _, err := srv.GetBackend("null") // null has a RefuseAll type, which is unsupported here - assert.ErrorIs(t, err, backend.ErrUnsupportedType) - }) -} - -func TestAddBackend(t *testing.T) { - s := initStorage(t) - srv := backend.New(s) - - t.Run("OK", func(t *testing.T) { - conf := generateConfig("test_add") - require.NoError(t, srv.AddBackend(conf)) - - var parsedConf backend.BackendConfig - storageConf, err := s.GetConnector("test_add") - require.NoError(t, err) - require.NoError(t, parsedConf.FromConnector(storageConf)) - assert.Equal(t, conf, parsedConf) - }) - - t.Run("Already exists", func(t *testing.T) { - require.ErrorIs(t, srv.AddBackend(generateConfig("test0")), storage.ErrAlreadyExists) - }) -} - -func TestRemoveBackend(t *testing.T) { - s := initStorage(t) - srv := backend.New(s) - - t.Run("OK", func(t *testing.T) { - require.NoError(t, srv.AddBackend(generateConfig("to_remove"))) - _, err := s.GetConnector("to_remove") - require.NoError(t, err) // no error means it's present - - require.NoError(t, srv.RemoveBackend("to_remove")) - _, err = s.GetConnector("to_remove") - assert.ErrorIs(t, err, storage.ErrNotFound) // means it's been deleted - }) - - t.Run("No present", func(t *testing.T) { - require.ErrorIs(t, srv.RemoveBackend("toto"), storage.ErrNotFound) - }) -} diff --git a/polyculeconnect/services/connector.go b/polyculeconnect/services/connector.go deleted file mode 100644 index 90cc6a0..0000000 --- a/polyculeconnect/services/connector.go +++ /dev/null @@ -1,29 +0,0 @@ -package services - -import ( - "encoding/json" - "fmt" - - "git.faercol.me/faercol/polyculeconnect/polyculeconnect/config" - dex_server "github.com/dexidp/dex/server" - "github.com/dexidp/dex/storage" -) - -func CreateConnector(backend *config.BackendConfig, dexConf *dex_server.Config, connectorIDs []string) error { - for _, id := range connectorIDs { - if id == backend.ID { - return nil - } - } - - backendConfJson, err := json.Marshal(backend.Config) - if err != nil { - return fmt.Errorf("failed to serialize oidc config for backend %q: %s", backend.Name, err.Error()) - } - return dexConf.Storage.CreateConnector(storage.Connector{ - ID: backend.ID, - Name: backend.Name, - Type: string(backend.Type), - Config: backendConfJson, - }) -} diff --git a/polyculeconnect/services/storage.go b/polyculeconnect/services/storage.go deleted file mode 100644 index e85d1a6..0000000 --- a/polyculeconnect/services/storage.go +++ /dev/null @@ -1,40 +0,0 @@ -package services - -import ( - "errors" - "fmt" - - "git.faercol.me/faercol/polyculeconnect/polyculeconnect/config" - "git.faercol.me/faercol/polyculeconnect/polyculeconnect/connector" - "git.faercol.me/faercol/polyculeconnect/polyculeconnect/logger" - "github.com/dexidp/dex/storage" - "github.com/dexidp/dex/storage/memory" - "github.com/dexidp/dex/storage/sql" -) - -func InitStorage(conf *config.AppConfig) (storage.Storage, error) { - var storageType storage.Storage - var err error - switch conf.StorageType { - case "memory": - storageType = memory.New(logger.L) - case "sqlite": - sqlconfig := sql.SQLite3{ - File: conf.StorageConfig.File, - } - storageType, err = sqlconfig.Open(logger.L) - if err != nil { - logger.L.Fatalf("Failed to initialize sqlite backend: %s", err.Error()) - } - default: - return storageType, fmt.Errorf("unsupported storage backend type: %s", conf.StorageType) - } - return storageType, nil -} - -func AddDefaultBackend(s storage.Storage) error { - if err := s.CreateConnector(connector.RefuseAllConnectorConfig); err != nil && !errors.Is(err, storage.ErrAlreadyExists) { - return fmt.Errorf("failed to add default backend: %w", err) - } - return nil -} diff --git a/polyculeconnect/static/scripts/index.js b/polyculeconnect/static/scripts/index.js index 1fdc2f3..670d679 100644 --- a/polyculeconnect/static/scripts/index.js +++ b/polyculeconnect/static/scripts/index.js @@ -42,6 +42,6 @@ function goBackToLogin() { window.location.href = nextURL; } -if (window.location.pathname === "/auth" && !urlParams.has(connectorIDParam)) { +if (window.location.pathname === "/authorize" && !urlParams.has(connectorIDParam)) { handleLoginPage(); }