diff --git a/backend/auth/jwt.go b/backend/auth/jwt.go index c0c52a7c7..5aec8d5e4 100644 --- a/backend/auth/jwt.go +++ b/backend/auth/jwt.go @@ -18,22 +18,6 @@ type CustomClaims struct { Role string `json:"role"` } -// From extracts the CustomClaims from the fiber context -// Returns nil if the claims are not present -func From(c *fiber.Ctx) (*CustomClaims, error) { - rawClaims := c.Locals("claims") - if rawClaims == nil { - return nil, utilities.Forbidden() - } - - claims, ok := rawClaims.(*CustomClaims) - if !ok { - return nil, fmt.Errorf("claims are not of type CustomClaims. got: %T", rawClaims) - } - - return claims, nil -} - type JWTType string const ( diff --git a/backend/auth/locals.go b/backend/auth/locals.go new file mode 100644 index 000000000..8c1955655 --- /dev/null +++ b/backend/auth/locals.go @@ -0,0 +1,52 @@ +package auth + +import ( + "fmt" + + "github.com/GenerateNU/sac/backend/utilities" + "github.com/gofiber/fiber/v2" + "github.com/google/uuid" +) + +type localsKey byte + +const ( + claimsKey localsKey = 0 + userIDKey localsKey = 1 +) + +func CustomClaimsFrom(c *fiber.Ctx) (*CustomClaims, error) { + rawClaims := c.Locals(claimsKey) + if rawClaims == nil { + return nil, utilities.Forbidden() + } + + claims, ok := rawClaims.(*CustomClaims) + if !ok { + return nil, fmt.Errorf("claims are not of type CustomClaims. got: %T", rawClaims) + } + + return claims, nil +} + +func SetClaims(c *fiber.Ctx, claims *CustomClaims) { + c.Locals(claimsKey, claims) +} + +func UserIDFrom(c *fiber.Ctx) (*uuid.UUID, error) { + userID := c.Locals(userIDKey) + if userID == nil { + return nil, utilities.Forbidden() + } + + id, ok := userID.(*uuid.UUID) + if !ok { + return nil, fmt.Errorf("userID is not of type uuid.UUID. got: %T", userID) + } + + return id, nil +} + +func SetUserID(c *fiber.Ctx, id *uuid.UUID) { + c.Locals(userIDKey, id) +} diff --git a/backend/auth/password.go b/backend/auth/password.go index 724e63563..82856f1ef 100644 --- a/backend/auth/password.go +++ b/backend/auth/password.go @@ -16,6 +16,10 @@ func ValidatePassword(password string) error { errs = append(errs, "must be at least 8 characters long") } + if len(password) > 128 { // see https://github.com/OWASP/ASVS/issues/756 + errs = append(errs, "must be at most 128 characters long") + } + if !hasDigit(password) { errs = append(errs, "must contain at least one digit") } diff --git a/backend/config/config.go b/backend/config/config.go index ff19ecb23..ae7097431 100644 --- a/backend/config/config.go +++ b/backend/config/config.go @@ -8,15 +8,17 @@ import ( ) type Settings struct { - Application ApplicationSettings - Database DatabaseSettings - SuperUser SuperUserSettings - Auth AuthSettings - AWS AWSSettings - Pinecone PineconeSettings - OpenAI OpenAISettings - Resend ResendSettings - Calendar CalendarSettings + Application ApplicationSettings + Database DatabaseSettings + SuperUser SuperUserSettings + Auth AuthSettings + AWS AWSSettings + Pinecone PineconeSettings + OpenAI OpenAISettings + Resend ResendSettings + Calendar CalendarSettings + GoogleSettings OAuthSettings + OutlookSettings OAuthSettings } type intermediateSettings struct { diff --git a/backend/config/local.go b/backend/config/local.go index 864140f4f..fa6e883ac 100644 --- a/backend/config/local.go +++ b/backend/config/local.go @@ -65,5 +65,19 @@ func readLocal(v *viper.Viper, path string, useDevDotEnv bool) (*Settings, error settings.Resend = *resendSettings + googleSettings, err := readGoogleOAuthSettings() + if err != nil { + return nil, fmt.Errorf("failed to read Google OAuth settings: %w", err) + } + + settings.GoogleSettings = *googleSettings + + outlookSettings, err := readOutlookOAuthSettings() + if err != nil { + return nil, fmt.Errorf("failed to read Outlook OAuth settings: %w", err) + } + + settings.OutlookSettings = *outlookSettings + return settings, nil } diff --git a/backend/config/oauth.go b/backend/config/oauth.go new file mode 100644 index 000000000..4c7ac44a7 --- /dev/null +++ b/backend/config/oauth.go @@ -0,0 +1,102 @@ +package config + +import ( + "errors" + "os" + + m "github.com/garrettladley/mattress" +) + +type OAuthSettings struct { + BaseURL string + TokenURL string + ClientID *m.Secret[string] + ClientSecret *m.Secret[string] + Scopes string + RedirectURI string + ResponseType string + ResponseMode string + AccessType string + IncludeGrantedScopes string + Prompt string +} + +type OAuthResources struct { + GoogleOAuthSettings *OAuthSettings + OutlookOAuthSettings *OAuthSettings +} + +/** + * GOOGLE +**/ +func readGoogleOAuthSettings() (*OAuthSettings, error) { + clientID := os.Getenv("GOOGLE_OAUTH_CLIENT_ID") + if clientID == "" { + return nil, errors.New("GOOGLE_OAUTH_CLIENT_ID is not set") + } + + secretClientID, err := m.NewSecret(clientID) + if err != nil { + return nil, errors.New("failed to create secret from client ID") + } + + clientSecret := os.Getenv("GOOGLE_OAUTH_CLIENT_SECRET") + if clientSecret == "" { + return nil, errors.New("GOOGLE_OAUTH_CLIENT_SECRET is not set") + } + + secretClientSecret, err := m.NewSecret(clientSecret) + if err != nil { + return nil, errors.New("failed to create secret from client secret") + } + + return &OAuthSettings{ + BaseURL: "https://accounts.google.com/o/oauth2/v2", + TokenURL: "https://oauth2.googleapis.com", + ClientID: secretClientID, + ClientSecret: secretClientSecret, + Scopes: "https://www.googleapis.com/auth/calendar.events https://www.googleapis.com/auth/calendar.readonly", + ResponseType: "code", + RedirectURI: "http://localhost:3000", + IncludeGrantedScopes: "true", + AccessType: "offline", + Prompt: "consent", + }, nil +} + +/** + * OUTLOOK +**/ +func readOutlookOAuthSettings() (*OAuthSettings, error) { + clientID := os.Getenv("OUTLOOK_OAUTH_CLIENT_ID") + if clientID == "" { + return nil, errors.New("OUTLOOK_OAUTH_CLIENT_ID is not set") + } + + secretClientID, err := m.NewSecret(clientID) + if err != nil { + return nil, errors.New("failed to create secret from client ID") + } + + clientSecret := os.Getenv("OUTLOOK_OAUTH_CLIENT_SECRET") + if clientSecret == "" { + return nil, errors.New("OUTLOOK_OAUTH_CLIENT_SECRET is not set") + } + + secretClientSecret, err := m.NewSecret(clientSecret) + if err != nil { + return nil, errors.New("failed to create secret from client secret") + } + + return &OAuthSettings{ + BaseURL: "https://login.microsoftonline.com/common/oauth2/v2.0", + TokenURL: "https://login.microsoftonline.com/common/oauth2/v2.0", + ClientID: secretClientID, + ClientSecret: secretClientSecret, + Scopes: "offline_access user.read calendars.readwrite", + ResponseType: "code", + RedirectURI: "http://localhost:3000", + ResponseMode: "query", + Prompt: "consent", + }, nil +} diff --git a/backend/config/production.go b/backend/config/production.go index 43e10f0f4..8045320e2 100644 --- a/backend/config/production.go +++ b/backend/config/production.go @@ -98,6 +98,16 @@ func readProd(v *viper.Viper) (*Settings, error) { return nil, fmt.Errorf("failed to read Resend settings: %w", err) } + googleSettings, err := readGoogleOAuthSettings() + if err != nil { + return nil, fmt.Errorf("failed to read Google OAuth settings: %w", err) + } + + outlookSettings, err := readOutlookOAuthSettings() + if err != nil { + return nil, fmt.Errorf("failed to read Outlook OAuth settings: %w", err) + } + return &Settings{ Application: ApplicationSettings{ Port: uint16(portInt), @@ -119,10 +129,12 @@ func readProd(v *viper.Viper) (*Settings, error) { AccessKey: authAccessKey, RefreshKey: authRefreshKey, }, - Pinecone: *pineconeSettings, - OpenAI: *openAISettings, - AWS: *awsSettings, - Resend: *resendSettings, - Calendar: prodSettings.Calendar, + Pinecone: *pineconeSettings, + OpenAI: *openAISettings, + AWS: *awsSettings, + Resend: *resendSettings, + Calendar: prodSettings.Calendar, + GoogleSettings: *googleSettings, + OutlookSettings: *outlookSettings, }, nil } diff --git a/backend/constants/auth.go b/backend/constants/auth.go index 9cd14511f..9a014df3b 100644 --- a/backend/constants/auth.go +++ b/backend/constants/auth.go @@ -5,6 +5,7 @@ import "time" const ( ACCESS_TOKEN_EXPIRY time.Duration = time.Minute * 24 * 30 // temporary TODO: change to 60 minutes REFRESH_TOKEN_EXPIRY time.Duration = time.Minute * 24 * 30 + CSRF_TOKEN_LENGTH int = 32 ) var SPECIAL_CHARACTERS = []rune{' ', '!', '"', '#', '$', '%', '&', '\'', '(', ')', '*', '+', ',', '-', '.', '/', ':', ';', '<', '=', '>', '?', '@', '[', '\\', ']', '^', '_', '`', '{', '|', '}', '~'} // see https://owasp.org/www-community/password-special-characters diff --git a/backend/entities/auth/base/models.go b/backend/entities/auth/base/models.go index a15a48349..0657ac760 100644 --- a/backend/entities/auth/base/models.go +++ b/backend/entities/auth/base/models.go @@ -7,8 +7,8 @@ type VerifyEmailRequestBody struct { type VerifyPasswordResetTokenRequestBody struct { Token string `json:"token" validate:"required"` - NewPassword string `json:"new_password" validate:"required,min=8,password"` - VerifyNewPassword string `json:"verify_new_password" validate:"required,min=8,password,eqfield=NewPassword"` + NewPassword string `json:"new_password" validate:"required"` // MARK: must be validated manually + VerifyNewPassword string `json:"verify_new_password" validate:"required,eqfield=NewPassword"` // MARK: must be validated manually } type EmailRequestBody struct { diff --git a/backend/entities/auth/models.go b/backend/entities/auth/models.go index 7272b50d8..dc65fbb76 100644 --- a/backend/entities/auth/models.go +++ b/backend/entities/auth/models.go @@ -2,12 +2,12 @@ package auth type LoginResponseBody struct { Email string `json:"email" validate:"required,email"` - Password string `json:"password" validate:"required,max=255"` // MARK: must be validated manually + Password string `json:"password" validate:"required"` // MARK: must be validated manually } type UpdatePasswordRequestBody struct { - OldPassword string `json:"old_password" validate:"required,max=255"` // MARK: must be validated manually - NewPassword string `json:"new_password" validate:"required,not_equal_if_not_empty=OldPassword,max=255"` // MARK: must be validated manually + OldPassword string `json:"old_password" validate:"required"` // MARK: must be validated manually + NewPassword string `json:"new_password" validate:"required,not_equal_if_not_empty=OldPassword"` // MARK: must be validated manually } type RefreshTokenRequestBody struct { diff --git a/backend/entities/models/oauth.go b/backend/entities/models/oauth.go new file mode 100644 index 000000000..acb736504 --- /dev/null +++ b/backend/entities/models/oauth.go @@ -0,0 +1,35 @@ +package models + +import ( + "time" + + "github.com/google/uuid" +) + +type OAuthResource string + +const ( + Google OAuthResource = "google" + Outlook OAuthResource = "outlook" +) + +type UserOAuthTokens struct { + UserID uuid.UUID `json:"user_id" validate:"required,uuid4"` + RefreshToken string `json:"refresh_token" validate:"max=255"` + AccessToken string `json:"access_token" validate:"max=255"` + CSRFToken string `json:"csrf_token" validate:"max=255"` + ResourceType OAuthResource `json:"resource_type" validate:"required"` + ExpiresAt time.Time `json:"expires_at" validate:"required"` +} + +type OAuthToken struct { + AccessToken string `json:"access_token" validate:"required"` + ExpiresIn int `json:"expires_in" validate:"required"` + RefreshToken string `json:"refresh_token" validate:"required"` + Scope string `json:"scope" validate:"required"` + TokenType string `json:"token_type" validate:"required"` +} + +func (UserOAuthTokens) TableName() string { + return "user_oauth_tokens" +} diff --git a/backend/entities/oauth/base/controller.go b/backend/entities/oauth/base/controller.go new file mode 100644 index 000000000..3a3e3d0c5 --- /dev/null +++ b/backend/entities/oauth/base/controller.go @@ -0,0 +1,89 @@ +package base + +import ( + "errors" + "net/http" + + "github.com/GenerateNU/sac/backend/auth" + "github.com/GenerateNU/sac/backend/entities/models" + "github.com/gofiber/fiber/v2" +) + +type OAuthController struct { + OAuthService OAuthServiceInterface +} + +func NewOAuthController(oauthService OAuthServiceInterface) *OAuthController { + return &OAuthController{OAuthService: oauthService} +} + +func (oc *OAuthController) Authorize(c *fiber.Ctx) error { + // Extract the resource type from the query params: + resourceType := models.OAuthResource(c.Query("type")) + if resourceType == "" { + return errors.New("resource type is required") + } + + // Extract the user making the call: + userID, err := auth.UserIDFrom(c) + if err != nil { + return err + } + + // Call the respective authorize method: + authUrl, err := oc.OAuthService.Authorize(*userID, resourceType) + if err != nil { + return err + } + + return c.Status(http.StatusOK).JSON(*authUrl) +} + +func (oc *OAuthController) Token(c *fiber.Ctx) error { + // Extract the resource type from the query params: + resourceType := models.OAuthResource(c.Query("type")) + if resourceType == "" { + return errors.New("resource type is required") + } + + // Parse the body of the request: + var tokenBody OAuthTokenRequestBody + if err := c.BodyParser(&tokenBody); err != nil { + return err + } + + // Extract the user making the call: + userID, err := auth.UserIDFrom(c) + if err != nil { + return err + } + + // Call the respective token method: + err = oc.OAuthService.Token(*userID, tokenBody, resourceType) + if err != nil { + return err + } + + return c.SendStatus(http.StatusNoContent) +} + +func (oc *OAuthController) Revoke(c *fiber.Ctx) error { + // Extract the resource type from the query params: + resourceType := models.OAuthResource(c.Query("type")) + if resourceType == "" { + return errors.New("resource type is required") + } + + // Extract the user making the call: + userID, err := auth.UserIDFrom(c) + if err != nil { + return err + } + + err = oc.OAuthService.Revoke(*userID, resourceType) + if err != nil { + return err + } + + return c.Status(http.StatusOK).JSON("Successfully revoked token") +} diff --git a/backend/entities/oauth/base/models.go b/backend/entities/oauth/base/models.go new file mode 100644 index 000000000..ae04dca26 --- /dev/null +++ b/backend/entities/oauth/base/models.go @@ -0,0 +1,6 @@ +package base + +type OAuthTokenRequestBody struct { + Code string `json:"code" validate:"omitempty"` + State string `json:"state" validate:"omitempty"` +} diff --git a/backend/entities/oauth/base/routes.go b/backend/entities/oauth/base/routes.go new file mode 100644 index 000000000..fc0971e22 --- /dev/null +++ b/backend/entities/oauth/base/routes.go @@ -0,0 +1,14 @@ +package base + +import "github.com/GenerateNU/sac/backend/types" + +func OAuth(params types.RouteParams) { + oauthController := NewOAuthController(&OAuthService{ServiceParams: params.ServiceParams}) + + // api/v1/calendar/* + calendar := params.Router.Group("/oauth") + + calendar.Get("/authorize", oauthController.Authorize) + calendar.Post("/token", oauthController.Token) + calendar.Delete("/revoke", oauthController.Revoke) +} diff --git a/backend/entities/oauth/base/service.go b/backend/entities/oauth/base/service.go new file mode 100644 index 000000000..7da6d6cfb --- /dev/null +++ b/backend/entities/oauth/base/service.go @@ -0,0 +1,194 @@ +package base + +import ( + "errors" + "fmt" + "io" + "net/http" + "net/url" + + go_json "github.com/goccy/go-json" + + "github.com/GenerateNU/sac/backend/auth" + "github.com/GenerateNU/sac/backend/constants" + "github.com/GenerateNU/sac/backend/entities/models" + "github.com/GenerateNU/sac/backend/integrations/oauth" + "github.com/GenerateNU/sac/backend/types" + "github.com/GenerateNU/sac/backend/utilities" + "github.com/google/uuid" +) + +type OAuthServiceInterface interface { + Authorize(userID uuid.UUID, resource models.OAuthResource) (*string, error) + Token(userID uuid.UUID, tokenBody OAuthTokenRequestBody, resource models.OAuthResource) error + Revoke(userID uuid.UUID, resource models.OAuthResource) error +} + +type OAuthService struct { + ServiceParams types.ServiceParams +} + +func New(serviceParams types.ServiceParams) OAuthServiceInterface { + return &OAuthService{ + ServiceParams: serviceParams, + } +} + +func (e *OAuthService) Authorize(userID uuid.UUID, resource models.OAuthResource) (*string, error) { + // Parse the resource client: + client, err := e.parseResourceClient(resource) + if err != nil { + return nil, err + } + + // Create a CSRF Token: + csrfToken, err := auth.GenerateURLSafeToken(constants.CSRF_TOKEN_LENGTH) + if err != nil { + return nil, err + } + + // Save the CSRF Token to the database: + if err := CreateOAuthToken(e.ServiceParams.DB, + models.UserOAuthTokens{ + UserID: userID, + CSRFToken: *csrfToken, + ResourceType: resource, + }); err != nil { + return nil, err + } + + // Retrieve the authorize URL: + authorizeURL := client.ResourceClient.AuthorizeURL(*csrfToken) + + return &authorizeURL, nil +} + +func (e *OAuthService) Token(userID uuid.UUID, tokenBody OAuthTokenRequestBody, resource models.OAuthResource) error { + // Parse the resource client: + client, err := e.parseResourceClient(resource) + if err != nil { + return err + } + + // Retrieve the CSRF Token from the database: + oauthToken, err := GetOAuthToken(e.ServiceParams.DB, userID, resource) + if err != nil { + return err + } + + // Validate the CSRF Token: + if oauthToken.CSRFToken != tokenBody.State || oauthToken.CSRFToken == "" { + return errors.New("invalid CSRF token") + } + + // Exchange the code for an access token: + token, err := e.tokenExchange(client, tokenBody.Code) + if err != nil { + return err + } + + // Save the refresh token to the database: + if err := SaveOAuthTokens(e.ServiceParams.DB, userID, *token, resource); err != nil { + return err + } + + return nil +} + +func (e *OAuthService) Revoke(userID uuid.UUID, resource models.OAuthResource) error { + // Parse the resource client: + client, err := e.parseResourceClient(resource) + if err != nil { + return err + } + + // Retrieve access token: + oauthToken, err := GetOAuthToken(e.ServiceParams.DB, userID, resource) + if err != nil { + return err + } + + if oauthToken.AccessToken == "" { + return errors.New("user does not have an access token") + } + + // Revoke the token on resource server: + if err := client.ResourceClient.Revoke(oauthToken.AccessToken); err != nil { + return err + } + + // Remove refresh token from the database: + if err := DeleteOAuthToken(e.ServiceParams.DB, userID, resource); err != nil { + return err + } + + return nil +} + +func (e *OAuthService) parseResourceClient(resourceType models.OAuthResource) (*oauth.OAuthClient, error) { + var resourceClient oauth.OAuthResourceClientInterface + switch resourceType { + case "google": + resourceClient = &e.ServiceParams.Integrations.OAuth.Google + case "outlook": + resourceClient = &e.ServiceParams.Integrations.OAuth.Outlook + default: + return nil, errors.New("invalid resource type") + } + + return &oauth.OAuthClient{ + ResourceClient: resourceClient, + }, nil +} + +func (e *OAuthService) tokenExchange(client *oauth.OAuthClient, code string) (*models.OAuthToken, error) { + // Retrieve the client config + config := client.ResourceClient.GetConfig() + tokenUrl := fmt.Sprintf("%s/token", config.TokenURL) + + // Create the form data: + formData := url.Values{ + "client_id": {config.ClientID.Expose()}, + "client_secret": {config.ClientSecret.Expose()}, + "code": {code}, + "redirect_uri": {config.RedirectURI}, + "grant_type": {"authorization_code"}, + } + + encodedData := formData.Encode() + + resp, err := utilities.Request(http.MethodPost, tokenUrl, []byte(encodedData), utilities.FormURLEncoded()) + if err != nil { + return nil, err + } + + if !utilities.IsOk(resp.StatusCode) { + return nil, errors.New("failed to exchange code for token") + } + defer resp.Body.Close() + + body, err := io.ReadAll(resp.Body) + if err != nil { + return nil, err + } + + // Parse the data: + var oauthToken models.OAuthToken + if err = go_json.Unmarshal(body, &oauthToken); err != nil { + return nil, err + } + + if err := utilities.Validate(e.ServiceParams.Validate, oauthToken); err != nil { + return nil, err + } + + if oauthToken.AccessToken == "" { + return nil, errors.New("access token is empty") + } + + if oauthToken.RefreshToken == "" { + return nil, errors.New("refresh token is empty") + } + + return &oauthToken, nil +} diff --git a/backend/entities/oauth/base/transactions.go b/backend/entities/oauth/base/transactions.go new file mode 100644 index 000000000..f2433369d --- /dev/null +++ b/backend/entities/oauth/base/transactions.go @@ -0,0 +1,59 @@ +package base + +import ( + "errors" + "time" + + "github.com/GenerateNU/sac/backend/entities/models" + "github.com/GenerateNU/sac/backend/utilities" + "github.com/google/uuid" + "gorm.io/gorm" +) + +func CreateOAuthToken(db *gorm.DB, oauthToken models.UserOAuthTokens) error { + if err := db.Where("user_id = ? AND resource_type = ?", oauthToken.UserID, oauthToken.ResourceType).Save(&oauthToken).Error; err != nil { + return err + } + + return nil +} + +func GetOAuthToken(db *gorm.DB, userID uuid.UUID, resourceType models.OAuthResource) (*models.UserOAuthTokens, error) { + var oauthToken models.UserOAuthTokens + + if err := db.Where("user_id = ? AND resource_type = ?", userID, resourceType).First(&oauthToken).Error; err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return nil, utilities.ErrNotFound + } + return nil, err + } + + return &oauthToken, nil +} + +func SaveOAuthTokens(db *gorm.DB, userID uuid.UUID, tokens models.OAuthToken, resourceType models.OAuthResource) error { + oauthToken := models.UserOAuthTokens{ + UserID: userID, + AccessToken: tokens.AccessToken, + RefreshToken: tokens.RefreshToken, + ExpiresAt: time.Now().Add(time.Duration(tokens.ExpiresIn) * time.Second), + ResourceType: resourceType, + } + + if err := db.Where("user_id = ? AND resource_type = ?", userID, resourceType).Save(&oauthToken).Error; err != nil { + return err + } + + return nil +} + +func DeleteOAuthToken(db *gorm.DB, userID uuid.UUID, resourceType models.OAuthResource) error { + if err := db.Where("user_id = ? AND resource_type = ?", userID, resourceType).Delete(&models.UserOAuthTokens{}).Error; err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return utilities.ErrNotFound + } + return err + } + + return nil +} diff --git a/backend/entities/users/base/controller.go b/backend/entities/users/base/controller.go index 2a158273c..47ca36ac5 100644 --- a/backend/entities/users/base/controller.go +++ b/backend/entities/users/base/controller.go @@ -63,12 +63,12 @@ func (u *UserController) GetUsers(c *fiber.Ctx) error { // @Failure 500 {object} error // @Router /auth/me [get] func (u *UserController) GetMe(c *fiber.Ctx) error { - claims, err := auth.From(c) + userID, err := auth.UserIDFrom(c) if err != nil { return err } - user, err := u.userService.GetMe(claims.Issuer) + user, err := u.userService.GetMe(*userID) if err != nil { return err } diff --git a/backend/entities/users/base/service.go b/backend/entities/users/base/service.go index 92b03cfa2..15dfa8bd5 100644 --- a/backend/entities/users/base/service.go +++ b/backend/entities/users/base/service.go @@ -7,6 +7,7 @@ import ( authEntities "github.com/GenerateNU/sac/backend/entities/auth" "github.com/GenerateNU/sac/backend/entities/models" "github.com/garrettladley/fiberpaginate" + "github.com/google/uuid" "github.com/GenerateNU/sac/backend/entities/users" "github.com/GenerateNU/sac/backend/types" @@ -15,7 +16,7 @@ import ( type UserServiceInterface interface { GetUsers(pageInfo fiberpaginate.PageInfo) ([]models.User, error) - GetMe(id string) (*models.User, error) + GetMe(id uuid.UUID) (*models.User, error) GetUser(id string) (*models.User, error) UpdateUser(id string, userBody UpdateUserRequestBody) (*models.User, error) UpdatePassword(id string, passwordBody authEntities.UpdatePasswordRequestBody) error @@ -34,18 +35,8 @@ func (u *UserService) GetUsers(pageInfo fiberpaginate.PageInfo) ([]models.User, return GetUsers(u.DB, pageInfo) } -func (u *UserService) GetMe(id string) (*models.User, error) { - idAsUUID, err := utilities.ValidateID(id) - if err != nil { - return nil, err - } - - user, err := users.GetUser(u.DB, *idAsUUID) - if err != nil { - return nil, err - } - - return user, nil +func (u *UserService) GetMe(id uuid.UUID) (*models.User, error) { + return users.GetUser(u.DB, id) } func (u *UserService) GetUser(id string) (*models.User, error) { diff --git a/backend/integrations/expose.go b/backend/integrations/expose.go index 244fff701..aee8dca51 100644 --- a/backend/integrations/expose.go +++ b/backend/integrations/expose.go @@ -3,6 +3,7 @@ package integrations import ( "github.com/GenerateNU/sac/backend/integrations/email" "github.com/GenerateNU/sac/backend/integrations/file" + "github.com/GenerateNU/sac/backend/integrations/oauth" "github.com/GenerateNU/sac/backend/integrations/search" ) @@ -11,4 +12,5 @@ type Integrations struct { AI search.AIClientInterface Email email.EmailClientInterface File file.FileClientInterface + OAuth oauth.OAuthResources } diff --git a/backend/integrations/oauth/google.go b/backend/integrations/oauth/google.go new file mode 100644 index 000000000..ce24a521b --- /dev/null +++ b/backend/integrations/oauth/google.go @@ -0,0 +1,46 @@ +package oauth + +import ( + "errors" + "fmt" + "net/http" + "net/url" + + "github.com/GenerateNU/sac/backend/config" + "github.com/GenerateNU/sac/backend/utilities" +) + +type GoogleOAuthClient struct { + OAuthConfig config.OAuthSettings +} + +func (client *GoogleOAuthClient) AuthorizeURL(state string) string { + values := url.Values{} + values.Set("client_id", client.OAuthConfig.ClientID.Expose()) + values.Set("redirect_uri", client.OAuthConfig.RedirectURI) + values.Set("response_type", client.OAuthConfig.ResponseType) + values.Set("scope", client.OAuthConfig.Scopes) + values.Set("access_type", client.OAuthConfig.AccessType) + values.Set("include_granted_scopes", client.OAuthConfig.IncludeGrantedScopes) + values.Set("prompt", client.OAuthConfig.Prompt) + values.Set("state", state) + + return fmt.Sprintf("%s/auth?%s", client.OAuthConfig.BaseURL, values.Encode()) +} + +func (client *GoogleOAuthClient) GetConfig() config.OAuthSettings { + return client.OAuthConfig +} + +func (client *GoogleOAuthClient) Revoke(token string) error { + resp, err := utilities.Request(http.MethodPost, fmt.Sprintf("%s/revoke?token=%s", client.OAuthConfig.TokenURL, token), nil, utilities.FormURLEncoded()) + if err != nil { + return err + } + + if !utilities.IsOk(resp.StatusCode) { + return errors.New("failed to revoke token") + } + + return nil +} diff --git a/backend/integrations/oauth/oauth.go b/backend/integrations/oauth/oauth.go new file mode 100644 index 000000000..14b34339f --- /dev/null +++ b/backend/integrations/oauth/oauth.go @@ -0,0 +1,18 @@ +package oauth + +import "github.com/GenerateNU/sac/backend/config" + +type OAuthResourceClientInterface interface { + AuthorizeURL(state string) string + GetConfig() config.OAuthSettings + Revoke(token string) error +} + +type OAuthClient struct { + ResourceClient OAuthResourceClientInterface +} + +type OAuthResources struct { + Outlook OutlookOAuthClient + Google GoogleOAuthClient +} diff --git a/backend/integrations/oauth/outlook.go b/backend/integrations/oauth/outlook.go new file mode 100644 index 000000000..df6bc2cb0 --- /dev/null +++ b/backend/integrations/oauth/outlook.go @@ -0,0 +1,34 @@ +package oauth + +import ( + "fmt" + "net/url" + + "github.com/GenerateNU/sac/backend/config" +) + +type OutlookOAuthClient struct { + OAuthConfig config.OAuthSettings +} + +func (client *OutlookOAuthClient) AuthorizeURL(state string) string { + values := url.Values{} + values.Set("client_id", client.OAuthConfig.ClientID.Expose()) + values.Set("redirect_uri", client.OAuthConfig.RedirectURI) + values.Set("response_type", client.OAuthConfig.ResponseType) + values.Set("response_mode", client.OAuthConfig.ResponseMode) + values.Set("scope", client.OAuthConfig.Scopes) + values.Set("access_type", client.OAuthConfig.AccessType) + values.Set("state", state) + + return fmt.Sprintf("%s/authorize?%s", client.OAuthConfig.BaseURL, values.Encode()) +} + +func (client *OutlookOAuthClient) GetConfig() config.OAuthSettings { + return client.OAuthConfig +} + +func (client *OutlookOAuthClient) Revoke(token string) error { + // Outlook does not expose a revokation endpoint :/ + return nil +} diff --git a/backend/integrations/search/ai.go b/backend/integrations/search/ai.go index cc84d26ff..127cee913 100644 --- a/backend/integrations/search/ai.go +++ b/backend/integrations/search/ai.go @@ -1,7 +1,6 @@ package search import ( - "bytes" "errors" "fmt" "net/http" @@ -11,7 +10,6 @@ import ( "github.com/GenerateNU/sac/backend/config" "github.com/GenerateNU/sac/backend/utilities" - "github.com/gofiber/fiber/v2" ) type AIClientInterface interface { @@ -55,24 +53,11 @@ func (c *OpenAIClient) CreateEmbedding(items []Searchable) ([]Embedding, error) return nil, fmt.Errorf("failed to create embedding request body: %w", err) } - req, err := http.NewRequest(fiber.MethodPost, - "https://api.openai.com/v1/embeddings", - bytes.NewBuffer(embeddingBody)) + resp, err := utilities.Request(http.MethodPost, "https://api.openai.com/v1/embeddings", embeddingBody, openAIModifiers(c.Settings)...) if err != nil { return nil, fmt.Errorf("failed to create embedding: %w", err) } - req = utilities.ApplyModifiers(req, - utilities.Authorization(c.Settings.APIKey.Expose()), - utilities.JSON(), - ) - - resp, err := http.DefaultClient.Do(req) - if err != nil { - return nil, fmt.Errorf("failed to create embedding: %w", err) - } - defer resp.Body.Close() - var embeddingResultBody CreateEmbeddingResponseBody err = json.NewDecoder(resp.Body).Decode(&embeddingResultBody) @@ -115,23 +100,10 @@ func (c *OpenAIClient) CreateModeration(items []Searchable) ([]ModerationResult, return nil, fmt.Errorf("failed to create moderation request body: %w", err) } - req, err := http.NewRequest(fiber.MethodPost, - "https://api.openai.com/v1/moderations", - bytes.NewBuffer(moderationBody)) - if err != nil { - return nil, fmt.Errorf("failed to create moderation: %w", err) - } - - req = utilities.ApplyModifiers(req, - utilities.Authorization(c.Settings.APIKey.Expose()), - utilities.JSON(), - ) - - resp, err := http.DefaultClient.Do(req) + resp, err := utilities.Request(http.MethodPost, "https://api.openai.com/v1/moderations", moderationBody, openAIModifiers(c.Settings)...) if err != nil { return nil, fmt.Errorf("failed to create moderation: %w", err) } - defer resp.Body.Close() var moderationResultBody CreateModerationResponseBody @@ -146,3 +118,10 @@ func (c *OpenAIClient) CreateModeration(items []Searchable) ([]ModerationResult, return moderationResultBody.Results, nil } + +func openAIModifiers(settings config.OpenAISettings) []utilities.RequestModifier { + return []utilities.RequestModifier{ + utilities.Authorization(settings.APIKey.Expose()), + utilities.JSON(), + } +} diff --git a/backend/integrations/search/search.go b/backend/integrations/search/search.go index 52ad00f79..696fc7120 100644 --- a/backend/integrations/search/search.go +++ b/backend/integrations/search/search.go @@ -1,7 +1,6 @@ package search import ( - "bytes" "fmt" "net/http" @@ -9,7 +8,6 @@ import ( "gorm.io/gorm" "github.com/goccy/go-json" - "github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2/log" "github.com/GenerateNU/sac/backend/config" @@ -77,12 +75,12 @@ func (c *PineconeClient) Seed(db *gorm.DB) error { return nil } -func pineconeRequest(settings config.PineconeSettings, req *http.Request) *http.Request { - return utilities.ApplyModifiers(req, +func pineconeModifiers(settings config.PineconeSettings) []utilities.RequestModifier { + return []utilities.RequestModifier{ utilities.HeaderKV("Api-Key", settings.APIKey.Expose()), utilities.AcceptJSON(), utilities.JSON(), - ) + } } type Vector struct { @@ -123,21 +121,11 @@ func (c *PineconeClient) Upsert(items []Searchable) error { return fmt.Errorf("failed to marshal upsert body: %w", err) } - req, err := http.NewRequest(fiber.MethodPost, - fmt.Sprintf("%s/vectors/upsert", c.PineconeSettings.IndexHost.Expose()), - bytes.NewBuffer(upsertBody)) + resp, err := utilities.Request(http.MethodPost, fmt.Sprintf("%s/vectors/upsert", c.PineconeSettings.IndexHost.Expose()), upsertBody, pineconeModifiers(c.PineconeSettings)...) if err != nil { - return fmt.Errorf("failed to create upsert request: %w", err) + return fmt.Errorf("upsert request failed: %w", err) } - req = pineconeRequest(c.PineconeSettings, req) - - resp, err := http.DefaultClient.Do(req) - if err != nil { - return fmt.Errorf("failed to upsert to pinecone: %w", err) - } - defer resp.Body.Close() - if resp.StatusCode != http.StatusOK { return fmt.Errorf("failed to upsert to pinecone: %s", resp.Status) } @@ -180,20 +168,10 @@ func (c *PineconeClient) Delete(items []Searchable) error { return fmt.Errorf("failed to marshal delete body: %w", err) } - req, err := http.NewRequest(fiber.MethodPost, - fmt.Sprintf("%s/vectors/delete", c.PineconeSettings.IndexHost.Expose()), - bytes.NewBuffer(deleteBody)) - if err != nil { - return fmt.Errorf("failed to create delete request: %w", err) - } - - req = pineconeRequest(c.PineconeSettings, req) - - resp, err := http.DefaultClient.Do(req) + resp, err := utilities.Request(http.MethodPost, fmt.Sprintf("%s/vectors/delete", c.PineconeSettings.IndexHost.Expose()), deleteBody, pineconeModifiers(c.PineconeSettings)...) if err != nil { - return fmt.Errorf("failed to delete from pinecone: %w", err) + return fmt.Errorf("delete request failed: %w", err) } - defer resp.Body.Close() if resp.StatusCode != http.StatusOK { return fmt.Errorf("failed to delete from pinecone: %s", resp.Status) @@ -251,20 +229,10 @@ func (c *PineconeClient) Search(item Searchable) ([]string, error) { return []string{}, fmt.Errorf("failed to marshal search body: %w", err) } - req, err := http.NewRequest(fiber.MethodPost, - fmt.Sprintf("%s/query", c.PineconeSettings.IndexHost.Expose()), - bytes.NewBuffer(searchBody)) + resp, err := utilities.Request(http.MethodPost, fmt.Sprintf("%s/query", c.PineconeSettings.IndexHost.Expose()), searchBody, pineconeModifiers(c.PineconeSettings)...) if err != nil { - return []string{}, fmt.Errorf("failed to create search request: %w", err) - } - - req = pineconeRequest(c.PineconeSettings, req) - - resp, err := http.DefaultClient.Do(req) - if err != nil { - return []string{}, fmt.Errorf("failed to search to pinecone: %w", err) + return []string{}, fmt.Errorf("search request failed: %w", err) } - defer resp.Body.Close() if resp.StatusCode != http.StatusOK { return []string{}, fmt.Errorf("failed to search to pinecone: %w", err) diff --git a/backend/main.go b/backend/main.go index 87113b713..f420ec916 100644 --- a/backend/main.go +++ b/backend/main.go @@ -15,15 +15,13 @@ import ( "github.com/GenerateNU/sac/backend/integrations" "github.com/GenerateNU/sac/backend/integrations/email" "github.com/GenerateNU/sac/backend/integrations/file" + "github.com/GenerateNU/sac/backend/integrations/oauth" "github.com/GenerateNU/sac/backend/integrations/search" "github.com/GenerateNU/sac/backend/server" "github.com/GenerateNU/sac/backend/telemetry" "github.com/GenerateNU/sac/backend/tests/api/mocks" - "go.opentelemetry.io/otel" ) -var tracer = otel.Tracer("sac") - func main() { onlyMigrate := flag.Bool("only-migrate", false, "Specify if you want to only perform the database migration") onlySeedPinecone := flag.Bool("seed-pinecone", false, "Specify if want to only perform the pinecone database seeding") @@ -72,7 +70,7 @@ func main() { tp := telemetry.InitTracer() - slog.Info("appease linter since we aren't tracing anything yet", "tracer", tracer) + slog.Info("appease linter since we aren't tracing anything yet", "tracer", telemetry.Tracer) defer func() { if err := tp.Shutdown(context.Background()); err != nil { @@ -113,11 +111,25 @@ func configureIntegrations(config *config.Settings, connectToPinecone bool) inte pinecone = mocks.NewPineconeMockClient() } + // OAuth Integrations: + googleClient := oauth.GoogleOAuthClient{ + OAuthConfig: config.GoogleSettings, + } + outlookClient := oauth.OutlookOAuthClient{ + OAuthConfig: config.OutlookSettings, + } + + oauthResources := oauth.OAuthResources{ + Google: googleClient, + Outlook: outlookClient, + } + integrations := integrations.Integrations{ File: file.NewAWSProvider(config.AWS), AI: openAi, Email: email.NewResendClient(config.Resend, true), Search: pinecone, + OAuth: oauthResources, } return integrations } diff --git a/backend/middleware/auth/auth.go b/backend/middleware/auth/auth.go index 1a0962766..e5a51fdb7 100644 --- a/backend/middleware/auth/auth.go +++ b/backend/middleware/auth/auth.go @@ -1,11 +1,13 @@ package auth import ( + "fmt" "slices" "strings" "github.com/GenerateNU/sac/backend/auth" "github.com/GenerateNU/sac/backend/utilities" + "github.com/google/uuid" "github.com/GenerateNU/sac/backend/entities/models" @@ -15,7 +17,7 @@ import ( ) func (m *AuthMiddlewareService) IsSuper(c *fiber.Ctx) bool { - claims, err := auth.From(c) + claims, err := auth.CustomClaimsFrom(c) if err != nil { _ = err return false @@ -26,7 +28,7 @@ func (m *AuthMiddlewareService) IsSuper(c *fiber.Ctx) bool { return claims.Role == string(models.Super) } -func GetAuthroizationToken(c *fiber.Ctx) *string { +func GetAuthorizationToken(c *fiber.Ctx) *string { accessToken := c.Get("Authorization") if accessToken == "" { return nil @@ -42,7 +44,7 @@ func GetAuthroizationToken(c *fiber.Ctx) *string { func (m *AuthMiddlewareService) Authenticate(c *fiber.Ctx) error { return func(c *fiber.Ctx) error { - accessToken := GetAuthroizationToken(c) + accessToken := GetAuthorizationToken(c) if accessToken == nil { return utilities.Unauthorized() } @@ -65,7 +67,15 @@ func (m *AuthMiddlewareService) Authenticate(c *fiber.Ctx) error { // return errors.Unauthorized.FiberError(c) // } - c.Locals("claims", claims) + auth.SetClaims(c, claims) + + rawUserID := claims.Issuer + userID, err := uuid.Parse(rawUserID) + if err != nil { + return fmt.Errorf("invalid user id: %s", rawUserID) + } + + auth.SetUserID(c, &userID) return nil }(c) @@ -78,7 +88,7 @@ func (m *AuthMiddlewareService) Authorize(requiredPermissions ...auth.Permission return utilities.Unauthorized() } - claims, err := auth.From(c) + claims, err := auth.CustomClaimsFrom(c) if err != nil { return err } diff --git a/backend/middleware/auth/club.go b/backend/middleware/auth/club.go index 5a084fd7b..d44d636f1 100644 --- a/backend/middleware/auth/club.go +++ b/backend/middleware/auth/club.go @@ -25,12 +25,7 @@ func (m *AuthMiddlewareService) ClubAuthorizeById(c *fiber.Ctx, extractor Extrac return err } - claims, err := auth.From(c) - if err != nil { - return err - } - - issuerUUID, err := utilities.ValidateID(claims.Issuer) + userID, err := auth.UserIDFrom(c) if err != nil { return err } @@ -40,7 +35,7 @@ func (m *AuthMiddlewareService) ClubAuthorizeById(c *fiber.Ctx, extractor Extrac return err } - if slices.Contains(clubAdmin, *issuerUUID) { + if slices.Contains(clubAdmin, *userID) { return c.Next() } diff --git a/backend/middleware/auth/event.go b/backend/middleware/auth/event.go index 542f6e7c2..23bf6db10 100644 --- a/backend/middleware/auth/event.go +++ b/backend/middleware/auth/event.go @@ -25,12 +25,7 @@ func (m *AuthMiddlewareService) EventAuthorizeById(c *fiber.Ctx, extractor Extra return err } - claims, err := auth.From(c) - if err != nil { - return err - } - - issuerUUID, err := utilities.ValidateID(claims.Issuer) + userID, err := auth.UserIDFrom(c) if err != nil { return err } @@ -40,7 +35,7 @@ func (m *AuthMiddlewareService) EventAuthorizeById(c *fiber.Ctx, extractor Extra return err } - if slices.Contains(eventHostAdmin, *issuerUUID) { + if slices.Contains(eventHostAdmin, *userID) { return c.Next() } diff --git a/backend/middleware/auth/user.go b/backend/middleware/auth/user.go index bf489faa2..fe72f7e45 100644 --- a/backend/middleware/auth/user.go +++ b/backend/middleware/auth/user.go @@ -21,17 +21,12 @@ func (m *AuthMiddlewareService) UserAuthorizeById(c *fiber.Ctx) error { return err } - claims, err := auth.From(c) + userID, err := auth.UserIDFrom(c) if err != nil { return err } - issuerIDAsUUID, err := utilities.ValidateID(claims.Issuer) - if err != nil { - return err - } - - if issuerIDAsUUID.String() == idAsUUID.String() { + if idAsUUID == userID { return c.Next() } diff --git a/backend/middleware/utility/limiter.go b/backend/middleware/utility/limiter.go index dc6ed2887..6f08befdf 100644 --- a/backend/middleware/utility/limiter.go +++ b/backend/middleware/utility/limiter.go @@ -3,6 +3,7 @@ package utility import ( "errors" "fmt" + "net/http" "time" "github.com/GenerateNU/sac/backend/utilities" @@ -18,7 +19,7 @@ func (u *UtilityMiddlewareService) Limiter(rate int, expiration time.Duration) f return fmt.Sprintf("%s-%s", c.IP(), c.Path()) }, LimitReached: func(c *fiber.Ctx) error { - return utilities.NewAPIError(fiber.StatusTooManyRequests, errors.New("too many requests")) + return utilities.NewAPIError(http.StatusTooManyRequests, errors.New("too many requests")) }, }) } diff --git a/backend/migrations/000001_init.down.sql b/backend/migrations/000001_init.down.sql index 53a2ab3b3..cf79cfbe6 100644 --- a/backend/migrations/000001_init.down.sql +++ b/backend/migrations/000001_init.down.sql @@ -40,6 +40,10 @@ DROP TABLE IF EXISTS user_tags CASCADE; DROP TABLE IF EXISTS verifications CASCADE; +DROP TYPE IF EXISTS OAuthResourceType CASCADE; + +DROP TABLE IF EXISTS user_oauth_tokens CASCADE; + DROP EXTENSION IF EXISTS "uuid-ossp"; COMMIT; diff --git a/backend/migrations/000001_init.up.sql b/backend/migrations/000001_init.up.sql index a397a8cba..85b301896 100644 --- a/backend/migrations/000001_init.up.sql +++ b/backend/migrations/000001_init.up.sql @@ -246,4 +246,16 @@ CREATE TABLE IF NOT EXISTS verifications( CREATE UNIQUE INDEX IF NOT EXISTS uni_verifications_token ON verifications USING btree ("token"); +CREATE TYPE OAuthResourceType AS ENUM ('google', 'outlook'); + +CREATE TABLE IF NOT EXISTS user_oauth_tokens( + user_id uuid NOT NULL, + refresh_token varchar, + access_token varchar, + csrf_token varchar(255), + resource_type OAuthResourceType, + expires_at timestamp with time zone NOT NULL, + PRIMARY KEY(user_id,resource_type) +); + COMMIT; diff --git a/backend/server/server.go b/backend/server/server.go index ebb1e8ac7..0a4fd9f29 100644 --- a/backend/server/server.go +++ b/backend/server/server.go @@ -14,6 +14,7 @@ import ( contacts "github.com/GenerateNU/sac/backend/entities/contacts/base" events "github.com/GenerateNU/sac/backend/entities/events/base" files "github.com/GenerateNU/sac/backend/entities/files/base" + oauth "github.com/GenerateNU/sac/backend/entities/oauth/base" pocs "github.com/GenerateNU/sac/backend/entities/pocs/base" tags "github.com/GenerateNU/sac/backend/entities/tags/base" users "github.com/GenerateNU/sac/backend/entities/users/base" @@ -87,6 +88,7 @@ func allRoutes(app *fiber.App, routeParams types.RouteParams) { categories.CategoryRoutes(routeParams) events.EventRoutes(routeParams) files.File(routeParams) + oauth.OAuth(routeParams) } func newFiberApp(appSettings config.ApplicationSettings) *fiber.App { diff --git a/backend/telemetry/telemetry.go b/backend/telemetry/telemetry.go index 842bd0376..18daac819 100644 --- a/backend/telemetry/telemetry.go +++ b/backend/telemetry/telemetry.go @@ -6,6 +6,7 @@ import ( "github.com/gofiber/fiber/v2" "go.opentelemetry.io/otel/sdk/resource" + "go.opentelemetry.io/otel/trace" "go.opentelemetry.io/otel" stdout "go.opentelemetry.io/otel/exporters/stdout/stdouttrace" @@ -18,6 +19,12 @@ import ( semconv "go.opentelemetry.io/otel/semconv/v1.4.0" ) +var Tracer trace.Tracer + +func init() { + Tracer = otel.Tracer("sac") +} + func InitTracer() *sdktrace.TracerProvider { exporter, err := stdout.New(stdout.WithPrettyPrint()) // uncomment the following line to use Jaeger diff --git a/backend/tests/search_test.go b/backend/tests/search_test.go index 0f8c42177..8f2c8e4c9 100644 --- a/backend/tests/search_test.go +++ b/backend/tests/search_test.go @@ -1,6 +1,7 @@ package tests import ( + "fmt" "testing" "github.com/GenerateNU/sac/backend/config" @@ -181,7 +182,7 @@ func TestPineconeSearchWorks(t *testing.T) { gock.New("https://api.openai.com"). Post("/v1/embeddings"). - MatchHeader("Authorization", "Bearer "+mockConfig.OpenAI.APIKey.Expose()). + MatchHeader("Authorization", fmt.Sprintf("Bearer %s", mockConfig.OpenAI.APIKey.Expose())). MatchHeader("content-type", "application/json"). MatchType("json"). JSON(search.CreateEmbeddingRequestBody{ @@ -199,7 +200,7 @@ func TestPineconeSearchWorks(t *testing.T) { gock.New("https://api.openai.com"). Post("/v1/moderations"). - MatchHeader("Authorization", "Bearer "+mockConfig.OpenAI.APIKey.Expose()). + MatchHeader("Authorization", fmt.Sprintf("Bearer %s", mockConfig.OpenAI.APIKey.Expose())). MatchHeader("Content-Type", "application/json"). MatchType("json"). JSON(search.CreateModerationRequestBody{ @@ -235,7 +236,7 @@ func TestOpenAIEmbeddingWorks(t *testing.T) { gock.New("https://api.openai.com"). Post("/v1/embeddings"). - MatchHeader("Authorization", "Bearer "+mockConfig.OpenAI.APIKey.Expose()). + MatchHeader("Authorization", fmt.Sprintf("Bearer %s", mockConfig.OpenAI.APIKey.Expose())). MatchHeader("content-type", "application/json"). MatchType("json"). JSON(search.CreateEmbeddingRequestBody{ diff --git a/backend/utilities/http.go b/backend/utilities/http.go index 55127897d..48e3b919f 100644 --- a/backend/utilities/http.go +++ b/backend/utilities/http.go @@ -1,6 +1,31 @@ package utilities -import "net/http" +import ( + "bytes" + "fmt" + "net/http" +) + +var httpClient *http.Client + +func init() { + httpClient = &http.Client{} +} + +func Request(method string, url string, body []byte, modifiers ...RequestModifier) (*http.Response, error) { + req, err := http.NewRequest(method, url, bytes.NewReader(body)) + if err != nil { + return nil, err + } + + req = ApplyModifiers(req, modifiers...) + + return httpClient.Do(req) +} + +func IsOk(statusCode int) bool { + return statusCode >= 200 && statusCode < 300 +} type RequestModifier interface { Modify(req *http.Request) *http.Request @@ -26,7 +51,7 @@ func HeaderKV(key, value string) RequestModifier { func Authorization(apiKey string) RequestModifier { return HeaderKVModifier{ Key: "Authorization", - Value: "Bearer " + apiKey, + Value: fmt.Sprintf("Bearer %s", apiKey), } } @@ -37,6 +62,13 @@ func JSON() RequestModifier { } } +func FormURLEncoded() RequestModifier { + return HeaderKVModifier{ + Key: "Content-Type", + Value: "application/x-www-form-urlencoded", + } +} + func AcceptJSON() RequestModifier { return HeaderKVModifier{ Key: "Accept", diff --git a/config/.env.template b/config/.env.template index 64e07fbf4..66e72ed9e 100644 --- a/config/.env.template +++ b/config/.env.template @@ -6,4 +6,14 @@ SAC_RESEND_API_KEY="SAC_RESEND_API_KEY" SAC_AWS_BUCKET_NAME="SAC_AWS_BUCKET_NAME" SAC_AWS_ID="SAC_AWS_ID" SAC_AWS_SECRET="SAC_AWS_SECRET" -SAC_AWS_REGION="SAC_AWS_REGION" \ No newline at end of file +SAC_AWS_REGION="SAC_AWS_REGION" + +GOOGLE_OAUTH_CLIENT_ID=GOOGLE_OAUTH_CLIENT_ID +GOOGLE_OAUTH_CLIENT_SECRET=GOOGLE_OAUTH_CLIENT_SECRET +GOOGLE_API_KEY=GOOGLE_API_KEY + +OUTLOOK_OAUTH_CLIENT_ID=test +OUTLOOK_OAUTH_CLIENT_SECRET=test + +AUTHSECRET__ACCESS_TOKEN=test +AUTHSECRET__REFRESH_TOKEN=test diff --git a/go.work.sum b/go.work.sum index 6257c9aef..69986c7fb 100644 --- a/go.work.sum +++ b/go.work.sum @@ -1,5 +1,6 @@ cloud.google.com/go v0.44.3/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= cloud.google.com/go v0.110.8/go.mod h1:Iz8AkXJf1qmxC3Oxoep8R1T36w8B92yU29PcBhHO5fk= +cloud.google.com/go v0.110.10 h1:LXy9GEO+timppncPIAZoOj3l58LIU9k+kn48AN7IO3Y= cloud.google.com/go v0.110.10/go.mod h1:v1OoFqYxiBkUrruItNM3eT4lLByNjxmJSV/xDKJNnic= cloud.google.com/go/accessapproval v1.7.4/go.mod h1:/aTEh45LzplQgFYdQdwPMR9YdX0UlhBmvB84uAmQKUc= cloud.google.com/go/accesscontextmanager v1.8.4/go.mod h1:ParU+WbMpD34s5JFEnGAnPBYAgUHozaTmDJU7aCU9+M= @@ -28,7 +29,9 @@ cloud.google.com/go/cloudtasks v1.12.4/go.mod h1:BEPu0Gtt2dU6FxZHNqqNdGqIG86qyWK cloud.google.com/go/compute v1.20.1/go.mod h1:4tCnrn48xsqlwSAiLf1HXMQk8CONslYbdiEZc9FEIbM= cloud.google.com/go/compute v1.23.0/go.mod h1:4tCnrn48xsqlwSAiLf1HXMQk8CONslYbdiEZc9FEIbM= cloud.google.com/go/compute v1.23.1/go.mod h1:CqB3xpmPKKt3OJpW2ndFIXnA9A4xAy/F3Xp1ixncW78= +cloud.google.com/go/compute v1.23.3 h1:6sVlXXBmbd7jNX0Ipq0trII3e4n1/MsADLK6a+aiVlk= cloud.google.com/go/compute v1.23.3/go.mod h1:VCgBUoMnIVIR0CscqQiPJLAG25E3ZRZMzcFZeQ+h8CI= +cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY= cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= cloud.google.com/go/contactcenterinsights v1.11.3/go.mod h1:HHX5wrz5LHVAwfI2smIotQG9x8Qd6gYilaHcLLLmNis= cloud.google.com/go/container v1.27.1/go.mod h1:b1A1gJeTBXVLQ6GGw9/9M4FG94BEGsqJ5+t4d/3N7O4= @@ -54,6 +57,7 @@ cloud.google.com/go/errorreporting v0.3.0/go.mod h1:xsP2yaAp+OAW4OIm60An2bbLpqIh cloud.google.com/go/essentialcontacts v1.6.5/go.mod h1:jjYbPzw0x+yglXC890l6ECJWdYeZ5dlYACTFL0U/VuM= cloud.google.com/go/eventarc v1.13.3/go.mod h1:RWH10IAZIRcj1s/vClXkBgMHwh59ts7hSWcqD3kaclg= cloud.google.com/go/filestore v1.7.4/go.mod h1:S5JCxIbFjeBhWMTfIYH2Jx24J6BqjwpkkPl+nBA5DlI= +cloud.google.com/go/firestore v1.14.0 h1:8aLcKnMPoldYU3YHgu4t2exrKhLQkqaXAGqT0ljrFVw= cloud.google.com/go/firestore v1.14.0/go.mod h1:96MVaHLsEhbvkBEdZgfN+AS/GIkco1LRpH9Xp9YZfzQ= cloud.google.com/go/functions v1.15.4/go.mod h1:CAsTc3VlRMVvx+XqXxKqVevguqJpnVip4DdonFsX28I= cloud.google.com/go/gkebackup v1.3.4/go.mod h1:gLVlbM8h/nHIs09ns1qx3q3eaXcGSELgNu1DWXYz1HI= @@ -64,6 +68,7 @@ cloud.google.com/go/grafeas v0.3.0/go.mod h1:P7hgN24EyONOTMyeJH6DxG4zD7fwiYa5Q6G cloud.google.com/go/gsuiteaddons v1.6.4/go.mod h1:rxtstw7Fx22uLOXBpsvb9DUbC+fiXs7rF4U29KHM/pE= cloud.google.com/go/iam v1.1.1/go.mod h1:A5avdyVL2tCppe4unb0951eI9jreack+RJ0/d+KUZOU= cloud.google.com/go/iam v1.1.3/go.mod h1:3khUlaBXfPKKe7huYgEpDn6FtgRyMEqbkvBxrQyY5SE= +cloud.google.com/go/iam v1.1.5 h1:1jTsCu4bcsNsE4iiqNT5SHwrDRCfRmIaaaVFhRveTJI= cloud.google.com/go/iam v1.1.5/go.mod h1:rB6P/Ic3mykPbFio+vo7403drjlgvoWfYpJhMXEbzv8= cloud.google.com/go/iap v1.9.3/go.mod h1:DTdutSZBqkkOm2HEOTBzhZxh2mwwxshfD/h3yofAiCw= cloud.google.com/go/ids v1.4.4/go.mod h1:z+WUc2eEl6S/1aZWzwtVNWoSZslgzPxAboS0lZX0HjI= @@ -73,6 +78,7 @@ cloud.google.com/go/language v1.12.2/go.mod h1:9idWapzr/JKXBBQ4lWqVX/hcadxB194ry cloud.google.com/go/lifesciences v0.9.4/go.mod h1:bhm64duKhMi7s9jR9WYJYvjAFJwRqNj+Nia7hF0Z7JA= cloud.google.com/go/logging v1.8.1/go.mod h1:TJjR+SimHwuC8MZ9cjByQulAMgni+RkXeI3wwctHJEI= cloud.google.com/go/longrunning v0.5.2/go.mod h1:nqo6DQbNV2pXhGDbDMoN2bWz68MjZUzqv2YttZiveCs= +cloud.google.com/go/longrunning v0.5.4 h1:w8xEcbZodnA2BbW6sVirkkoC+1gP8wS57EUUgGS0GVg= cloud.google.com/go/longrunning v0.5.4/go.mod h1:zqNVncI0BOP8ST6XQD1+VcvuShMmq7+xFSzOL++V0dI= cloud.google.com/go/managedidentities v1.6.4/go.mod h1:WgyaECfHmF00t/1Uk8Oun3CQ2PGUtjc3e9Alh79wyiM= cloud.google.com/go/maps v1.6.1/go.mod h1:4+buOHhYXFBp58Zj/K+Lc1rCmJssxxF4pJ5CJnhdz18= @@ -108,9 +114,11 @@ cloud.google.com/go/security v1.15.4/go.mod h1:oN7C2uIZKhxCLiAAijKUCuHLZbIt/ghYE cloud.google.com/go/securitycenter v1.24.2/go.mod h1:l1XejOngggzqwr4Fa2Cn+iWZGf+aBLTXtB/vXjy5vXM= cloud.google.com/go/servicedirectory v1.11.3/go.mod h1:LV+cHkomRLr67YoQy3Xq2tUXBGOs5z5bPofdq7qtiAw= cloud.google.com/go/shell v1.7.4/go.mod h1:yLeXB8eKLxw0dpEmXQ/FjriYrBijNsONpwnWsdPqlKM= +cloud.google.com/go/spanner v1.51.0 h1:l3exhhsVMKsx1E7Xd1QajYSvHmI1KZoWPW5tRxIIdvQ= cloud.google.com/go/spanner v1.51.0/go.mod h1:c5KNo5LQ1X5tJwma9rSQZsXNBDNvj4/n8BVc3LNahq0= cloud.google.com/go/speech v1.20.1/go.mod h1:wwolycgONvfz2EDU8rKuHRW3+wc9ILPsAWoikBEWavY= cloud.google.com/go/storage v1.30.1/go.mod h1:NfxhC0UJE1aXSx7CIIbCf7y9HKT7BiccwkR7+P7gN8E= +cloud.google.com/go/storage v1.35.1 h1:B59ahL//eDfx2IIKFBeT5Atm9wnNmj3+8xG/W4WB//w= cloud.google.com/go/storage v1.35.1/go.mod h1:M6M/3V/D3KpzMTJyPOR/HU6n2Si5QdaXYEsng2xgOs8= cloud.google.com/go/storagetransfer v1.10.3/go.mod h1:Up8LY2p6X68SZ+WToswpQbQHnJpOty/ACcMafuey8gc= cloud.google.com/go/talent v1.6.5/go.mod h1:Mf5cma696HmE+P2BWJ/ZwYqeJXEeU0UqjHFXVLadEDI= @@ -127,187 +135,312 @@ cloud.google.com/go/vpcaccess v1.7.4/go.mod h1:lA0KTvhtEOb/VOdnH/gwPuOzGgM+CWsmG cloud.google.com/go/webrisk v1.9.4/go.mod h1:w7m4Ib4C+OseSr2GL66m0zMBywdrVNTDKsdEsfMl7X0= cloud.google.com/go/websecurityscanner v1.6.4/go.mod h1:mUiyMQ+dGpPPRkHgknIZeCzSHJ45+fY4F52nZFDHm2o= cloud.google.com/go/workflows v1.12.3/go.mod h1:fmOUeeqEwPzIU81foMjTRQIdwQHADi/vEr1cx9R1m5g= +github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 h1:/vQbFIOMbk2FiG/kXiLl8BRyzTWDw7gX/Hz7Dd5eDMs= github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4/go.mod h1:hN7oaIRCjzsZ2dE+yG5k+rsdt3qcwykqK6HVGcKwsw4= +github.com/99designs/keyring v1.2.1 h1:tYLp1ULvO7i3fI5vE21ReQuj99QFSs7lGm0xWyJo87o= github.com/99designs/keyring v1.2.1/go.mod h1:fc+wB5KTk9wQ9sDx0kFXB3A0MaeGHM9AwRStKOQ5vOA= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.4.0 h1:rTnT/Jrcm+figWlYz4Ixzt0SJVR2cMC8lvZcimipiEY= github.com/Azure/azure-sdk-for-go/sdk/azcore v1.4.0/go.mod h1:ON4tFdPTwRcgWEaVDrN3584Ef+b7GgSJaXxe5fW9t4M= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.1.2 h1:+5VZ72z0Qan5Bog5C+ZkgSqUbeVUd9wgtHOrIKuc5b8= github.com/Azure/azure-sdk-for-go/sdk/internal v1.1.2/go.mod h1:eWRD7oawr1Mu1sLCawqVc0CUiF43ia3qQMxLscsKQ9w= +github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.0.0 h1:u/LLAOFgsMv7HmNL4Qufg58y+qElGOt5qv0z1mURkRY= github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.0.0/go.mod h1:2e8rMJtl2+2j+HXbTBwnyGpm5Nou7KhvSfxOq8JpTag= +github.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs= github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= +github.com/Azure/go-autorest/autorest/adal v0.9.16 h1:P8An8Z9rH1ldbOLdFpxYorgOt2sywL9V24dAwWHPuGc= github.com/Azure/go-autorest/autorest/adal v0.9.16/go.mod h1:tGMin8I49Yij6AQ+rvV+Xa/zwxYQB5hmsd6DkfAx2+A= +github.com/Azure/go-autorest/autorest/date v0.3.0 h1:7gUk1U5M/CQbp9WoqinNzJar+8KY+LPI6wiWrP/myHw= github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74= +github.com/Azure/go-autorest/logger v0.2.1 h1:IG7i4p/mDa2Ce4TRyAO8IHnVhAVF3RFU+ZtXWSmf4Tg= github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= +github.com/Azure/go-autorest/tracing v0.6.0 h1:TYi4+3m5t6K48TGI9AUdb+IzbnSxvnvUMfuitfgcfuo= github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= +github.com/ClickHouse/clickhouse-go v1.4.3 h1:iAFMa2UrQdR5bHJ2/yaSLffZkxpcOYQMCUuKeNXGdqc= github.com/ClickHouse/clickhouse-go v1.4.3/go.mod h1:EaI/sW7Azgz9UATzd5ZdZHRUhHgv5+JMS9NSr2smCJI= +github.com/PuerkitoBio/goquery v1.8.1 h1:uQxhNlArOIdbrH1tr0UXwdVFgDcZDrZVdcpygAcwmWM= github.com/PuerkitoBio/goquery v1.8.1/go.mod h1:Q8ICL1kNUJ2sXGoAhPGUdYDJvgQgHzJsnnd3H7Ho5jQ= 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/a-h/htmlformat v0.0.0-20231108124658-5bd994fe268e h1:Eog54DQpku7NpPNff9wzQYT61TGu9jjq5N8UhAkqIgw= github.com/a-h/htmlformat v0.0.0-20231108124658-5bd994fe268e/go.mod h1:FMIm5afKmEfarNbIXOaPHFY8X7fo+fRQB6I9MPG2nB0= +github.com/a-h/parse v0.0.0-20240121214402-3caf7543159a h1:vlmAfVwFK9sRpDlJyuHY8htP+KfGHB2VH02u0SoIufk= github.com/a-h/parse v0.0.0-20240121214402-3caf7543159a/go.mod h1:3mnrkvGpurZ4ZrTDbYU84xhwXW2TjTKShSwjRi2ihfQ= +github.com/a-h/pathvars v0.0.14 h1:5/C0U5zuUtvAfVQCR5vow4E4MIRU7QIOLz8z26EeeQw= github.com/a-h/pathvars v0.0.14/go.mod h1:7rLTtvDVyKneR/N65hC0lh2sZ2KRyAmWFaOvv00uxb0= +github.com/a-h/protocol v0.0.0-20230224160810-b4eec67c1c22 h1:ehNdbGOAR8KTrLY/S90/9RJ4p/cgeNdt1sRt0DSiRWs= github.com/a-h/protocol v0.0.0-20230224160810-b4eec67c1c22/go.mod h1:Gm0KywveHnkiIhqFSMZglXwWZRQICg3KDWLYdglv/d8= github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da h1:KjTM2ks9d14ZYCvmHS9iAKVt9AyzRSqNU1qabPih5BY= github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da/go.mod h1:eHEWzANqSiWQsof+nXEI9bUVUyV6F53Fp89EuCh2EAA= +github.com/andybalholm/cascadia v1.3.1 h1:nhxRkql1kdYCc8Snf7D5/D3spOX+dBgjA6u8x004T2c= github.com/andybalholm/cascadia v1.3.1/go.mod h1:R4bJ1UQfqADjvDa4P6HZHLh/3OxWWEqc0Sk8XGwHqvA= +github.com/apache/arrow/go/v10 v10.0.1 h1:n9dERvixoC/1JjDmBcs9FPaEryoANa2sCgVFo6ez9cI= github.com/apache/arrow/go/v10 v10.0.1/go.mod h1:YvhnlEePVnBS4+0z3fhPfUy7W1Ikj0Ih0vcRo/gZ1M0= github.com/apache/arrow/go/v12 v12.0.0/go.mod h1:d+tV/eHZZ7Dz7RPrFKtPK02tpr+c9/PEd/zm8mDS9Vg= +github.com/apache/thrift v0.16.0 h1:qEy6UW60iVOlUy+b9ZR0d5WzUWYGOo4HfopoyBaNmoY= github.com/apache/thrift v0.16.0/go.mod h1:PHK3hniurgQaNMZYaCLEqXKsYK8upmhPbmdP2FXSqgU= +github.com/armon/go-metrics v0.4.1 h1:hR91U9KYmb6bLBYLQjyM+3j+rcd/UhE+G78SFnF8gJA= github.com/armon/go-metrics v0.4.1/go.mod h1:E6amYzXo6aW1tqzoZGT755KkbgrJsSdpwZ+3JqfkOG4= github.com/aws/aws-sdk-go v1.50.5 h1:H2Aadcgwr7a2aqS6ZwcE+l1mA6ZrTseYCvjw2QLmxIA= github.com/aws/aws-sdk-go v1.50.5/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk= +github.com/aws/aws-sdk-go-v2 v1.16.16 h1:M1fj4FE2lB4NzRb9Y0xdWsn2P0+2UHVxwKyOa4YJNjk= github.com/aws/aws-sdk-go-v2 v1.16.16/go.mod h1:SwiyXi/1zTUZ6KIAmLK5V5ll8SiURNUYOqTerZPaF9k= +github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.8 h1:tcFliCWne+zOuUfKNRn8JdFBuWPDuISDH08wD2ULkhk= github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.8/go.mod h1:JTnlBSot91steJeti4ryyu/tLd4Sk84O5W22L7O2EQU= +github.com/aws/aws-sdk-go-v2/credentials v1.12.20 h1:9+ZhlDY7N9dPnUmf7CDfW9In4sW5Ff3bh7oy4DzS1IE= github.com/aws/aws-sdk-go-v2/credentials v1.12.20/go.mod h1:UKY5HyIux08bbNA7Blv4PcXQ8cTkGh7ghHMFklaviR4= +github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.11.33 h1:fAoVmNGhir6BR+RU0/EI+6+D7abM+MCwWf8v4ip5jNI= github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.11.33/go.mod h1:84XgODVR8uRhmOnUkKGUZKqIMxmjmLOR8Uyp7G/TPwc= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.23 h1:s4g/wnzMf+qepSNgTvaQQHNxyMLKSawNhKCPNy++2xY= github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.23/go.mod h1:2DFxAQ9pfIRy0imBCJv+vZ2X6RKxves6fbnEuSry6b4= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.17 h1:/K482T5A3623WJgWT8w1yRAFK4RzGzEl7y39yhtn9eA= github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.17/go.mod h1:pRwaTYCJemADaqCbUAxltMoHKata7hmB5PjEXeu0kfg= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.0.14 h1:ZSIPAkAsCCjYrhqfw2+lNzWDzxzHXEckFkTePL5RSWQ= github.com/aws/aws-sdk-go-v2/internal/v4a v1.0.14/go.mod h1:AyGgqiKv9ECM6IZeNQtdT8NnMvUb3/2wokeq2Fgryto= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.9 h1:Lh1AShsuIJTwMkoxVCAYPJgNG5H+eN6SmoUn8nOZ5wE= github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.9/go.mod h1:a9j48l6yL5XINLHLcOKInjdvknN+vWqPBxqeIDw7ktw= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.18 h1:BBYoNQt2kUZUUK4bIPsKrCcjVPUMNsgQpNAwhznK/zo= github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.18/go.mod h1:NS55eQ4YixUJPTC+INxi2/jCqe1y2Uw3rnh9wEOVJxY= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.17 h1:Jrd/oMh0PKQc6+BowB+pLEwLIgaQF29eYbe7E1Av9Ug= github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.17/go.mod h1:4nYOrY41Lrbk2170/BGkcJKBhws9Pfn8MG3aGqjjeFI= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.13.17 h1:HfVVR1vItaG6le+Bpw6P4midjBDMKnjMyZnw9MXYUcE= github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.13.17/go.mod h1:YqMdV+gEKCQ59NrB7rzrJdALeBIsYiVi8Inj3+KcqHI= +github.com/aws/aws-sdk-go-v2/service/s3 v1.27.11 h1:3/gm/JTX9bX8CpzTgIlrtYpB3EVBDxyg/GY/QdcIEZw= github.com/aws/aws-sdk-go-v2/service/s3 v1.27.11/go.mod h1:fmgDANqTUCxciViKl9hb/zD5LFbvPINFRgWhDbR+vZo= +github.com/aws/smithy-go v1.13.3 h1:l7LYxGuzK6/K+NzJ2mC+VvLUbae0sL3bXU//04MkmnA= github.com/aws/smithy-go v1.13.3/go.mod h1:Tg+OJXh4MB2R/uN61Ko2f6hTZwB/ZYGOtib8J3gBHzA= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/cenkalti/backoff/v4 v4.1.2/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= +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/census-instrumentation/opencensus-proto v0.4.1 h1:iKLQ0xPNFxR/2hzXZMrBo8f1j86j5WHzznCCQxV/b8g= github.com/census-instrumentation/opencensus-proto v0.4.1/go.mod h1:4T9NM4+4Vw91VeyqjLS6ao50K5bOcLKN6Q42XnYaRYw= github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cli/browser v1.3.0 h1:LejqCrpWr+1pRqmEPDGnTZOjsMe7sehifLynZJuqJpo= github.com/cli/browser v1.3.0/go.mod h1:HH8s+fOAxjhQoBUAsKuPCbqUuxZDhQ2/aD+SzsEfBTk= +github.com/cloudflare/golz4 v0.0.0-20150217214814-ef862a3cdc58 h1:F1EaeKL/ta07PY/k9Os/UFtwERei2/XzGemhpGnBKNg= github.com/cloudflare/golz4 v0.0.0-20150217214814-ef862a3cdc58/go.mod h1:EOBUe0h4xcZ5GoxqC5SDxFQ8gwyZPKQoEzownBlhI80= +github.com/cncf/udpa/go v0.0.0-20220112060539-c52dc94e7fbe h1:QQ3GSy+MqSHxm/d8nCtnAiZdYFd45cYZPs8vOOIYKfk= github.com/cncf/udpa/go v0.0.0-20220112060539-c52dc94e7fbe/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20231109132714-523115ebc101 h1:7To3pQ+pZo0i3dsWEbinPNFs5gPSBOsJtx3wTT94VBY= github.com/cncf/xds/go v0.0.0-20231109132714-523115ebc101/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cockroachdb/cockroach-go/v2 v2.1.1 h1:3XzfSMuUT0wBe1a3o5C0eOTcArhmmFAg2Jzh/7hhKqo= github.com/cockroachdb/cockroach-go/v2 v2.1.1/go.mod h1:7NtUnP6eK+l6k483WSYNrq3Kb23bWV10IRV1TyeSpwM= +github.com/coreos/go-semver v0.3.0 h1:wkHLiw0WNATZnSG7epLsujiMCgPAc9xhjJ4tgnAxmfM= github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-systemd/v22 v22.3.2 h1:D9/bQk5vlXQFZ6Kwuu6zaiXJ9oTPe68++AzAJc1DzSI= github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/cpuguy83/go-md2man/v2 v2.0.3 h1:qMCsGGgs+MAzDFyp9LpAe1Lqy/fY/qCovCm0qnXZOBM= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/cznic/mathutil v0.0.0-20180504122225-ca4c9f2c1369 h1:XNT/Zf5l++1Pyg08/HV04ppB0gKxAqtZQBRYiYrUuYk= github.com/cznic/mathutil v0.0.0-20180504122225-ca4c9f2c1369/go.mod h1:e6NPNENfs9mPDVNRekM7lKScauxd5kXTr1Mfyig6TDM= +github.com/danieljoos/wincred v1.1.2 h1:QLdCxFs1/Yl4zduvBdcHB8goaYk9RARS2SgLLRuAyr0= github.com/danieljoos/wincred v1.1.2/go.mod h1:GijpziifJoIBfYh+S7BbkdUTU4LfM+QnGqR5Vl2tAx0= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/dvsekhvalnov/jose2go v1.6.0 h1:Y9gnSnP4qEI0+/uQkHvFXeD2PLPJeXEL+ySMEA2EjTY= github.com/dvsekhvalnov/jose2go v1.6.0/go.mod h1:QsHjhyTlD/lAVqn/NSbVZmSCGeDehTB/mPZadG+mhXU= +github.com/edsrzf/mmap-go v0.0.0-20170320065105-0bce6a688712 h1:aaQcKT9WumO6JEJcRyTqFVq4XUZiUcKR2/GI31TOcz8= github.com/edsrzf/mmap-go v0.0.0-20170320065105-0bce6a688712/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= +github.com/envoyproxy/go-control-plane v0.11.1 h1:wSUXTlLfiAQRWs2F+p+EKOY9rUyis1MyGqJ2DIk5HpM= github.com/envoyproxy/go-control-plane v0.11.1/go.mod h1:uhMcXKCQMEJHiAb0w+YGefQLaTEw+YhGluxZkrTmD0g= +github.com/envoyproxy/protoc-gen-validate v1.0.2 h1:QkIBuU5k+x7/QXPvPPnWXWlCdaBFApVqftFV6k087DA= github.com/envoyproxy/protoc-gen-validate v1.0.2/go.mod h1:GpiZQP3dDbg4JouG/NNS7QWXpgx6x8QiMKdmN72jogE= github.com/fatih/color v1.14.1/go.mod h1:2oHN61fhTpgcxD3TSWCgKDiH1+x4OiDVVGH8WlgGZGg= +github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= +github.com/form3tech-oss/jwt-go v3.2.5+incompatible h1:/l4kBbb4/vGSsdtB5nUe8L7B9mImVMaBPw9L/0TBHU8= github.com/form3tech-oss/jwt-go v3.2.5+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= +github.com/fsouza/fake-gcs-server v1.17.0 h1:OeH75kBZcZa3ZE+zz/mFdJ2btt9FgqfjI7gIh9+5fvk= github.com/fsouza/fake-gcs-server v1.17.0/go.mod h1:D1rTE4YCyHFNa99oyJJ5HyclvN/0uQR+pM/VdlL83bw= github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU= github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA= +github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs= github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= +github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/gobuffalo/here v0.6.0 h1:hYrd0a6gDmWxBM4TnrGw8mQg24iSVoIkHEk7FodQcBI= github.com/gobuffalo/here v0.6.0/go.mod h1:wAG085dHOYqUpf+Ap+WOdrPTp5IYcDAs/x7PLa8Y5fM= +github.com/gocql/gocql v0.0.0-20210515062232-b7ef815b4556 h1:N/MD/sr6o61X+iZBAT2qEUF023s4KbA8RWfKzl0L6MQ= github.com/gocql/gocql v0.0.0-20210515062232-b7ef815b4556/go.mod h1:DL0ekTmBSTdlNF25Orwt/JMzqIq3EJ4MVa/J/uK64OY= +github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 h1:ZpnhV/YsD2/4cESfV5+Hoeu/iUR3ruzNvZ+yQfO03a0= github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2/go.mod h1:bBOAhwG1umN6/6ZUMtDFBMQR8jRg9O75tm9K00oMsK4= +github.com/golang-jwt/jwt/v4 v4.4.2 h1:rcc4lwaZgFMCZ5jxF9ABolDcIHdBytAFgqFPbSJQAYs= github.com/golang-jwt/jwt/v4 v4.4.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= +github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe h1:lXe2qZdvpiX5WZkZR4hgp4KJVfY3nMkvmwbVkpv1rVY= github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= +github.com/golang-sql/sqlexp v0.1.0 h1:ZCD6MBpcuOVfGVqsEmY5/4FtYiKz6tSyUv9LPEDei6A= github.com/golang-sql/sqlexp v0.1.0/go.mod h1:J4ad9Vo8ZCWQ2GMrC4UCQy1JpCbwU9m3EOqtpKwwwHI= github.com/golang/glog v1.1.2/go.mod h1:zR+okUeTbrL6EL3xHUDxZuEtGv04p5shwip1+mL/rLQ= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA= +github.com/google/flatbuffers v2.0.8+incompatible h1:ivUb1cGomAB101ZM1T0nOiWz9pSrTMoa9+EiY7igmkM= github.com/google/flatbuffers v2.0.8+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-github/v39 v39.2.0 h1:rNNM311XtPOz5rDdsJXAp2o8F67X9FnROXTvto3aSnQ= github.com/google/go-github/v39 v39.2.0/go.mod h1:C1s8C5aCC9L+JXIYpJM5GYytdX52vC1bLvHEF1IhBrE= github.com/google/go-pkcs11 v0.2.1-0.20230907215043-c6f79328ddf9/go.mod h1:6eQoGcuNJpa7jnd5pMGdkSaQpNDYvPlXWMcjXXThLlY= +github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= github.com/google/martian/v3 v3.3.2/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk= github.com/google/s2a-go v0.1.4/go.mod h1:Ej+mSEMGRnqRzjc7VtF+jdBwYG5fuJfiZ8ELkjEwM0A= +github.com/google/s2a-go v0.1.7 h1:60BLSyTrOV4/haCDW4zb1guZItoSq8foHCXrAnjBo/o= github.com/google/s2a-go v0.1.7/go.mod h1:50CgR4k1jNlWBu4UfS4AcfhVe1r6pdZPygJ3R8F0Qdw= github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/enterprise-certificate-proxy v0.2.4/go.mod h1:AwSRAtLfXpU5Nm3pW+v7rGDHp09LsPtGY9MduiEsR9k= +github.com/googleapis/enterprise-certificate-proxy v0.3.2 h1:Vie5ybvEvT75RniqhfFxPRy3Bf7vr3h0cechB90XaQs= github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0= +github.com/googleapis/gax-go/v2 v2.12.0 h1:A+gCJKdRfqXkr+BIRGtZLibNXf0m1f9E4HG56etFpas= github.com/googleapis/gax-go/v2 v2.12.0/go.mod h1:y+aIqrI5eb1YGMVJfuV3185Ts/D7qKpsEkdD5+I6QGU= +github.com/googleapis/google-cloud-go-testing v0.0.0-20210719221736-1c9a4c676720 h1:zC34cGQu69FG7qzJ3WiKW244WfhDC3xxYMeNOX2gtUQ= github.com/googleapis/google-cloud-go-testing v0.0.0-20210719221736-1c9a4c676720/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= +github.com/gorilla/handlers v1.4.2 h1:0QniY0USkHQ1RGCLfKxeNHK9bkDHGRYGNDFBCS+YARg= github.com/gorilla/handlers v1.4.2/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ= +github.com/gorilla/mux v1.7.4 h1:VuZ8uybHlWmqV03+zRzdwKL4tUnIp1MAQtp1mIFE1bc= github.com/gorilla/mux v1.7.4/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= github.com/grpc-ecosystem/grpc-gateway/v2 v2.11.3/go.mod h1:o//XUCC/F+yRGJoPO/VU0GSB0f8Nhgmxx0VIRUvaC0w= +github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c h1:6rhixN/i8ZofjG1Y75iExal34USq5p+wiN1tpie8IrU= github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c/go.mod h1:NMPJylDgVpX0MLRlPy15sqSwOFv/U1GZ2m21JhFfek0= +github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed h1:5upAirOpQc1Q53c0bnx2ufif5kANL7bfZWcc6VJWJd8= github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed/go.mod h1:tMWxXQ9wFIaZeTI9F+hmhFiGpFmhOHzyShyFUhRm0H4= +github.com/hashicorp/consul/api v1.25.1 h1:CqrdhYzc8XZuPnhIYZWH45toM0LB9ZeYr/gvpLVI3PE= github.com/hashicorp/consul/api v1.25.1/go.mod h1:iiLVwR/htV7mas/sy0O+XSuEnrdBUUydemjxcUrAt4g= github.com/hashicorp/consul/sdk v0.14.1/go.mod h1:vFt03juSzocLRFo59NkeQHHmQa6+g7oU0pfzdI1mUhg= +github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= +github.com/hashicorp/go-hclog v1.5.0 h1:bI2ocEMgcVlz55Oj1xZNBsVi900c7II+fWDyV9o+13c= github.com/hashicorp/go-hclog v1.5.0/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= +github.com/hashicorp/go-immutable-radix v1.3.1 h1:DKHmCUm2hRBK510BaiZlwvpD40f8bJFeZnpfm2KLowc= github.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= github.com/hashicorp/go-msgpack v0.5.5/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= +github.com/hashicorp/go-rootcerts v1.0.2 h1:jzhAVGtqPKbwpyCPELlgNWhE1znq+qwJtW5Oi2viEzc= github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= github.com/hashicorp/go-sockaddr v1.0.2/go.mod h1:rB4wwRAUzs07qva3c5SdrY/NEtAUjGlgmH/UkBUC97A= github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-version v1.2.1/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc= github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/hashicorp/memberlist v0.5.0/go.mod h1:yvyXLpo0QaGE59Y7hDTsTzDD25JYBZ4mHgHUZ8lrOI0= +github.com/hashicorp/serf v0.10.1 h1:Z1H2J60yRKvfDYAOZLd2MU0ND4AH/WDz7xYHDWQsIPY= github.com/hashicorp/serf v0.10.1/go.mod h1:yL2t6BqATOLGc5HF7qbFkTfXoPIY0WZdWHfEvMqbG+4= +github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8= github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= +github.com/jackc/pgconn v1.14.3 h1:bVoTr12EGANZz66nZPkMInAV/KHD2TxH9npjXXgiB3w= github.com/jackc/pgconn v1.14.3/go.mod h1:RZbme4uasqzybK2RK5c65VsHxoyaml09lx3tXOcO/VM= +github.com/jackc/pgerrcode v0.0.0-20220416144525-469b46aa5efa h1:s+4MhCQ6YrzisK6hFJUX53drDT4UsSW3DEhKn0ifuHw= github.com/jackc/pgerrcode v0.0.0-20220416144525-469b46aa5efa/go.mod h1:a/s9Lp5W7n/DD0VrVoyJ00FbP2ytTPDVOivvn2bMlds= +github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE= github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8= +github.com/jackc/pgproto3/v2 v2.3.3 h1:1HLSx5H+tXR9pW3in3zaztoEwQYRC9SQaYUHjTSUOag= github.com/jackc/pgproto3/v2 v2.3.3/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= +github.com/jackc/pgtype v1.14.0 h1:y+xUdabmyMkJLyApYuPj38mW+aAIqCe5uuBB51rH3Vw= github.com/jackc/pgtype v1.14.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4= +github.com/jackc/pgx/v4 v4.18.2 h1:xVpYkNR5pk5bMCZGfClbO962UIqVABcAGt7ha1s/FeU= github.com/jackc/pgx/v4 v4.18.2/go.mod h1:Ey4Oru5tH5sB6tV7hDmfWFahwF15Eb7DNXlRKx2CkVw= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/k0kubun/pp v2.3.0+incompatible h1:EKhKbi34VQDWJtq+zpsKSEhkHHs9w2P8Izbq8IhLVSo= github.com/k0kubun/pp v2.3.0+incompatible/go.mod h1:GWse8YhT0p8pT4ir3ZgBbfZild3tgzSScAn6HmfYukg= +github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 h1:iQTw/8FWTuc7uiaSepXwyf3o52HaUYcV+Tu66S3F5GA= github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8= +github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= +github.com/klauspost/asmfmt v1.3.2 h1:4Ri7ox3EwapiOjCki+hw14RyKk201CN4rzyCJRFLpK4= github.com/klauspost/asmfmt v1.3.2/go.mod h1:AG8TuvYojzulgDAMCnYn50l/5QV3Bs/tp6j0HLHbNSE= github.com/klauspost/compress v1.17.0/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= +github.com/klauspost/cpuid/v2 v2.0.9 h1:lgaqFMSdTdQYdZ04uHyN2d/eKdOMyi2YLSvlQIBFYa4= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= +github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8= github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/pty v1.1.1 h1:VkoXIwSboBpnk99O/KFauAEILuNHv5DVFKZMBN/gUgw= +github.com/ktrysmt/go-bitbucket v0.6.4 h1:C8dUGp0qkwncKtAnozHCbbqhptefzEd1I0sfnuy9rYQ= github.com/ktrysmt/go-bitbucket v0.6.4/go.mod h1:9u0v3hsd2rqCHRIpbir1oP7F58uo5dq19sBYvuMoyQ4= github.com/lyft/protoc-gen-star/v2 v2.0.3/go.mod h1:amey7yeodaJhXSbf/TlLvWiqQfLOSpEk//mLlc+axEk= +github.com/markbates/pkger v0.15.1 h1:3MPelV53RnGSW07izx5xGxl4e/sdRD6zqseIk0rMASY= github.com/markbates/pkger v0.15.1/go.mod h1:0JoVlrol20BSywW79rN3kdFFsE5xYM+rSCQDXbLhiuI= github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y= github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= +github.com/microsoft/go-mssqldb v1.0.0 h1:k2p2uuG8T5T/7Hp7/e3vMGTnnR0sU4h8d1CcC71iLHU= github.com/microsoft/go-mssqldb v1.0.0/go.mod h1:+4wZTUnz/SV6nffv+RRRB/ss8jPng5Sho2SmM1l2ts4= +github.com/minio/asm2plan9s v0.0.0-20200509001527-cdd76441f9d8 h1:AMFGa4R4MiIpspGNG7Z948v4n35fFGB3RR3G/ry4FWs= github.com/minio/asm2plan9s v0.0.0-20200509001527-cdd76441f9d8/go.mod h1:mC1jAcsrzbxHt8iiaC+zU4b1ylILSosueou12R++wfY= +github.com/minio/c2goasm v0.0.0-20190812172519-36a3d3bbc4f3 h1:+n/aFZefKZp7spd8DFdX7uMikMLXX4oubIzJF4kv/wI= github.com/minio/c2goasm v0.0.0-20190812172519-36a3d3bbc4f3/go.mod h1:RagcQ7I8IeTMnF8JTXieKnO4Z6JCsikNEzj0DwauVzE= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/mtibben/percent v0.2.1 h1:5gssi8Nqo8QU/r2pynCm+hBQHpkB/uNK7BJCFogWdzs= github.com/mtibben/percent v0.2.1/go.mod h1:KG9uO+SZkUp+VkRHsCdYQV3XSZrrSpR3O9ibNBTZrns= +github.com/mutecomm/go-sqlcipher/v4 v4.4.0 h1:sV1tWCWGAVlPhNGT95Q+z/txFxuhAYWwHD1afF5bMZg= github.com/mutecomm/go-sqlcipher/v4 v4.4.0/go.mod h1:PyN04SaWalavxRGH9E8ZftG6Ju7rsPrGmQRjrEaVpiY= +github.com/nakagami/firebirdsql v0.0.0-20190310045651-3c02a58cfed8 h1:P48LjvUQpTReR3TQRbxSeSBsMXzfK0uol7eRcr7VBYQ= github.com/nakagami/firebirdsql v0.0.0-20190310045651-3c02a58cfed8/go.mod h1:86wM1zFnC6/uDBfZGNwB65O+pR2OFi5q/YQaEUid1qA= +github.com/natefinch/atomic v1.0.1 h1:ZPYKxkqQOx3KZ+RsbnP/YsgvxWQPGxjC0oBt2AhwV0A= github.com/natefinch/atomic v1.0.1/go.mod h1:N/D/ELrljoqDyT3rZrsUmtsuzvHkeB/wWjHV22AZRbM= +github.com/nats-io/nats.go v1.31.0 h1:/WFBHEc/dOKBF6qf1TZhrdEfTmOZ5JzdJ+Y3m6Y/p7E= github.com/nats-io/nats.go v1.31.0/go.mod h1:di3Bm5MLsoB4Bx61CBTsxuarI36WbhAwOm8QrW39+i8= +github.com/nats-io/nkeys v0.4.6 h1:IzVe95ru2CT6ta874rt9saQRkWfe2nFj1NtvYSLqMzY= github.com/nats-io/nkeys v0.4.6/go.mod h1:4DxZNzenSVd1cYQoAa8948QY3QDjrHfcfVADymtkpts= +github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw= github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= +github.com/neo4j/neo4j-go-driver v1.8.1-0.20200803113522-b626aa943eba h1:fhFP5RliM2HW/8XdcO5QngSfFli9GcRIpMXvypTQt6E= github.com/neo4j/neo4j-go-driver v1.8.1-0.20200803113522-b626aa943eba/go.mod h1:ncO5VaFWh0Nrt+4KT4mOZboaczBZcLuHrG+/sUeP8gI= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= +github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc= github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= +github.com/onsi/gomega v1.15.0 h1:WjP/FQ/sk43MRmnEcT+MlDw2TFvkrXlprrPST/IudjU= github.com/onsi/gomega v1.15.0/go.mod h1:cIuvLEne0aoVhAgh/O6ac0Op8WWw9H6eYCriF+tEHG0= github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= +github.com/pierrec/lz4/v4 v4.1.16 h1:kQPfno+wyx6C5572ABwV+Uo3pDFzQ7yhyGchSyRda0c= github.com/pierrec/lz4/v4 v4.1.16/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= +github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 h1:KoWmjvw+nsYOo29YJK9vDA65RGE3NrOnUtO7a+RF9HU= github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= +github.com/pkg/sftp v1.13.6 h1:JFZT4XbOU7l77xGSpOdW+pwIMqP044IyjXX6FGyEKFo= github.com/pkg/sftp v1.13.6/go.mod h1:tz1ryNURKu77RL+GuCzmoJYxQczL3wLNNpPWagdg4Qk= github.com/prometheus/client_golang v1.11.1/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= github.com/prometheus/client_model v0.4.0/go.mod h1:oMQmHW1/JoDwqLtg57MGgP/Fb1CJEYF2imWWhWtMkYU= github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= +github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 h1:OdAsTTz6OkFY5QxjkYwrChwuRruF69c169dPK26NUlk= github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= +github.com/rqlite/gorqlite v0.0.0-20230708021416-2acd02b70b79 h1:V7x0hCAgL8lNGezuex1RW1sh7VXXCqfw8nXZti66iFg= github.com/rqlite/gorqlite v0.0.0-20230708021416-2acd02b70b79/go.mod h1:xF/KoXmrRyahPfo5L7Szb5cAAUl53dMWBh9cMruGEZg= +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 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= +github.com/sagikazarmark/crypt v0.17.0 h1:ZA/7pXyjkHoK4bW4mIdnCLvL8hd+Nrbiw7Dqk7D4qUk= github.com/sagikazarmark/crypt v0.17.0/go.mod h1:SMtHTvdmsZMuY/bpZoqokSoChIrcJ/epOxZN58PbZDg= +github.com/segmentio/asm v1.2.0 h1:9BQrFxC+YOHJlTlHGkTrFWf59nbL3XnCoFLTwDCI7ys= github.com/segmentio/asm v1.2.0/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs= +github.com/segmentio/encoding v0.4.0 h1:MEBYvRqiUB2nfR2criEXWqwdY6HJOUrCn5hboVOVmy8= github.com/segmentio/encoding v0.4.0/go.mod h1:/d03Cd8PoaDeceuhUUUQWjU0KhWjrmYrWPgtJHYZSnI= +github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ= github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= +github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/sirupsen/logrus v1.9.2 h1:oxx1eChJGI6Uks2ZC4W1zpLlVgqB8ner4EuQwV4Ik1Y= github.com/sirupsen/logrus v1.9.2/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/snowflakedb/gosnowflake v1.6.19 h1:KSHXrQ5o7uso25hNIzi/RObXtnSGkFgie91X82KcvMY= github.com/snowflakedb/gosnowflake v1.6.19/go.mod h1:FM1+PWUdwB9udFDsXdfD58NONC0m+MlOSmQRvimobSM= github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= github.com/stretchr/testify v1.7.5/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= @@ -316,29 +449,45 @@ github.com/swaggo/swag v1.16.2 h1:28Pp+8DkQoV+HLzLx8RGJZXNGKbFqnuvSbAAtoxiY04= github.com/swaggo/swag v1.16.2/go.mod h1:6YzXnDcpr0767iOejs318CwYkCQqyGer6BizOg03f+E= github.com/urfave/cli/v2 v2.3.0 h1:qph92Y649prgesehzOrQjdWyxFOp/QVM+6imKHad91M= github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI= +github.com/xanzy/go-gitlab v0.15.0 h1:rWtwKTgEnXyNUGrOArN7yyc3THRkpYcKXIXia9abywQ= github.com/xanzy/go-gitlab v0.15.0/go.mod h1:8zdQa/ri1dfn8eS3Ir1SyfvOKlw7WBJ8DVThkpGiXrs= +github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c= github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= +github.com/xdg-go/scram v1.1.1 h1:VOMT+81stJgXW3CpHyqHN3AXDYIMsx56mEFrB37Mb/E= github.com/xdg-go/scram v1.1.1/go.mod h1:RaEWvsqvNKKvBPvcKeFjrG2cJqOkHTiyTpzz23ni57g= +github.com/xdg-go/stringprep v1.0.3 h1:kdwGpVNwPFtjs98xCGkHjQtGKh86rDcRZN17QEMCOIs= github.com/xdg-go/stringprep v1.0.3/go.mod h1:W3f5j4i+9rC0kuIEJL0ky1VpHXQU3ocBgklLGvcBnW8= +github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d h1:splanxYIlg+5LfHAM6xpdFEAYOk8iySO56hMFq6uLyA= github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA= github.com/yuin/goldmark v1.4.13 h1:fVcFKWvrslecOb/tg+Cc05dkeYx540o0FuFt3nUVDoE= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +github.com/zeebo/xxh3 v1.0.2 h1:xZmwmqxHZA8AI603jOQ0tMqmBr9lPeFwGg6d+xy9DC0= github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaDcA= +gitlab.com/nyarla/go-crypt v0.0.0-20160106005555-d9a5dc2b789b h1:7gd+rd8P3bqcn/96gOZa3F5dpJr/vEiDQYlNb/y2uNs= gitlab.com/nyarla/go-crypt v0.0.0-20160106005555-d9a5dc2b789b/go.mod h1:T3BPAOm2cqquPa0MKWeNkmOM5RQsRhkrwMWonFMN7fE= go.etcd.io/etcd/api/v3 v3.5.10 h1:szRajuUUbLyppkhs9K6BRtjY37l66XQQmw7oZRANE4k= go.etcd.io/etcd/api/v3 v3.5.10/go.mod h1:TidfmT4Uycad3NM/o25fG3J07odo4GBB9hoxaodFCtI= +go.etcd.io/etcd/client/pkg/v3 v3.5.10 h1:kfYIdQftBnbAq8pUWFXfpuuxFSKzlmM5cSn76JByiT0= go.etcd.io/etcd/client/pkg/v3 v3.5.10/go.mod h1:DYivfIviIuQ8+/lCq4vcxuseg2P2XbHygkKwFo9fc8U= +go.etcd.io/etcd/client/v2 v2.305.10 h1:MrmRktzv/XF8CvtQt+P6wLUlURaNpSDJHFZhe//2QE4= go.etcd.io/etcd/client/v2 v2.305.10/go.mod h1:m3CKZi69HzilhVqtPDcjhSGp+kA1OmbNn0qamH80xjA= +go.etcd.io/etcd/client/v3 v3.5.10 h1:W9TXNZ+oB3MCd/8UjxHTWK5J9Nquw9fQBLJd5ne5/Ao= go.etcd.io/etcd/client/v3 v3.5.10/go.mod h1:RVeBnDz2PUEZqTpgqwAtUd8nAPf5kjyFyND7P1VkOKc= +go.lsp.dev/jsonrpc2 v0.10.0 h1:Pr/YcXJoEOTMc/b6OTmcR1DPJ3mSWl/SWiU1Cct6VmI= go.lsp.dev/jsonrpc2 v0.10.0/go.mod h1:fmEzIdXPi/rf6d4uFcayi8HpFP1nBF99ERP1htC72Ac= +go.lsp.dev/pkg v0.0.0-20210717090340-384b27a52fb2 h1:hCzQgh6UcwbKgNSRurYWSqh8MufqRRPODRBblutn4TE= go.lsp.dev/pkg v0.0.0-20210717090340-384b27a52fb2/go.mod h1:gtSHRuYfbCT0qnbLnovpie/WEmqyJ7T4n6VXiFMBtcw= +go.lsp.dev/uri v0.3.0 h1:KcZJmh6nFIBeJzTugn5JTU6OOyG0lDOo3R9KwTxTYbo= go.lsp.dev/uri v0.3.0/go.mod h1:P5sbO1IQR+qySTWOCnhnK7phBx+W3zbLqSMDJNTw88I= +go.mongodb.org/mongo-driver v1.7.5 h1:ny3p0reEpgsR2cfA5cjgwFZg3Cv/ofFh/8jbhGtz9VI= go.mongodb.org/mongo-driver v1.7.5/go.mod h1:VXEWRZ6URJIkUq2SCAyapmhH0ZLRBP+FT4xhp5Zvxng= +go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= go.opentelemetry.io/proto/otlp v0.19.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U= go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ= go.uber.org/zap v1.21.0/go.mod h1:wjWOCqI0f2ZZrJF/UufIOkiC8ii6tm1iqIsLo76RfJw= +go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220314234659-1baeb1ce4c0b/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= @@ -393,6 +542,7 @@ golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/telemetry v0.0.0-20240208230135-b75ee8823808/go.mod h1:KG1lNk5ZFNssSZLrpVb4sMXKMpGwGXOxSG3rnu2gZQQ= +golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2 h1:IRJeR9r1pYWsHKTRe/IInb7lYvbBVIqOgsX/u0mbOWY= golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= @@ -404,6 +554,7 @@ golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY= golang.org/x/term v0.17.0 h1:mkTF7LCd6WGJNL3K1Ad7kwxNfYAW6a8a8QqtMblp/4U= golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk= +golang.org/x/term v0.20.0 h1:VnkxpohqXaOBYJtBmEppKUG6mXpi+4O6purfc2+sMhw= golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= @@ -425,51 +576,80 @@ google.golang.org/api v0.128.0/go.mod h1:Y611qgqaE92On/7g65MQgxYul3c0rEB894kniWL google.golang.org/api v0.149.0/go.mod h1:Mwn1B7JTXrzXtnvmzQE2BD6bYZQ8DShKZDZbeN9I7qI= google.golang.org/api v0.150.0/go.mod h1:ccy+MJ6nrYFgE3WgRx/AMXOxOmU8Q4hSa+jjibzhxcg= google.golang.org/api v0.152.0/go.mod h1:3qNJX5eOmhiWYc67jRA/3GsDw97UFb5ivv7Y2PrriAY= +google.golang.org/api v0.153.0 h1:N1AwGhielyKFaUqH07/ZSIQR3uNPcV7NVw0vj+j4iR4= google.golang.org/api v0.153.0/go.mod h1:3qNJX5eOmhiWYc67jRA/3GsDw97UFb5ivv7Y2PrriAY= +google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/genproto v0.0.0-20230822172742-b8732ec3820d/go.mod h1:yZTlhN0tQnXo3h00fuXNCxJdLdIdnVFVBaRJ5LWBbw4= google.golang.org/genproto v0.0.0-20231016165738-49dd2c1f3d0b/go.mod h1:CgAqfJo+Xmu0GwA0411Ht3OU3OntXwsGmrmjI8ioGXI= google.golang.org/genproto v0.0.0-20231030173426-d783a09b4405/go.mod h1:3WDQMjmJk36UQhjQ89emUzb1mdaHcPeeAh4SCBKznB4= +google.golang.org/genproto v0.0.0-20231106174013-bbf56f31fb17 h1:wpZ8pe2x1Q3f2KyT5f8oP/fa9rHAKgFPr/HZdNuS+PQ= google.golang.org/genproto v0.0.0-20231106174013-bbf56f31fb17/go.mod h1:J7XzRzVy1+IPwWHZUzoD0IccYZIrXILAQpc+Qy9CMhY= google.golang.org/genproto/googleapis/api v0.0.0-20230822172742-b8732ec3820d/go.mod h1:KjSP20unUpOx5kyQUFa7k4OJg0qeJ7DEZflGDu2p6Bk= google.golang.org/genproto/googleapis/api v0.0.0-20231016165738-49dd2c1f3d0b/go.mod h1:IBQ646DjkDkvUIsVq/cc03FUFQ9wbZu7yE396YcL870= google.golang.org/genproto/googleapis/api v0.0.0-20231030173426-d783a09b4405/go.mod h1:oT32Z4o8Zv2xPQTg0pbVaPr0MPOH6f14RgXt7zfIpwg= +google.golang.org/genproto/googleapis/api v0.0.0-20231106174013-bbf56f31fb17 h1:JpwMPBpFN3uKhdaekDpiNlImDdkUAyiJ6ez/uxGaUSo= google.golang.org/genproto/googleapis/api v0.0.0-20231106174013-bbf56f31fb17/go.mod h1:0xJLfVdJqpAPl8tDg1ujOCGzx6LFLttXT5NhllGOXY4= google.golang.org/genproto/googleapis/bytestream v0.0.0-20231120223509-83a465c0220f/go.mod h1:iIgEblxoG4klcXsG0d9cpoxJ4xndv6+1FkDROCHhPRI= google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d/go.mod h1:+Bk1OCOj40wS2hwAMA+aCW9ypzm63QTBBHp6lQ3p+9M= google.golang.org/genproto/googleapis/rpc v0.0.0-20231016165738-49dd2c1f3d0b/go.mod h1:swOH3j0KzcDDgGUWr+SNpyTen5YrXjS3eyPzFYKc6lc= google.golang.org/genproto/googleapis/rpc v0.0.0-20231030173426-d783a09b4405/go.mod h1:67X1fPuzjcrkymZzZV1vvkFeTn2Rvc6lYF9MYFGCcwE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20231120223509-83a465c0220f h1:ultW7fxlIvee4HYrtnaRPon9HpEgFk5zYpmfMgtKB5I= google.golang.org/genproto/googleapis/rpc v0.0.0-20231120223509-83a465c0220f/go.mod h1:L9KNLi232K1/xB6f7AlSX692koaRnKaWSR0stBki0Yc= google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ= google.golang.org/grpc v1.57.0/go.mod h1:Sd+9RMTACXwmub0zcNY2c4arhtrbBYD1AUHI/dt16Mo= +google.golang.org/grpc v1.59.0 h1:Z5Iec2pjwb+LEOqzpB2MR12/eKFhDPhuqW91O+4bwUk= google.golang.org/grpc v1.59.0/go.mod h1:aUPDwccQo6OTjy7Hct4AfBPD1GptF4fyUjIkQ9YtF98= google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gorm.io/driver/postgres v1.5.4 h1:Iyrp9Meh3GmbSuyIAGyjkN+n9K+GHX9b9MqsTL4EJCo= gorm.io/driver/postgres v1.5.4/go.mod h1:Bgo89+h0CRcdA33Y6frlaHHVuTdOf87pmyzwW9C/BH0= lukechampine.com/frand v1.4.2 h1:RzFIpOvkMXuPMBb9maa4ND4wjBn71E1Jpf8BzJHMaVw= lukechampine.com/frand v1.4.2/go.mod h1:4S/TM2ZgrKejMcKMbeLjISpJMO+/eZ1zu3vYX9dtj3s= +lukechampine.com/uint128 v1.2.0 h1:mBi/5l91vocEN8otkC5bDLhi2KdCticRiwbdB0O+rjI= lukechampine.com/uint128 v1.2.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk= +modernc.org/b v1.0.0 h1:vpvqeyp17ddcQWF29Czawql4lDdABCDRbXRAS4+aF2o= modernc.org/b v1.0.0/go.mod h1:uZWcZfRj1BpYzfN9JTerzlNUnnPsV9O2ZA8JsRcubNg= +modernc.org/cc/v3 v3.36.3 h1:uISP3F66UlixxWEcKuIWERa4TwrZENHSL8tWxZz8bHg= modernc.org/cc/v3 v3.36.3/go.mod h1:NFUHyPn4ekoC/JHeZFfZurN6ixxawE1BnVonP/oahEI= +modernc.org/ccgo/v3 v3.16.9 h1:AXquSwg7GuMk11pIdw7fmO1Y/ybgazVkMhsZWCV0mHM= modernc.org/ccgo/v3 v3.16.9/go.mod h1:zNMzC9A9xeNUepy6KuZBbugn3c0Mc9TeiJO4lgvkJDo= +modernc.org/db v1.0.0 h1:2c6NdCfaLnshSvY7OU09cyAY0gYXUZj4lmg5ItHyucg= modernc.org/db v1.0.0/go.mod h1:kYD/cO29L/29RM0hXYl4i3+Q5VojL31kTUVpVJDw0s8= +modernc.org/file v1.0.0 h1:9/PdvjVxd5+LcWUQIfapAWRGOkDLK90rloa8s/au06A= modernc.org/file v1.0.0/go.mod h1:uqEokAEn1u6e+J45e54dsEA/pw4o7zLrA2GwyntZzjw= +modernc.org/fileutil v1.0.0 h1:Z1AFLZwl6BO8A5NldQg/xTSjGLetp+1Ubvl4alfGx8w= modernc.org/fileutil v1.0.0/go.mod h1:JHsWpkrk/CnVV1H/eGlFf85BEpfkrp56ro8nojIq9Q8= +modernc.org/golex v1.0.0 h1:wWpDlbK8ejRfSyi0frMyhilD3JBvtcx2AdGDnU+JtsE= modernc.org/golex v1.0.0/go.mod h1:b/QX9oBD/LhixY6NDh+IdGv17hgB+51fET1i2kPSmvk= +modernc.org/internal v1.0.0 h1:XMDsFDcBDsibbBnHB2xzljZ+B1yrOVLEFkKL2u15Glw= modernc.org/internal v1.0.0/go.mod h1:VUD/+JAkhCpvkUitlEOnhpVxCgsBI90oTzSCRcqQVSM= +modernc.org/libc v1.17.1 h1:Q8/Cpi36V/QBfuQaFVeisEBs3WqoGAJprZzmf7TfEYI= modernc.org/libc v1.17.1/go.mod h1:FZ23b+8LjxZs7XtFMbSzL/EhPxNbfZbErxEHc7cbD9s= +modernc.org/lldb v1.0.0 h1:6vjDJxQEfhlOLwl4bhpwIz00uyFK4EmSYcbwqwbynsc= modernc.org/lldb v1.0.0/go.mod h1:jcRvJGWfCGodDZz8BPwiKMJxGJngQ/5DrRapkQnLob8= +modernc.org/mathutil v1.5.0 h1:rV0Ko/6SfM+8G+yKiyI830l3Wuz1zRutdslNoQ0kfiQ= modernc.org/mathutil v1.5.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= +modernc.org/memory v1.2.1 h1:dkRh86wgmq/bJu2cAS2oqBCz/KsMZU7TUM4CibQ7eBs= modernc.org/memory v1.2.1/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU= +modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4= modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= +modernc.org/ql v1.0.0 h1:bIQ/trWNVjQPlinI6jdOQsi195SIturGo3mp5hsDqVU= modernc.org/ql v1.0.0/go.mod h1:xGVyrLIatPcO2C1JvI/Co8c0sr6y91HKFNy4pt9JXEY= +modernc.org/sortutil v1.1.0 h1:oP3U4uM+NT/qBQcbg/K2iqAX0Nx7B1b6YZtq3Gk/PjM= modernc.org/sortutil v1.1.0/go.mod h1:ZyL98OQHJgH9IEfN71VsamvJgrtRX9Dj2gX+vH86L1k= +modernc.org/sqlite v1.18.1 h1:ko32eKt3jf7eqIkCgPAeHMBXw3riNSLhl2f3loEF7o8= modernc.org/sqlite v1.18.1/go.mod h1:6ho+Gow7oX5V+OiOQ6Tr4xeqbx13UZ6t+Fw9IRUG4d4= +modernc.org/strutil v1.1.3 h1:fNMm+oJklMGYfU9Ylcywl0CO5O6nTfaowNsh2wpPjzY= modernc.org/strutil v1.1.3/go.mod h1:MEHNA7PdEnEwLvspRMtWTNnp2nnyvMfkimT1NKNAGbw= +modernc.org/token v1.0.0 h1:a0jaWiNMDhDUtqOj09wvjWWAqd3q7WpBulmL9H2egsk= modernc.org/token v1.0.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= +modernc.org/zappy v1.0.0 h1:dPVaP+3ueIUv4guk8PuZ2wiUGcJ1WUVvIheeSSTD0yk= modernc.org/zappy v1.0.0/go.mod h1:hHe+oGahLVII/aTTyWK/b53VDHMAGCBYYeZ9sn83HC4= +sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo= sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8=