From 319dfb15f0765dbeaeaeb239d62d8e9f61b1ed82 Mon Sep 17 00:00:00 2001 From: Lanre Adelowo Date: Mon, 19 Aug 2024 20:19:00 +0100 Subject: [PATCH] [Backend] Implement User authentication using Google Social login (#2) * Set up models * implement swagger auto gene * set up http handlers * move socialauthentication to it's own package * complete handler implementation * set up http tests * add login tests --- .dockerignore | 2 +- .gitignore | 2 + Dockerfile | 8 +- cmd/http.go | 34 +- cmd/main.go | 46 ++- config/config.go | 56 ++++ docs/docs.go | 209 ++++++++++++ docs/swagger.json | 184 ++++++++++ docs/swagger.yaml | 127 +++++++ errors.go | 5 + generate.go | 10 + go.mod | 58 ++-- go.sum | 317 ++++-------------- internal/pkg/socialauth/google.go | 90 +++++ internal/pkg/socialauth/mocks/social.go | 72 ++++ internal/pkg/socialauth/social.go | 21 ++ internal/pkg/util/strings.go | 5 + internal/pkg/util/strings_test.go | 23 ++ mocks/user.go | 84 +++++ plan.go | 67 ++++ server/auth.go | 118 +++++++ server/auth_test.go | 268 +++++++++++++++ server/http.go | 13 +- server/otel.go | 11 +- server/{respons.go => response.go} | 10 + ...te_user_because_of_duplicate_emails.golden | 1 + .../could_not_create_user_in_datastore.golden | 1 + .../could_not_fetch_user_details.golden | 1 + .../no_code_to_exchange_provided.golden | 1 + .../token_exchange_fails.golden | 1 + .../user_was_succesfully_created.golden | 1 + swagger.go | 13 + user.go | 79 +++++ user_enum.go | 49 +++ workspace.go | 52 +++ 35 files changed, 1710 insertions(+), 329 deletions(-) create mode 100644 .gitignore create mode 100644 docs/docs.go create mode 100644 docs/swagger.json create mode 100644 docs/swagger.yaml create mode 100644 errors.go create mode 100644 generate.go create mode 100644 internal/pkg/socialauth/google.go create mode 100644 internal/pkg/socialauth/mocks/social.go create mode 100644 internal/pkg/socialauth/social.go create mode 100644 internal/pkg/util/strings.go create mode 100644 internal/pkg/util/strings_test.go create mode 100644 mocks/user.go create mode 100644 plan.go create mode 100644 server/auth_test.go rename server/{respons.go => response.go} (74%) create mode 100644 server/testdata/TestAuthHandler_Login/could_not_create_user_because_of_duplicate_emails.golden create mode 100644 server/testdata/TestAuthHandler_Login/could_not_create_user_in_datastore.golden create mode 100644 server/testdata/TestAuthHandler_Login/could_not_fetch_user_details.golden create mode 100644 server/testdata/TestAuthHandler_Login/no_code_to_exchange_provided.golden create mode 100644 server/testdata/TestAuthHandler_Login/token_exchange_fails.golden create mode 100644 server/testdata/TestAuthHandler_Login/user_was_succesfully_created.golden create mode 100644 swagger.go create mode 100644 user.go create mode 100644 user_enum.go create mode 100644 workspace.go diff --git a/.dockerignore b/.dockerignore index 170e0f9e..860c0f22 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1 +1 @@ -./web/ +./web/ui/ diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..95c1507d --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +docker-data/ +config.yml diff --git a/Dockerfile b/Dockerfile index 16ff2eea..09b7f897 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,8 +1,8 @@ FROM golang:1.22.4 as build-env -WORKDIR /go +WORKDIR /go/malak -COPY ./go.mod /go/ -COPY ./go.sum /go/ +COPY ./go.mod /go/malak +COPY ./go.sum /go/malak # Get dependancies - will also be cached if we won't change mod/sum RUN go mod download @@ -15,5 +15,5 @@ RUN go install ./cmd FROM gcr.io/distroless/base COPY --from=build-env /go/bin/cmd / -CMD ["/cmd"] +CMD ["/cmd http"] diff --git a/cmd/http.go b/cmd/http.go index ef5b694f..16a64480 100644 --- a/cmd/http.go +++ b/cmd/http.go @@ -7,9 +7,9 @@ import ( "syscall" "github.com/ayinke-llc/malak/config" - "github.com/spf13/cobra" - + "github.com/ayinke-llc/malak/server" "github.com/sirupsen/logrus" + "github.com/spf13/cobra" sdktrace "go.opentelemetry.io/otel/sdk/trace" ) @@ -40,28 +40,20 @@ func addHTTPCommand(c *cobra.Command, cfg *config.Config) { signal.Notify(sig, syscall.SIGINT, syscall.SIGTERM) - logrus.SetOutput(os.Stdout) - - var formatter logrus.Formatter = &logrus.JSONFormatter{} - - if cfg.Logging.Format == config.LogFormatText { - formatter = &logrus.TextFormatter{} - } - - logrus.SetFormatter(formatter) - - lvl, err := logrus.ParseLevel(cfg.Logging.Level) - if err != nil { - lvl = logrus.DebugLevel - } - - logrus.SetLevel(lvl) - h, _ := os.Hostname() logger := logrus.WithField("host", h). WithField("app", "malak") + srv, cleanupSrv := server.New(logger, *cfg) + _ = srv + + go func() { + if err := srv.ListenAndServe(); err != nil { + logger.WithError(err).Error("error with http server") + } + }() + // opts, err := redis.ParseURL(cfg.Database.Redis.DSN) // if err != nil { // log.Fatal(err) @@ -83,8 +75,8 @@ func addHTTPCommand(c *cobra.Command, cfg *config.Config) { <-sig - logger.Debug("shutting down server") - // cleanup() + logger.Debug("shutting down Malak's server") + cleanupSrv() }, } diff --git a/cmd/main.go b/cmd/main.go index 2f35dd41..dff51e8e 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -9,6 +9,8 @@ import ( "time" "github.com/ayinke-llc/malak/config" + "github.com/google/uuid" + "github.com/sirupsen/logrus" "github.com/spf13/cobra" "github.com/spf13/viper" ) @@ -52,7 +54,33 @@ func Execute() error { return err } - return initializeConfig(cfg, confFile) + if err := initializeConfig(cfg, confFile); err != nil { + return err + } + + logrus.SetOutput(os.Stdout) + + var formatter logrus.Formatter = &logrus.JSONFormatter{} + + if cfg.Logging.Format == config.LogFormatText { + formatter = &logrus.TextFormatter{} + } + + logrus.SetFormatter(formatter) + + lvl, err := logrus.ParseLevel(cfg.Logging.Level) + if err != nil { + lvl = logrus.DebugLevel + } + + logrus.SetLevel(lvl) + + if err := cfg.Validate(); err != nil { + logrus.WithError(err).Error("could not validate configuration") + return err + } + + return nil }, } @@ -65,6 +93,7 @@ func Execute() error { return rootCmd.Execute() } + func initializeConfig(cfg *config.Config, pathToFile string) error { homePath, err := os.UserHomeDir() if err != nil { @@ -95,19 +124,24 @@ func initializeConfig(cfg *config.Config, pathToFile string) error { } func setDefaults() { + viper.SetDefault("logging.level", "debug") - viper.SetDefault("logging.level.format", config.LogFormatJson) + viper.SetDefault("logging.format", config.LogFormatJson) viper.SetDefault("database.redis.dsn", "redis://localhost:3379") - viper.SetDefault("database.postgres.database_type", config.DatabaseTypePostgres) viper.SetDefault("database.postgres.log_queries", true) viper.SetDefault("database.postgres.dsn", "postgres://makal:makal@localhost:3432/makal?sslmode=disable") viper.SetDefault("otel.is_enabled", true) - viper.SetDefault("otel.use_tls", true) + viper.SetDefault("otel.use_tls", false) viper.SetDefault("otel.service_name", "makal") - viper.SetDefault("otel.endpoint", "localhost:9500") + viper.SetDefault("otel.endpoint", "localhost:3318") + + viper.SetDefault("http.port", 3200) + + viper.SetDefault("biling.stripe.is_enabled", false) + viper.SetDefault("billing.default_plan", uuid.Nil) - viper.SetDefault("http.port", 4200) + viper.SetDefault("auth.google.scopes", []string{"profile", "email"}) } diff --git a/config/config.go b/config/config.go index f681a76a..7437502b 100644 --- a/config/config.go +++ b/config/config.go @@ -1,5 +1,12 @@ package config +import ( + "errors" + + "github.com/ayinke-llc/malak/internal/pkg/util" + "github.com/google/uuid" +) + // Only Postgres for now. Later on we can add support for sqlite3 // ENUM(postgres) type DatabaseType string @@ -35,4 +42,53 @@ type Config struct { HTTP struct { Port int `yaml:"port" mapstructure:"port"` } `yaml:"http" mapstructure:"http"` + + Billing struct { + Stripe struct { + APIKey string `yaml:"api_key" mapstructure:"api_key"` + APISecret string `yaml:"api_secret" mapstructure:"api_secret"` + + // If stripe is not enabled, then fake ids can be used in the + // plans table really + // Ideally self hosted users will want to disable this + IsEnabled bool `yaml:"is_enabled" mapstructure:"is_enabled"` + } `yaml:"stripe" mapstructure:"stripe"` + + // Newly created workspaces will have this plan automatically + // applied upon creation + DefaultPlan uuid.UUID `yaml:"default_plan" mapstructure:"default_plan"` + } `yaml:"billing" mapstructure:"billing"` + + Email struct { + } + + Auth struct { + Google struct { + ClientID string `yaml:"client_id" mapstructure:"client_id"` + ClientSecret string `yaml:"client_secret" mapstructure:"client_secret"` + RedirectURI string `yaml:"redirect_uri" mapstructure:"redirect_uri"` + Scopes []string `yaml:"scopes" mapstructure:"scopes"` + IsEnabled bool `yaml:"is_enabled" mapstructure:"is_enabled"` + } `yaml:"google" mapstructure:"google"` + } `yaml:"auth" mapstructure:"auth"` +} + +func (c *Config) Validate() error { + + if !c.Auth.Google.IsEnabled { + return errors.New("at least one oauth authentication provider has to be turned on") + } + + if c.Auth.Google.IsEnabled { + + if util.IsStringEmpty(c.Auth.Google.ClientID) { + return errors.New("please provide Google oauth key") + } + + if util.IsStringEmpty(c.Auth.Google.ClientSecret) { + return errors.New("please provide Google oauth secret") + } + } + + return nil } diff --git a/docs/docs.go b/docs/docs.go new file mode 100644 index 00000000..da427b11 --- /dev/null +++ b/docs/docs.go @@ -0,0 +1,209 @@ +// Package docs Code generated by swaggo/swag. DO NOT EDIT +package docs + +import "github.com/swaggo/swag" + +const docTemplate = `{ + "schemes": {{ marshal .Schemes }}, + "swagger": "2.0", + "info": { + "description": "{{escape .Description}}", + "title": "{{.Title}}", + "contact": { + "name": "Ayinke Ventures", + "email": "lanre@ayinke.ventures" + }, + "version": "{{.Version}}" + }, + "host": "{{.Host}}", + "basePath": "{{.BasePath}}", + "paths": { + "/auth/login": { + "post": { + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "auth" + ], + "summary": "Sign in with a social login provider", + "parameters": [ + { + "description": "auth exchange data", + "name": "message", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/server.authenticateUserRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/server.createdUserResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/server.APIStatus" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/server.APIStatus" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/server.APIStatus" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/server.APIStatus" + } + } + } + } + } + }, + "definitions": { + "malak.Role": { + "type": "string", + "enum": [ + "admin", + "member", + "billing" + ], + "x-enum-varnames": [ + "RoleAdmin", + "RoleMember", + "RoleBilling" + ] + }, + "malak.User": { + "type": "object", + "properties": { + "created_at": { + "type": "string" + }, + "email": { + "type": "string" + }, + "full_name": { + "type": "string" + }, + "id": { + "type": "string" + }, + "metadata": { + "$ref": "#/definitions/malak.UserMetadata" + }, + "roles": { + "type": "array", + "items": { + "$ref": "#/definitions/malak.UserRole" + } + }, + "updated_at": { + "type": "string" + } + } + }, + "malak.UserMetadata": { + "type": "object", + "properties": { + "current_workspace": { + "description": "Used to keep track of the last used workspace\nIn the instance of multiple workspaces\nSo when next the user logs in, we remember and take them to the\nright place rather than always a list of all their workspaces and they\nhave to select one", + "type": "string" + } + } + }, + "malak.UserRole": { + "type": "object", + "properties": { + "created_at": { + "type": "string" + }, + "id": { + "type": "string" + }, + "role": { + "$ref": "#/definitions/malak.Role" + }, + "updated_at": { + "type": "string" + }, + "user_id": { + "type": "string" + }, + "workspace_id": { + "type": "string" + } + } + }, + "server.APIStatus": { + "type": "object", + "properties": { + "message": { + "description": "Generic message that tells you the status of the operation", + "type": "string" + } + } + }, + "server.authenticateUserRequest": { + "type": "object", + "properties": { + "code": { + "type": "string" + } + } + }, + "server.createdUserResponse": { + "type": "object", + "properties": { + "message": { + "description": "Generic message that tells you the status of the operation", + "type": "string" + }, + "user": { + "$ref": "#/definitions/malak.User" + } + } + } + }, + "securityDefinitions": { + "ApiKeyAuth": { + "type": "apiKey", + "name": "Authorization", + "in": "header" + } + } +}` + +// SwaggerInfo holds exported Swagger Info so clients can modify it +var SwaggerInfo = &swag.Spec{ + Version: "0.1.0", + Host: "malak.ayinke.ventures", + BasePath: "/v1", + Schemes: []string{}, + Title: "Malak's API documentation", + Description: "", + InfoInstanceName: "swagger", + SwaggerTemplate: docTemplate, + LeftDelim: "{{", + RightDelim: "}}", +} + +func init() { + swag.Register(SwaggerInfo.InstanceName(), SwaggerInfo) +} diff --git a/docs/swagger.json b/docs/swagger.json new file mode 100644 index 00000000..8741e5ea --- /dev/null +++ b/docs/swagger.json @@ -0,0 +1,184 @@ +{ + "swagger": "2.0", + "info": { + "title": "Malak's API documentation", + "contact": { + "name": "Ayinke Ventures", + "email": "lanre@ayinke.ventures" + }, + "version": "0.1.0" + }, + "host": "malak.ayinke.ventures", + "basePath": "/v1", + "paths": { + "/auth/login": { + "post": { + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "auth" + ], + "summary": "Sign in with a social login provider", + "parameters": [ + { + "description": "auth exchange data", + "name": "message", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/server.authenticateUserRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/server.createdUserResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/server.APIStatus" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/server.APIStatus" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/server.APIStatus" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/server.APIStatus" + } + } + } + } + } + }, + "definitions": { + "malak.Role": { + "type": "string", + "enum": [ + "admin", + "member", + "billing" + ], + "x-enum-varnames": [ + "RoleAdmin", + "RoleMember", + "RoleBilling" + ] + }, + "malak.User": { + "type": "object", + "properties": { + "created_at": { + "type": "string" + }, + "email": { + "type": "string" + }, + "full_name": { + "type": "string" + }, + "id": { + "type": "string" + }, + "metadata": { + "$ref": "#/definitions/malak.UserMetadata" + }, + "roles": { + "type": "array", + "items": { + "$ref": "#/definitions/malak.UserRole" + } + }, + "updated_at": { + "type": "string" + } + } + }, + "malak.UserMetadata": { + "type": "object", + "properties": { + "current_workspace": { + "description": "Used to keep track of the last used workspace\nIn the instance of multiple workspaces\nSo when next the user logs in, we remember and take them to the\nright place rather than always a list of all their workspaces and they\nhave to select one", + "type": "string" + } + } + }, + "malak.UserRole": { + "type": "object", + "properties": { + "created_at": { + "type": "string" + }, + "id": { + "type": "string" + }, + "role": { + "$ref": "#/definitions/malak.Role" + }, + "updated_at": { + "type": "string" + }, + "user_id": { + "type": "string" + }, + "workspace_id": { + "type": "string" + } + } + }, + "server.APIStatus": { + "type": "object", + "properties": { + "message": { + "description": "Generic message that tells you the status of the operation", + "type": "string" + } + } + }, + "server.authenticateUserRequest": { + "type": "object", + "properties": { + "code": { + "type": "string" + } + } + }, + "server.createdUserResponse": { + "type": "object", + "properties": { + "message": { + "description": "Generic message that tells you the status of the operation", + "type": "string" + }, + "user": { + "$ref": "#/definitions/malak.User" + } + } + } + }, + "securityDefinitions": { + "ApiKeyAuth": { + "type": "apiKey", + "name": "Authorization", + "in": "header" + } + } +} \ No newline at end of file diff --git a/docs/swagger.yaml b/docs/swagger.yaml new file mode 100644 index 00000000..108da445 --- /dev/null +++ b/docs/swagger.yaml @@ -0,0 +1,127 @@ +basePath: /v1 +definitions: + malak.Role: + enum: + - admin + - member + - billing + type: string + x-enum-varnames: + - RoleAdmin + - RoleMember + - RoleBilling + malak.User: + properties: + created_at: + type: string + email: + type: string + full_name: + type: string + id: + type: string + metadata: + $ref: '#/definitions/malak.UserMetadata' + roles: + items: + $ref: '#/definitions/malak.UserRole' + type: array + updated_at: + type: string + type: object + malak.UserMetadata: + properties: + current_workspace: + description: |- + Used to keep track of the last used workspace + In the instance of multiple workspaces + So when next the user logs in, we remember and take them to the + right place rather than always a list of all their workspaces and they + have to select one + type: string + type: object + malak.UserRole: + properties: + created_at: + type: string + id: + type: string + role: + $ref: '#/definitions/malak.Role' + updated_at: + type: string + user_id: + type: string + workspace_id: + type: string + type: object + server.APIStatus: + properties: + message: + description: Generic message that tells you the status of the operation + type: string + type: object + server.authenticateUserRequest: + properties: + code: + type: string + type: object + server.createdUserResponse: + properties: + message: + description: Generic message that tells you the status of the operation + type: string + user: + $ref: '#/definitions/malak.User' + type: object +host: malak.ayinke.ventures +info: + contact: + email: lanre@ayinke.ventures + name: Ayinke Ventures + title: Malak's API documentation + version: 0.1.0 +paths: + /auth/login: + post: + consumes: + - application/json + parameters: + - description: auth exchange data + in: body + name: message + required: true + schema: + $ref: '#/definitions/server.authenticateUserRequest' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/server.createdUserResponse' + "400": + description: Bad Request + schema: + $ref: '#/definitions/server.APIStatus' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/server.APIStatus' + "404": + description: Not Found + schema: + $ref: '#/definitions/server.APIStatus' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/server.APIStatus' + summary: Sign in with a social login provider + tags: + - auth +securityDefinitions: + ApiKeyAuth: + in: header + name: Authorization + type: apiKey +swagger: "2.0" diff --git a/errors.go b/errors.go new file mode 100644 index 00000000..69db3766 --- /dev/null +++ b/errors.go @@ -0,0 +1,5 @@ +package malak + +type malakError string + +func (m malakError) Error() string { return string(m) } diff --git a/generate.go b/generate.go new file mode 100644 index 00000000..c7d35e01 --- /dev/null +++ b/generate.go @@ -0,0 +1,10 @@ +package malak + +// Swagger generation +// +//go:generate swag init -g swagger.go +// +// +// Mocks generation +//go:generate mockgen -source=internal/pkg/socialauth/social.go -destination=internal/pkg/socialauth/mocks/social.go -package=socialauth_mocks +//go:generate mockgen -source=user.go -destination=mocks/user.go -package=malak_mocks diff --git a/go.mod b/go.mod index f3d03bd4..1247bbaf 100644 --- a/go.mod +++ b/go.mod @@ -5,12 +5,18 @@ go 1.22.4 require ( github.com/go-chi/chi/v5 v5.1.0 github.com/go-chi/render v1.0.3 - github.com/go-chi/telemetry v0.3.4 + github.com/google/uuid v1.6.0 github.com/riandyrn/otelchi v0.9.0 github.com/rs/cors v1.11.0 + github.com/sebdah/goldie/v2 v2.5.5 github.com/sirupsen/logrus v1.9.3 github.com/spf13/cobra v1.8.1 github.com/spf13/viper v1.19.0 + github.com/stretchr/testify v1.9.0 + github.com/swaggo/swag v1.16.3 + github.com/teris-io/shortid v0.0.0-20220617161101-71ec9f2aa569 + github.com/uptrace/bun v1.2.1 + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 go.opentelemetry.io/contrib/instrumentation/runtime v0.53.0 go.opentelemetry.io/otel v1.28.0 go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.28.0 @@ -19,71 +25,61 @@ require ( go.opentelemetry.io/otel/sdk v1.28.0 go.opentelemetry.io/otel/sdk/metric v1.28.0 go.opentelemetry.io/otel/trace v1.28.0 - gopkg.in/DataDog/dd-trace-go.v1 v1.66.0 + go.uber.org/mock v0.4.0 + golang.org/x/oauth2 v0.20.0 ) require ( - github.com/DataDog/appsec-internal-go v1.6.0 // indirect - github.com/DataDog/datadog-agent/pkg/obfuscate v0.48.0 // indirect - github.com/DataDog/datadog-agent/pkg/remoteconfig/state v0.48.1 // indirect - github.com/DataDog/datadog-go/v5 v5.3.0 // indirect - github.com/DataDog/go-libddwaf/v3 v3.2.1 // indirect - github.com/DataDog/go-tuf v1.0.2-0.5.2 // indirect - github.com/DataDog/sketches-go v1.4.5 // indirect - github.com/Microsoft/go-winio v0.6.1 // indirect + cloud.google.com/go/compute/metadata v0.3.0 // indirect + github.com/KyleBanks/depth v1.2.1 // indirect + github.com/PuerkitoBio/purell v1.1.1 // indirect + github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect github.com/ajg/form v1.5.1 // indirect - github.com/beorn7/perks v1.0.1 // indirect github.com/cenkalti/backoff/v4 v4.3.0 // indirect - github.com/cespare/xxhash/v2 v2.3.0 // indirect - github.com/dustin/go-humanize v1.0.1 // indirect - github.com/ebitengine/purego v0.6.0-alpha.5 // indirect + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect - github.com/golang/mock v1.6.0 // indirect - github.com/google/uuid v1.6.0 // indirect + github.com/go-openapi/jsonpointer v0.19.5 // indirect + github.com/go-openapi/jsonreference v0.19.6 // indirect + github.com/go-openapi/spec v0.20.4 // indirect + github.com/go-openapi/swag v0.19.15 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 // indirect github.com/hashicorp/hcl v1.0.1-vault-5 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/jinzhu/inflection v1.0.0 // indirect + github.com/josharian/intern v1.0.0 // indirect github.com/magiconair/properties v1.8.7 // indirect + github.com/mailru/easyjson v0.7.6 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect - github.com/outcaste-io/ristretto v0.2.3 // indirect github.com/pelletier/go-toml/v2 v2.2.2 // indirect - github.com/philhofer/fwd v1.1.2 // indirect - github.com/pkg/errors v0.9.1 // indirect - github.com/prometheus/client_golang v1.19.0 // indirect - github.com/prometheus/client_model v0.6.1 // indirect - github.com/prometheus/common v0.52.3 // indirect - github.com/prometheus/procfs v0.13.0 // indirect + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/sagikazarmark/locafero v0.4.0 // indirect github.com/sagikazarmark/slog-shim v0.1.0 // indirect - github.com/secure-systems-lab/go-securesystemslib v0.7.0 // indirect + github.com/sergi/go-diff v1.0.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 github.com/spf13/pflag v1.0.5 // indirect github.com/subosito/gotenv v1.6.0 // indirect - github.com/tinylib/msgp v1.1.8 // indirect - github.com/twmb/murmur3 v1.1.8 // indirect - github.com/uber-go/tally/v4 v4.1.16 // indirect + github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc // indirect + github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect + github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect go.opentelemetry.io/otel/metric v1.28.0 // indirect go.opentelemetry.io/proto/otlp v1.3.1 // indirect go.uber.org/atomic v1.11.0 // indirect go.uber.org/multierr v1.9.0 // indirect golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect - golang.org/x/mod v0.17.0 // indirect golang.org/x/net v0.26.0 // indirect - golang.org/x/sync v0.7.0 // indirect golang.org/x/sys v0.21.0 // indirect golang.org/x/text v0.16.0 // indirect - golang.org/x/time v0.5.0 // indirect golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect - golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20240701130421-f6361c86f094 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094 // indirect google.golang.org/grpc v1.64.0 // indirect google.golang.org/protobuf v1.34.2 // indirect gopkg.in/ini.v1 v1.67.0 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 15bd7e5c..de88c93a 100644 --- a/go.sum +++ b/go.sum @@ -1,130 +1,60 @@ -cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -github.com/DataDog/appsec-internal-go v1.6.0 h1:QHvPOv/O0s2fSI/BraZJNpRDAtdlrRm5APJFZNBxjAw= -github.com/DataDog/appsec-internal-go v1.6.0/go.mod h1:pEp8gjfNLtEOmz+iZqC8bXhu0h4k7NUsW/qiQb34k1U= -github.com/DataDog/datadog-agent/pkg/obfuscate v0.48.0 h1:bUMSNsw1iofWiju9yc1f+kBd33E3hMJtq9GuU602Iy8= -github.com/DataDog/datadog-agent/pkg/obfuscate v0.48.0/go.mod h1:HzySONXnAgSmIQfL6gOv9hWprKJkx8CicuXuUbmgWfo= -github.com/DataDog/datadog-agent/pkg/remoteconfig/state v0.48.1 h1:5nE6N3JSs2IG3xzMthNFhXfOaXlrsdgqmJ73lndFf8c= -github.com/DataDog/datadog-agent/pkg/remoteconfig/state v0.48.1/go.mod h1:Vc+snp0Bey4MrrJyiV2tVxxJb6BmLomPvN1RgAvjGaQ= -github.com/DataDog/datadog-go/v5 v5.3.0 h1:2q2qjFOb3RwAZNU+ez27ZVDwErJv5/VpbBPprz7Z+s8= -github.com/DataDog/datadog-go/v5 v5.3.0/go.mod h1:XRDJk1pTc00gm+ZDiBKsjh7oOOtJfYfglVCmFb8C2+Q= -github.com/DataDog/go-libddwaf/v3 v3.2.1 h1:lZPc6UxCOwioHc++nsldKR50FpIrRh1uGnGLuryqnE8= -github.com/DataDog/go-libddwaf/v3 v3.2.1/go.mod h1:AP+7Atb8ftSsrha35wht7+K3R+xuzfVSQhabSO4w6CY= -github.com/DataDog/go-tuf v1.0.2-0.5.2 h1:EeZr937eKAWPxJ26IykAdWA4A0jQXJgkhUjqEI/w7+I= -github.com/DataDog/go-tuf v1.0.2-0.5.2/go.mod h1:zBcq6f654iVqmkk8n2Cx81E1JnNTMOAx1UEO/wZR+P0= -github.com/DataDog/gostackparse v0.7.0 h1:i7dLkXHvYzHV308hnkvVGDL3BR4FWl7IsXNPz/IGQh4= -github.com/DataDog/gostackparse v0.7.0/go.mod h1:lTfqcJKqS9KnXQGnyQMCugq3u1FP6UZMfWR0aitKFMM= -github.com/DataDog/sketches-go v1.4.5 h1:ki7VfeNz7IcNafq7yI/j5U/YCkO3LJiMDtXz9OMQbyE= -github.com/DataDog/sketches-go v1.4.5/go.mod h1:7Y8GN8Jf66DLyDhc94zuWA3uHEt/7ttt8jHOBWWrSOg= -github.com/Microsoft/go-winio v0.5.0/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= -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= +cloud.google.com/go/compute/metadata v0.3.0 h1:Tz+eQXMEqDIKRsmY3cHTL6FVaynIjX2QxYC4trgAKZc= +cloud.google.com/go/compute/metadata v0.3.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k= +github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc= +github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE= +github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI= +github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= +github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M= +github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU= github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY= -github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= -github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= -github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= -github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= -github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= -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/cactus/go-statsd-client/v5 v5.0.0/go.mod h1:COEvJ1E+/E2L4q6QE5CkjWPi4eeDw9maJBMIuMPBZbY= github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= -github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= -github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cpuguy83/go-md2man/v2 v2.0.4/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/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/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2 h1:tdlZCpZ/P9DhczCTSixgIKmwPv6+wP5DGjqLYw5SUiA= -github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= -github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= -github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= -github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= -github.com/eapache/queue v1.1.0 h1:YOEu7KNc61ntiQlcEeUIoDTJ2o8mQznoNvUhiigpIqc= -github.com/eapache/queue/v2 v2.0.0-20230407133247-75960ed334e4 h1:8EXxF+tCLqaVk8AOC29zl2mnhQjwyLxxOTuhUazWRsg= -github.com/eapache/queue/v2 v2.0.0-20230407133247-75960ed334e4/go.mod h1:I5sHm0Y0T1u5YjlyqC5GVArM7aNZRUYtTjmJ8mPJFds= -github.com/ebitengine/purego v0.6.0-alpha.5 h1:EYID3JOAdmQ4SNZYJHu9V6IqOeRQDBYxqKAg9PyoHFY= -github.com/ebitengine/purego v0.6.0-alpha.5/go.mod h1:ah1In8AOtksoNK6yk5z1HTJeUkC1Ez4Wk2idgGslMwQ= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= -github.com/glebarez/go-sqlite v1.22.0 h1:uAcMJhaA6r3LHMTFgP0SifzgXg46yJkgxqyuyec+ruQ= -github.com/glebarez/go-sqlite v1.22.0/go.mod h1:PlBIdHe0+aUEFn+r2/uthrWq4FxbzugL0L8Li6yQJbc= 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-chi/render v1.0.3 h1:AsXqd2a1/INaIfUSKq3G5uA8weYx20FOsM7uSoCyyt4= github.com/go-chi/render v1.0.3/go.mod h1:/gr3hVkmYR0YlEy3LxCuVRFzEu9Ruok+gFqbIofjao0= -github.com/go-chi/telemetry v0.3.4 h1:iCe1lbqP4pOOYkxyy3Y2LjkNA1iMgp1owp0JYgdTRhM= -github.com/go-chi/telemetry v0.3.4/go.mod h1:N+qwgqriyLwEPFyXAjj22GMdDlNuCaEourUPJfZBoPw= -github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= -github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= -github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= -github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= -github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= -github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= 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/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= -github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= -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/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.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -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.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= +github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY= +github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= +github.com/go-openapi/jsonreference v0.19.6 h1:UBIxjkht+AWIgYzCDSv2GN+E/togfwXUJFRTWhl2Jjs= +github.com/go-openapi/jsonreference v0.19.6/go.mod h1:diGHMEHg2IqXZGKxqyvWdfWU/aim5Dprw5bqpKkTvns= +github.com/go-openapi/spec v0.20.4 h1:O8hJrt0UMnhHcluhIdUgCLRWyM2x7QkBXRvOs7m+O1M= +github.com/go-openapi/spec v0.20.4/go.mod h1:faYFR1CvsJZ0mNsmsphTMSoRrNV3TEDoAM7FOEWeq8I= +github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= +github.com/go-openapi/swag v0.19.15 h1:D2NRCBzS9/pEY3gP9Nl8aDqGUcPFrwG2p+CNFrLyrCM= +github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= 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= -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/pprof v0.0.0-20230817174616-7a8ec2ada47b h1:h9U78+dx9a4BKdQkBBos92HalKpaGKHrp+3Uo6yTodo= -github.com/google/pprof v0.0.0-20230817174616-7a8ec2ada47b/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik= 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/grpc-ecosystem/grpc-gateway/v2 v2.20.0 h1:bkypFPDjIYGfCYD5mRBvpqxfYX1YCS1PXdKYWi8FsN0= github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0/go.mod h1:P+Lt/0by1T8bfcF3z737NnSbmxQAppXMRziHUxPOC8k= -github.com/hashicorp/go-secure-stdlib/parseutil v0.1.7 h1:UpiO20jno/eV1eVZcxqWnUohyKRe1g8FPV/xH1s/2qs= -github.com/hashicorp/go-secure-stdlib/parseutil v0.1.7/go.mod h1:QmrqtbKuxxSWTN3ETMPuB+VtEiBJ/A9XhoYGv8E1uD8= -github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 h1:kes8mmyCpxJsI7FTwtzRqEy9CdjCtrXrXGuOpxEA7Ts= -github.com/hashicorp/go-secure-stdlib/strutil v0.1.2/go.mod h1:Gou2R9+il93BqX25LAKCLuM+y9U2T4hlwvT1yprcna4= -github.com/hashicorp/go-sockaddr v1.0.2 h1:ztczhD1jLxIRjVejw8gFomI1BQZOe2WoVOu0SyteCQc= -github.com/hashicorp/go-sockaddr v1.0.2/go.mod h1:rB4wwRAUzs07qva3c5SdrY/NEtAUjGlgmH/UkBUC97A= github.com/hashicorp/hcl v1.0.1-vault-5 h1:kI3hhbbyzr4dldA8UdTb7ZlVVlI2DACdCfz31RPDgJM= github.com/hashicorp/hcl v1.0.1-vault-5/go.mod h1:XYhtn6ijBSAj6n4YqAaf7RBPS4I06AItNorpy+MoQNM= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= -github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= -github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= -github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= -github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= -github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= -github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= +github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= +github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= +github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= @@ -134,83 +64,38 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= -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/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.7.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA= +github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= -github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= -github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= -github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs= -github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= -github.com/outcaste-io/ristretto v0.2.3 h1:AK4zt/fJ76kjlYObOeNwh4T3asEuaCmp26pOvUOL9w0= -github.com/outcaste-io/ristretto v0.2.3/go.mod h1:W8HywhmtlopSB1jeMg3JtdIhf+DYkLAr0VN/s4+MHac= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM= github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= -github.com/philhofer/fwd v1.1.2 h1:bnDivRJ1EWPjUIRXV5KfORO897HTbpFAQddBdE8t7Gw= -github.com/philhofer/fwd v1.1.2/go.mod h1:qkPdfjR2SIEbspLqpe1tO4n5yICnr2DY7mqEx2tUTP0= -github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 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/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/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= -github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= -github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= -github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= -github.com/prometheus/client_golang v1.19.0 h1:ygXvpU1AoN1MhdzckN+PyD9QJOSD4x7kmXYlnfbA6JU= -github.com/prometheus/client_golang v1.19.0/go.mod h1:ZRM9uEAypZakd+q/x7+gmsvXdURP+DABIEIjnmDdp+k= -github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= -github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= -github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= -github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= -github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= -github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= -github.com/prometheus/common v0.52.3 h1:5f8uj6ZwHSscOGNdIQg6OiZv/ybiK2CO2q2drVZAQSA= -github.com/prometheus/common v0.52.3/go.mod h1:BrxBKv3FWBIGXw89Mg1AeBq7FSyRzXWI3l3e7W3RN5U= -github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= -github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= -github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= -github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= -github.com/prometheus/procfs v0.13.0 h1:GqzLlQyfsPbaEHaQkO7tbDlriv/4o5Hudv6OXHGKX7o= -github.com/prometheus/procfs v0.13.0/go.mod h1:cd4PFCR54QLnGKPaKGA6l+cfuNXtht43ZKY6tow0Y1g= -github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= -github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/riandyrn/otelchi v0.9.0 h1:BuQxXR7/JF2yYOQl21Yyz5d52hns/96ecAaPUZiKQzc= github.com/riandyrn/otelchi v0.9.0/go.mod h1:iX30kllzThsf8oEcEbl3GifPJZtN4cnCWUUc+UhE4yM= -github.com/richardartoul/molecule v1.0.1-0.20240531184615-7ca0df43c0b3 h1:4+LEVOB87y175cLJC/mbsgKmoDOjrBldtXvioEy96WY= -github.com/richardartoul/molecule v1.0.1-0.20240531184615-7ca0df43c0b3/go.mod h1:vl5+MqJ1nBINuSsUI2mGgH79UweUT/B5Fy8857PqyyI= github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= github.com/rs/cors v1.11.0 h1:0B9GE/r9Bc2UxRMMtymBkHTenPkHDv0CW4Y98GBY+po= github.com/rs/cors v1.11.0/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkBk= -github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc= 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/secure-systems-lab/go-securesystemslib v0.7.0 h1:OwvJ5jQf9LnIAS83waAjPbcMsODrTQUpJ02eNLUoxBg= -github.com/secure-systems-lab/go-securesystemslib v0.7.0/go.mod h1:/2gYnlnHVQ6xeGtfIqFy7Do03K4cdCY0A/GlJLDKLHI= -github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= -github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= -github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= -github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/sebdah/goldie/v2 v2.5.5 h1:rx1mwF95RxZ3/83sdS4Yp7t2C5TCokvWP4TBRbAyEWY= +github.com/sebdah/goldie/v2 v2.5.5/go.mod h1:oZ9fp0+se1eapSRjfYbsV/0Hqhbuu3bJVvKI/NNtssI= +github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ= +github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= 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/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI= -github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY= github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0= @@ -222,32 +107,33 @@ github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An github.com/spf13/viper v1.19.0 h1:RWq5SEjt8o25SROyN3z2OrDB9l7RPd3lwTWU8EcEdcI= github.com/spf13/viper v1.19.0/go.mod h1:GQUN9bilAbhU/jgc1bKs99f/suXKeUMct8Adx5+Ntkg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.1.1/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/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= -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.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +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/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= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= -github.com/tinylib/msgp v1.1.8 h1:FCXC1xanKO4I8plpHGH2P7koL/RzZs12l/+r7vakfm0= -github.com/tinylib/msgp v1.1.8/go.mod h1:qkpG+2ldGg4xRFmx+jfTvZPxfGFhi64BcnL9vkCm/Tw= -github.com/twmb/murmur3 v1.1.5/go.mod h1:Qq/R7NUyOfr65zD+6Q5IHKsJLwP7exErjN6lyyq3OSQ= -github.com/twmb/murmur3 v1.1.8 h1:8Yt9taO/WN3l08xErzjeschgZU2QSrwm1kclYq+0aRg= -github.com/twmb/murmur3 v1.1.8/go.mod h1:Qq/R7NUyOfr65zD+6Q5IHKsJLwP7exErjN6lyyq3OSQ= -github.com/uber-go/tally/v4 v4.1.16 h1:by2hveWRh/cUReButk6ns1sHK/hiKry7BuOV6iY16XI= -github.com/uber-go/tally/v4 v4.1.16/go.mod h1:RW5DgqsyEPs0lA4b0YNf4zKj7DveKHd73hnO6zVlyW0= -github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= -github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +github.com/swaggo/swag v1.16.3 h1:PnCYjPCah8FK4I26l2F/KQ4yz3sILcVUN3cTlBFA9Pg= +github.com/swaggo/swag v1.16.3/go.mod h1:DImHIuOFXKpMFAQjcC7FG4m3Dg4+QuUgUzJmKjI/gRk= +github.com/teris-io/shortid v0.0.0-20220617161101-71ec9f2aa569 h1:xzABM9let0HLLqFypcxvLmlvEciCHL7+Lv+4vwZqecI= +github.com/teris-io/shortid v0.0.0-20220617161101-71ec9f2aa569/go.mod h1:2Ly+NIftZN4de9zRmENdYbvPQeaVIYKWpLFStLFEBgI= +github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc h1:9lRDQMhESg+zvGYmW5DyG0UqvY96Bu5QYsTLvCHdrgo= +github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc/go.mod h1:bciPuU6GHm1iF1pBvUfxfsH0Wmnc2VbpgvbI9ZWuIRs= +github.com/uptrace/bun v1.2.1 h1:2ENAcfeCfaY5+2e7z5pXrzFKy3vS8VXvkCag6N2Yzfk= +github.com/uptrace/bun v1.2.1/go.mod h1:cNg+pWBUMmJ8rHnETgf65CEvn3aIKErrwOD6IA8e+Ec= +github.com/vmihailenco/msgpack/v5 v5.4.1 h1:cQriyiUvjTwOHg8QZaPihLWeRAAVoCpE00IUPn0Bjt8= +github.com/vmihailenco/msgpack/v5 v5.4.1/go.mod h1:GaZTsDaehaPpQVyxrf5mtQlH+pc21PIudVV/E3rRQok= +github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g= +github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 h1:jq9TW8u3so/bN+JPT166wjOI6/vQPF6Xe7nMNIltagk= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0/go.mod h1:p8pYQP+m5XfbZm9fxtSKAbM6oIllS7s2AfxrChvc7iw= go.opentelemetry.io/contrib/instrumentation/runtime v0.53.0 h1:nOlJEAJyrcy8hexK65M+dsCHIx7CVVbybcFDNkcTcAc= go.opentelemetry.io/contrib/instrumentation/runtime v0.53.0/go.mod h1:u79lGGIlkg3Ryw425RbMjEkGYNxSnXRyR286O840+u4= go.opentelemetry.io/otel v1.28.0 h1:/SqNcYk+idO0CxKEUOtKQClMK/MimZihKYMruSMViUo= @@ -268,144 +154,55 @@ go.opentelemetry.io/otel/trace v1.28.0 h1:GhQ9cUuQGmNDd5BTCP2dAvv75RdMxEfTmYejp+ go.opentelemetry.io/otel/trace v1.28.0/go.mod h1:jPyXzNPg6da9+38HEwElrQiHlVMTnVfM3/yv2OlIHaI= go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0= go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8= -go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= -go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= 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/goleak v1.2.1/go.mod h1:qlT2yGI9QafXHhZZLxlSuNsMw3FFLxBr+tBRlmO1xH4= -go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= -go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU= +go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc= 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= -golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/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.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI= -golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM= 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/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.7.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-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= -golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.3.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE= +golang.org/x/net v0.0.0-20210421230115-4e50805a0758/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM= 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.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -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-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/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/oauth2 v0.20.0 h1:4mQdhULixXKP1rwYBW0vAijoXnkTG0BLCDRzfe1idMo= +golang.org/x/oauth2 v0.20.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= 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-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/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-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -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= -golang.org/x/sys v0.0.0-20220627191245-f75cf1eec38b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 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.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws= golang.org/x/sys v0.21.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.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA= -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.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.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= -golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= -golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.4.0/go.mod h1:UE5sM2OK9E/d67R0ANs2xJizIymRP5gJU295PvKXxjQ= golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg= golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= -golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 h1:+cNy6SZtPcJQH3LJVLOSmiC7MMxXNOb3PU/VUEz+EhU= -golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= -google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/genproto/googleapis/api v0.0.0-20240701130421-f6361c86f094 h1:0+ozOGcrp+Y8Aq8TLNN2Aliibms5LEzsq99ZZmAGYm0= google.golang.org/genproto/googleapis/api v0.0.0-20240701130421-f6361c86f094/go.mod h1:fJ/e3If/Q67Mj99hin0hMhiNyCRmt6BQ2aWIJshUSJw= google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094 h1:BwIjyKYGsK9dMCBOorzRri8MQwmi7mT9rGHsCEinZkA= google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094/go.mod h1:Ue6ibwXGpU+dqIcODieyLOcgj7z8+IcskoNIgZxtrFY= google.golang.org/grpc v1.64.0 h1:KH3VH9y/MgNQg1dE7b3XfVK0GsPSIzJwdF617gUSbvY= google.golang.org/grpc v1.64.0/go.mod h1:oxjF8E3FBnjp+/gVFYdWacaLDx9na1aqy9oovLpxQYg= -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.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= -gopkg.in/DataDog/dd-trace-go.v1 v1.66.0 h1:025+lLubGtpiDWrRmSOxoFBPIiVRVYRcqP9oLabVOeg= -gopkg.in/DataDog/dd-trace-go.v1 v1.66.0/go.mod h1:Av6AXGmQCQAbDnwNoPiuUz1k3GS8TwQjj+vEdwmEpmM= -gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= 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-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/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/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= -gopkg.in/validator.v2 v2.0.0-20200605151824-2b28d334fa05/go.mod h1:o4V0GXN9/CAmCsvJ0oXYZvrZOe7syiDZSN1GWGZTGzc= -gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.5/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-20200615113413-eeeca48fe776/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/gotraceui v0.2.0 h1:dmNsfQ9Vl3GwbiVD7Z8d/osC6WtGGrasyrC2suc4ZIQ= -honnef.co/go/gotraceui v0.2.0/go.mod h1:qHo4/W75cA3bX0QQoSvDjbJa4R8mAyyFjbWAj63XElc= -modernc.org/libc v1.37.6 h1:orZH3c5wmhIQFTXF+Nt+eeauyd+ZIt2BX6ARe+kD+aw= -modernc.org/libc v1.37.6/go.mod h1:YAXkAZ8ktnkCKaN9sw/UDeUVkGYJ/YquGO4FTi5nmHE= -modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4= -modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo= -modernc.org/memory v1.7.2 h1:Klh90S215mmH8c9gO98QxQFsY+W451E8AnzjoE2ee1E= -modernc.org/memory v1.7.2/go.mod h1:NO4NVCQy0N7ln+T9ngWqOQfi7ley4vpwvARR+Hjw95E= -modernc.org/sqlite v1.28.0 h1:Zx+LyDDmXczNnEQdvPuEfcFVA2ZPyaD7UCZDjef3BHQ= -modernc.org/sqlite v1.28.0/go.mod h1:Qxpazz0zH8Z1xCFyi5GSL3FzbtZ3fvbjmywNogldEW0= diff --git a/internal/pkg/socialauth/google.go b/internal/pkg/socialauth/google.go new file mode 100644 index 00000000..54cd768c --- /dev/null +++ b/internal/pkg/socialauth/google.go @@ -0,0 +1,90 @@ +package socialauth + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "strings" + + "github.com/ayinke-llc/malak/config" + "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/trace" + "go.opentelemetry.io/otel/trace/noop" + "golang.org/x/oauth2" + "golang.org/x/oauth2/google" +) + +var tracer = otel.Tracer("malak.socialauth") +var noopTracer = noop.NewTracerProvider().Tracer("malak.socialauth") + +func getTracer(ctx context.Context, + operationName string, isTracingEnabled bool) (context.Context, trace.Span) { + + if !isTracingEnabled { + return noopTracer.Start(ctx, operationName) + } + + return tracer.Start(ctx, operationName) +} + +type googleAuthenticator struct { + cfg *oauth2.Config + client *http.Client + config config.Config +} + +func NewGoogle(cfg config.Config) SocialAuthProvider { + return &googleAuthenticator{ + config: cfg, + cfg: &oauth2.Config{ + ClientID: cfg.Auth.Google.ClientID, + ClientSecret: cfg.Auth.Google.ClientSecret, + Endpoint: google.Endpoint, + RedirectURL: cfg.Auth.Google.RedirectURI, + Scopes: cfg.Auth.Google.Scopes, + }, + client: &http.Client{ + Transport: otelhttp.NewTransport(http.DefaultTransport), + }, + } +} + +func (g *googleAuthenticator) Validate( + ctx context.Context, opts ValidateOptions) (*oauth2.Token, error) { + + ctx, span := getTracer(ctx, "google.Validate", g.config.Otel.IsEnabled) + defer span.End() + + return g.cfg.Exchange(ctx, opts.Code) +} + +func (g *googleAuthenticator) User(ctx context.Context, token *oauth2.Token) (User, error) { + + ctx, span := getTracer(ctx, "google.User", g.config.Otel.IsEnabled) + defer span.End() + + userInfoEndpoint := "https://www.googleapis.com/oauth2/v2/userinfo" + + urlEndpoint := fmt.Sprintf("%s?access_token=%s", userInfoEndpoint, token.AccessToken) + req, err := http.NewRequest(http.MethodGet, urlEndpoint, strings.NewReader("")) + if err != nil { + return User{}, err + } + + resp, err := g.client.Do(req.WithContext(ctx)) + if err != nil { + return User{}, err + } + + defer resp.Body.Close() + + user := User{} + + if err := json.NewDecoder(resp.Body).Decode(&user); err != nil { + return User{}, err + } + + return user, nil +} diff --git a/internal/pkg/socialauth/mocks/social.go b/internal/pkg/socialauth/mocks/social.go new file mode 100644 index 00000000..dd9746fe --- /dev/null +++ b/internal/pkg/socialauth/mocks/social.go @@ -0,0 +1,72 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: internal/pkg/socialauth/social.go +// +// Generated by this command: +// +// mockgen -source=internal/pkg/socialauth/social.go -destination=internal/pkg/socialauth/mocks/social.go -package=socialauth_mocks +// + +// Package socialauth_mocks is a generated GoMock package. +package socialauth_mocks + +import ( + context "context" + reflect "reflect" + + socialauth "github.com/ayinke-llc/malak/internal/pkg/socialauth" + gomock "go.uber.org/mock/gomock" + oauth2 "golang.org/x/oauth2" +) + +// MockSocialAuthProvider is a mock of SocialAuthProvider interface. +type MockSocialAuthProvider struct { + ctrl *gomock.Controller + recorder *MockSocialAuthProviderMockRecorder +} + +// MockSocialAuthProviderMockRecorder is the mock recorder for MockSocialAuthProvider. +type MockSocialAuthProviderMockRecorder struct { + mock *MockSocialAuthProvider +} + +// NewMockSocialAuthProvider creates a new mock instance. +func NewMockSocialAuthProvider(ctrl *gomock.Controller) *MockSocialAuthProvider { + mock := &MockSocialAuthProvider{ctrl: ctrl} + mock.recorder = &MockSocialAuthProviderMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockSocialAuthProvider) EXPECT() *MockSocialAuthProviderMockRecorder { + return m.recorder +} + +// User mocks base method. +func (m *MockSocialAuthProvider) User(arg0 context.Context, arg1 *oauth2.Token) (socialauth.User, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "User", arg0, arg1) + ret0, _ := ret[0].(socialauth.User) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// User indicates an expected call of User. +func (mr *MockSocialAuthProviderMockRecorder) User(arg0, arg1 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "User", reflect.TypeOf((*MockSocialAuthProvider)(nil).User), arg0, arg1) +} + +// Validate mocks base method. +func (m *MockSocialAuthProvider) Validate(arg0 context.Context, arg1 socialauth.ValidateOptions) (*oauth2.Token, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Validate", arg0, arg1) + ret0, _ := ret[0].(*oauth2.Token) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Validate indicates an expected call of Validate. +func (mr *MockSocialAuthProviderMockRecorder) Validate(arg0, arg1 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Validate", reflect.TypeOf((*MockSocialAuthProvider)(nil).Validate), arg0, arg1) +} diff --git a/internal/pkg/socialauth/social.go b/internal/pkg/socialauth/social.go new file mode 100644 index 00000000..8b87b1ff --- /dev/null +++ b/internal/pkg/socialauth/social.go @@ -0,0 +1,21 @@ +package socialauth + +import ( + "context" + + "golang.org/x/oauth2" +) + +type User struct { + Email string `json:"email,omitempty"` + Name string `json:"name,omitempty"` +} + +type ValidateOptions struct { + Code string +} + +type SocialAuthProvider interface { + User(context.Context, *oauth2.Token) (User, error) + Validate(context.Context, ValidateOptions) (*oauth2.Token, error) +} diff --git a/internal/pkg/util/strings.go b/internal/pkg/util/strings.go new file mode 100644 index 00000000..80fd7737 --- /dev/null +++ b/internal/pkg/util/strings.go @@ -0,0 +1,5 @@ +package util + +import "strings" + +func IsStringEmpty(s string) bool { return len(strings.TrimSpace(s)) == 0 } diff --git a/internal/pkg/util/strings_test.go b/internal/pkg/util/strings_test.go new file mode 100644 index 00000000..500fef2f --- /dev/null +++ b/internal/pkg/util/strings_test.go @@ -0,0 +1,23 @@ +package util + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestIsStringEmpty(t *testing.T) { + tt := []struct { + s string + empty bool + }{ + {"", true}, + {" ", true}, + {".", false}, + {". ff ", false}, + } + + for _, v := range tt { + require.Equal(t, v.empty, IsStringEmpty(v.s)) + } +} diff --git a/mocks/user.go b/mocks/user.go new file mode 100644 index 00000000..0e34ffad --- /dev/null +++ b/mocks/user.go @@ -0,0 +1,84 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: user.go +// +// Generated by this command: +// +// mockgen -source=user.go -destination=mocks/user.go -package=malak_mocks +// + +// Package malak_mocks is a generated GoMock package. +package malak_mocks + +import ( + context "context" + reflect "reflect" + + malak "github.com/ayinke-llc/malak" + gomock "go.uber.org/mock/gomock" +) + +// MockUserRepository is a mock of UserRepository interface. +type MockUserRepository struct { + ctrl *gomock.Controller + recorder *MockUserRepositoryMockRecorder +} + +// MockUserRepositoryMockRecorder is the mock recorder for MockUserRepository. +type MockUserRepositoryMockRecorder struct { + mock *MockUserRepository +} + +// NewMockUserRepository creates a new mock instance. +func NewMockUserRepository(ctrl *gomock.Controller) *MockUserRepository { + mock := &MockUserRepository{ctrl: ctrl} + mock.recorder = &MockUserRepositoryMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockUserRepository) EXPECT() *MockUserRepositoryMockRecorder { + return m.recorder +} + +// Create mocks base method. +func (m *MockUserRepository) Create(arg0 context.Context, arg1 *malak.User) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Create", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// Create indicates an expected call of Create. +func (mr *MockUserRepositoryMockRecorder) Create(arg0, arg1 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Create", reflect.TypeOf((*MockUserRepository)(nil).Create), arg0, arg1) +} + +// Get mocks base method. +func (m *MockUserRepository) Get(arg0 context.Context, arg1 *malak.FindUserOptions) (*malak.User, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Get", arg0, arg1) + ret0, _ := ret[0].(*malak.User) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Get indicates an expected call of Get. +func (mr *MockUserRepositoryMockRecorder) Get(arg0, arg1 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Get", reflect.TypeOf((*MockUserRepository)(nil).Get), arg0, arg1) +} + +// Update mocks base method. +func (m *MockUserRepository) Update(arg0 context.Context, arg1 *malak.User) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Update", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// Update indicates an expected call of Update. +func (mr *MockUserRepositoryMockRecorder) Update(arg0, arg1 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Update", reflect.TypeOf((*MockUserRepository)(nil).Update), arg0, arg1) +} diff --git a/plan.go b/plan.go new file mode 100644 index 00000000..e83f43fb --- /dev/null +++ b/plan.go @@ -0,0 +1,67 @@ +package malak + +import ( + "context" + "errors" + "time" + + "github.com/google/uuid" +) + +var ( + ErrPlanNotFound = errors.New("plan does not exists") + ErrCounterExhausted = errors.New("no more units left") +) + +type Counter int64 + +func (c Counter) Add() { c++ } + +func (c Counter) Take() error { + if c <= 0 { + return ErrCounterExhausted + } + + c-- + return nil +} + +func (c Counter) TakeN(n int64) error { + if c <= 0 { + return c.Take() + } + + c -= Counter(n) + return nil +} + +type PlanMetadata struct { + Team struct { + Size Counter `json:"size,omitempty"` + Enabled bool `json:"enabled,omitempty"` + } `json:"team,omitempty"` +} + +type Plan struct { + ID uuid.UUID `bun:"type:uuid,default:uuid_generate_v4()" json:"id,omitempty"` + + PlanName string `json:"plan_name,omitempty"` + + // Can use a fake id really + StripeReference string `json:"stripe_reference,omitempty"` + Metadata PlanMetadata `json:"metadata,omitempty" bson:"metadata"` + + CreatedAt time.Time `bun:",nullzero,notnull,default:current_timestamp" json:"created_at,omitempty" bson:"created_at"` + UpdatedAt time.Time `bun:",nullzero,notnull,default:current_timestamp" json:"updated_at,omitempty" bson:"updated_at"` + DeletedAt *time.Time `bun:",soft_delete,nullzero" json:"-,omitempty" bson:"deleted_at"` +} + +type FetchPlanOptions struct { + Reference string + ID uuid.UUID +} + +type PlanRepository interface { + Get(context.Context, *FetchPlanOptions) (*Plan, error) + List(context.Context) ([]*Plan, error) +} diff --git a/server/auth.go b/server/auth.go index abb4e431..a87cf0ac 100644 --- a/server/auth.go +++ b/server/auth.go @@ -1 +1,119 @@ package server + +import ( + "errors" + "net/http" + + "github.com/ayinke-llc/malak" + "github.com/ayinke-llc/malak/config" + "github.com/ayinke-llc/malak/internal/pkg/socialauth" + "github.com/ayinke-llc/malak/internal/pkg/util" + "github.com/go-chi/chi/v5" + "github.com/go-chi/render" + "github.com/sirupsen/logrus" + "go.opentelemetry.io/otel/attribute" +) + +type authHandler struct { + logger *logrus.Entry + googleCfg socialauth.SocialAuthProvider + cfg config.Config + userRepo malak.UserRepository +} + +type authenticateUserRequest struct { + GenericRequest + + Code string `json:"code,omitempty"` +} + +func (a *authenticateUserRequest) Validate() error { + if util.IsStringEmpty(a.Code) { + return errors.New("please provide a valid oauth2 code") + } + + return nil +} + +// @Summary Sign in with a social login provider +// @Tags auth +// @Accept json +// @Produce json +// @Param message body authenticateUserRequest true "auth exchange data" +// @Success 200 {object} createdUserResponse +// @Failure 400 {object} APIStatus +// @Failure 401 {object} APIStatus +// @Failure 404 {object} APIStatus +// @Failure 500 {object} APIStatus +// @Router /auth/login [post] +func (a *authHandler) Login(w http.ResponseWriter, r *http.Request) { + + ctx, span, rid := getTracer(r.Context(), r, "Login", a.cfg.Otel.IsEnabled) + defer span.End() + + provider := chi.URLParam(r, "provider") + + logger := a.logger.WithField("method", "login"). + WithField("request_id", rid). + WithField("provider", provider) + + span.SetAttributes(attribute.String("auth_provider", provider)) + + logger.Debug("Authenticating user") + + if provider != "google" { + _ = render.Render(w, r, newAPIStatus(http.StatusBadRequest, "unspported provider")) + return + } + + req := new(authenticateUserRequest) + + if err := render.Bind(r, req); err != nil { + _ = render.Render(w, r, newAPIStatus(http.StatusBadRequest, "invalid request body")) + return + } + + if err := req.Validate(); err != nil { + _ = render.Render(w, r, newAPIStatus(http.StatusBadRequest, err.Error())) + return + } + + token, err := a.googleCfg.Validate(ctx, socialauth.ValidateOptions{ + Code: req.Code, + }) + if err != nil { + logger.WithError(err).Error("could not exchange token") + _ = render.Render(w, r, newAPIStatus(http.StatusBadRequest, "could not verify your sign in with Google")) + return + } + + u, err := a.googleCfg.User(ctx, token) + if err != nil { + logger.WithError(err).Error("could not fetch user details") + _ = render.Render(w, r, newAPIStatus(http.StatusBadRequest, "could not fetch user details from oauth2 provider")) + return + } + + user := &malak.User{ + Email: malak.Email(u.Email), + FullName: u.Name, + Metadata: &malak.UserMetadata{}, + } + + err = a.userRepo.Create(ctx, user) + if errors.Is(err, malak.ErrUserExists) { + _ = render.Render(w, r, newAPIStatus(http.StatusBadRequest, err.Error())) + return + } + + if err != nil { + logger.WithError(err).Error("an error occurred while creating user") + _ = render.Render(w, r, newAPIStatus(http.StatusInternalServerError, "an error occurred while creating user")) + return + } + + _ = render.Render(w, r, createdUserResponse{ + User: user, + APIStatus: newAPIStatus(http.StatusOK, "user Successfully created"), + }) +} diff --git a/server/auth_test.go b/server/auth_test.go new file mode 100644 index 00000000..61960628 --- /dev/null +++ b/server/auth_test.go @@ -0,0 +1,268 @@ +package server + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "io" + "net/http" + "net/http/httptest" + "testing" + + "github.com/ayinke-llc/malak" + "github.com/ayinke-llc/malak/config" + "github.com/ayinke-llc/malak/internal/pkg/socialauth" + socialauth_mocks "github.com/ayinke-llc/malak/internal/pkg/socialauth/mocks" + malak_mocks "github.com/ayinke-llc/malak/mocks" + "github.com/go-chi/chi/v5" + "github.com/sebdah/goldie/v2" + "github.com/sirupsen/logrus" + "github.com/stretchr/testify/require" + "go.uber.org/mock/gomock" + "golang.org/x/oauth2" +) + +func verifyMatch(t *testing.T, v interface{}) { + g := goldie.New(t, goldie.WithFixtureDir("./testdata")) + + b := new(bytes.Buffer) + + if d, ok := v.(*httptest.ResponseRecorder); ok { + _, err := io.Copy(b, d.Body) + require.NoError(t, err) + } else { + err := json.NewEncoder(b).Encode(v) + require.NoError(t, err) + } + + g.Assert(t, t.Name(), b.Bytes()) +} + +func getConfig() config.Config { + return config.Config{ + Otel: struct { + Endpoint string "yaml:\"endpoint\" mapstructure:\"endpoint\"" + UseTLS bool "yaml:\"use_tls\" mapstructure:\"use_tls\"" + Headers string "yaml:\"headers\" mapstructure:\"headers\"" + IsEnabled bool "yaml:\"is_enabled\" mapstructure:\"is_enabled\"" + }{ + IsEnabled: false, + }, + } +} + +func TestAuthHandler_Login(t *testing.T) { + + tt := []struct { + name string + mockFn func(googleMock *socialauth_mocks.MockSocialAuthProvider, userRepo *malak_mocks.MockUserRepository) + expectedStatusCode int + req authenticateUserRequest + provider string + }{ + { + name: "no code to exchange provided", + mockFn: func(googleMock *socialauth_mocks.MockSocialAuthProvider, userRepo *malak_mocks.MockUserRepository) { + googleMock.EXPECT(). + Validate(gomock.Any(), gomock.Any()). + Times(0) + + userRepo.EXPECT(). + Create(gomock.Any(), gomock.Any()). + Times(0) + }, + provider: "google", + expectedStatusCode: http.StatusBadRequest, + req: authenticateUserRequest{}, + }, + { + name: "token exchange fails", + mockFn: func(googleMock *socialauth_mocks.MockSocialAuthProvider, userRepo *malak_mocks.MockUserRepository) { + googleMock.EXPECT(). + Validate(gomock.Any(), socialauth.ValidateOptions{ + Code: "invalid-token", + }). + Times(1). + Return(nil, errors.New("could not valdate token")) + + userRepo.EXPECT(). + Create(gomock.Any(), gomock.Any()). + Times(0) + }, + expectedStatusCode: http.StatusBadRequest, + req: authenticateUserRequest{ + Code: "invalid-token", + }, + provider: "google", + }, + { + name: "could not fetch user details", + mockFn: func(googleMock *socialauth_mocks.MockSocialAuthProvider, userRepo *malak_mocks.MockUserRepository) { + googleMock.EXPECT(). + Validate(gomock.Any(), socialauth.ValidateOptions{ + Code: "token", + }). + Times(1). + Return(&oauth2.Token{ + AccessToken: "access-token", + }, nil) + + googleMock.EXPECT(). + User(gomock.Any(), gomock.Any()). + Times(1). + Return(socialauth.User{}, errors.New("could not fetch user")) + + userRepo.EXPECT(). + Create(gomock.Any(), gomock.Any()). + Times(0) + }, + expectedStatusCode: http.StatusBadRequest, + req: authenticateUserRequest{ + Code: "token", + }, + provider: "google", + }, + { + name: "could not create user because of duplicate emails", + mockFn: func(googleMock *socialauth_mocks.MockSocialAuthProvider, userRepo *malak_mocks.MockUserRepository) { + googleMock.EXPECT(). + Validate(gomock.Any(), socialauth.ValidateOptions{ + Code: "token", + }). + Times(1). + Return(&oauth2.Token{ + AccessToken: "access-token", + }, nil) + + user := socialauth.User{ + Email: "test@test.com", + Name: "TEST TEST", + } + + googleMock.EXPECT(). + User(gomock.Any(), gomock.Any()). + Times(1). + Return(user, nil) + + userRepo.EXPECT(). + Create(gomock.Any(), gomock.Any()). + Times(1). + Return(malak.ErrUserExists) + }, + expectedStatusCode: http.StatusBadRequest, + req: authenticateUserRequest{ + Code: "token", + }, + provider: "google", + }, + { + name: "could not create user in datastore", + mockFn: func(googleMock *socialauth_mocks.MockSocialAuthProvider, userRepo *malak_mocks.MockUserRepository) { + googleMock.EXPECT(). + Validate(gomock.Any(), socialauth.ValidateOptions{ + Code: "token", + }). + Times(1). + Return(&oauth2.Token{ + AccessToken: "access-token", + }, nil) + + user := socialauth.User{ + Email: "test@test.com", + Name: "TEST TEST", + } + + googleMock.EXPECT(). + User(gomock.Any(), gomock.Any()). + Times(1). + Return(user, nil) + + userRepo.EXPECT(). + Create(gomock.Any(), gomock.Any()). + Times(1). + Return(errors.New("unknown error")) + }, + expectedStatusCode: http.StatusInternalServerError, + req: authenticateUserRequest{ + Code: "token", + }, + provider: "google", + }, + { + name: "user was succesfully created", + mockFn: func(googleMock *socialauth_mocks.MockSocialAuthProvider, userRepo *malak_mocks.MockUserRepository) { + googleMock.EXPECT(). + Validate(gomock.Any(), socialauth.ValidateOptions{ + Code: "token", + }). + Times(1). + Return(&oauth2.Token{ + AccessToken: "access-token", + }, nil) + + user := socialauth.User{ + Email: "test@test.com", + Name: "TEST TEST", + } + + googleMock.EXPECT(). + User(gomock.Any(), gomock.Any()). + Times(1). + Return(user, nil) + + userRepo.EXPECT(). + Create(gomock.Any(), gomock.Any()). + Times(1). + Return(nil) + }, + expectedStatusCode: http.StatusOK, + req: authenticateUserRequest{ + Code: "token", + }, + provider: "google", + }, + } + + for _, v := range tt { + + t.Run(v.name, func(t *testing.T) { + + logrus.SetOutput(io.Discard) + + logger := logrus.WithField("test", true) + + controller := gomock.NewController(t) + defer controller.Finish() + + googleCfg := socialauth_mocks.NewMockSocialAuthProvider(controller) + userRepo := malak_mocks.NewMockUserRepository(controller) + + v.mockFn(googleCfg, userRepo) + + a := &authHandler{ + logger: logger, + cfg: getConfig(), + googleCfg: googleCfg, + userRepo: userRepo, + } + + var b = bytes.NewBuffer(nil) + + require.NoError(t, json.NewEncoder(b).Encode(&v.req)) + + rr := httptest.NewRecorder() + + req := httptest.NewRequest(http.MethodPost, "/", b) + ctx := chi.NewRouteContext() + ctx.URLParams.Add("provider", v.provider) + req = req.WithContext(context.WithValue(req.Context(), chi.RouteCtxKey, ctx)) + req.Header.Add("Content-Type", "application/json") + + a.Login(rr, req) + + require.Equal(t, v.expectedStatusCode, rr.Code) + verifyMatch(t, rr) + }) + } +} diff --git a/server/http.go b/server/http.go index 85c749f1..950101e5 100644 --- a/server/http.go +++ b/server/http.go @@ -14,11 +14,10 @@ import ( func New(logger *logrus.Entry, cfg config.Config, - httpPort int, ) (*http.Server, func()) { srv := &http.Server{ Handler: buildRoutes(logger, cfg), - Addr: fmt.Sprintf(":%d", httpPort), + Addr: fmt.Sprintf(":%d", cfg.HTTP.Port), } return srv, initOTELCapabilities(cfg, logger) @@ -39,8 +38,7 @@ func (rw *responseWriter) WriteHeader(code int) { } func buildRoutes(logger *logrus.Entry, - cfg config.Config, -) http.Handler { + cfg config.Config) http.Handler { router := chi.NewRouter() @@ -50,8 +48,15 @@ func buildRoutes(logger *logrus.Entry, router.Use(jsonResponse) router.Use(otelchi.Middleware("malak.server", otelchi.WithChiRoutes(router))) + auth := &authHandler{ + logger: logger, + } + router.Route("/v1", func(r chi.Router) { + r.Route("/auth", func(r chi.Router) { + r.Post("/connect/{provider}", auth.Login) + }) }) return cors.AllowAll(). diff --git a/server/otel.go b/server/otel.go index 3073abc7..eac08416 100644 --- a/server/otel.go +++ b/server/otel.go @@ -19,16 +19,23 @@ import ( "go.opentelemetry.io/otel/sdk/resource" sdktrace "go.opentelemetry.io/otel/sdk/trace" "go.opentelemetry.io/otel/trace" + "go.opentelemetry.io/otel/trace/noop" ) var tracer = otel.Tracer("malak.server") +var noopTracer = noop.NewTracerProvider().Tracer("malak.server") func getTracer(ctx context.Context, r *http.Request, - operationName string) (context.Context, trace.Span, string) { + operationName string, isEnabled bool) (context.Context, trace.Span, string) { + + rid := retrieveRequestID(r) + if !isEnabled { + ctx, span := noopTracer.Start(ctx, operationName) + return ctx, span, rid + } ctx, span := tracer.Start(ctx, operationName) - rid := retrieveRequestID(r) span.SetAttributes(attribute.String("request_id", rid)) return ctx, span, rid } diff --git a/server/respons.go b/server/response.go similarity index 74% rename from server/respons.go rename to server/response.go index 442fdc30..64af3e30 100644 --- a/server/respons.go +++ b/server/response.go @@ -3,9 +3,14 @@ package server import ( "net/http" + "github.com/ayinke-llc/malak" "github.com/go-chi/render" ) +type GenericRequest struct{} + +func (g GenericRequest) Bind(_ *http.Request) error { return nil } + type meta struct { Paging pagingInfo `json:"paging"` } @@ -37,3 +42,8 @@ func newAPIStatus(code int, s string) APIStatus { Message: s, } } + +type createdUserResponse struct { + User *malak.User `json:"user,omitempty"` + APIStatus +} diff --git a/server/testdata/TestAuthHandler_Login/could_not_create_user_because_of_duplicate_emails.golden b/server/testdata/TestAuthHandler_Login/could_not_create_user_because_of_duplicate_emails.golden new file mode 100644 index 00000000..9cf08d0e --- /dev/null +++ b/server/testdata/TestAuthHandler_Login/could_not_create_user_because_of_duplicate_emails.golden @@ -0,0 +1 @@ +{"message":"User with email already exists"} diff --git a/server/testdata/TestAuthHandler_Login/could_not_create_user_in_datastore.golden b/server/testdata/TestAuthHandler_Login/could_not_create_user_in_datastore.golden new file mode 100644 index 00000000..5387b5fb --- /dev/null +++ b/server/testdata/TestAuthHandler_Login/could_not_create_user_in_datastore.golden @@ -0,0 +1 @@ +{"message":"an error occurred while creating user"} diff --git a/server/testdata/TestAuthHandler_Login/could_not_fetch_user_details.golden b/server/testdata/TestAuthHandler_Login/could_not_fetch_user_details.golden new file mode 100644 index 00000000..69f229d1 --- /dev/null +++ b/server/testdata/TestAuthHandler_Login/could_not_fetch_user_details.golden @@ -0,0 +1 @@ +{"message":"could not fetch user details from oauth2 provider"} diff --git a/server/testdata/TestAuthHandler_Login/no_code_to_exchange_provided.golden b/server/testdata/TestAuthHandler_Login/no_code_to_exchange_provided.golden new file mode 100644 index 00000000..2103c560 --- /dev/null +++ b/server/testdata/TestAuthHandler_Login/no_code_to_exchange_provided.golden @@ -0,0 +1 @@ +{"message":"please provide a valid oauth2 code"} diff --git a/server/testdata/TestAuthHandler_Login/token_exchange_fails.golden b/server/testdata/TestAuthHandler_Login/token_exchange_fails.golden new file mode 100644 index 00000000..1f86b5ec --- /dev/null +++ b/server/testdata/TestAuthHandler_Login/token_exchange_fails.golden @@ -0,0 +1 @@ +{"message":"could not verify your sign in with Google"} diff --git a/server/testdata/TestAuthHandler_Login/user_was_succesfully_created.golden b/server/testdata/TestAuthHandler_Login/user_was_succesfully_created.golden new file mode 100644 index 00000000..b92dd542 --- /dev/null +++ b/server/testdata/TestAuthHandler_Login/user_was_succesfully_created.golden @@ -0,0 +1 @@ +{"user":{"id":"00000000-0000-0000-0000-000000000000","email":"test@test.com","full_name":"TEST TEST","metadata":{"current_workspace":"00000000-0000-0000-0000-000000000000"},"roles":null,"created_at":"0001-01-01T00:00:00Z","updated_at":"0001-01-01T00:00:00Z"},"message":"user Successfully created"} diff --git a/swagger.go b/swagger.go new file mode 100644 index 00000000..68636b19 --- /dev/null +++ b/swagger.go @@ -0,0 +1,13 @@ +// @title Malak's API documentation +// @version 0.1.0 + +// @contact.name Ayinke Ventures +// @contact.email lanre@ayinke.ventures + +// @host malak.ayinke.ventures +// @BasePath /v1 + +// @securityDefinitions.apikey ApiKeyAuth +// @in header +// @name Authorization +package malak diff --git a/user.go b/user.go new file mode 100644 index 00000000..8f49701a --- /dev/null +++ b/user.go @@ -0,0 +1,79 @@ +package malak + +import ( + "context" + "database/sql/driver" + "strings" + "time" + + "github.com/google/uuid" + "github.com/uptrace/bun" +) + +const ( + ErrUserNotFound = malakError("user not found") + ErrUserExists = malakError("User with email already exists") +) + +// ENUM(admin,member,billing) +type Role string + +type UserRole struct { + ID uuid.UUID `bun:"type:uuid,default:uuid_generate_v4(),pk" json:"id,omitempty"` + + Role Role `json:"role,omitempty"` + + CreatedAt time.Time `json:"created_at,omitempty"` + UpdatedAt time.Time `json:"updated_at,omitempty"` + + WorkspaceID uuid.UUID `json:"workspace_id,omitempty"` + UserID uuid.UUID `json:"user_id,omitempty"` + + bun.BaseModel `bun:"table:roles" json:"-"` +} + +type UserRoles []*UserRole + +func (m UserRoles) IsEmpty() bool { return len(m) == 0 } + +type Email string + +func (e Email) String() string { return strings.ToLower(string(e)) } + +func (e Email) Value() (driver.Value, error) { return driver.Value(e.String()), nil } + +type UserMetadata struct { + // Used to keep track of the last used workspace + // In the instance of multiple workspaces + // So when next the user logs in, we remember and take them to the + // right place rather than always a list of all their workspaces and they + // have to select one + CurrentWorkspace uuid.UUID `json:"current_workspace"` +} + +type User struct { + ID uuid.UUID `bun:"type:uuid,default:uuid_generate_v4(),pk" json:"id,omitempty"` + Email Email `json:"email,omitempty"` + + FullName string `json:"full_name,omitempty"` + Metadata *UserMetadata `json:"metadata,omitempty" ` + + Roles UserRoles `json:"roles" bun:"rel:has-many,join:id=user_id"` + + CreatedAt time.Time `bun:",nullzero,notnull,default:current_timestamp" json:"created_at,omitempty" ` + UpdatedAt time.Time `bun:",nullzero,notnull,default:current_timestamp" json:"updated_at,omitempty" ` + DeletedAt *time.Time `bun:",soft_delete,nullzero" json:"-,omitempty" ` + + bun.BaseModel `bun:"table:roles" json:"-"` +} + +type FindUserOptions struct { + Email Email `json:"email,omitempty"` + ID uuid.UUID +} + +type UserRepository interface { + Create(context.Context, *User) error + Update(context.Context, *User) error + Get(context.Context, *FindUserOptions) (*User, error) +} diff --git a/user_enum.go b/user_enum.go new file mode 100644 index 00000000..e073a31f --- /dev/null +++ b/user_enum.go @@ -0,0 +1,49 @@ +// Code generated by go-enum DO NOT EDIT. +// Version: +// Revision: +// Build Date: +// Built By: + +package malak + +import ( + "errors" + "fmt" +) + +const ( + // RoleAdmin is a Role of type admin. + RoleAdmin Role = "admin" + // RoleMember is a Role of type member. + RoleMember Role = "member" + // RoleBilling is a Role of type billing. + RoleBilling Role = "billing" +) + +var ErrInvalidRole = errors.New("not a valid Role") + +// String implements the Stringer interface. +func (x Role) String() string { + return string(x) +} + +// IsValid provides a quick way to determine if the typed value is +// part of the allowed enumerated values +func (x Role) IsValid() bool { + _, err := ParseRole(string(x)) + return err == nil +} + +var _RoleValue = map[string]Role{ + "admin": RoleAdmin, + "member": RoleMember, + "billing": RoleBilling, +} + +// ParseRole attempts to convert a string to a Role. +func ParseRole(name string) (Role, error) { + if x, ok := _RoleValue[name]; ok { + return x, nil + } + return Role(""), fmt.Errorf("%s is %w", name, ErrInvalidRole) +} diff --git a/workspace.go b/workspace.go new file mode 100644 index 00000000..9fa2d510 --- /dev/null +++ b/workspace.go @@ -0,0 +1,52 @@ +package malak + +import ( + "context" + "errors" + "time" + + "github.com/google/uuid" + "github.com/teris-io/shortid" +) + +var ErrWorkspaceNotFound = errors.New("workspace not found") + +type Workspace struct { + ID uuid.UUID `bun:"type:uuid,default:uuid_generate_v4(),pk" json:"id,omitempty"` + + WorkspaceName string `json:"workspace_name,omitempty"` + PlanID uuid.UUID `json:"plan_id,omitempty"` + + // Not required + StripeCustomerID string `json:"stripe_customer_id,omitempty"` + SubscriptionID string `json:"subscription_id,omitempty"` + + Metadata PlanMetadata `json:"metadata,omitempty"` + IsActive bool `json:"is_active,omitempty"` + + CreatedAt time.Time `bun:",nullzero,notnull,default:current_timestamp" json:"created_at,omitempty" bson:"created_at"` + UpdatedAt time.Time `bun:",nullzero,notnull,default:current_timestamp" json:"updated_at,omitempty" bson:"updated_at"` + DeletedAt *time.Time `bun:",soft_delete,nullzero" json:"-,omitempty" bson:"deleted_at"` +} + +func NewWorkspace(u *User, plan *Plan) *Workspace { + return &Workspace{ + WorkspaceName: shortid.MustGenerate(), + Metadata: plan.Metadata, + PlanID: plan.ID, + } +} + +type FindWorkspaceOptions struct { + StripeCustomerID string + ID uuid.UUID +} + +type WorkspaceRepository interface { + Create(context.Context, *Workspace) error + Get(context.Context, *FindWorkspaceOptions) (*Workspace, error) + Update(context.Context, *Workspace) error + List(context.Context, *User) ([]Workspace, error) + MarkInActive(context.Context, *Workspace) error + MarkActive(context.Context, *Workspace) error +}