diff --git a/Dockerfile b/Dockerfile index 6f55040..a93fb2f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -9,6 +9,9 @@ ARG TARGETARCH ENV GO111MODULE=on \ CGO_ENABLED=0 +RUN apk --no-cache add ca-certificates \ + && update-ca-certificates + # Set the working directory inside the container WORKDIR /app diff --git a/Dockerfile-local b/Dockerfile-local index 6ecb65a..f7a4175 100644 --- a/Dockerfile-local +++ b/Dockerfile-local @@ -11,6 +11,11 @@ ENV POSTGRES_DB=fern ENV POSTGRES_USER=fern ENV POSTGRES_PASSWORD=fern +RUN apt-get update && apt-get install -y \ + ca-certificates \ + && rm -rf /var/lib/apt/lists/* + + # Initialize the database and create the user and database (adjust commands as needed) RUN service postgresql start && \ su postgres -c "createuser --superuser $POSTGRES_USER" && \ diff --git a/config/config.go b/config/config.go index 2101541..b4656b0 100644 --- a/config/config.go +++ b/config/config.go @@ -5,6 +5,7 @@ import ( "embed" "fmt" "os" + "strconv" "github.com/spf13/viper" ) @@ -12,6 +13,7 @@ import ( type config struct { Db *dbConfig Server *serverConfig + Auth *authConfig Header string } @@ -31,6 +33,13 @@ type serverConfig struct { Port string `mapstructure:"port"` } +type authConfig struct { + JSONWebKeysEndpoint string `mapstructure:"json-web-keys-endpoint"` + TokenEndpoint string `mapstructure:"token-endpoint"` + Enabled bool `mapstructure:"enabled"` + ScopeClaimName string `mapstructure:"scope-claim-name"` +} + var configuration *config //go:embed config.yaml @@ -70,6 +79,15 @@ func LoadConfig() (*config, error) { if os.Getenv("FERN_DATABASE") != "" { configuration.Db.Database = os.Getenv("FERN_DATABASE") } + if os.Getenv("AUTH_JSON_WEB_KEYS_ENDPOINT") != "" { + configuration.Auth.JSONWebKeysEndpoint = os.Getenv("AUTH_JSON_WEB_KEYS_ENDPOINT") + } + if os.Getenv("AUTH_ENABLED") != "" { + configuration.Auth.Enabled, _ = strconv.ParseBool(os.Getenv("AUTH_ENABLED")) + } + if os.Getenv("SCOPE_CLAIM_NAME") != "" { + configuration.Auth.ScopeClaimName = os.Getenv("SCOPE_CLAIM_NAME") + } if os.Getenv("FERN_HEADER_NAME") != "" { configuration.Header = os.Getenv("FERN_HEADER_NAME") } @@ -85,6 +103,10 @@ func GetServer() *serverConfig { return configuration.Server } +func GetAuth() *authConfig { + return configuration.Auth +} + func GetHeaderName() string { return configuration.Header } diff --git a/config/config.yaml b/config/config.yaml index 0ac848f..3cd3a80 100644 --- a/config/config.yaml +++ b/config/config.yaml @@ -10,4 +10,8 @@ db: max-idle-conns: 10 server: port: :8080 +auth: + json-web-keys-endpoint: "" + enabled: "true" + scope-claim-name: "scope" header: "Fern Acceptance Test Report" \ No newline at end of file diff --git a/config/config_test.go b/config/config_test.go index 33bc05d..e66faf4 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -26,6 +26,7 @@ var _ = Describe("When LoadConfig is invoked", func() { appConfig, err := config.LoadConfig() Expect(err).NotTo(HaveOccurred()) + Expect(appConfig.Auth.JSONWebKeysEndpoint).To(Equal("")) Expect(appConfig.Server.Port).To(Equal(":8080")) Expect(appConfig.Db.Driver).To(Equal("postgres")) Expect(appConfig.Db.Host).To(Equal("localhost")) @@ -59,6 +60,9 @@ var _ = Describe("When LoadConfig is invoked", func() { It("should override configuration with environment variables", func() { + os.Setenv("AUTH_JSON_WEB_KEYS_ENDPOINT", "https://test-idp-base-url.com/oauth2/abc123/v1/keys") + os.Setenv("AUTH_ENABLED", "false") + os.Setenv("SCOPE_CLAIM_NAME", "fern_scope") os.Setenv("FERN_USERNAME", "fern") os.Setenv("FERN_PASSWORD", "fern") os.Setenv("FERN_HOST", "localhost") @@ -76,6 +80,10 @@ var _ = Describe("When LoadConfig is invoked", func() { Expect(result.Db.Host).To(Equal("localhost")) Expect(result.Db.Port).To(Equal("5432")) Expect(result.Db.Database).To(Equal("fern")) + Expect(result.Auth.JSONWebKeysEndpoint).To(Equal("https://test-idp-base-url.com/oauth2/abc123/v1/keys")) + Expect(result.Auth.Enabled).To(Equal(false)) + Expect(result.Auth.ScopeClaimName).To(Equal("fern_scope")) + Expect(result.Header).To(Equal("Custom Fern Report Header")) Expect(result.Header).To(Equal("Custom Fern Report Header")) }) diff --git a/go.mod b/go.mod index 9264f5a..af7ed03 100644 --- a/go.mod +++ b/go.mod @@ -7,6 +7,9 @@ require ( github.com/gin-contrib/cors v1.7.1 github.com/gin-gonic/gin v1.9.1 github.com/golang-migrate/migrate/v4 v4.17.1 + github.com/lestrrat-go/iter v1.0.2 + github.com/lestrrat-go/jwx v1.2.29 + github.com/lestrrat-go/jwx/v2 v2.0.21 github.com/markbates/pkger v0.17.1 github.com/onsi/ginkgo/v2 v2.13.2 github.com/onsi/gomega v1.30.0 @@ -19,11 +22,10 @@ require ( require ( github.com/bytedance/sonic v1.11.6 // indirect github.com/bytedance/sonic/loader v0.1.1 // indirect - github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d // indirect - github.com/chenzhuoyu/iasm v0.9.1 // indirect github.com/cloudwego/base64x v0.1.4 // indirect github.com/cloudwego/iasm v0.2.0 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect + github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/gabriel-vasile/mimetype v1.4.3 // indirect github.com/gin-contrib/sse v0.1.0 // indirect @@ -48,6 +50,11 @@ require ( github.com/json-iterator/go v1.1.12 // indirect github.com/klauspost/cpuid/v2 v2.2.7 // indirect github.com/leodido/go-urn v1.4.0 // indirect + github.com/lestrrat-go/backoff/v2 v2.0.8 // indirect + github.com/lestrrat-go/blackmagic v1.0.2 // indirect + github.com/lestrrat-go/httpcc v1.0.1 // indirect + github.com/lestrrat-go/httprc v1.0.5 // indirect + github.com/lestrrat-go/option v1.0.1 // indirect github.com/lib/pq v1.10.9 // indirect github.com/magiconair/properties v1.8.7 // indirect github.com/mattn/go-isatty v0.0.20 // indirect @@ -55,10 +62,12 @@ require ( github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/pelletier/go-toml/v2 v2.2.1 // indirect + github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/rogpeppe/go-internal v1.11.0 // indirect github.com/sagikazarmark/locafero v0.4.0 // indirect github.com/sagikazarmark/slog-shim v0.1.0 // indirect + github.com/segmentio/asm v1.2.0 // indirect github.com/sourcegraph/conc v0.3.0 // indirect github.com/spf13/afero v1.11.0 // indirect github.com/spf13/cast v1.6.0 // indirect diff --git a/go.sum b/go.sum index 4181672..98d4b23 100644 --- a/go.sum +++ b/go.sum @@ -4,22 +4,10 @@ github.com/DATA-DOG/go-sqlmock v1.5.2 h1:OcvFkGmslmlZibjAjaHm3L//6LiuBgolP7Oputl github.com/DATA-DOG/go-sqlmock v1.5.2/go.mod h1:88MAG/4G7SMwSE3CeA0ZKzrT5CiOU3OJ+JlNzwDqpNU= github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= -github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM= -github.com/bytedance/sonic v1.10.0-rc/go.mod h1:ElCzW+ufi8qKqNW0FY314xriJhyJhuoJ3gFZdAHF7NM= -github.com/bytedance/sonic v1.10.2 h1:GQebETVBxYB7JGWJtLBi07OVzWwt+8dWA00gEVW2ZFE= -github.com/bytedance/sonic v1.10.2/go.mod h1:iZcSUejdk5aukTND/Eu/ivjQuEL0Cu9/rf50Hi0u/g4= github.com/bytedance/sonic v1.11.6 h1:oUp34TzMlL+OY1OUWxHqsdkgC/Zfc85zGqw9siXjrc0= github.com/bytedance/sonic v1.11.6/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4= github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM= github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= -github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY= -github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk= -github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d h1:77cEq6EriyTZ0g/qfRdp61a3Uu/AWrgIq2s0ClJV1g0= -github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d/go.mod h1:8EPpVsBuRksnlj1mLy4AWzRNQYxauNi62uWcE3to6eA= -github.com/chenzhuoyu/iasm v0.9.0 h1:9fhXjVzq5hUy2gkhhgHl95zG2cEAhw9OSGs8toWWAwo= -github.com/chenzhuoyu/iasm v0.9.0/go.mod h1:Xjy2NpN3h7aUqeqM+woSuuvxmIe6+DDsiNLIrkAmYog= -github.com/chenzhuoyu/iasm v0.9.1 h1:tUHQJXo3NhBqw6s33wkGn9SP3bvrWLdlVIJ3hQBL7P0= -github.com/chenzhuoyu/iasm v0.9.1/go.mod h1:Xjy2NpN3h7aUqeqM+woSuuvxmIe6+DDsiNLIrkAmYog= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= @@ -27,19 +15,19 @@ github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/ github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w= github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg= github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY= -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/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/dhui/dktest v0.3.16 h1:i6gq2YQEtcrjKbeJpBkWjE8MmLZPYllcjOFbTZuPDnw= -github.com/dhui/dktest v0.3.16/go.mod h1:gYaA3LRmM8Z4vJl2MA0THIigJoZrwOansEOsp+kqxp0= +github.com/decred/dcrd/crypto/blake256 v1.0.1/go.mod h1:2OfgNZ5wDpcsFmHmCK5gZTPcCXqlm2ArzUIkw9czNJo= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 h1:8UrgZ3GkP4i/CLijOJx79Yu+etlyjdBU4sfcs2WYQMs= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0= github.com/dhui/dktest v0.4.1 h1:/w+IWuDXVymg3IrRJCHHOkMK10m9aNVMOyD0X12YVTg= +github.com/dhui/dktest v0.4.1/go.mod h1:DdOqcUpL7vgyP4GlF3X3w7HbSlz8cEQzwewPveYEQbA= github.com/docker/distribution v2.8.2+incompatible h1:T3de5rq0dB1j30rp0sA2rER+m322EBzniBPB6ZIzuh8= github.com/docker/distribution v2.8.2+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= -github.com/docker/docker v20.10.24+incompatible h1:Ugvxm7a8+Gz6vqQYQQ2W7GYq5EUPaAiuPgIfVyI3dYE= -github.com/docker/docker v20.10.24+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker v24.0.9+incompatible h1:HPGzNmwfLZWdxHqK9/II92pyi1EpYKsAqcl4G0Of9v0= +github.com/docker/docker v24.0.9+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= @@ -50,50 +38,35 @@ github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nos github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0= github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk= -github.com/gin-contrib/cors v1.4.0 h1:oJ6gwtUl3lqV0WEIwM/LxPF1QZ5qe2lGWdY2+bz7y0g= -github.com/gin-contrib/cors v1.4.0/go.mod h1:bs9pNM0x/UsmHPBWT2xZz9ROh8xYjYkiURUfmBoMlcs= github.com/gin-contrib/cors v1.7.1 h1:s9SIppU/rk8enVvkzwiC2VK3UZ/0NNGsWfUKvV55rqs= github.com/gin-contrib/cors v1.7.1/go.mod h1:n/Zj7B4xyrgk/cX1WCX2dkzFfaNm/xJb6oIUk7WTtps= github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= -github.com/gin-gonic/gin v1.8.1/go.mod h1:ji8BvRH1azfM+SYow9zQ6SZMvR8qOMZHmsCuWR9tTTk= github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg= github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU= github.com/go-logr/logr v1.3.0 h1:2y3SDp0ZXuc6/cjLSZ+Q3ir+QB9T/iG5yYRXqsagWSY= github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= -github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= -github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs= github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= -github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA= github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= -github.com/go-playground/validator/v10 v10.10.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos= -github.com/go-playground/validator/v10 v10.15.5 h1:LEBecTWb/1j5TNY1YYG2RcOUN3R7NLylN+x8TTueE24= -github.com/go-playground/validator/v10 v10.15.5/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= github.com/go-playground/validator/v10 v10.19.0 h1:ol+5Fu+cSq9JD7SoSqe04GMI92cbn0+wvQ3bZ8b/AU4= github.com/go-playground/validator/v10 v10.19.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= -github.com/gobuffalo/here v0.6.0 h1:hYrd0a6gDmWxBM4TnrGw8mQg24iSVoIkHEk7FodQcBI= github.com/gobuffalo/here v0.6.0/go.mod h1:wAG085dHOYqUpf+Ap+WOdrPTp5IYcDAs/x7PLa8Y5fM= github.com/gobuffalo/here v0.6.7 h1:hpfhh+kt2y9JLDfhYUxxCRxQol540jsVfKUZzjlbp8o= github.com/gobuffalo/here v0.6.7/go.mod h1:vuCfanjqckTuRlqAitJz6QC4ABNnS27wLb816UhsPcc= -github.com/goccy/go-json v0.9.7/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -github.com/golang-migrate/migrate/v4 v4.16.2 h1:8coYbMKUyInrFk1lfGfRovTLAW7PhWp8qQDT2iKfuoA= -github.com/golang-migrate/migrate/v4 v4.16.2/go.mod h1:pfcJX4nPHaVdc5nmdCikFBWtm+UBpiZjRNNsyBbp0/o= 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/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.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 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.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= @@ -109,12 +82,8 @@ github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= -github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk= -github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= github.com/jackc/pgservicefile v0.0.0-20231201235250-de7065d80cb9 h1:L0QtFUgDarD7Fpv9jeVMgy/+Ec0mtnmYuImjTz6dtDA= github.com/jackc/pgservicefile v0.0.0-20231201235250-de7065d80cb9/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= -github.com/jackc/pgx/v5 v5.5.4 h1:Xp2aQS8uXButQdnCMWNmvx6UysWQQC+u1EoizjguY+8= -github.com/jackc/pgx/v5 v5.5.4/go.mod h1:ez9gk+OAat140fv9ErkZDYFWmXLfV+++K0uAOiwgm1A= github.com/jackc/pgx/v5 v5.5.5 h1:amBjrZVmksIdNjxGW/IiIMzxMKZFelXbUoPNb+8sjQw= github.com/jackc/pgx/v5 v5.5.5/go.mod h1:ez9gk+OAat140fv9ErkZDYFWmXLfV+++K0uAOiwgm1A= github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk= @@ -127,36 +96,41 @@ github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnr github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/kisielk/sqlstruct v0.0.0-20201105191214-5f3e10d3ab46/go.mod h1:yyMNCyc/Ib3bDTKd379tNMpB/7/H5TjM2Y9QJ5THLbE= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= -github.com/klauspost/cpuid/v2 v2.2.5 h1:0E5MSMDEoAulmXNFquVs//DdoomxaoTY1kUhbc/qbZg= -github.com/klauspost/cpuid/v2 v2.2.5/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM= github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= 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/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= 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/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY= -github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q= -github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4= github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= -github.com/lib/pq v1.10.2 h1:AqzbZs4ZoCBp+GtejcpCpcxM3zlSMx29dXbUSeVtJb8= -github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/lestrrat-go/backoff/v2 v2.0.8 h1:oNb5E5isby2kiro9AgdHLv5N5tint1AnDVVf2E2un5A= +github.com/lestrrat-go/backoff/v2 v2.0.8/go.mod h1:rHP/q/r9aT27n24JQLa7JhSQZCKBBOiM/uP402WwN8Y= +github.com/lestrrat-go/blackmagic v1.0.2 h1:Cg2gVSc9h7sz9NOByczrbUvLopQmXrfFx//N+AkAr5k= +github.com/lestrrat-go/blackmagic v1.0.2/go.mod h1:UrEqBzIR2U6CnzVyUtfM6oZNMt/7O7Vohk2J0OGSAtU= +github.com/lestrrat-go/httpcc v1.0.1 h1:ydWCStUeJLkpYyjLDHihupbn2tYmZ7m22BGkcvZZrIE= +github.com/lestrrat-go/httpcc v1.0.1/go.mod h1:qiltp3Mt56+55GPVCbTdM9MlqhvzyuL6W/NMDA8vA5E= +github.com/lestrrat-go/httprc v1.0.5 h1:bsTfiH8xaKOJPrg1R+E3iE/AWZr/x0Phj9PBTG/OLUk= +github.com/lestrrat-go/httprc v1.0.5/go.mod h1:mwwz3JMTPBjHUkkDv/IGJ39aALInZLrhBp0X7KGUZlo= +github.com/lestrrat-go/iter v1.0.2 h1:gMXo1q4c2pHmC3dn8LzRhJfP1ceCbgSiT9lUydIzltI= +github.com/lestrrat-go/iter v1.0.2/go.mod h1:Momfcq3AnRlRjI5b5O8/G5/BvpzrhoFTZcn06fEOPt4= +github.com/lestrrat-go/jwx v1.2.29 h1:QT0utmUJ4/12rmsVQrJ3u55bycPkKqGYuGT4tyRhxSQ= +github.com/lestrrat-go/jwx v1.2.29/go.mod h1:hU8k2l6WF0ncx20uQdOmik/Gjg6E3/wIRtXSNFeZuB8= +github.com/lestrrat-go/jwx/v2 v2.0.21 h1:jAPKupy4uHgrHFEdjVjNkUgoBKtVDgrQPB/h55FHrR0= +github.com/lestrrat-go/jwx/v2 v2.0.21/go.mod h1:09mLW8zto6bWL9GbwnqAli+ArLf+5M33QLQPDggkUWM= +github.com/lestrrat-go/option v1.0.0/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I= +github.com/lestrrat-go/option v1.0.1 h1:oAzP2fvZGQKWkvHa1/SAcFolBEca1oN+mQ7eooNBEYU= +github.com/lestrrat-go/option v1.0.1/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I= github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= -github.com/markbates/pkger v0.15.1 h1:3MPelV53RnGSW07izx5xGxl4e/sdRD6zqseIk0rMASY= -github.com/markbates/pkger v0.15.1/go.mod h1:0JoVlrol20BSywW79rN3kdFFsE5xYM+rSCQDXbLhiuI= github.com/markbates/pkger v0.17.1 h1:/MKEtWqtc0mZvu9OinB9UzVN9iYCwLWuyUv4Bw+PCno= github.com/markbates/pkger v0.17.1/go.mod h1:0JoVlrol20BSywW79rN3kdFFsE5xYM+rSCQDXbLhiuI= -github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= @@ -178,27 +152,21 @@ github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8 github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.0.2 h1:9yCKha/T5XdGtO0q9Q9a6T5NUCsTn/DrBg0D7ufOcFM= github.com/opencontainers/image-spec v1.0.2/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= -github.com/pelletier/go-toml/v2 v2.0.1/go.mod h1:r9LEWfGN8R5k0VXJ+0BkIe7MYkRdwZOjgMj2KwnJFUo= -github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4= -github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= github.com/pelletier/go-toml/v2 v2.2.1 h1:9TA9+T8+8CUCO2+WYnDLCgrYi9+omqKXyjDtosvtEhg= github.com/pelletier/go-toml/v2 v2.2.1/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= -github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= 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/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -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.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ= github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4= github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= -github.com/sirupsen/logrus v1.9.2 h1:oxx1eChJGI6Uks2ZC4W1zpLlVgqB8ner4EuQwV4Ik1Y= -github.com/sirupsen/logrus v1.9.2/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/segmentio/asm v1.2.0 h1:9BQrFxC+YOHJlTlHGkTrFWf59nbL3XnCoFLTwDCI7ys= +github.com/segmentio/asm v1.2.0/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs= github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= @@ -211,7 +179,6 @@ github.com/spf13/viper v1.18.2 h1:LUXCnvUvSM6FXAsj6nnfc8Q2tp1dIgUfY9Kc8GsSOiQ= github.com/spf13/viper v1.18.2/go.mod h1:EKmWIqdnk5lOcmR72yw6hS+8OPYcwD0jteitLMVB+yk= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 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/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= @@ -222,8 +189,6 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ 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.2/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/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= @@ -231,92 +196,89 @@ github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8 github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= -github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M= -github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY= -github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU= -github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE= github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= -go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= -go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= -go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI= -go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= -golang.org/x/arch v0.5.0 h1:jpGode6huXQxcskEIpOCvrU+tzo81b6+oFLUYXWtH/Y= -golang.org/x/arch v0.5.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= golang.org/x/arch v0.7.0 h1:pskyeJh/3AmoQ8CPE95vxHLqp1G1GfGNXTmcl9NEKTc= golang.org/x/arch v0.7.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= -golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k= -golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= +golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30= golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= -golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g= -golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k= golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f h1:99ci1mjWVBWwJiEKYY6jWa4d2nTQVIEhZIptnrVb1XY= golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f/go.mod h1:/lliqkxwWAhPjf5oSOIJup2XcqJaw8RGS6k3TGEc7GI= -golang.org/x/mod v0.13.0 h1:I/DsJXRlw/8l/0c24sM9yb0T4z9liZTduXvdAWYiysY= -golang.org/x/mod v0.13.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +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/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA= +golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c= -golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +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.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w= golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8= -golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE= -golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +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.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/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-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= -golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 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.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.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= +golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +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.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.14.0 h1:jvNa2pY0M4r62jkRQ6RwEZZyPcymeL9XZMLBbV7U2nc= -golang.org/x/tools v0.14.0/go.mod h1:uYBEerGOWcJyEORxN+Ek8+TT266gXkNlHdJBwexUsBg= +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/tools v0.20.0 h1:hz/CVckiOxybQvFw6h7b/q80NTr9IUQb4s1IIzW7KNY= golang.org/x/tools v0.20.0/go.mod h1:WvitBU7JJf6A4jOdg4S1tviW9bhUxkgeCui/0JHctQg= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= -google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= 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/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -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= -gorm.io/driver/postgres v1.5.4 h1:Iyrp9Meh3GmbSuyIAGyjkN+n9K+GHX9b9MqsTL4EJCo= -gorm.io/driver/postgres v1.5.4/go.mod h1:Bgo89+h0CRcdA33Y6frlaHHVuTdOf87pmyzwW9C/BH0= gorm.io/driver/postgres v1.5.7 h1:8ptbNJTDbEmhdr62uReG5BGkdQyeasu/FZHxI0IMGnM= gorm.io/driver/postgres v1.5.7/go.mod h1:3e019WlBaYI5o5LIdNV+LyxCMNtLOQETBXL2h4chKpA= -gorm.io/gorm v1.25.5 h1:zR9lOiiYf09VNh5Q1gphfyia1JpiClIWG9hQaxB/mls= -gorm.io/gorm v1.25.5/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8= gorm.io/gorm v1.25.10 h1:dQpO+33KalOA+aFYGlK+EfxcI5MbO7EP2yYygwh9h+s= gorm.io/gorm v1.25.10/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8= nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50= diff --git a/main.go b/main.go index c0a5fea..9787e06 100644 --- a/main.go +++ b/main.go @@ -1,12 +1,13 @@ package main import ( - "html/template" - "log" - + "context" "github.com/guidewire/fern-reporter/config" "github.com/guidewire/fern-reporter/pkg/api/routers" + "github.com/guidewire/fern-reporter/pkg/auth" "github.com/guidewire/fern-reporter/pkg/db" + "html/template" + "log" "time" @@ -39,6 +40,14 @@ func initServer() { serverConfig := config.GetServer() gin.SetMode(gin.DebugMode) router := gin.Default() + + if config.GetAuth().Enabled { + checkAuthConfig() + configJWTMiddleware(router) + } else { + log.Println("Auth is disabled, JWT Middleware is not configured.") + } + router.Use(cors.New(cors.Config{ AllowMethods: []string{"GET", "POST"}, AllowHeaders: []string{"Origin", "Content-Length", "Content-Type", "ACCESS_TOKEN"}, @@ -64,6 +73,30 @@ func initServer() { } } +func checkAuthConfig() { + if config.GetAuth().ScopeClaimName == "" { + log.Fatal("Set SCOPE_CLAIM_NAME environment variable or add a default value in config.yaml") + } + if config.GetAuth().JSONWebKeysEndpoint == "" { + log.Fatal("Set AUTH_JSON_WEB_KEYS_ENDPOINT environment variable or add a default value in config.yaml") + } +} + +func configJWTMiddleware(router *gin.Engine) { + authConfig := config.GetAuth() + ctx := context.Background() + + keyFetcher, err := auth.NewDefaultJWKSFetcher(ctx, authConfig.JSONWebKeysEndpoint) + if err != nil { + log.Fatalf("Failed to create JWKS fetcher: %v", err) + } + + jwtValidator := &auth.DefaultJWTValidator{} + + router.Use(auth.JWTMiddleware(authConfig.JSONWebKeysEndpoint, keyFetcher, jwtValidator)) + log.Println("JWT Middleware configured successfully.") +} + func CalculateDuration(start, end time.Time) string { duration := end.Sub(start) return duration.String() // or format as needed diff --git a/pkg/api/handlers/handlers.go b/pkg/api/handlers/handlers.go index 485aaa6..bdf9337 100644 --- a/pkg/api/handlers/handlers.go +++ b/pkg/api/handlers/handlers.go @@ -3,13 +3,12 @@ package handlers import ( "errors" "fmt" + "github.com/guidewire/fern-reporter/pkg/models" "github.com/guidewire/fern-reporter/config" "log" "net/http" "strconv" - "github.com/guidewire/fern-reporter/pkg/models" - "github.com/gin-gonic/gin" "gorm.io/gorm" ) diff --git a/pkg/api/routers/routers.go b/pkg/api/routers/routers.go index 4a9596c..197307b 100644 --- a/pkg/api/routers/routers.go +++ b/pkg/api/routers/routers.go @@ -1,31 +1,61 @@ package routers import ( + "github.com/guidewire/fern-reporter/config" "github.com/guidewire/fern-reporter/pkg/api/handlers" + "github.com/guidewire/fern-reporter/pkg/auth" "github.com/guidewire/fern-reporter/pkg/db" "github.com/gin-gonic/gin" ) +var ( + testRun *gin.RouterGroup +) + func RegisterRouters(router *gin.Engine) { - // router.GET("/", handlers.Home) handler := handlers.NewHandler(db.GetDb()) - api := router.Group("/api") + authEnabled := config.GetAuth().Enabled + + var api *gin.RouterGroup + if authEnabled { + api = router.Group("/api", auth.ScopeMiddleware()) + } else { + api = router.Group("/api") + } + + api.Use() { - testRun := api.Group("/testrun/") + testRun = api.Group("/testrun/") testRun.GET("/", handler.GetTestRunAll) testRun.GET("/:id", handler.GetTestRunByID) testRun.POST("/", handler.CreateTestRun) testRun.PUT("/:id", handler.UpdateTestRun) testRun.DELETE("/:id", handler.DeleteTestRun) } - reports := router.Group("/reports/testruns") + + var reports *gin.RouterGroup + if authEnabled { + reports = router.Group("/reports/testruns", auth.ScopeMiddleware()) + } else { + reports = router.Group("/reports/testruns") + } + + reports.Use() { testRunReport := reports.GET("/", handler.ReportTestRunAll) testRunReport.GET("/:id", handler.ReportTestRunById) } - ping := router.Group("/ping") + + var ping *gin.RouterGroup + if authEnabled { + ping = router.Group("/ping", auth.ScopeMiddleware()) + } else { + ping = router.Group("/ping") + } + + ping.Use() { ping.GET("/", handler.Ping) } diff --git a/pkg/api/routers/routers_suite_test.go b/pkg/api/routers/routers_suite_test.go index ed2c6e9..812d19d 100644 --- a/pkg/api/routers/routers_suite_test.go +++ b/pkg/api/routers/routers_suite_test.go @@ -1,6 +1,7 @@ package routers_test import ( + "github.com/guidewire/fern-reporter/config" "testing" . "github.com/onsi/ginkgo/v2" @@ -11,3 +12,8 @@ func TestRouters(t *testing.T) { RegisterFailHandler(Fail) RunSpecs(t, "Routers Suite") } + +var _ = BeforeSuite(func() { + _, err := config.LoadConfig() + Expect(err).NotTo(HaveOccurred()) +}) diff --git a/pkg/auth/README.md b/pkg/auth/README.md new file mode 100644 index 0000000..3796aa9 --- /dev/null +++ b/pkg/auth/README.md @@ -0,0 +1,34 @@ +# Auth Middleware +This repository provides JWT authentication and scope-based authorization middleware for the Gin web framework. +It leverages the lestrrat-go/jwx library for handling JWT tokens and JSON Web Key Sets (JWKS). + +## Features +- **JWKs Fetching and Caching:** Efficient management of fetching and caching JWKs for JWT validation. +- **JWT Validation Middleware:** Middleware that validates JWTs from the `Authorization` header of incoming HTTP requests using the cached JWKs. +- **Offline Validation:** Since the JWKs are cached, validation can be performed offline. +- **Scope Middleware:** Middleware to check user permissions based on token scopes. + +## Configuration +You can load configuration values using the `config.yaml` or environment variables. + +### Environment Variables +Ensure the following environment variables are set or set a default values in `config.yaml`: +- `SCOPE_CLAIM_NAME`: Name of the claim used for scopes. +- `AUTH_JSON_WEB_KEYS_ENDPOINT`: URL of the JWKS endpoint. + +### Configuration Files +Load necessary configurations using `config.yaml`. + +## Middleware Setup + +### JWT Middleware +- Fetches JWKS from the specified URL. +- Validates the JWT token present in the Authorization header. +- Extracts and validates the scope claim from the token. + +### Scope Middleware +- Checks if the user has the required permissions based on the scope extracted from the JWT token. + +## Usage +To use the middleware, import the package and apply the middleware to your Gin router. +Ensure the necessary environment variables and configurations are set before running the server. \ No newline at end of file diff --git a/pkg/auth/middleware.go b/pkg/auth/middleware.go new file mode 100644 index 0000000..7544a6f --- /dev/null +++ b/pkg/auth/middleware.go @@ -0,0 +1,219 @@ +package auth + +import ( + "bytes" + "context" + "fmt" + "github.com/gin-gonic/gin" + "github.com/guidewire/fern-reporter/config" + "github.com/lestrrat-go/jwx/v2/jwk" + "github.com/lestrrat-go/jwx/v2/jwt" + "golang.org/x/exp/slices" + "io" + "log" + "net/http" + "strings" + "time" +) + +const FP = "fernproject" + +// JWKSFetcher interface for fetching keys from JWKS +type JWKSFetcher interface { + Register(jwksUrl string, options ...jwk.RegisterOption) error + Refresh(ctx context.Context, jwksUrl string) (jwk.Set, error) + Get(ctx context.Context, jwksUrl string) (jwk.Set, error) + FetchKeys(ctx context.Context, jwksUrl string) (jwk.Set, error) +} + +// DefaultJWKSFetcher struct for fetching keys from JWKS +type DefaultJWKSFetcher struct { + cache *jwk.Cache +} + +// NewDefaultJWKSFetcher creates a new JWKSFetcher +func NewDefaultJWKSFetcher(ctx context.Context, jwksUrl string) (*DefaultJWKSFetcher, error) { + cache := jwk.NewCache(ctx) + if err := cache.Register(jwksUrl, jwk.WithMinRefreshInterval(12*time.Hour)); err != nil { + log.Printf("Error registering JWKS URL: %v", err) + return nil, err + } + if _, err := cache.Refresh(ctx, jwksUrl); err != nil { + log.Printf("Error refreshing JWKS cache: %v", err) + return nil, err + } + log.Printf("JWKS cache initialized and refreshed for URL: %s", jwksUrl) + return &DefaultJWKSFetcher{cache: cache}, nil +} + +func (f *DefaultJWKSFetcher) Register(jwksUrl string, options ...jwk.RegisterOption) error { + return f.cache.Register(jwksUrl, options...) +} + +func (f *DefaultJWKSFetcher) Refresh(ctx context.Context, jwksUrl string) (jwk.Set, error) { + return f.cache.Refresh(ctx, jwksUrl) +} + +func (f *DefaultJWKSFetcher) Get(ctx context.Context, jwksUrl string) (jwk.Set, error) { + return f.cache.Get(ctx, jwksUrl) +} + +func (f *DefaultJWKSFetcher) FetchKeys(ctx context.Context, jwksUrl string) (jwk.Set, error) { + return f.Get(ctx, jwksUrl) +} + +// JWTValidator interface for validating JWT tokens +type JWTValidator interface { + ParseAndValidateToken(ctx context.Context, tokenString string, set jwk.Set) (jwt.Token, error) +} + +// DefaultJWTValidator struct for validating JWT tokens +type DefaultJWTValidator struct{} + +func (v *DefaultJWTValidator) ParseAndValidateToken(ctx context.Context, tokenString string, set jwk.Set) (jwt.Token, error) { + return jwt.Parse([]byte(tokenString), jwt.WithKeySet(set), jwt.WithContext(ctx)) +} + +// JWTMiddleware Middleware for handling JWT authentication. +func JWTMiddleware(jwksUrl string, fetcher JWKSFetcher, validator JWTValidator) gin.HandlerFunc { + return func(c *gin.Context) { + ctx := c.Request.Context() + set, err := fetcher.FetchKeys(ctx, jwksUrl) + if err != nil { + log.Printf("Failed to get JWKS: %v", err) + c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"error": "failed to get JWKS"}) + return + } + + authHeader := c.GetHeader("Authorization") + if authHeader == "" { + c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "authorization header missing"}) + return + } + + authHeaderParts := strings.SplitN(authHeader, " ", 2) + if len(authHeaderParts) != 2 || authHeaderParts[0] != "Bearer" { + c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "authorization header format must be Bearer {token}"}) + return + } + + token, err := validator.ParseAndValidateToken(ctx, authHeaderParts[1], set) + if err != nil { + c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "invalid token"}) + return + } + + authConfig := config.GetAuth() + scope, ok := token.PrivateClaims()[authConfig.ScopeClaimName].([]interface{}) + if !ok || len(scope) == 0 { + c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "scope claim is missing or empty"}) + return + } + + c.Set("scope", scope) + c.Next() + } +} + +type RequestBody struct { + Project string `json:"project" binding:"required"` +} + +// ScopeMiddleware Middleware for checking if the user has the necessary scope for the request. +func ScopeMiddleware() gin.HandlerFunc { + permissions := map[string]string{ + "POST": "fern.write", + } + + return func(c *gin.Context) { + scope, ok := c.Get("scope") + if !ok { + c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "unable to retrieve scope"}) + return + } + + requiredPermission, ok := permissions[c.Request.Method] + if !ok { + c.AbortWithStatusJSON(http.StatusForbidden, gin.H{"error": "invalid method"}) + return + } + + scopes := convertToStringSlice(scope.([]interface{})) + + if !slices.Contains(scopes, requiredPermission) || !containsSubstring(scopes, FP) { + c.AbortWithStatusJSON(http.StatusForbidden, gin.H{"error": "insufficient scope"}) + return + } + + bodyBytes, err := readRequestBody(c) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "Unable to read request body"}) + return + } + + var requestBody RequestBody + if err := c.BindJSON(&requestBody); err != nil { + c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "failed to read request body"}) + return + } + + c.Request.Body = io.NopCloser(bytes.NewBuffer(bodyBytes)) + + fernProjectName, err := validateProjectName(scopes, requestBody.Project) + if err != nil { + c.AbortWithStatusJSON(http.StatusForbidden, gin.H{"error": err.Error()}) + return + } + + c.Set("fernProjectName", fernProjectName) + c.Next() + } +} + +// readRequestBody reads and returns the request body bytes. +func readRequestBody(c *gin.Context) ([]byte, error) { + bodyBytes, err := io.ReadAll(c.Request.Body) + if err != nil { + return nil, err + } + c.Request.Body = io.NopCloser(bytes.NewBuffer(bodyBytes)) + return bodyBytes, nil +} + +// validateProjectName checks if the project name from the request body matches the scope claim and returns the fernProjectName. +func validateProjectName(scopes []string, projectName string) (string, error) { + for _, v := range scopes { + if strings.HasPrefix(v, FP+".") { + parts := strings.SplitN(v, ".", 2) + if len(parts) != 2 || len(parts[1]) == 0 { + return "", fmt.Errorf("fern project scope claim is not formatted properly") + } + + fernProjectName := parts[1] + if projectName != fernProjectName { + return "", fmt.Errorf("project name does not match fern project scope claim") + } + return fernProjectName, nil + } + } + return "", fmt.Errorf("fern project scope claim not found") +} + +// convertToStringSlice converts a slice of interface{} to a slice of strings. +func convertToStringSlice(slice []interface{}) []string { + strSlice := make([]string, len(slice)) + for i, v := range slice { + strSlice[i] = fmt.Sprint(v) + } + return strSlice +} + +// containsSubstring checks if any string in the slice contains the specified substring. +func containsSubstring(slice []string, substring string) bool { + for _, v := range slice { + if strings.Contains(v, substring) { + return true + } + } + return false +} diff --git a/pkg/auth/middleware_suite_test.go b/pkg/auth/middleware_suite_test.go new file mode 100644 index 0000000..2d8e54b --- /dev/null +++ b/pkg/auth/middleware_suite_test.go @@ -0,0 +1,18 @@ +package auth_test + +import ( + "github.com/guidewire/fern-reporter/config" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + "testing" +) + +func TestAuth(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "Auth Suite") +} + +var _ = BeforeSuite(func() { + _, err := config.LoadConfig() + Expect(err).NotTo(HaveOccurred()) +}) diff --git a/pkg/auth/middleware_test.go b/pkg/auth/middleware_test.go new file mode 100644 index 0000000..a694c0c --- /dev/null +++ b/pkg/auth/middleware_test.go @@ -0,0 +1,151 @@ +package auth_test + +import ( + "fmt" + "github.com/guidewire/fern-reporter/config" + "github.com/guidewire/fern-reporter/pkg/auth" + "github.com/guidewire/fern-reporter/pkg/auth/mocks" + "net/http" + "net/http/httptest" + "strings" + + "github.com/gin-gonic/gin" + "github.com/lestrrat-go/jwx/v2/jwk" + "github.com/lestrrat-go/jwx/v2/jwt" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + "github.com/stretchr/testify/mock" +) + +var _ = Describe("JWTMiddleware", func() { + var ( + mockFetcher *mocks.JWKSFetcher + mockValidator *mocks.JWTValidator + router *gin.Engine + recorder *httptest.ResponseRecorder + ) + + BeforeEach(func() { + gin.SetMode(gin.TestMode) + mockFetcher = new(mocks.JWKSFetcher) + mockValidator = new(mocks.JWTValidator) + router = gin.New() + recorder = httptest.NewRecorder() + }) + + It("should abort with 500 if key fetcher fails", func() { + mockFetcher.On("FetchKeys", mock.Anything, "test_url").Return(nil, fmt.Errorf("error")) + router.Use(auth.JWTMiddleware("test_url", mockFetcher, mockValidator)) + + req, _ := http.NewRequest("GET", "/", nil) + router.ServeHTTP(recorder, req) + + Expect(recorder.Code).To(Equal(http.StatusInternalServerError)) + }) + + It("should abort with 401 if authorization header is missing", func() { + mockFetcher.On("FetchKeys", mock.Anything, "test_url").Return(jwk.NewSet(), nil) + router.Use(auth.JWTMiddleware("test_url", mockFetcher, mockValidator)) + + req, _ := http.NewRequest("GET", "/", nil) + router.ServeHTTP(recorder, req) + + Expect(recorder.Code).To(Equal(http.StatusUnauthorized)) + }) + + It("should abort with 401 if token is invalid", func() { + mockFetcher.On("FetchKeys", mock.Anything, "test_url").Return(jwk.NewSet(), nil) + mockValidator.On("ParseAndValidateToken", mock.Anything, "invalid_token", mock.Anything).Return(nil, fmt.Errorf("invalid token")) + router.Use(auth.JWTMiddleware("test_url", mockFetcher, mockValidator)) + + req, _ := http.NewRequest("GET", "/", nil) + req.Header.Set("Authorization", "Bearer invalid_token") + router.ServeHTTP(recorder, req) + + Expect(recorder.Code).To(Equal(http.StatusUnauthorized)) + }) + + It("should set scope and call next handler if token is valid", func() { + config.GetAuth().ScopeClaimName = "scope" + jwkSet := jwk.NewSet() + mockFetcher.On("FetchKeys", mock.Anything, "test_url").Return(jwkSet, nil) + + mockToken := jwt.New() + scope := []interface{}{"fern.write", "fernproject.project-a"} + err := mockToken.Set("scope", scope) + Expect(err).To(BeNil()) + + mockValidator.On("ParseAndValidateToken", mock.Anything, "valid_token", jwkSet).Return(mockToken, nil) + + router.Use(auth.JWTMiddleware("test_url", mockFetcher, mockValidator)) + router.POST("/", func(c *gin.Context) { + c.JSON(http.StatusOK, gin.H{"message": "success"}) + }) + + // Create the request body + body := `{"Project": "project-a"}` + req, _ := http.NewRequest("POST", "/", strings.NewReader(body)) + req.Header.Set("Authorization", "Bearer valid_token") + req.Header.Set("Content-Type", "application/json") + router.ServeHTTP(recorder, req) + + Expect(recorder.Code).To(Equal(http.StatusOK)) + Expect(recorder.Body.String()).To(ContainSubstring("success")) + }) +}) + +var _ = Describe("ScopeMiddleware", func() { + var ( + router *gin.Engine + recorder *httptest.ResponseRecorder + ) + + BeforeEach(func() { + gin.SetMode(gin.TestMode) + router = gin.New() + recorder = httptest.NewRecorder() + }) + + It("should abort with 401 if scope is not set in context", func() { + router.Use(auth.ScopeMiddleware()) + router.GET("/", func(c *gin.Context) { + c.JSON(http.StatusOK, gin.H{"message": "success"}) + }) + + req, _ := http.NewRequest("GET", "/", nil) + router.ServeHTTP(recorder, req) + + Expect(recorder.Code).To(Equal(http.StatusUnauthorized)) + }) + + It("should abort with 403 if method is not in permissions map", func() { + router.Use(func(c *gin.Context) { + c.Set("scope", "fern.write") + }) + router.Use(auth.ScopeMiddleware()) + router.DELETE("/", func(c *gin.Context) { + c.JSON(http.StatusOK, gin.H{"message": "success"}) + }) + + req, _ := http.NewRequest("DELETE", "/", nil) + router.ServeHTTP(recorder, req) + + Expect(recorder.Code).To(Equal(http.StatusForbidden)) + }) + + It("should abort with 403 if scope does not include required permission", func() { + scopes := []interface{}{"fern.read"} + router.Use(func(c *gin.Context) { + c.Set("scope", scopes) + }) + router.Use(auth.ScopeMiddleware()) + router.POST("/", func(c *gin.Context) { + c.JSON(http.StatusOK, gin.H{"message": "success"}) + }) + + req, _ := http.NewRequest("POST", "/", nil) + router.ServeHTTP(recorder, req) + + Expect(recorder.Code).To(Equal(http.StatusForbidden)) + }) +}) diff --git a/pkg/auth/mocks/JWKSFetcher.go b/pkg/auth/mocks/JWKSFetcher.go new file mode 100644 index 0000000..b123576 --- /dev/null +++ b/pkg/auth/mocks/JWKSFetcher.go @@ -0,0 +1,144 @@ +// Code generated by mockery v2.43.0. DO NOT EDIT. + +package mocks + +import ( + context "context" + + jwk "github.com/lestrrat-go/jwx/v2/jwk" + mock "github.com/stretchr/testify/mock" +) + +// JWKSFetcher is an autogenerated mock type for the JWKSFetcher type +type JWKSFetcher struct { + mock.Mock +} + +// FetchKeys provides a mock function with given fields: ctx, jwksUrl +func (_m *JWKSFetcher) FetchKeys(ctx context.Context, jwksUrl string) (jwk.Set, error) { + ret := _m.Called(ctx, jwksUrl) + + if len(ret) == 0 { + panic("no return value specified for FetchKeys") + } + + var r0 jwk.Set + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string) (jwk.Set, error)); ok { + return rf(ctx, jwksUrl) + } + if rf, ok := ret.Get(0).(func(context.Context, string) jwk.Set); ok { + r0 = rf(ctx, jwksUrl) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(jwk.Set) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, string) error); ok { + r1 = rf(ctx, jwksUrl) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// Get provides a mock function with given fields: ctx, jwksUrl +func (_m *JWKSFetcher) Get(ctx context.Context, jwksUrl string) (jwk.Set, error) { + ret := _m.Called(ctx, jwksUrl) + + if len(ret) == 0 { + panic("no return value specified for Get") + } + + var r0 jwk.Set + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string) (jwk.Set, error)); ok { + return rf(ctx, jwksUrl) + } + if rf, ok := ret.Get(0).(func(context.Context, string) jwk.Set); ok { + r0 = rf(ctx, jwksUrl) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(jwk.Set) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, string) error); ok { + r1 = rf(ctx, jwksUrl) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// Refresh provides a mock function with given fields: ctx, jwksUrl +func (_m *JWKSFetcher) Refresh(ctx context.Context, jwksUrl string) (jwk.Set, error) { + ret := _m.Called(ctx, jwksUrl) + + if len(ret) == 0 { + panic("no return value specified for Refresh") + } + + var r0 jwk.Set + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string) (jwk.Set, error)); ok { + return rf(ctx, jwksUrl) + } + if rf, ok := ret.Get(0).(func(context.Context, string) jwk.Set); ok { + r0 = rf(ctx, jwksUrl) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(jwk.Set) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, string) error); ok { + r1 = rf(ctx, jwksUrl) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// Register provides a mock function with given fields: jwksUrl, options +func (_m *JWKSFetcher) Register(jwksUrl string, options ...jwk.RegisterOption) error { + _va := make([]interface{}, len(options)) + for _i := range options { + _va[_i] = options[_i] + } + var _ca []interface{} + _ca = append(_ca, jwksUrl) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + if len(ret) == 0 { + panic("no return value specified for Register") + } + + var r0 error + if rf, ok := ret.Get(0).(func(string, ...jwk.RegisterOption) error); ok { + r0 = rf(jwksUrl, options...) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// NewJWKSFetcher creates a new instance of JWKSFetcher. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewJWKSFetcher(t interface { + mock.TestingT + Cleanup(func()) +}) *JWKSFetcher { + mock := &JWKSFetcher{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/pkg/auth/mocks/JWTValidator.go b/pkg/auth/mocks/JWTValidator.go new file mode 100644 index 0000000..b2bac0f --- /dev/null +++ b/pkg/auth/mocks/JWTValidator.go @@ -0,0 +1,61 @@ +// Code generated by mockery v2.43.0. DO NOT EDIT. + +package mocks + +import ( + context "context" + + jwk "github.com/lestrrat-go/jwx/v2/jwk" + jwt "github.com/lestrrat-go/jwx/v2/jwt" + + mock "github.com/stretchr/testify/mock" +) + +// JWTValidator is an autogenerated mock type for the JWTValidator type +type JWTValidator struct { + mock.Mock +} + +// ParseAndValidateToken provides a mock function with given fields: ctx, tokenString, set +func (_m *JWTValidator) ParseAndValidateToken(ctx context.Context, tokenString string, set jwk.Set) (jwt.Token, error) { + ret := _m.Called(ctx, tokenString, set) + + if len(ret) == 0 { + panic("no return value specified for ParseAndValidateToken") + } + + var r0 jwt.Token + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string, jwk.Set) (jwt.Token, error)); ok { + return rf(ctx, tokenString, set) + } + if rf, ok := ret.Get(0).(func(context.Context, string, jwk.Set) jwt.Token); ok { + r0 = rf(ctx, tokenString, set) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(jwt.Token) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, string, jwk.Set) error); ok { + r1 = rf(ctx, tokenString, set) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// NewJWTValidator creates a new instance of JWTValidator. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewJWTValidator(t interface { + mock.TestingT + Cleanup(func()) +}) *JWTValidator { + mock := &JWTValidator{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/pkg/auth/mocks/KeyFetcher.go b/pkg/auth/mocks/KeyFetcher.go new file mode 100644 index 0000000..e68e62d --- /dev/null +++ b/pkg/auth/mocks/KeyFetcher.go @@ -0,0 +1,59 @@ +// Code generated by mockery v2.43.0. DO NOT EDIT. + +package mocks + +import ( + context "context" + + jwk "github.com/lestrrat-go/jwx/v2/jwk" + mock "github.com/stretchr/testify/mock" +) + +// KeyFetcher is an autogenerated mock type for the KeyFetcher type +type KeyFetcher struct { + mock.Mock +} + +// FetchKeys provides a mock function with given fields: ctx, jwksUrl +func (_m *KeyFetcher) FetchKeys(ctx context.Context, jwksUrl string) (jwk.Set, error) { + ret := _m.Called(ctx, jwksUrl) + + if len(ret) == 0 { + panic("no return value specified for FetchKeys") + } + + var r0 jwk.Set + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string) (jwk.Set, error)); ok { + return rf(ctx, jwksUrl) + } + if rf, ok := ret.Get(0).(func(context.Context, string) jwk.Set); ok { + r0 = rf(ctx, jwksUrl) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(jwk.Set) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, string) error); ok { + r1 = rf(ctx, jwksUrl) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// NewKeyFetcher creates a new instance of KeyFetcher. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewKeyFetcher(t interface { + mock.TestingT + Cleanup(func()) +}) *KeyFetcher { + mock := &KeyFetcher{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/pkg/auth/mocks/Set.go b/pkg/auth/mocks/Set.go new file mode 100644 index 0000000..3d8c523 --- /dev/null +++ b/pkg/auth/mocks/Set.go @@ -0,0 +1,320 @@ +// Code generated by mockery v2.43.0. DO NOT EDIT. + +package mocks + +import ( + context "context" + + arrayiter "github.com/lestrrat-go/iter/arrayiter" + + jwk "github.com/lestrrat-go/jwx/v2/jwk" + + mapiter "github.com/lestrrat-go/iter/mapiter" + + mock "github.com/stretchr/testify/mock" +) + +// Set is an autogenerated mock type for the Set type +type Set struct { + mock.Mock +} + +// AddKey provides a mock function with given fields: _a0 +func (_m *Set) AddKey(_a0 jwk.Key) error { + ret := _m.Called(_a0) + + if len(ret) == 0 { + panic("no return value specified for AddKey") + } + + var r0 error + if rf, ok := ret.Get(0).(func(jwk.Key) error); ok { + r0 = rf(_a0) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// Clear provides a mock function with given fields: +func (_m *Set) Clear() error { + ret := _m.Called() + + if len(ret) == 0 { + panic("no return value specified for Clear") + } + + var r0 error + if rf, ok := ret.Get(0).(func() error); ok { + r0 = rf() + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// Clone provides a mock function with given fields: +func (_m *Set) Clone() (jwk.Set, error) { + ret := _m.Called() + + if len(ret) == 0 { + panic("no return value specified for Clone") + } + + var r0 jwk.Set + var r1 error + if rf, ok := ret.Get(0).(func() (jwk.Set, error)); ok { + return rf() + } + if rf, ok := ret.Get(0).(func() jwk.Set); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(jwk.Set) + } + } + + if rf, ok := ret.Get(1).(func() error); ok { + r1 = rf() + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// Get provides a mock function with given fields: _a0 +func (_m *Set) Get(_a0 string) (interface{}, bool) { + ret := _m.Called(_a0) + + if len(ret) == 0 { + panic("no return value specified for Get") + } + + var r0 interface{} + var r1 bool + if rf, ok := ret.Get(0).(func(string) (interface{}, bool)); ok { + return rf(_a0) + } + if rf, ok := ret.Get(0).(func(string) interface{}); ok { + r0 = rf(_a0) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(interface{}) + } + } + + if rf, ok := ret.Get(1).(func(string) bool); ok { + r1 = rf(_a0) + } else { + r1 = ret.Get(1).(bool) + } + + return r0, r1 +} + +// Index provides a mock function with given fields: _a0 +func (_m *Set) Index(_a0 jwk.Key) int { + ret := _m.Called(_a0) + + if len(ret) == 0 { + panic("no return value specified for Index") + } + + var r0 int + if rf, ok := ret.Get(0).(func(jwk.Key) int); ok { + r0 = rf(_a0) + } else { + r0 = ret.Get(0).(int) + } + + return r0 +} + +// Iterate provides a mock function with given fields: _a0 +func (_m *Set) Iterate(_a0 context.Context) mapiter.Iterator { + ret := _m.Called(_a0) + + if len(ret) == 0 { + panic("no return value specified for Iterate") + } + + var r0 mapiter.Iterator + if rf, ok := ret.Get(0).(func(context.Context) mapiter.Iterator); ok { + r0 = rf(_a0) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(mapiter.Iterator) + } + } + + return r0 +} + +// Key provides a mock function with given fields: _a0 +func (_m *Set) Key(_a0 int) (jwk.Key, bool) { + ret := _m.Called(_a0) + + if len(ret) == 0 { + panic("no return value specified for Key") + } + + var r0 jwk.Key + var r1 bool + if rf, ok := ret.Get(0).(func(int) (jwk.Key, bool)); ok { + return rf(_a0) + } + if rf, ok := ret.Get(0).(func(int) jwk.Key); ok { + r0 = rf(_a0) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(jwk.Key) + } + } + + if rf, ok := ret.Get(1).(func(int) bool); ok { + r1 = rf(_a0) + } else { + r1 = ret.Get(1).(bool) + } + + return r0, r1 +} + +// Keys provides a mock function with given fields: _a0 +func (_m *Set) Keys(_a0 context.Context) arrayiter.Iterator { + ret := _m.Called(_a0) + + if len(ret) == 0 { + panic("no return value specified for Keys") + } + + var r0 arrayiter.Iterator + if rf, ok := ret.Get(0).(func(context.Context) arrayiter.Iterator); ok { + r0 = rf(_a0) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(arrayiter.Iterator) + } + } + + return r0 +} + +// Len provides a mock function with given fields: +func (_m *Set) Len() int { + ret := _m.Called() + + if len(ret) == 0 { + panic("no return value specified for Len") + } + + var r0 int + if rf, ok := ret.Get(0).(func() int); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(int) + } + + return r0 +} + +// LookupKeyID provides a mock function with given fields: _a0 +func (_m *Set) LookupKeyID(_a0 string) (jwk.Key, bool) { + ret := _m.Called(_a0) + + if len(ret) == 0 { + panic("no return value specified for LookupKeyID") + } + + var r0 jwk.Key + var r1 bool + if rf, ok := ret.Get(0).(func(string) (jwk.Key, bool)); ok { + return rf(_a0) + } + if rf, ok := ret.Get(0).(func(string) jwk.Key); ok { + r0 = rf(_a0) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(jwk.Key) + } + } + + if rf, ok := ret.Get(1).(func(string) bool); ok { + r1 = rf(_a0) + } else { + r1 = ret.Get(1).(bool) + } + + return r0, r1 +} + +// Remove provides a mock function with given fields: _a0 +func (_m *Set) Remove(_a0 string) error { + ret := _m.Called(_a0) + + if len(ret) == 0 { + panic("no return value specified for Remove") + } + + var r0 error + if rf, ok := ret.Get(0).(func(string) error); ok { + r0 = rf(_a0) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// RemoveKey provides a mock function with given fields: _a0 +func (_m *Set) RemoveKey(_a0 jwk.Key) error { + ret := _m.Called(_a0) + + if len(ret) == 0 { + panic("no return value specified for RemoveKey") + } + + var r0 error + if rf, ok := ret.Get(0).(func(jwk.Key) error); ok { + r0 = rf(_a0) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// Set provides a mock function with given fields: _a0, _a1 +func (_m *Set) Set(_a0 string, _a1 interface{}) error { + ret := _m.Called(_a0, _a1) + + if len(ret) == 0 { + panic("no return value specified for Set") + } + + var r0 error + if rf, ok := ret.Get(0).(func(string, interface{}) error); ok { + r0 = rf(_a0, _a1) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// NewSet creates a new instance of Set. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewSet(t interface { + mock.TestingT + Cleanup(func()) +}) *Set { + mock := &Set{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +}