diff --git a/iac/modules/job-api/jobs/api.hcl b/iac/modules/job-api/jobs/api.hcl index f64f2e340c..50b1b86495 100644 --- a/iac/modules/job-api/jobs/api.hcl +++ b/iac/modules/job-api/jobs/api.hcl @@ -151,7 +151,6 @@ job "api" { AUTH_DB_READ_REPLICA_CONNECTION_STRING = "${postgres_read_replica_connection_string}" AUTH_DB_MAX_OPEN_CONNECTIONS = "${auth_db_max_open_connections}" AUTH_DB_MIN_IDLE_CONNECTIONS = "${auth_db_min_idle_connections}" - SUPABASE_JWT_SECRETS = "${supabase_jwt_secrets}" LOKI_URL = "${loki_url}" CLICKHOUSE_CONNECTION_STRING = "${clickhouse_connection_string}" diff --git a/iac/modules/job-api/main.tf b/iac/modules/job-api/main.tf index 991192560a..f94031022e 100644 --- a/iac/modules/job-api/main.tf +++ b/iac/modules/job-api/main.tf @@ -1,6 +1,7 @@ locals { default_job_env_vars = { - GIN_MODE : "release" + GIN_MODE = "release" + AUTH_PROVIDER_CONFIG = jsonencode(var.auth_provider_config) } job_env_vars = merge(local.default_job_env_vars, var.job_env_vars) @@ -27,7 +28,6 @@ resource "nomad_job" "api" { api_docker_image = var.api_docker_image postgres_connection_string = var.postgres_connection_string postgres_read_replica_connection_string = var.postgres_read_replica_connection_string - supabase_jwt_secrets = var.supabase_jwt_secrets posthog_api_key = var.posthog_api_key analytics_collector_host = var.analytics_collector_host analytics_collector_api_token = var.analytics_collector_api_token diff --git a/iac/modules/job-api/variables.tf b/iac/modules/job-api/variables.tf index 827611047c..09f767a165 100644 --- a/iac/modules/job-api/variables.tf +++ b/iac/modules/job-api/variables.tf @@ -67,9 +67,35 @@ variable "postgres_read_replica_connection_string" { sensitive = true } -variable "supabase_jwt_secrets" { - type = string +variable "auth_provider_config" { + type = object({ + jwt = optional(list(object({ + issuer = object({ + url = string + discoveryURL = optional(string) + audiences = list(string) + audienceMatchPolicy = optional(string) + }) + claimMappings = optional(object({ + username = object({ + claim = string + }) + })) + jwksCacheDuration = optional(string) + }))) + bearer = optional(list(object({ + hmac = object({ + secrets = list(string) + }) + claimMappings = optional(object({ + username = object({ + claim = string + }) + })) + }))) + }) sensitive = true + default = null } variable "posthog_api_key" { diff --git a/iac/modules/job-dashboard-api/main.tf b/iac/modules/job-dashboard-api/main.tf index 1156c559fc..e63a1a0201 100644 --- a/iac/modules/job-dashboard-api/main.tf +++ b/iac/modules/job-dashboard-api/main.tf @@ -8,7 +8,7 @@ locals { AUTH_DB_READ_REPLICA_CONNECTION_STRING = var.auth_db_read_replica_connection_string SUPABASE_DB_CONNECTION_STRING = var.supabase_db_connection_string CLICKHOUSE_CONNECTION_STRING = var.clickhouse_connection_string - SUPABASE_JWT_SECRETS = var.supabase_jwt_secrets + AUTH_PROVIDER_CONFIG = jsonencode(var.auth_provider_config) REDIS_URL = var.redis_url REDIS_CLUSTER_URL = var.redis_cluster_url REDIS_TLS_CA_BASE64 = var.redis_tls_ca_base64 diff --git a/iac/modules/job-dashboard-api/variables.tf b/iac/modules/job-dashboard-api/variables.tf index 10c2174600..aafce4882c 100644 --- a/iac/modules/job-dashboard-api/variables.tf +++ b/iac/modules/job-dashboard-api/variables.tf @@ -50,9 +50,35 @@ variable "clickhouse_connection_string" { sensitive = true } -variable "supabase_jwt_secrets" { - type = string +variable "auth_provider_config" { + type = object({ + jwt = optional(list(object({ + issuer = object({ + url = string + discoveryURL = optional(string) + audiences = list(string) + audienceMatchPolicy = optional(string) + }) + claimMappings = optional(object({ + username = object({ + claim = string + }) + })) + jwksCacheDuration = optional(string) + }))) + bearer = optional(list(object({ + hmac = object({ + secrets = list(string) + }) + claimMappings = optional(object({ + username = object({ + claim = string + }) + })) + }))) + }) sensitive = true + default = null } variable "enable_auth_user_sync_background_worker" { @@ -64,6 +90,7 @@ variable "enable_billing_http_team_provision_sink" { type = bool default = false } + variable "otel_collector_grpc_port" { type = number default = 4317 diff --git a/iac/provider-aws/main.tf b/iac/provider-aws/main.tf index e72da5fb01..ddbfa50c2c 100644 --- a/iac/provider-aws/main.tf +++ b/iac/provider-aws/main.tf @@ -182,12 +182,20 @@ module "nomad" { clickhouse_node_pool = local.clickhouse_pool_name clickhouse_jobs_prefix = local.clickhouse_jobs_prefix - api_cluster_size = var.api_cluster_size - api_internal_grpc_port = var.api_internal_grpc_port - api_repository_name = module.init.api_repository_name - db_migrator_repository_name = module.init.db_migrator_repository_name - postgres_connection_string = module.init.postgres_connection_string - supabase_jwt_secrets = module.init.supabase_jwt_secrets + api_cluster_size = var.api_cluster_size + api_internal_grpc_port = var.api_internal_grpc_port + api_repository_name = module.init.api_repository_name + db_migrator_repository_name = module.init.db_migrator_repository_name + postgres_connection_string = module.init.postgres_connection_string + auth_provider_config = { + bearer = [ + { + hmac = { + secrets = split(",", trimspace(module.init.supabase_jwt_secrets)) + } + } + ] + } admin_token = module.init.admin_token sandbox_access_token_hash_seed = module.init.sandbox_access_token_hash_seed diff --git a/iac/provider-aws/nomad/main.tf b/iac/provider-aws/nomad/main.tf index 3cf47b0cae..4dc25b93b3 100644 --- a/iac/provider-aws/nomad/main.tf +++ b/iac/provider-aws/nomad/main.tf @@ -126,7 +126,7 @@ module "api" { environment = var.environment api_docker_image = data.aws_ecr_image.api.image_uri postgres_connection_string = var.postgres_connection_string - supabase_jwt_secrets = var.supabase_jwt_secrets + auth_provider_config = var.auth_provider_config nomad_acl_token = var.nomad_acl_token admin_token = var.admin_token redis_url = var.redis_url diff --git a/iac/provider-aws/nomad/variables.tf b/iac/provider-aws/nomad/variables.tf index a03e5cc999..6a2809139e 100644 --- a/iac/provider-aws/nomad/variables.tf +++ b/iac/provider-aws/nomad/variables.tf @@ -188,9 +188,35 @@ variable "postgres_connection_string" { sensitive = true } -variable "supabase_jwt_secrets" { - type = string +variable "auth_provider_config" { + type = object({ + jwt = optional(list(object({ + issuer = object({ + url = string + discoveryURL = optional(string) + audiences = list(string) + audienceMatchPolicy = optional(string) + }) + claimMappings = optional(object({ + username = object({ + claim = string + }) + })) + jwksCacheDuration = optional(string) + }))) + bearer = optional(list(object({ + hmac = object({ + secrets = list(string) + }) + claimMappings = optional(object({ + username = object({ + claim = string + }) + })) + }))) + }) sensitive = true + default = null } variable "admin_token" { diff --git a/iac/provider-gcp/nomad/main.tf b/iac/provider-gcp/nomad/main.tf index c4a27c82b0..b724045907 100644 --- a/iac/provider-gcp/nomad/main.tf +++ b/iac/provider-gcp/nomad/main.tf @@ -6,6 +6,23 @@ locals { enable_billing_http_team_provision_sink = var.enable_billing_http_team_provision_sink dashboard_api_billing_server_url = local.enable_billing_http_team_provision_sink ? trimspace(data.google_secret_manager_secret_version.billing_server_url[0].secret_data) : "" dashboard_api_billing_server_api_token = local.enable_billing_http_team_provision_sink ? trimspace(data.google_secret_manager_secret_version.billing_server_api_token[0].secret_data) : "" + default_auth_provider_config = { + jwt = [] + bearer = [ + { + hmac = { + secrets = split(",", trimspace(data.google_secret_manager_secret_version.supabase_jwt_secrets.secret_data)) + } + claimMappings = null + } + ] + } + # jsonencode/jsondecode strips Terraform's static type info from + # var.auth_provider_config so that the conditional below does not fail with + # "Inconsistent conditional result types" when the typed object literal in + # `default_auth_provider_config` (a tuple of objects) is compared with the + # variable's declared object type (a list of objects). + auth_provider_config = var.auth_provider_config != null ? jsondecode(jsonencode(var.auth_provider_config)) : local.default_auth_provider_config } # API @@ -117,7 +134,7 @@ module "api" { api_docker_image = data.google_artifact_registry_docker_image.api_image.self_link postgres_connection_string = data.google_secret_manager_secret_version.postgres_connection_string.secret_data postgres_read_replica_connection_string = trimspace(data.google_secret_manager_secret_version.postgres_read_replica_connection_string.secret_data) - supabase_jwt_secrets = trimspace(data.google_secret_manager_secret_version.supabase_jwt_secrets.secret_data) + auth_provider_config = local.auth_provider_config posthog_api_key = trimspace(data.google_secret_manager_secret_version.posthog_api_key.secret_data) environment = var.environment analytics_collector_host = trimspace(data.google_secret_manager_secret_version.analytics_collector_host.secret_data) @@ -166,7 +183,7 @@ module "dashboard_api" { auth_db_read_replica_connection_string = trimspace(data.google_secret_manager_secret_version.postgres_read_replica_connection_string.secret_data) supabase_db_connection_string = trimspace(data.google_secret_manager_secret_version.supabase_db_connection_string.secret_data) clickhouse_connection_string = local.clickhouse_connection_string - supabase_jwt_secrets = trimspace(data.google_secret_manager_secret_version.supabase_jwt_secrets.secret_data) + auth_provider_config = local.auth_provider_config redis_url = local.redis_url redis_cluster_url = local.redis_cluster_url redis_tls_ca_base64 = trimspace(data.google_secret_manager_secret_version.redis_tls_ca_base64.secret_data) diff --git a/iac/provider-gcp/nomad/variables.tf b/iac/provider-gcp/nomad/variables.tf index 4ebca3a886..904495187c 100644 --- a/iac/provider-gcp/nomad/variables.tf +++ b/iac/provider-gcp/nomad/variables.tf @@ -473,6 +473,37 @@ variable "supabase_db_connection_string_secret_version" { type = any } +variable "auth_provider_config" { + type = object({ + jwt = optional(list(object({ + issuer = object({ + url = string + discoveryURL = optional(string) + audiences = list(string) + audienceMatchPolicy = optional(string) + }) + claimMappings = optional(object({ + username = object({ + claim = string + }) + })) + jwksCacheDuration = optional(string) + }))) + bearer = optional(list(object({ + hmac = object({ + secrets = list(string) + }) + claimMappings = optional(object({ + username = object({ + claim = string + }) + })) + }))) + }) + sensitive = true + default = null +} + variable "enable_auth_user_sync_background_worker" { type = bool default = false @@ -482,6 +513,7 @@ variable "enable_billing_http_team_provision_sink" { type = bool default = false } + variable "volume_token_issuer" { type = string } diff --git a/packages/api/Makefile b/packages/api/Makefile index 5c580ee5cd..399e8901ad 100644 --- a/packages/api/Makefile +++ b/packages/api/Makefile @@ -35,7 +35,7 @@ build-debug: run: make build-debug POSTGRES_CONNECTION_STRING=$(POSTGRES_CONNECTION_STRING) \ - SUPABASE_JWT_SECRETS=$(SUPABASE_JWT_SECRETS) \ + AUTH_PROVIDER_CONFIG='{"bearer":[{"hmac":{"secrets":["$(SUPABASE_JWT_SECRETS)"]}}]}' \ GOTRACEBACK=crash \ GODEBUG=madvdontneed=1 \ SANDBOX_ACCESS_TOKEN_HASH_SEED=$(SANDBOX_ACCESS_TOKEN_HASH_SEED) \ diff --git a/packages/api/go.mod b/packages/api/go.mod index 064b21a81b..04a3f03d21 100644 --- a/packages/api/go.mod +++ b/packages/api/go.mod @@ -75,6 +75,8 @@ require ( github.com/Masterminds/goutils v1.1.1 // indirect github.com/Masterminds/semver/v3 v3.4.0 // indirect github.com/Masterminds/sprig/v3 v3.3.0 // indirect + github.com/MicahParks/jwkset v0.11.0 // indirect + github.com/MicahParks/keyfunc/v3 v3.8.0 // indirect github.com/Microsoft/go-winio v0.6.2 // indirect github.com/Workiva/go-datastructures v1.1.6 // indirect github.com/air-verse/air v1.61.7 // indirect diff --git a/packages/api/go.sum b/packages/api/go.sum index 9e77bd0f1b..d7b37d3e6d 100644 --- a/packages/api/go.sum +++ b/packages/api/go.sum @@ -57,6 +57,10 @@ github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1 github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= github.com/Masterminds/sprig/v3 v3.3.0 h1:mQh0Yrg1XPo6vjYXgtf5OtijNAKJRNcTdOOGZe3tPhs= github.com/Masterminds/sprig/v3 v3.3.0/go.mod h1:Zy1iXRYNqNLUolqCpL4uhk6SHUMAOSCzdgBfDb35Lz0= +github.com/MicahParks/jwkset v0.11.0 h1:yc0zG+jCvZpWgFDFmvs8/8jqqVBG9oyIbmBtmjOhoyQ= +github.com/MicahParks/jwkset v0.11.0/go.mod h1:U2oRhRaLgDCLjtpGL2GseNKGmZtLs/3O7p+OZaL5vo0= +github.com/MicahParks/keyfunc/v3 v3.8.0 h1:Hx2dgIjAXGk9slakM6rV9BOeaWDPEXXZ4Us8guNBfds= +github.com/MicahParks/keyfunc/v3 v3.8.0/go.mod h1:z66bkCviwqfg2YUp+Jcc/xRE9IXLcMq6DrgV/+Htru0= github.com/Microsoft/go-winio v0.5.0/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= diff --git a/packages/api/internal/api/api.gen.go b/packages/api/internal/api/api.gen.go index af4998a59c..89fb6d9ce5 100644 --- a/packages/api/internal/api/api.gen.go +++ b/packages/api/internal/api/api.gen.go @@ -25,11 +25,13 @@ import ( ) const ( - AccessTokenAuthScopes = "AccessTokenAuth.Scopes" - AdminTokenAuthScopes = "AdminTokenAuth.Scopes" - ApiKeyAuthScopes = "ApiKeyAuth.Scopes" - Supabase1TokenAuthScopes = "Supabase1TokenAuth.Scopes" - Supabase2TeamAuthScopes = "Supabase2TeamAuth.Scopes" + AccessTokenAuthScopes = "AccessTokenAuth.Scopes" + AdminTokenAuthScopes = "AdminTokenAuth.Scopes" + ApiKeyAuthScopes = "ApiKeyAuth.Scopes" + AuthProviderTeamAuthScopes = "AuthProviderTeamAuth.Scopes" + AuthProviderTokenAuthScopes = "AuthProviderTokenAuth.Scopes" + Supabase1TokenAuthScopes = "Supabase1TokenAuth.Scopes" + Supabase2TeamAuthScopes = "Supabase2TeamAuth.Scopes" ) // Defines values for AWSRegistryType. @@ -10411,6 +10413,8 @@ func (siw *ServerInterfaceWrapper) PostAccessTokens(c *gin.Context) { c.Set(Supabase1TokenAuthScopes, []string{}) + c.Set(AuthProviderTokenAuthScopes, []string{}) + for _, middleware := range siw.HandlerMiddlewares { middleware(c) if c.IsAborted() { @@ -10437,6 +10441,8 @@ func (siw *ServerInterfaceWrapper) DeleteAccessTokensAccessTokenID(c *gin.Contex c.Set(Supabase1TokenAuthScopes, []string{}) + c.Set(AuthProviderTokenAuthScopes, []string{}) + for _, middleware := range siw.HandlerMiddlewares { middleware(c) if c.IsAborted() { @@ -10506,6 +10512,10 @@ func (siw *ServerInterfaceWrapper) GetApiKeys(c *gin.Context) { c.Set(Supabase2TeamAuthScopes, []string{}) + c.Set(AuthProviderTeamAuthScopes, []string{}) + + c.Set(AuthProviderTokenAuthScopes, []string{}) + for _, middleware := range siw.HandlerMiddlewares { middleware(c) if c.IsAborted() { @@ -10523,6 +10533,10 @@ func (siw *ServerInterfaceWrapper) PostApiKeys(c *gin.Context) { c.Set(Supabase2TeamAuthScopes, []string{}) + c.Set(AuthProviderTeamAuthScopes, []string{}) + + c.Set(AuthProviderTokenAuthScopes, []string{}) + for _, middleware := range siw.HandlerMiddlewares { middleware(c) if c.IsAborted() { @@ -10551,6 +10565,10 @@ func (siw *ServerInterfaceWrapper) DeleteApiKeysApiKeyID(c *gin.Context) { c.Set(Supabase2TeamAuthScopes, []string{}) + c.Set(AuthProviderTeamAuthScopes, []string{}) + + c.Set(AuthProviderTokenAuthScopes, []string{}) + for _, middleware := range siw.HandlerMiddlewares { middleware(c) if c.IsAborted() { @@ -10579,6 +10597,10 @@ func (siw *ServerInterfaceWrapper) PatchApiKeysApiKeyID(c *gin.Context) { c.Set(Supabase2TeamAuthScopes, []string{}) + c.Set(AuthProviderTeamAuthScopes, []string{}) + + c.Set(AuthProviderTokenAuthScopes, []string{}) + for _, middleware := range siw.HandlerMiddlewares { middleware(c) if c.IsAborted() { @@ -10691,6 +10713,10 @@ func (siw *ServerInterfaceWrapper) GetSandboxes(c *gin.Context) { c.Set(Supabase2TeamAuthScopes, []string{}) + c.Set(AuthProviderTeamAuthScopes, []string{}) + + c.Set(AuthProviderTokenAuthScopes, []string{}) + // Parameter object where we will unmarshal all parameters from the context var params GetSandboxesParams @@ -10721,6 +10747,10 @@ func (siw *ServerInterfaceWrapper) PostSandboxes(c *gin.Context) { c.Set(Supabase2TeamAuthScopes, []string{}) + c.Set(AuthProviderTeamAuthScopes, []string{}) + + c.Set(AuthProviderTokenAuthScopes, []string{}) + for _, middleware := range siw.HandlerMiddlewares { middleware(c) if c.IsAborted() { @@ -10742,6 +10772,10 @@ func (siw *ServerInterfaceWrapper) GetSandboxesMetrics(c *gin.Context) { c.Set(Supabase2TeamAuthScopes, []string{}) + c.Set(AuthProviderTeamAuthScopes, []string{}) + + c.Set(AuthProviderTokenAuthScopes, []string{}) + // Parameter object where we will unmarshal all parameters from the context var params GetSandboxesMetricsParams @@ -10790,6 +10824,10 @@ func (siw *ServerInterfaceWrapper) DeleteSandboxesSandboxID(c *gin.Context) { c.Set(Supabase2TeamAuthScopes, []string{}) + c.Set(AuthProviderTeamAuthScopes, []string{}) + + c.Set(AuthProviderTokenAuthScopes, []string{}) + for _, middleware := range siw.HandlerMiddlewares { middleware(c) if c.IsAborted() { @@ -10820,6 +10858,10 @@ func (siw *ServerInterfaceWrapper) GetSandboxesSandboxID(c *gin.Context) { c.Set(Supabase2TeamAuthScopes, []string{}) + c.Set(AuthProviderTeamAuthScopes, []string{}) + + c.Set(AuthProviderTokenAuthScopes, []string{}) + for _, middleware := range siw.HandlerMiddlewares { middleware(c) if c.IsAborted() { @@ -10850,6 +10892,10 @@ func (siw *ServerInterfaceWrapper) PostSandboxesSandboxIDConnect(c *gin.Context) c.Set(Supabase2TeamAuthScopes, []string{}) + c.Set(AuthProviderTeamAuthScopes, []string{}) + + c.Set(AuthProviderTokenAuthScopes, []string{}) + for _, middleware := range siw.HandlerMiddlewares { middleware(c) if c.IsAborted() { @@ -10880,6 +10926,10 @@ func (siw *ServerInterfaceWrapper) GetSandboxesSandboxIDLogs(c *gin.Context) { c.Set(Supabase2TeamAuthScopes, []string{}) + c.Set(AuthProviderTeamAuthScopes, []string{}) + + c.Set(AuthProviderTokenAuthScopes, []string{}) + // Parameter object where we will unmarshal all parameters from the context var params GetSandboxesSandboxIDLogsParams @@ -10929,6 +10979,10 @@ func (siw *ServerInterfaceWrapper) GetSandboxesSandboxIDMetrics(c *gin.Context) c.Set(Supabase2TeamAuthScopes, []string{}) + c.Set(AuthProviderTeamAuthScopes, []string{}) + + c.Set(AuthProviderTokenAuthScopes, []string{}) + // Parameter object where we will unmarshal all parameters from the context var params GetSandboxesSandboxIDMetricsParams @@ -10978,6 +11032,10 @@ func (siw *ServerInterfaceWrapper) PutSandboxesSandboxIDNetwork(c *gin.Context) c.Set(Supabase2TeamAuthScopes, []string{}) + c.Set(AuthProviderTeamAuthScopes, []string{}) + + c.Set(AuthProviderTokenAuthScopes, []string{}) + for _, middleware := range siw.HandlerMiddlewares { middleware(c) if c.IsAborted() { @@ -11008,6 +11066,10 @@ func (siw *ServerInterfaceWrapper) PostSandboxesSandboxIDPause(c *gin.Context) { c.Set(Supabase2TeamAuthScopes, []string{}) + c.Set(AuthProviderTeamAuthScopes, []string{}) + + c.Set(AuthProviderTokenAuthScopes, []string{}) + for _, middleware := range siw.HandlerMiddlewares { middleware(c) if c.IsAborted() { @@ -11038,6 +11100,10 @@ func (siw *ServerInterfaceWrapper) PostSandboxesSandboxIDRefreshes(c *gin.Contex c.Set(Supabase2TeamAuthScopes, []string{}) + c.Set(AuthProviderTeamAuthScopes, []string{}) + + c.Set(AuthProviderTokenAuthScopes, []string{}) + for _, middleware := range siw.HandlerMiddlewares { middleware(c) if c.IsAborted() { @@ -11068,6 +11134,10 @@ func (siw *ServerInterfaceWrapper) PostSandboxesSandboxIDResume(c *gin.Context) c.Set(Supabase2TeamAuthScopes, []string{}) + c.Set(AuthProviderTeamAuthScopes, []string{}) + + c.Set(AuthProviderTokenAuthScopes, []string{}) + for _, middleware := range siw.HandlerMiddlewares { middleware(c) if c.IsAborted() { @@ -11098,6 +11168,10 @@ func (siw *ServerInterfaceWrapper) PostSandboxesSandboxIDSnapshots(c *gin.Contex c.Set(Supabase2TeamAuthScopes, []string{}) + c.Set(AuthProviderTeamAuthScopes, []string{}) + + c.Set(AuthProviderTokenAuthScopes, []string{}) + for _, middleware := range siw.HandlerMiddlewares { middleware(c) if c.IsAborted() { @@ -11128,6 +11202,10 @@ func (siw *ServerInterfaceWrapper) PostSandboxesSandboxIDTimeout(c *gin.Context) c.Set(Supabase2TeamAuthScopes, []string{}) + c.Set(AuthProviderTeamAuthScopes, []string{}) + + c.Set(AuthProviderTokenAuthScopes, []string{}) + for _, middleware := range siw.HandlerMiddlewares { middleware(c) if c.IsAborted() { @@ -11149,6 +11227,10 @@ func (siw *ServerInterfaceWrapper) GetSnapshots(c *gin.Context) { c.Set(Supabase2TeamAuthScopes, []string{}) + c.Set(AuthProviderTeamAuthScopes, []string{}) + + c.Set(AuthProviderTokenAuthScopes, []string{}) + // Parameter object where we will unmarshal all parameters from the context var params GetSnapshotsParams @@ -11193,6 +11275,8 @@ func (siw *ServerInterfaceWrapper) GetTeams(c *gin.Context) { c.Set(Supabase1TokenAuthScopes, []string{}) + c.Set(AuthProviderTokenAuthScopes, []string{}) + for _, middleware := range siw.HandlerMiddlewares { middleware(c) if c.IsAborted() { @@ -11223,6 +11307,10 @@ func (siw *ServerInterfaceWrapper) GetTeamsTeamIDMetrics(c *gin.Context) { c.Set(Supabase2TeamAuthScopes, []string{}) + c.Set(AuthProviderTeamAuthScopes, []string{}) + + c.Set(AuthProviderTokenAuthScopes, []string{}) + // Parameter object where we will unmarshal all parameters from the context var params GetTeamsTeamIDMetricsParams @@ -11272,6 +11360,10 @@ func (siw *ServerInterfaceWrapper) GetTeamsTeamIDMetricsMax(c *gin.Context) { c.Set(Supabase2TeamAuthScopes, []string{}) + c.Set(AuthProviderTeamAuthScopes, []string{}) + + c.Set(AuthProviderTokenAuthScopes, []string{}) + // Parameter object where we will unmarshal all parameters from the context var params GetTeamsTeamIDMetricsMaxParams @@ -11329,6 +11421,10 @@ func (siw *ServerInterfaceWrapper) GetTemplates(c *gin.Context) { c.Set(Supabase2TeamAuthScopes, []string{}) + c.Set(AuthProviderTeamAuthScopes, []string{}) + + c.Set(AuthProviderTokenAuthScopes, []string{}) + // Parameter object where we will unmarshal all parameters from the context var params GetTemplatesParams @@ -11359,6 +11455,10 @@ func (siw *ServerInterfaceWrapper) PostTemplates(c *gin.Context) { c.Set(Supabase2TeamAuthScopes, []string{}) + c.Set(AuthProviderTeamAuthScopes, []string{}) + + c.Set(AuthProviderTokenAuthScopes, []string{}) + for _, middleware := range siw.HandlerMiddlewares { middleware(c) if c.IsAborted() { @@ -11389,6 +11489,10 @@ func (siw *ServerInterfaceWrapper) GetTemplatesAliasesAlias(c *gin.Context) { c.Set(Supabase2TeamAuthScopes, []string{}) + c.Set(AuthProviderTeamAuthScopes, []string{}) + + c.Set(AuthProviderTokenAuthScopes, []string{}) + for _, middleware := range siw.HandlerMiddlewares { middleware(c) if c.IsAborted() { @@ -11408,6 +11512,10 @@ func (siw *ServerInterfaceWrapper) DeleteTemplatesTags(c *gin.Context) { c.Set(Supabase2TeamAuthScopes, []string{}) + c.Set(AuthProviderTeamAuthScopes, []string{}) + + c.Set(AuthProviderTokenAuthScopes, []string{}) + for _, middleware := range siw.HandlerMiddlewares { middleware(c) if c.IsAborted() { @@ -11427,6 +11535,10 @@ func (siw *ServerInterfaceWrapper) PostTemplatesTags(c *gin.Context) { c.Set(Supabase2TeamAuthScopes, []string{}) + c.Set(AuthProviderTeamAuthScopes, []string{}) + + c.Set(AuthProviderTokenAuthScopes, []string{}) + for _, middleware := range siw.HandlerMiddlewares { middleware(c) if c.IsAborted() { @@ -11459,6 +11571,10 @@ func (siw *ServerInterfaceWrapper) DeleteTemplatesTemplateID(c *gin.Context) { c.Set(Supabase2TeamAuthScopes, []string{}) + c.Set(AuthProviderTeamAuthScopes, []string{}) + + c.Set(AuthProviderTokenAuthScopes, []string{}) + for _, middleware := range siw.HandlerMiddlewares { middleware(c) if c.IsAborted() { @@ -11489,6 +11605,10 @@ func (siw *ServerInterfaceWrapper) GetTemplatesTemplateID(c *gin.Context) { c.Set(Supabase2TeamAuthScopes, []string{}) + c.Set(AuthProviderTeamAuthScopes, []string{}) + + c.Set(AuthProviderTokenAuthScopes, []string{}) + // Parameter object where we will unmarshal all parameters from the context var params GetTemplatesTemplateIDParams @@ -11540,6 +11660,10 @@ func (siw *ServerInterfaceWrapper) PatchTemplatesTemplateID(c *gin.Context) { c.Set(Supabase2TeamAuthScopes, []string{}) + c.Set(AuthProviderTeamAuthScopes, []string{}) + + c.Set(AuthProviderTokenAuthScopes, []string{}) + for _, middleware := range siw.HandlerMiddlewares { middleware(c) if c.IsAborted() { @@ -11570,6 +11694,10 @@ func (siw *ServerInterfaceWrapper) PostTemplatesTemplateID(c *gin.Context) { c.Set(Supabase2TeamAuthScopes, []string{}) + c.Set(AuthProviderTeamAuthScopes, []string{}) + + c.Set(AuthProviderTokenAuthScopes, []string{}) + for _, middleware := range siw.HandlerMiddlewares { middleware(c) if c.IsAborted() { @@ -11609,6 +11737,10 @@ func (siw *ServerInterfaceWrapper) PostTemplatesTemplateIDBuildsBuildID(c *gin.C c.Set(Supabase2TeamAuthScopes, []string{}) + c.Set(AuthProviderTeamAuthScopes, []string{}) + + c.Set(AuthProviderTokenAuthScopes, []string{}) + for _, middleware := range siw.HandlerMiddlewares { middleware(c) if c.IsAborted() { @@ -11650,6 +11782,10 @@ func (siw *ServerInterfaceWrapper) GetTemplatesTemplateIDBuildsBuildIDLogs(c *gi c.Set(Supabase2TeamAuthScopes, []string{}) + c.Set(AuthProviderTeamAuthScopes, []string{}) + + c.Set(AuthProviderTokenAuthScopes, []string{}) + // Parameter object where we will unmarshal all parameters from the context var params GetTemplatesTemplateIDBuildsBuildIDLogsParams @@ -11734,6 +11870,10 @@ func (siw *ServerInterfaceWrapper) GetTemplatesTemplateIDBuildsBuildIDStatus(c * c.Set(Supabase2TeamAuthScopes, []string{}) + c.Set(AuthProviderTeamAuthScopes, []string{}) + + c.Set(AuthProviderTokenAuthScopes, []string{}) + // Parameter object where we will unmarshal all parameters from the context var params GetTemplatesTemplateIDBuildsBuildIDStatusParams @@ -11802,6 +11942,10 @@ func (siw *ServerInterfaceWrapper) GetTemplatesTemplateIDFilesHash(c *gin.Contex c.Set(Supabase2TeamAuthScopes, []string{}) + c.Set(AuthProviderTeamAuthScopes, []string{}) + + c.Set(AuthProviderTokenAuthScopes, []string{}) + for _, middleware := range siw.HandlerMiddlewares { middleware(c) if c.IsAborted() { @@ -11832,6 +11976,10 @@ func (siw *ServerInterfaceWrapper) GetTemplatesTemplateIDTags(c *gin.Context) { c.Set(Supabase2TeamAuthScopes, []string{}) + c.Set(AuthProviderTeamAuthScopes, []string{}) + + c.Set(AuthProviderTokenAuthScopes, []string{}) + for _, middleware := range siw.HandlerMiddlewares { middleware(c) if c.IsAborted() { @@ -11853,6 +12001,10 @@ func (siw *ServerInterfaceWrapper) GetV2Sandboxes(c *gin.Context) { c.Set(Supabase2TeamAuthScopes, []string{}) + c.Set(AuthProviderTeamAuthScopes, []string{}) + + c.Set(AuthProviderTokenAuthScopes, []string{}) + // Parameter object where we will unmarshal all parameters from the context var params GetV2SandboxesParams @@ -11918,6 +12070,10 @@ func (siw *ServerInterfaceWrapper) GetV2SandboxesSandboxIDLogs(c *gin.Context) { c.Set(Supabase2TeamAuthScopes, []string{}) + c.Set(AuthProviderTeamAuthScopes, []string{}) + + c.Set(AuthProviderTokenAuthScopes, []string{}) + // Parameter object where we will unmarshal all parameters from the context var params GetV2SandboxesSandboxIDLogsParams @@ -11980,6 +12136,10 @@ func (siw *ServerInterfaceWrapper) PostV2Templates(c *gin.Context) { c.Set(Supabase2TeamAuthScopes, []string{}) + c.Set(AuthProviderTeamAuthScopes, []string{}) + + c.Set(AuthProviderTokenAuthScopes, []string{}) + for _, middleware := range siw.HandlerMiddlewares { middleware(c) if c.IsAborted() { @@ -12012,6 +12172,10 @@ func (siw *ServerInterfaceWrapper) PatchV2TemplatesTemplateID(c *gin.Context) { c.Set(Supabase2TeamAuthScopes, []string{}) + c.Set(AuthProviderTeamAuthScopes, []string{}) + + c.Set(AuthProviderTokenAuthScopes, []string{}) + for _, middleware := range siw.HandlerMiddlewares { middleware(c) if c.IsAborted() { @@ -12051,6 +12215,10 @@ func (siw *ServerInterfaceWrapper) PostV2TemplatesTemplateIDBuildsBuildID(c *gin c.Set(Supabase2TeamAuthScopes, []string{}) + c.Set(AuthProviderTeamAuthScopes, []string{}) + + c.Set(AuthProviderTokenAuthScopes, []string{}) + for _, middleware := range siw.HandlerMiddlewares { middleware(c) if c.IsAborted() { @@ -12070,6 +12238,10 @@ func (siw *ServerInterfaceWrapper) PostV3Templates(c *gin.Context) { c.Set(Supabase2TeamAuthScopes, []string{}) + c.Set(AuthProviderTeamAuthScopes, []string{}) + + c.Set(AuthProviderTokenAuthScopes, []string{}) + for _, middleware := range siw.HandlerMiddlewares { middleware(c) if c.IsAborted() { @@ -12089,6 +12261,10 @@ func (siw *ServerInterfaceWrapper) GetVolumes(c *gin.Context) { c.Set(Supabase2TeamAuthScopes, []string{}) + c.Set(AuthProviderTeamAuthScopes, []string{}) + + c.Set(AuthProviderTokenAuthScopes, []string{}) + for _, middleware := range siw.HandlerMiddlewares { middleware(c) if c.IsAborted() { @@ -12108,6 +12284,10 @@ func (siw *ServerInterfaceWrapper) PostVolumes(c *gin.Context) { c.Set(Supabase2TeamAuthScopes, []string{}) + c.Set(AuthProviderTeamAuthScopes, []string{}) + + c.Set(AuthProviderTokenAuthScopes, []string{}) + for _, middleware := range siw.HandlerMiddlewares { middleware(c) if c.IsAborted() { @@ -12138,6 +12318,10 @@ func (siw *ServerInterfaceWrapper) DeleteVolumesVolumeID(c *gin.Context) { c.Set(Supabase2TeamAuthScopes, []string{}) + c.Set(AuthProviderTeamAuthScopes, []string{}) + + c.Set(AuthProviderTokenAuthScopes, []string{}) + for _, middleware := range siw.HandlerMiddlewares { middleware(c) if c.IsAborted() { @@ -12168,6 +12352,10 @@ func (siw *ServerInterfaceWrapper) GetVolumesVolumeID(c *gin.Context) { c.Set(Supabase2TeamAuthScopes, []string{}) + c.Set(AuthProviderTeamAuthScopes, []string{}) + + c.Set(AuthProviderTokenAuthScopes, []string{}) + for _, middleware := range siw.HandlerMiddlewares { middleware(c) if c.IsAborted() { @@ -12264,168 +12452,170 @@ func RegisterHandlersWithOptions(router gin.IRouter, si ServerInterface, options // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "H4sIAAAAAAAC/+x9/VMcObLgv6KoexE3c9c0GHs33hLxfsAws8sb8BCAPRc35hzqquxuLfW1JRXQ6+B/", - "v1BKqlJVqb6apsE2MT+M6dJHKpXKTGWmMr96fhKlSQyx4N7BVy+lGY1AQIZ/Ud8Hzq+SG4hPjuUPLPYO", - "vJSKpTfxYhqBd1BrM/Ey+FfOMgi8A5HlMPG4v4SIys5ilcoOXGQsXngPDxOPpuw3WLUPbT6PG3WWszBo", - "HdR8HTdmnATQOqT+OG7ElC5YTAVL4lMWMSEbBcD9jKXyN+/AO6P3LMojEufRDDKSzAkTEHEiEpKByLOY", - "pJCRlC7Amyio/pVDtirBCnFcG4oA5jQPhXfwZm9v4s2TLKLCO/BYLN7uexMvUjPqzxGL9V8TAz6LBSwg", - "q8H/Ae4F7n9zDUd5xpNMgswFzQQRSyAh44LMsyRqATsuhutGIKdxMEvuW3el/D5uYwTQqHVQ/XHsiFEa", - "UgEdoxYNxo18m4R51D5u8XnMqA+yMU+TmAMygXd7e/J/fhILiJFOaZqGzMe93/0nT3Dfy/H+I4O5d+D9", - "j92Ss+yqr3z3lyxLMjVHlVDe04BIEIEL72Hivdt78/RzHuZiCbHQoxJQ7eTkb59+8l+TbMaCAGI147un", - "n/FDIsg8yeNAzfi3p5/xKInnIfNxR/+yDSq6hOwWMrOTD4bKkYwP/7i8gAXjIluhoMuSFDLBFI3TO36I", - "ckzKm6DJxw7/uCSqAfkNVuTkmMyTjPxydEFohYi8Sf04TeTYcuIkdg+rvpG7JWSA/FGOmmlICeMkTHwq", - "IGgZ+hL8DEQBvHsO1chewXDw1Q/1Ua9WKUiRVADaGAhiKTv+lDB61xMH7yo50p/q66S+Dc4F2ggtx01m", - "/wRFaIdBxOL3Usgf0diH8AI4irz6lvv4NYTgKMljh/j9UIhd1Bg44TnCMM/DcEWK3l5TOE68OWUjBhZL", - "KojqIiWlGtpzCl0bZ7UFVGe9Npi4VFLwNxa2YmIgtFqeQgPgGxaGTjTID6MGrqBY9e7Hgz2LAwmcs0V8", - "pQXsFV3wCy1mGngQdMEdlE4XqHNRHEj+Sx5SI7GlDiO1MocgLQCnWUZX+DfNFiBcU8jfizEJi8lnFOEH", - "gi4+e0Qrar2HSA0/UQspFw+Bvfzmui19uQrXSSBP9JypbZLLxqYSFYnPJFMid0ws5RcOBGe1tMo8Z06m", - "5UazARWHMdOtgeUGThAos0SJFOQNp8nil9gpCkK4hbBPAp0mi1Ns9zDxIuBcKuGNJZ0mC6I/EiP3HPjg", - "AtJm50sBqSSEEutpliD7ziBE1GtKDJMFAVyKC9csAi5o5JjgynwyyLYHKjYxoAJ25Cj91FdMVaJkorFZ", - "oP1SUJHzC6Ba3tdQrzZF/1VcVv68njgwC6plHR0cZyCZmsKim67trJKE4+S27vGZ3l9zDqrzT4ifZxnE", - "IlyRDNIkEyxekCQOlQBGPUX3GEkZFgvu3RkDvNyFo/OPLfz46Pwj8ZMMOIKGS1F82XPdFDvuhhOp98Xg", - "Cy16HIyWRZDkwk2TSS4k3XPwkzjgeFFEaDQmiexM6FxARu6WzF/aoBK+TPIwIHCfsgw6Ad/rlSsGSpeS", - "cZSBJLrD0vbhUDB0G9Fz9pQBhQg5CsFOSoEacgYnHguG8G17jiE8OqL8pu/QlLOcUX7D4sUxCMpCLvur", - "+2dD5NMIWiBqci63QeFqCURrYAq9PQPV9hRXi8CZGfRaJ9Z2XZcbfAU0Ojw/0Yr1evt7eH5CbmA1fmv1", - "BO9xbhqGv8+9gz+790TC+5FLYr6eeHEehnQWgrryD6YVDe8QMrlxXTgu6B25pWEOzQEbA4SUi48cHHCd", - "Uq7PulgyXiDxjnKSc2R6TiRW1/wslN26XBctqoaaBDVhVinxGEIQMEiB7YfNUqgG6mVG/Q0QjPUVMXPo", - "jGp6zPjNGYiM+Q6NNIBb5juWcoy/EzNWHYA5C4GvuIDoynlp/bX4TmRf8hNMF9MJgXvxbkLu5/xnJyuU", - "4vI8YS6ZeSa/kVR+NBgOGG6lg58JGr5fCXDhWH4jPKU+6v4zbGUfPxaLv75zXrHkWWgZVZ6rdQataw/l", - "+idmYxqotgGprNVs9SX7N5y9d+wo4zeEs39DXeuQMJ+x92Nl+MT7Jb79RLX7IgiYnIeG5zXyskH4Jb5l", - "WRJHUrm4pRmT7MOlBDVP8y/xbfAJMu607egPhi4gvg1Ilsex1AC1Xt869sRTJq6mzEkCB11jY4LfHOhq", - "oqhVm1Wz9jEuPZGtVv6aJdFJRBdgm9gCJseOWEyFWktE01QOqAxubdzXNtRNvIWftjX8+9G51TArZm5p", - "DTFkNCx6PEwMblcftBVervph4iUxDBC1NpgPk+62NqS9betwSvzaAzSIgkMmT+Wh78uj+t/cRY2Xqg3R", - "jch/X/7+AWn870fnWzACyl0cagR0LMelgtfx1EBLSjm/SzKHbnGuv0i5lvOS9WQlNW0cA8XY147Bcw6Z", - "W3h/1F+Gg+pGajHDpMSLC6utqk8DvVJngeCTVPTOM5izewee8XfU1yTLUz3IbZUxqntPkrWpiNY8l/nc", - "OY/6/ZHzpN2LwAs3M9jhjSGJRnRjXFSFTyFeiKVDy8Xfu0FsE8wa4OoME8e+uHAomcop4wKC1ls6DRl1", - "Gerkz0P0ST9kEAtjV0wzUG4MrZj33UJUb+e4aV6YMLoYaWHqeJhIUWSpIF29LGXlQZ7e1vsduVtCRYyT", - "OxaGDtND5x0PqipEp9fLaopCPEqyVf+Czkw77CNoQEWvg03TxJlpXve2921eh2KDcQAwBquUE91pMFa5", - "kDQ5bJGX2Lbhpe9bYmGsRwOVskQxXoFc3+OcTAEd83h9wCM2yEqpAf5U9u03f9uBBXZARHE47R2xzpZF", - "X5XTY46EwXGVgpGrGNO4w3Ap8dUgESMhA5jlC4wJmSfexLujGcpPVEldQvM0WfBjloEvnPp38cmyb2vX", - "lbYSzkAH0uAeGTDmSXZHM/nLjPo3+M/G7BPvfke237mlKFW57FiB59dilMrP74sh9QIukzxz3XTV7yNB", - "l7udZBS1glRuCUefw3Dw1axX1jDlr+fWgA8T74z6SxbDidys5jUlzQ8zf8kE+CLPwG1splYLs9BYXS1c", - "PP9XGrFw5R5qjt8GDHKWBC7KlGNE8tPQIT44lbVymNiyubjHqt+pigVacNbmmzTwqjbi/gpopGwpDqYK", - "NCIRftROCstP0zTLW86ibondcB/pOcZ4kCz/1MfYpXt1TiJVPdlNWQl/Mg4DzmIfCKSJv/y5dh1usaGg", - "/uQ2NeuIuKo9U8cpQWDA0df5BbuFmMiBs1tqecRVAF+nw6yKBwMSbq+fdpgyGhEwZ0fnxE/iOVvkmQpr", - "ahoyWmyk5SXgzFIt6u4u+WUdW82b/f904f4D3HU6UR7rSHBZIa/VvB2Kb5jcfcF9jEF8URO4FOEwuStQ", - "IJICkiUQ03lK/pD6DAchG8xpyGFCmCAzWNJbMOpCBEQqOSn4bL5i8YIEEK9+z7HP3hT/290zVBaDuEuy", - "G73L03LJsyQJgaJuSHORnNOcQ8WPqqZvBsElEZUX1jBckVR2qmoxytWGKo92iLXNeAE8j4aqXYdFhyNc", - "iFaGjemuRxHGZlKhVaejU//100eqvhrjA3t+UK3LVXHwnTLwEn8nNAyJNkr7SRTlsYlHRG7d0KQtnI9T", - "WM0x6PYB2J5ZEyv8Fxfvl7QZslun3Vaz4ul44+0z6MWaG3R5+jbn87H5j4J3ndkUltCUIySf8Q68//cn", - "3fn34c7/3dv525ed6//9HwMhcTD/D9rEXNPowpwLyIaRmm7sVKCSyBnsfoS/mwGSzF8CFxkajls9o78a", - "w1RPYJm+iGG4xFC/iupyqeLRYMwsvOgzbKZhTtk2hTSqquGdjNBqqhiicb519ZLkYPx0pRlgREyf8Xkk", - "sb2QCmZa3BS8uKBj3Ez/nLohuTST1xiQexZlbj6JuaCx72SmxnjOdJvSDti7Pzq4ZwCSVWgUMsGBLqXu", - "U+LyNjfXOrFOdgFtbZtLWmmei+pZbNmzckkFA6hS7rXmO8rY7IrV9ZcQYJSW4yieMo6cQ7UyUbUsqJHc", - "8DjNV2b3yuy2zuxe2VAvG6qwgX5e5GI6BSNzsR8rHqX+dCYwtgfeMJvI6yIaSo7OP3ZRSdGOFKGWA2mj", - "6Kmu3y3xHocYqVGdSRlxxwaV2B4WV6RK+SKxDBodT/F+mp9D5oPzbEmEy8FzjK5NVTsVUjxk7IDxG+6K", - "HxLq1YLeSxWFS/0lhu3sRmU4z9DIYTuMyRk3LPF/1Rv7EysCW2ezVK+P7XFAH6yxjYt07WigCrG3UGZl", - "a5sAOrwMFoLM3pkzeVlwrqYzIec235t+jndIkFEmOfBB8TNhnMySPEZv/wwIX+aCBMldPCUnQvns4kSg", - "8SYVJIY7i53TOFAtuEhSkkieS9HHxzgqmlbLDEiQxAoIydaC2aoKg5pEsFsI1T5MyCwXhAni09i86MW3", - "vTRY4cx+EgsW50CQX8YLIjI6nzN/+rkaWEADefE0K0d2h/HX6o88XgINxXKlGKsEbKBHoET/hZ6j/OW4", - "nK388ciet/z5owVB+eulgaWy0UdLGi82d//sjWAdLxhrB0IPIFehzFkdDvWqVa7bvr4hu9zzGnQksr65", - "+IIgiShzqD3vKZdnXH60XmsW9l91NuVJV3ZgNgsHBSRDfFt/R1BDiP0+ABk4Sq34NqgaDDcbXrApf/82", - "vep6DzqxiT+XxlCJSr1fJT8nt4ySNEvuV9P+HVzD4153mbeZxJukkItkJ8MmDi8PMomgFErThqoKsVxI", - "MNpE/4vuV1+sGc9lR2wdZJBLwqxSz0DmIV24F0mO1WDKu+L2h2hY2swLj+VE6DA60S6fwxZ30R9LEEvI", - "CteQcRfdUU7gPg2Zz0S4KhacZFIn1YuvcuQp+ZCHIYmAxlzqD3IEqV1Yo3AQHaRrYeZ7CNLaOsPeQkzY", - "C5QIIZuDv/LDoR6+06L99qPVHuuyew12ew12GxLs1iD15j1V46c4PSRNQuavivgtMltZqvY8aUrtqm/d", - "LVUqW0FjQksZ6haJSXxVXg0GbMTvRfuGSaAEzx62Qyc4TRbu1/IqrqcapoRX4ZDF0MAL/ugcR37penL/", - "TM/iEeDrCh5akhDMGWhXR9sbpjYnRonsrScyeC6sIvx20gGNvSqmeX++gap9KcsxyC5Q0ZcNZj+Gi3Wl", - "FggT17PL003M2csxce6JjYcazj7tX+hUYE7s9eVpKJifXA0vMLox7LmWY63gzFIghr0FND16ZXtlEmfg", - "5ZkdqjiUpbVb3j80be7DHvv5af6RQ3Dut+R86LKwz8PEzjtjAhmVkESjbZtBO8B3na2PT9vN2bKj+0U4", - "PhVtNWB3GsiPqL90xesqh7G2jf+UIn+Tv/08fopObHRY9jsHdSPirMeW3z7kjxnhOyLu1lIJrXNT7oW1", - "1RZhWVRrHw2LE1VvGO6I099dGVFMbAO2gIAEwIVOu6m9V4sM74LaNUB+of5SY0/qgTMglBydHF+QWZj4", - "N+rJO/ns/ecU/9t9u//Z+3lCKJnRDMjJOaFBgAPWGmKrJCPUXKgxwt00gnsapSFM/ST67E3IZ+9/TSs/", - "/Twlh3oBJm0PDe/oihNBb4BIOoQA5K4mt5CRAGJWNp2Oit1ARJ3ns5D5VwonFRnlIvRLFXhLWIXnk48X", - "p9x6b1EaCVQCH2Tp1eeebk1bB/O2761ebrlLXGK63Atw7/RxuRHK/xQngvA8TRN5w8EucmqS5eFYJEaU", - "3+gsE/9IuAN0g7JlwgW+t9SXQjR3zKA0SmB0q0aojpvnTmnadlf43b6n1M0gaPqUJBQ3r9J4tEmSoyHM", - "OMt0qjo0IjofFVXuyWNfMOnoDzNB0DWDfbF9TAho8x0rdT00PaflM9O2vu6MHThex2UO+B9MLFtTeRSG", - "2i4lbJh1R6pZDw3/WzE+stuYpnyZCPezJO3ZbKQFycNQHyOztXoYOyWfH+YBOmGBRqo1ajdSlRN0oVmh", - "/LjDw3yxG612zCgHt/s/jzp+puNA+1MXsEvMyzclHyUXKaDeRYu3OjNUsfI7ykmaJbcsgKBzMZqVSnEg", - "lpDdMQ5kTsOQkxn1b0watozelfCcHOsR6cx/s/+2GGLaS4MWJiZ6+1ykeAU0ckhVTB/u4Bs6l5Axq8t1", - "OlNr8WMjNrrMLkgQ2hqmV1Yb0hIGQ1L0uKEp01L3W+tcIzSMZTqRtT7mGln2qq81Zl8TYLWGD/zw+as0", - "9ThzqG3oWaKfxDr336UtS5qP9cqwtLKLFaJTO+4Drs52MPOFUyFwJrFVJmbMzq+uLIOu1K93s767mYMO", - "HHtkKA+5QINnQaQ9wLXER/Jns8ycu8O5h3EP3buHdbjOkoJNwa+dzW5XtTkAPRFEqukjcunqvLm9Fiml", - "alccRZLPyc5i2EkbkYJYslwrUbROjisPt3p/2+Won5U5aPt4qNkCK23tun7tHjlZXl0q2Csdas8kLNfP", - "mbK2h5lycZnSu3g0spAoHidX13BQt9wnPthXiQLMn+rat4JTLaj4Foy7KaRo6OjTT821gBPVniSZsmFY", - "No3ZyqE7Woorl/uyLieo70yHhXItv7TrPORpsMapU4Skuq7pmrK90GWxoQHOZr2ZNsOwl2Ef8fpZqexP", - "hW1Xz+OkECCGeqtM0RY+KD/a3T/bI71N0UTXRunV2OtHzv/4bPlDrhFPKlWUgFxHpGxfAsxZzPhy3KpM", - "n8HLWofV88coDYNZUbmox/OhkvUUz6la+YqDNzVOwq8shI9pmFDHmUgz4M53PDYzmLMQGQEN1RMH3cnk", - "tfC1K6V5/vPMobF/zEIrqA/HLm31OcKJRQF68WRgbyzYbUNc4/g3rQZD6xwgHOt6z3uLGgxw4JcAjFJL", - "sqLAQy+AlYoQjz1o25AUjnPlDqOowHiaLPijQimekhTawigqK2jNOv7odxzrhAon/g1k8tQ74gSKb5bJ", - "p336daQBMrCjyGEPwJdSxF+Cf4OxuFS9O4N78HNV2aeiF5WPeFqZBZqTnHOhzWNDs2zYumztTxshfdp/", - "GaS0zv7b2BobMz8IfwoRrah724m6AWahOjKn5LjoNsFEw+gUYjEXQIPpc+J6eFGDKTmisXZ+AaHolkPb", - "sp+ESUw4pBQfqRdhC9Fqx/T97MmbSeWng9s3GLlwMseRGDdDB5jCy7i6halDwk0AAc5ru9PMeaQLTpDl", - "TsfXX3CXHysO9IgiZJun3TqZImNyne95UiS77HoAZGuNd8skNIpxqeDhQMjzsjwmGSxoFoTAC7puVybn", - "JlO9g9fJn02ibcoxKIY3hUg7E527suB30Xkzbb4exTYA1x0nGopHwPn9iS8uIO0tGqbDbbBt13yNMzVE", - "E70UkDo1K4ezuqm79jxvb4BmIk7wbxVyckeZfnltXoS3p841IJzCgvqrHi/Dq09h4zrHq0fgO/UIvNrj", - "X+3x69njbV1fq/nGXtCq7m/ZD/z0vHSMQ+2F+sk6tHhalLjdgBK/TUNXcRCazqOinltFD+qsZltdtilr", - "23z1mPXaxA6zRR5JXly+kJezj0Ek1jH7B+WOkFn5q8EgNiteXFgzNe8A4684cqiN3G26axS1Q+0qGWTv", - "6RVdPEnVaMblnXiQg2ywwqTv3uasDY+Fogt3VJkc0V1wsFlquiphasFwCpcfUX60GlO3xageHCC1Waef", - "O8zBEZde5Tl/MLEsk4M+v6DsyFGqk5M6DNOjbpvKN+1KXbqVm8VzquWvQTevSv6gWA6XutKmyfdr74rj", - "KFa5Rq54uFMWdXPwRyeM30C2+JbEHM5dPx75WKgYatKeWV4t4TAO1q710b6UlnLhh7lUNDBPjlVAEVMw", - "UUxUqFQRNW5XboMnRJUBvokyUzyCidWlFAAKTVZqILk81MWAZpD9as6N4jBfTEkUFB7IWbBZCeBSCLRZ", - "HgYRiysDMrmyJdAAm6uN8f7PDjbcuaqWWtEvR+Q4+K++Mc5Pdn6zKb/sf5mndEY5vBkCi2ncDo5psY/n", - "dehoFV5sBpNbwXQ8iGBCiifvl/338hhbiYYPvL3pm+kepldJIaYp8w68t9O96Z5+R4f7t6u2Zwe3R2ks", - "zjeWqgQ9oZiTtFblRh4bfEtzEngH3nnChUUV3FMEB1y8T4KVfkMhdIAOTdNQv1fd/aeO0lDqRW8WzGqt", - "ntpTPG0jzLQaiQvb33uzsdl1Rf4GBB2psTS3t6wRIRLGOwWWa7YC/F3Z6GHi/WVvr7+tbGSfVrSzuqj5", - "z+uHa2Mj+NOrEsK1HKFKHLtfabnck+MHRSRYiN2R21j+TmjcTSuqmU0th/YUSKgZjUBAxlvNxWWT3QqA", - "aDauUcC7nvxlaj2P26R3apa+tu+eZUMlz9yVKh7f/aqcrw+7SqXY9Wnsqxw9LSwAv3N8Rc3inTRL1Btw", - "GgckhRifeNZuFKqKFMM6P8jJHKwC2b0E6ArBUVcnNVdz/x3vtpBMkJHi4+CCjRZvCqtcYWKd8L5nMU36", - "2dsYB8F142LVWi+A56FwcZFLixaJ2qSwqCzxMomzLsMVYfI8iijWRFdLRkqyKIYWdw9DwXKYLsot3rnt", - "4lv6VtL9jYWacJsv8Nag0eLJ12/mBf/3TKR6tXKtA4lUboadFvXbJFK5YAfNdFNpynZuYIUbsYC2NBty", - "UHyorS9evEF1fweh9FelPj1iewdacoo7ZNPg1b3XRebA5qKeWblx6tw1EWm2S15qByi+9vrcnMLatCfR", - "ee2dehaVtw6Ag9lVnti/MI13HFHYR3r3q7p/DdR8u2lFK76KWg71uOPVXdNxmKZb2ZxvXdMdfbqp8B1+", - "NWVD69uuc9l5w7u1efbQsAcO4hB7PYSiLZM/CKHIE68KhLSK8H/gZxWX6BLc6rs36EQudT0VH23zpjLJ", - "KOziJu/GSQADtA7VzAH0B/1hM7rGsPguLO75cP0ojUMtaGtCxa0zujRBBGz3q/yflhjOnfk7CF2UJ54n", - "rRvzAUcZzXHU5N7DZEyVGryl/CsHTN2hrymVemgv4mZiFWgcTC9FSbBv6DpSJ61WNRVLFRFeROpSU/2s", - "qaRugqSeSIQ1ai89aBk2hJPiOdIYQEcyDvEtSK7hbKWSwa6b15vyj9xKUNNgL3b2mk4zRpGMGFmDij4S", - "CZmzUFQzvkOZrjPnkP0Xnfmf8729/b/SNP2vNEsCfMeCmUGlekHjgNzSMAdOopwLMgPy8eKUQOwnAeBj", - "HxdDKqox2Pxo0/xnpDiTiC8Lbj1SrjU3D4lxbwgx7m1RHlpOtj+vpaBZWwmr5k7suYybvJpYAqIWB9Bk", - "eDaRP9G9vNj27V7KK9M6dEurkkf7bfwHIaoK+9y1ivC2s1G7MqcKGB7GTM/K6qxdPPUoiSK6o58CQkBC", - "8yhIb9vJMT4NWkAFEm/iwX0aYmV8Hc7pYpF6kC8s4J325fZQo4jen6iPb/b2asxs4uUx+1cOugHS+ZMq", - "fM4Er49jqSrcIioLpv6gR+FrUZ6m07Kl7OFWpmGXSavYpkur5M04FbMsljPQrFVjdMb78PK1vqcSnq03", - "zVJwzlYE72ztPOyJNnDjHGGdW6Ch4R+JLFrP/K6u0NvuPr1A3PGCeAKVwVW9RreLs3JdEbHyJl0Vfgqm", - "5OrqVDbBuFO4FxBrBb9DYSuIUFfzfTQtbl7505CNUgD3nkMBNKmOTGL4h8lzqaKaIramin6n59Yk6inY", - "ffeLAikAuFUDCfOwk93b/Y7h7aQbA0TFqco9tPYRnTif5WPueEfNLk7EkgrrsVLB41lMIhaGTCcCbjEi", - "YDYAt0XThKd31pNuQHtG72VrK/dzF5QtUIUsYlWoylrZe1IPH1f0egsSGHd9Hfmrcnm9HmZ52vruo/bp", - "jYrr5YAz2XoXfcSxLHJtqyNZPnikmTAHFOPab2k4sWq2T7Cpqs5S5vB+wvPpGhYwB7p9vAYsDeJgvYWN", - "A/l6G9E+tSIm65op7YO8hUv0d3rureLAqauukA5QQN+Vauootk7rxo0puYA0pL5+imXqD+iqTViEybx7", - "hTI1VGXgKfk9YgKF7ywRS6LqWBI/BJqpWEp7NIcenzuYkS499kxq/Guds6esc/bFFJP/QluqziM4ZU2s", - "pHgcYLhqDGJK/liC5KsC3Uto35S3yRks6S2YMs8R5ppSobsrSaK6rpnsszfF/3b3zOv46rn5TuqiNZM5", - "Dbl4OsxnFeZugn1s3u5kO0/Jvd/t/W1I2799Y5xe1XlrNbKcy8+1Wm5DLCPYb+tGVmXnqdzg0ZWr2Z++", - "4b9SyWgqyWCeAV8C7zLHYZPKIVX2NMkJmeC61FNCQnYLA8noopj3JcjmwLCZZnytrfeUCrfBQ3nNvoFU", - "ECoxYOnpWB3qXunfb/8qb9PdN4gmjx3PU82Obsn0/AIomJvH4QX5dlumVMH+NXhfUen/pRmFFWDBy48K", - "aDfFvnLtETRvSkMOeUGdQsYZF1gPzlTLLOJY9Jj/kxd3Ni4wm7ApJsqNiDURWCquA5+nlJ5t9SYT5yEz", - "WCWxYj5JxhYspqE1TcjmIMXFUEdMAceLkBPuLA2/p6quazXLRaM0KfqvqKNkaZkRCgcwjhO4Z1zwiX4L", - "pMtjaCdXPZ0atlUVWos6qGhJx4qAWHsyXuiRkhimQ8oQb5mJ2NVrXUYhg7ZtBxZ9p4YgUVaWdjOQS3kl", - "XgLRDUu6NiafCm+XdAn3KcuA3BvtxwqXY2UeE30Sp+SIhqGq08s4iUAsk4BEeShYGoKpYH0L2V3GhL6I", - "X12dTghQX1XMJDk3ZX4N8yoNqZSXJmLZKk1YjNf1CCjPdeUUszSj/g1lSqYq90tgSaKtQrgG0tJGy/2w", - "8aVTTLfqtmpXvbGOoGZtSwnl9UZUXK5J0y53Lkf/4U62rQZ0R2cXTeulYJsunXaJ2xH3V3urUSsyrqK2", - "SxhmK8KTPPPBij10iaTeE5XShbaUnqIfc1SXD3AvdDKV7ThCKiJuXT9IuenfZFhgAb0iYcywMOz5utMB", - "eaU/bDPyHtMkPTLgXi1oeztYz5vVtY2VxyDyN2urymQYQzzIdkBzF8+xkl2s6z/WqS1encffl/PYqqP+", - "KM+xKGuuP7Hb+O2Qtm9fDEPuPeC7Eb3vPORIQzoUyXXgjV9MvWgwFDmMDZzR+1dO8OI5wcTxei9jPqbx", - "lv+CW6hQCT7A029LWp7bZZjEtP0ZiSkCUxbG/8KblfG/4GZ8ybA2/nZfDJ/Re5t3vfKqTfMqZecapDua", - "pk6WU34ccNspEmi1HcTBdcOut62z6geLj9ZbDb6e8fYxSpsdQFfloqpvQbvdSLVUTR0PQm0iewr3j7NW", - "6SD77f7GYdBltVp8QWWRZ+r7kArjs39xD+GekMIq7GtX5+ne/Yr/aM/ccYR16di85jZQSpXK764cBp1c", - "Tqf5x/+1cLxqxkCqW7ZL4pYSfabjNmWuu3p/H3+TLF7lYzS1WVYa+tGkOVyefnO2x3YKLqv+dKYoK037", - "dGFM0a2SWfUpyPaKLp6Kd1ZnkhONYqDvWuodtec7e/VScV0xpiv1wqEu+UoXP/GfMXazWTKqQ9A+IcEo", - "yNYmmDcbBgQCGxSn3KWL0l/8So/d9FhlbV/L2hpDszC2qIF1hlap2THSvFF0HR5LWSk5solcjN+OOt99", - "Oazlg27ZPVuF2tDWremoWsMltg2Ny6pCNdLUYSuzTPA184y/OBWpkgG0+x5pnty03iDlQE/COZ7uJlqt", - "9LZ2YtBGWafW5KAvP5PKyzBaXIAuSBgPNFl8G/T27Vo+vi9rhq0wmUIfX3VZyIcxMdqqQL5d934QjSoZ", - "9L6s3vqE8tlUu3QI2H03L1M0sKRc+Zx+QBJoJpFoeBOrFXJDleJhiDpW2fu1UkOsuf/bTSPh5xlHz9m3", - "lEfCFT1oHsa86X8X43bGBCwDX6hS4sNYtaSK46JX68Ah3GI1oMGDnmIHB2ovVZTbkN2fZ0nU5lLGUUat", - "Uk28JXsrnjk562Cbq/sSYB35l2lucDPRrVhZu9mqSkM8hrG2JQDvY6wqXfKzsdaTOID7sqK35rMF4bSe", - "riL7gF112HX0kwX/fT7n0MLLRifE+W647dpMcWscqPXpSC/neWU3nexmzkL505LyZXdpARqTPA0TGpCQ", - "xTfGqkYzIkfAErOUxdaBpStQ34bqeL/Ktv+gfPlYBuRwdS7VsEM9nRIKw4jMEvqdnW+ehvQlXj4i5tvu", - "n/a+3C0hwwfb+kc8CnqXvgOnwEs5NsYx2hOehO7QdezP2sm1SffBk0TwFu6px4bwagUG8fqEsW7fsu/K", - "Tq844FVQV3rxT/vfc7WGSdsjpQLQ2YokMZAkI1GSqUofiIlB2dCFOsbrpUq7FFonqSc+4mKF5belevct", - "uZBeS1s85zPBzpSrgzJAtlnhLBbxjSZl/SataX0XvL2xMBc2sSGYbQF5I9a4GirVIvBij9dP/aIgz+Ip", - "kb3JDMLkTr0gVw1oBgTu/TAP2nG7MeveEeWwwyHmTLBbIDyfKfFCIir8JUlihDwCzulCXX8kt2yRGEAz", - "f1kBK6L3pxAv5AHf/8tftxtKaeXa/bS/nlnvh866e7tffaKw+aDyT/vPEVb+af+lu1c1Jn600kP1O6lN", - "gI04tu7qtN2RKBbdfd+xKE8CRDsjfQ122QR19wQdjA0xcBL78wUZPDGPR4yM4vAvK8bhCbnp2zZxvqbw", - "fvsswvvtcwlvDYDhfwaQVzneT3lJmEcwMEUKMa1dd/Xi09PbfNVco829Idp9mqv5FvfRwD6guqjiFsV6", - "3QzD2r0nqShqtmy7b1jUrIdxoG2TPQRicv81cfa9s4WSnCymsPtV/WP465R2IlONNJl90sOOVm4MPAOf", - "plQ21zxLoc2N/Y4NBzaf6IjdKRDSGrjzlFu391wH3qQP+XGpQs2S3ZpdzLPQO/CWQqT8YHeXpmwK+7Mp", - "TVPP6v+1zFdRpmv4WkvZV/0Rc2vYf+Mu7AgJeLVhynZuYFX5TXtki78LwX398P8DAAD//2+mXWFlJwEA", + "H4sIAAAAAAAC/+x9/XMbOa7gv8Lqe1U3cyfLjpPdeuuq94NjT3b9xs64bCdzdRNfiuqGJK77a5ts29qU", + "//crgmQ3W83+kiXZyajmh4nV/ABBEAABEPjm+UmUJjHEgntH37yUZjQCARn+RX0fOL9J7iA+O5U/sNg7", + "8lIq5t7Ii2kE3tFSm5GXwb9ylkHgHYksh5HH/TlEVHYWi1R24CJj8cx7ehp5NGW/wqJ5aPN52KiTnIVB", + "46Dm67Ax4ySAxiH1x2EjpnTGYipYEp+ziAnZKADuZyyVv3lH3gV9ZFEekTiPJpCRZEqYgIgTkZAMRJ7F", + "JIWMpHQG3khB9a8cskUJVojj2lAEMKV5KLyjNwcHI2+aZBEV3pHHYvH20Bt5kZpRf45YrP8aGfBZLGAG", + "2RL8H+FR4P7X13CSZzzJJMhc0EwQMQcSMi7INEuiBrDjYrh2BHIaB5PksXFXyu/DNkYAjRoH1R+Hjhil", + "IRXQMmrRYNjI90mYR83jFp+HjPokG/M0iTkgE3h3cCD/5yexgBjplKZpyHzc+/1/8gT3vRzvPzKYekfe", + "/9gvOcu++sr3f8myJFNzVAnlPQ2IBBG48J5G3ruDN5uf8zgXc4iFHpWAaicnf7v5yT8k2YQFAcRqxneb", + "n/FjIsg0yeNAzfi3zc94ksTTkPm4o3/ZBhVdQ3YPmdnJJ0PlSMbHv19fwYxxkS1Q0GVJCplgisbpAz9G", + "OSblTVDnY8e/XxPVgPwKC3J2SqZJRn45uSK0QkTeaPk4jeTYcuIkdg+rvpGHOWSA/FGOmmlICeMkTHwq", + "IGgY+hr8DEQBvHsO1cheQX/w1Q/Lo94sUpAiqQC0NhDEUnb8IWH0bkcO3lVypD/U19HyNjgXaCO0HDeZ", + "/BMUoR0HEYvfSyF/QmMfwivgKPKWt9zHryEEJ0keO8Tvx0LsosbACc8RhmkehgtS9PbqwnHkTSkbMLCY", + "U0FUFykp1dCeU+jaOFtaQHXWW4OJayUFf2VhIyZ6QqvlKdQAvmNh6ESD/DBo4AqKVe9uPNizOJDAOZvF", + "N1rA3tAZv9JipoYHQWfcQel0hjoXxYHkv+QhNRJb6jBSK3MI0gJwmmV0gX/TbAbCNYX8vRiTsJh8QRF+", + "JOjsi0e0otZ5iNTwI7WQcvEQ2Muvr9vSl6twnQXyRE+Z2ia5bGwqUZH4TDIl8sDEXH7hQHBWS6vMc+Zk", + "Wm40G1BxGDPdCliu4QSBMkuUSEHecJ7MfomdoiCEewi7JNB5MjvHdk8jLwLOpRJeW9J5MiP6IzFyz4EP", + "LiCtd74WkEpCKLGeZgmy7wxCRL2mxDCZEcCluHDNIuCCRo4Jbswng2x7oGITAypgT47STX3FVCVKRhqb", + "BdqvBRU5vwKq5f0S6tWm6L+Ky8oftyMHZkG1XEYHxxlIpqaw6KZtO6sk4Ti5jXt8offXnIPq/CPi51kG", + "sQgXJIM0yQSLZySJQyWAUU/RPQZShsWCO3fGAC934eTyUwM/Prn8RPwkA46g4VIUX/ZcN8WWu+FI6n0x", + "+EKLHgejZREkuXDTZJILSfcc/CQOOF4UERqNSSI7EzoVkJGHOfPnNqiEz5M8DAg8piyDVsAPOuWKgdKl", + "ZJxkIInuuLR9OBQM3UZ0nD1lQCFCjkKwk1Kg+pzBkceCPnzbnqMPj44ov+s6NOUsF5TfsXh2CoKykMv+", + "6v5ZE/k0ggaI6pzLbVC4mQPRGphCb8dAS3uKq0XgzAx6rSNru27LDb4BGh1fnmnFerX9Pb48I3ewGL61", + "eoL3ODcNw9+m3tEf7Xsi4f3EJTHfjrw4D0M6CUFd+XvTioa3D5ncuS4cV/SB3NMwh/qAtQFCysUnDg64", + "zinXZ13MGS+Q+EA5yTkyPScSq2t+EcpuXK6LFlVDTYKaMKuUeAohCOilwHbDZilUPfUyo/4GCMbqipg5", + "dEY1PWX87gJExnyHRhrAPfMdSznF34kZaxmAKQuBL7iA6MZ5af1QfCeyL/kJxrPxiMCjeDcij1P+s5MV", + "SnF5mTCXzLyQ30gqPxoMBwy30sHPBA3fLwS4cCy/EZ5SH3X/Cbayjx+LxV/fOa9Y8iw0jCrP1SqDLmsP", + "5fpHZmNqqLYBqazVbPU1+zdcvHfsKON3hLN/w7LWIWG+YO+HyvCR90t8/5lq90UQMDkPDS+XyMsG4Zf4", + "nmVJHEnl4p5mTLIPlxJUP82/xPfBZ8i407ajPxi6gPg+IFkex1ID1Hp949gjT5m46jInCRx0jY0JfnOg", + "q46iRm1WzdrFuPREtlr5IUuis4jOwDaxBUyOHbGYCrWWiKapHFAZ3Jq4r22oG3kzP21q+PeTS6thVszc", + "0BpiyGhY9HgaGdwuPmorvFz108hLYugham0wn0btbW1IO9suwynxaw9QIwoOmTyVx74vj+p/cxc1Xqs2", + "RDci/33920ek8b+fXG7BCCh3sa8R0LEclwq+jKcaWlLK+UOSOXSLS/1FyrWcl6wnK6lp7Rgoxr51DJ5z", + "yNzC+5P+0h9UN1KLGUYlXlxYbVR9auiVOgsEn6Wid5nBlD068Iy/o74mWZ7qQe6rjFHde5KsSUW05rnO", + "p8551O/PnCdtXwReuJnBDq8NSTSia+OiKnwO8UzMHVou/t4OYpNg1gBXZxg59sWFQ8lUzhkXEDTe0mnI", + "qMtQJ3/uo0/6IYNYGLtimoFyY2jFvOsWono7x03zwoTRxkgLU8fTSIoiSwVp62UpK0/y9Dbe78jDHCpi", + "nDywMHSYHlrveFBVIVq9XlZTFOJRki26F3Rh2mEfQQMqOh1smiYuTPNlb3vX5rUoNhgHAEOwSjnRnXpj", + "lQtJk/0WeY1ta176riUWxno0UClLFOMVyPU9zskU0DGP1wc8Yr2slBrgz2XfbvO3HVhgB0QUh9PeEets", + "WfRVOT3mSBgcVykYuYoxjTsMlxJfNRIxEjKAST7DmJBp4o28B5qh/ESV1CU0z5MZP2UZ+MKpfxefLPu2", + "dl1pK+EEdCAN7pEBY5pkDzSTv0yof4f/rM0+8h73ZPu9e4pSlcuOFXg+FKNUfn5fDKkXcJ3kmeumq34f", + "CLrc7SSjqBWkcks4+hz6g69mvbGGKX+9tAZ8GnkX1J+zGM7kZtWvKWl+nPlzJsAXeQZuYzO1WpiFxupq", + "4eL5H2jEwoV7qCl+6zHIRRK4KFOOEclPfYf46FTWymFiy+biHmv5TlUs0IJzab5RDa9qIx5vgEbKluJg", + "qkAjEuFH7aSw/DR1s7zlLGqX2DX3kZ5jiAfJ8k99il26V+skUtWT3ZSV8CfjMOAs9oFAmvjzn5euww02", + "FNSf3KZmHRFXtWfqOCUIDDj6Oj9j9xATOXB2Ty2PuArga3WYVfFgQMLt9dMWU0YtAubi5JL4STxlszxT", + "YU11Q0aDjbS8BFxYqsWyu0t+WcVW8+bwP124/wgPrU6U5zoSXFbIWzVvi+IbJg9fcR9jEF/VBC5FOEwe", + "ChSIpIBkDsR0HpPfpT7DQcgGUxpyGBEmyATm9B6MuhABkUpOCj6bLlg8IwHEi99y7HMwxv/2DwyVxSAe", + "kuxO7/K4XPIkSUKgqBvSXCSXNOdQ8aOq6etBcElE5YU1DBcklZ2qWoxytaHKox1iTTNeAc+jvmrXcdHh", + "BBeilWFjuutQhLGZVGjV6WjVf/30maqvxnjPnh9V63JVHHynDLzG3wkNQ6KN0n4SRXls4hGRW9c0aQvn", + "wxRWcwzafQC2Z9bECv/FxfslbYbs3mm31ax4PNx4+wJ6seYGbZ6+9fl8bP6j4F1lNoUlNOUIyWe8I+//", + "/UH3/n28938P9v72de/2f/9HT0gczP+jNjEvaXRhzgVk/UhNN3YqUEnkDHY/wd/NAEnmz4GLDA3HjZ7R", + "D8Yw1RFYpi9iGC7R16+iulyreDQYMgsv+vSbqZ9TtkkhjapqeCsjtJoqhmicb229JDkYP11pBhgQ02d8", + "HklsL6SCmQY3BS8u6Bg30z2nbkiuzeRLDMg9izI3n8Vc0Nh3MlNjPGe6TWkH7NwfHdzTA8kqNAqZYE+X", + "UvspcXmb62sdWSe7gHZpm0taqZ+L6lls2LNySQUDqFLureY7ytjsitX15xBglJbjKJ4zjpxDtTJRtSxY", + "Irn+cZo7Zrdjdltndjs21MmGKmygmxe5mE7ByFzsx4pHWX46ExjbA6+ZTeR1EQ0lJ5ef2qikaEeKUMue", + "tFH0VNfvhniPY4zUqM6kjLhDg0psD4srUqV8kVgGjQ6neD/NLyHzwXm2JMLl4DlG16aqnQop7jN2wPgd", + "d8UPCfVqQe+lisKl/hzDdvajMpynb+SwHcbkjBuW+L/pjP2JFYGtslmq16fmOKCP1tjGRbpyNFCF2Bso", + "s7K1dQAdXgYLQWbvzJm8LjhX3ZmQc5vvjb/EeyTIKJMc+Kj4mTBOJkkeo7d/AoTPc0GC5CEekzOhfHZx", + "ItB4kwoSw4PFzmkcqBZcJClJJM+l6ONjHBVNq2UGJEhiBYRka8FkUYVBTSLYPYRqH0ZkkgvCBPFpbF70", + "4tteGixwZj+JBYtzIMgv4xkRGZ1OmT/+Ug0soIG8eJqVI7vD+Gv1Rx7PgYZivlCMVQLW0yNQov9Kz1H+", + "clrOVv54Ys9b/vzJgqD89drAUtnokzmNZ+u7f3ZGsA4XjEsHQg8gV6HMWS0O9apVrt2+via73MsadCSy", + "vrv4giCJKHOoPe8pl2dcfrReaxb2X3U25UlXdmA2CXsFJEN8v/yOYAkh9vsAZOAoteL7oGowXG94wbr8", + "/dv0qus9aMUm/lwaQyUq9X6V/JzcM0rSLHlcjLt3cAWP+7LLvMkkXieFXCR7GTZxeHmQSQSlUBrXVFWI", + "5UKCwSb6X3S/5cWa8Vx2xMZBerkkzCr1DGQa0pl7keRUDaa8K25/iIalybzwXE6EDqMz7fI5bnAX/T4H", + "MYescA0Zd9ED5QQe05D5TISLYsFJJnVSvfgqRx6Tj3kYkghozKX+IEeQ2oU1CgfRQroWZn6EIK2tM+wt", + "xIS9QokQsin4Cz/s6+E7L9pvP1rtuS67XbDbLtitT7BbjdTr91SNn+L0kDQJmb8o4rfIZGGp2tOkLrWr", + "vnW3VKlsBY0JLWWoWyQm8U15NeixEb8V7WsmgRI8e9gWneA8mblfy6u4nmqYEl6FQxZDDS/4o3Mc+aXt", + "yf0LPYtHgG8reGhIQjBloF0dTW+YmpwYJbK3nsjgpbCK8NtJBzT2qpjm3fkGqvalLMcgu0BFX9aY/RAu", + "1pZaIExczy7P1zFnJ8fEuUc2HpZw9vnwSqcCc2KvK09DwfzkaniB0bVhz7UcawUXlgLR7y2g6dEp2yuT", + "OAMvL+xQxb4srdny/rFuc+/32M9P808cgku/IedDm4V9GiZ23hkTyKiEJBptmwzaAb7rbHx82mzOlh3d", + "L8LxqWijAbvVQH5C/bkrXlc5jLVt/KcU+Zv87efhU7Rio8Wy3zqoGxEXHbb85iH/nBG+A+JuLZXQOjfl", + "XlhbbRGWRbX20bA4UfWG4Y44/c2VEcXENmALCEgAXOi0m9p7NcvwLqhdA+QX6s819qQeOAFCycnZ6RWZ", + "hIl/p568ky/ef47xv/23h1+8n0eEkgnNgJxdEhoEOOBSQ2yVZISaCzVGuJtG8EijNISxn0RfvBH54v2v", + "ceWnn8fkWC/ApO2h4QNdcCLoHRBJhxCA3NXkHjISQMzKpuNBsRuIqMt8EjL/RuGkIqNchH6tAm8Jq/B8", + "8unqnFvvLUojgUrggyy9+tzTrWnrYN7mvdXLLXeJS0yXewHunT4tN0L5n+JEEJ6naSJvONhFTk2yPByK", + "xIjyO51l4h8Jd4BuUDZPuMD3lvpSiOaOCZRGCYxu1QjVcfPcKU2b7gq/2feUZTMImj4lCcX1qzQebZLk", + "aAgzzjKdqg6NiM5HRZV78tAXTDr6w0wQtM1gX2yfEwJaf8dKXQ9NL2n5zLSprztjB47XcpkD/jsT88ZU", + "HoWhtk0J62fdkWrWU83/VoyP7DamKZ8nwv0sSXs2a2lB8jDUx8hsrR7GTsnnh3mATligkWqN2o1U5QSd", + "aVYoP+7xMJ/tR4s9M8rR/eHPg46f6djT/tQG7Bzz8o3JJ8lFCqj30eKtzgxVrPyBcpJmyT0LIGhdjGal", + "UhyIOWQPjAOZ0jDkZEL9O5OGLaMPJTxnp3pEOvHfHL4thhh30qCFiZHePhcp3gCNHFIV04c7+IbOJWTM", + "6nKdztRa/NSIjTazCxKEtobplS0NaQmDPil63NCUaam7rXWuEWrGMp3IWh9zjSx71bcas7sEWI3hA3/6", + "/FWaepw51Nb0LNFPYp3779qWJfXHemVYWtnFCtFZOu49rs52MPOVUyFwJrFVJmbMzq+uLL2u1Lu7Wdfd", + "zEEHjj0ylIdcoMazINIe4KXER/Jns8ycu8O5+3EP3buDdbjOkoJNwa+dzW5XtTkAHRFEqukzcunqvLmd", + "FimlalccRZLPyc6i30kbkIJYslwrUbROjisPt3p/2+aon5Q5aLt4qNkCK23tqn7tDjlZXl0q2Csdai8k", + "LFfPmbKyh5lycZ3Sh3gwspAonidXV3BQN9wnPtpXiQLMn5a1bwWnWlDxLRh2U0jR0NGln5prASeqPUky", + "ZcOwbBqThUN3tBRXLvdlVU6wvDMtFsqV/NKu85CnwQqnThGS6rqia8r2QpfFhno4m/Vm2gzDXoZ9xJfP", + "SmV/Kmy7eh5HhQAx1FtlirbwQfnR7P7ZHumtiybaNkqvxl4/cv7nZ8vvc43YqFRRAnIVkbJ9CTBlMePz", + "YasyfXovaxVWz5+jNPRmReWins+HStZTPKdq5CsO3lQ7CR9YCJ/SMKGOM5FmwJ3veGxmMGUhMgIaqicO", + "upPJa+FrV0r9/OeZQ2P/lIVWUB+OXdrqc4QTiwJ04snAXluw24a4wvGvWw361jlAOFb1nncWNejhwC8B", + "GKSWZEWBh04AKxUhnnvQtiEpHOfKHUZRgfE8mfFnhVJskhSawigqK2jMOv7sdxyrhAon/h1k8tQ74gSK", + "b5bJp3n6VaQBMrCTyGEPwJdSxJ+Df4exuFS9O4NH8HNV2aeiF5WPeBqZBZqTnHOhzWNNs6zZumztTxMh", + "fT58HaS0yv7b2BoaM98LfwoRjah724q6HmahZWSOyWnRbYSJhtEpxGIugAbjl8R1/6IGY3JCY+38AkLR", + "LYe2ZT8Jk5hwSCk+Ui/CFqLFnun7xZM3k8pPR/dvMHLhbIojMW6GDjCFl3F1C1OHhJsAApzXdqeZ80hn", + "nCDLHQ+vv+AuP1Yc6AFFyNZPu8tkiozJdb6nSZHssu0BkK01PsyT0CjGpYKHAyHPy/KYZDCjWRACL+i6", + "WZmcmkz1Dl4nfzaJtinHoBheFyLNTHTqyoLfRuf1tPl6FNsAvOw40VA8A84fT3xxAWln0TAdboNt2+ar", + "nak+mui1gNSpWTmc1XXdteN5ew00E3GCf6uQkwfK9Mtr8yK8OXWuAeEcZtRfdHgZdj6FtescO4/AD+oR", + "2Nnjd/b41ezxtq6v1XxjL2hU97fsB948Lx3iUHulfrIWLZ4WJW7XoMRv09BVHIS686io51bRg1qr2VaX", + "bcra1l89Zp02seNslkeSF5cv5OXsQxCJdcz+QbkjZFb+ajCIzYoXF9ZM9TvA8CuOHGotd5v2GkXNULtK", + "Btl7ekNnG6kazbi8E/dykPVWmPTd25y1/rFQdOaOKpMjugsO1ktNVyXMUjCcwuUnlB+NxtRtMaonB0hN", + "1umXDnNwxKVXec7vTMzL5KAvLyhbcpTq5KQOw/Sg26byTbtSl27lZvGSavku6Gan5PeK5XCpK02afLf2", + "rjiOYpUr5IqHB2VRNwd/cML4NWSLb0jM4dz104GPhYqhRs2Z5dUSjuNg5VofzUtpKBd+nEtFA/PkWAUU", + "MQUTxUSFShVR47blNtggqgzwdZSZ4hFMLK6lAFBoslIDyeWhLgY0g+yDOTeKw3w1JVFQeCBnwWYlgHMh", + "0GZ5HEQsrgzI5MrmQANsrjbG+z972HDvplpqRb8ckePgv7rGuDzb+9WmfKt/LuaXymmS4RlrH0k22TsL", + "OkdaI6au85ROKIc3fbBlGjcjzLQ47LHacrSKtDCDSWJhOmJFMCEFqPfL4XvJaKxUyEfewfjN+AATwKQQ", + "05R5R97b8cH4QL/0QwrbV2jZQ7Qoncr5ClQVyScUs6Yu1eGRBxtf+5wF3pF3mXBh0S331JEALt4nwUK/", + "8hA6hIimaahf1O7/U8eRKAWoM09ntZrQ0mNBbcXMtKKLCzs8eLO22U+0cFmGoCV5l5ZHlr0kRMJ4p8By", + "zVaAvy8bPY28vxwcdLeVjWx+gpZgFzX/cfs0+tZ0fv64fbo1Jo4/vCqV3Mrhq5Sz/42WuDg7fVIUhHXk", + "HamZ5e+Exu2EpJrZpHRsT4FUnNEIBGS80dpdNtmvAIhW7yXyeNeRfk2t53k7+E7N0tX23evbbSkP9qX6", + "yve/Kcfy075Sl/Z9Gvsq/1AD88DvHF+Is3gvzRL1vp3GAUkhxuerS7clVSGLYQ0j5IEOJoOiTAJ0g+Co", + "a6Gaq04cjjdpSEPIgvHhc8GAi/eSVX4ysnhD15OfOnEdrI334LpxsWqtV8DzULj4z7VFqERtUlhUzXid", + "lLusnyjC5HkUUaz3rpaMlGRRDC3uVYaC5TBtlFu84dvHPAGNpPsrCzXh1l8XrkCjxXO2X012gh+ZSPVq", + "5Vp7EqncDDvl6/dJpHLBDpppp9KU7d3BAjdiBk0pROSg+AhdXyp5jer+DkLp5krxesb29rRSFffjujGv", + "fa+LrIj1Rb2wWuTU1h3y0/o26ilZzS7Le34PTdtGi5vBWHu9ESXb3uAX0bGXAXDwyErWgVemYm+FlmwG", + "sv9N3RN7KuHtJKZ1cEVkx3rc4Zq36dhP6a7s6feudG+Ll1DhOxybyojZtcuXsvOaN3n9zKhmkO3Fjw46", + "6Eubhnf01cVfVGGXRvXkH/hZxZO6lBL13et1/ue6Do6PPhVTUWbQpiBt7MdJAD00KtXMAfRH/WE9elS/", + "uDwsyvp0+yxtSi1oa5LPrQ+7tFwEbP+b/J+WT86d+TsIXUwpniaNG/MRRxnMqNTknjwh/asL4Q3sXzlg", + "yhV9BavUsXsVty6rsGZveilKuX1HV61l0mrUpbHEFOFFhDU1VevqmvQ6SGpDkq9WM+tJi74+nBTPkcYA", + "BgDgEN+DwOvPViqZB9t5vSnbya3EQjX2YmcdajXRFEmkkTWoqDGRkCkLRTVTP5RpVnMO2X/Rif8lPzg4", + "/CtN0/9KsyTA90eY0VVqJTQOyD0Nc+AkyrkgEyCfrs4JxH4SAD7ScjGkooqGzY/WzX8GijOJ+LJQ2jPl", + "Wn3zkBgP+hDjwRbloeUcVarYtnW3aqrMDkODSaOKFT+Wwj7qfNI+GxuyORTUsl2DQ2Vah0pqFW5ptjTs", + "aLGNFivMet8q1dzMtO36rSqsvB/rvihr+LZx8JMkiuiefjAKAQnN0zG922en+IBsBhVIvJEHj2kodQkT", + "9OtiyHqQryzgrZb65oC0iD6eqY9vDg6WWOfIy2P2rxx0AzweG1UvnWmAn8fAVVBOVJbV3Z2gISfoW1H7", + "qNXYpxwSVhprl5Wv2N1rq57SMD24rMTU09K3xFaN++f1q6avTMI33qJL6T5ZELyPNnPMDe372vnPKjdc", + "Q/o7alqdw+zrYtPN3vIrRDkvaC5QyYhVYgW7zjDXxT0r6RVUDbNgTG5uzmUTDKGGRwGxvvO0KKMF7erC", + "1M8m4fUrthqyQcrtwUsotyZrl6lx8DR6KTVbU8TW1OzdcbePu0lVVQiX9jc1UtxwqwoYViIg+/eHLcPb", + "aWd6CKZzlX1r5ZM9ciamwOoJjqp1nIg5FdZzvUKisJhELAyZToXdYI7BfBhu27B5oNFaUb0G7QV9lK2t", + "7OdtUDZAFbKIVaEqq8UfyDvGsLLvW5D3uOurSHuVzW7HA57BA7qu6Pahj4obd4+j3Hg9f8ZpLpLUq5Nc", + "vhSmmTDnGh+E3NNwJA+xPr8jbKrKGpXJ7zd4rF3DAhYPsE9lj6VBHKy2sGEg324jlGyp+s+qdmL7/G/B", + "rrBjFza7sIpxp646XjoeBX2OqinxsVBerviFjpNcMhONyRWkIfX100dT70NXScOiZ+adOZSp2CoDj8lv", + "ERMo6ieJmBNVN5b4IdBMxffaozkuG7mDh+lSfy9019jVFdxkXcGvyE9jEF/VawhXgqwweShr0CXFaxbD", + "jGMQY/L7HCQ7FugWREuxvPJOYE7vwZRVjzC3mwonX0gS1XUEZZ+DMf63f2CyUVTPzQ9Sh7CePK3P7dhh", + "UazIBBPbZYsEJ9vZJNN/d/C3Pm3/9ucQEKocY6MB6VJ+Xiq52Mfqg/22bq5WNqyKdQI995prauvFjri2", + "RVwZTDPgc+BtFkpsUmEJysQo+S4TXBdyS0jI7qEn9V0V874GTSAwTK0e821rWeWtwOChNCHcQSoIlRiw", + "LhNY++1RXRLe/vXgoOuaU+fowzm42dEtGfG/X8LnJmNEQfXtxror7LECp1UdX6F5XQEWvP7YkWaj9k5G", + "bP6omDKzfXIdpJBxxgXWljSVd4sgKT3m/+TFfZQLzExuChNzoweYqEAVNITvusr4B/UGGuchE1gksWJ1", + "ScZmLKahNU3IpiCFU19PWAHHq5BK7owvv6WqRnQ1Y06tzDE6EKmj/HGZXQ4HMJ4reGRc8JF+RKdL7Wgv", + "43JqRmyrqj0XNZXRJ4HVRbGObTzTIyUxjPuUNN8y77ErYbvsZAZt245a29nGbM4jyuL2br5zDUKFX6qG", + "5XEwVrCKJJHkDI8py4A8GhXNCuFkZSolfYDH5ISGoSoVzjiJQMyTgER5KFgagimifw/ZQ8aEtk3c3JyP", + "CFBfFe0lOTeVxg3PK03SlJfGdtkqTViMFowIKM918SazNKOj9uVlNxp3r4GTWftYT3smF1eqzOV+2PjS", + "We4bFXC1q95QT1y9vK6E8nYtejjXpFlYu/ToO4bQjyHYSkf7+4Si6XIR67pPrVm+t8SiLr1WqkLxQb1b", + "KGGYLAhP8swHKx7WJQA7D2JKZ9rmfI7+50FdPsKj0EmWtuOJqgjUVR1R5ab/mUJVi0Uryse0K/1yWjgd", + "xzf6wzafrGDWtWe+VFEL2t7GLycKbNv9/nmg5G/WPpbpc/qEBdiB+218zEqPs2pQgE6Gs4sI+LEiAiRR", + "rCMcAJMfbCUW4G2ftm+/dybfyRf2I/rYyhuQ9HQ0m4tPGGenevBjCLkf97igjzsG8uoZyMjxlDZjPtZC", + "kP+Ce6hQCb6G1U+vGt6+ZpgJuvmVlamk5Sexvo59tZ+SmcdauBlfMyrAUVBro/GOF/TRZnk7FvdKWJyy", + "C/ZSY01TJ6cqP/a4rxUJ/prOb++ajbfbVp/16+Fnq9AGXy94fxqkWG+OHEtcVN9ztzv5llLJtTzqtmlz", + "E845Z3npXmbyw7XDoCshNnjqyrr81PchFSZ+49W9Sn19hFlhlvu6IsP+N/xHc66fE6xAyqZLTh2l+alK", + "Hsqd08pTdUEX/F8Df63mT6W6ZbO60FCM1XTcpmJgJsflFdV7uripFCgqO62pwrXQ0A+m6P5C/89i4m0m", + "/LIsXGvmxdLxQmfGUdCoPqg+BbXf0NmmOHV1JjnRIHb9rqEgXnMax53r8VlkaIryuV2Kx7qUOJ39xH/G", + "GOV6KcIWbWCDdKYgW5nO3qwZEAhsUJzKAZ2VsQM7Mt4IGVcZ6bey1FPfVLYNKu4y+6yUkBpoKCq69o8Z", + "rlTAWkdC2x/+htN+zV7K/N+w6bZ6uKYdX9FpuYJ7dBvapFVLcaCtyVbUmeArVpT4UdS/Svbl9hu5ef/W", + "eBeXA22ET23uTl8tc7pyUuZaTcPGxMyvP0HUd201ugJdxDfuaTP6Psj0+zU97cxJS8qgqTv1TVdgfhry", + "8gFTfpS1qvuSthKU78tC6RtUIkxhaYcWcOjmnIp05pQrz+SOcvpSTj3JTc1VXa1hH6oUNH1UzQrJrJS6", + "ZkWy2W6aGz/POLplv6c8N67gWvO47U332za3yy5gGfi4hlFPwSCp4rTo1ThwCPdY0673oOfYwYHaaxXN", + "2Wf3p1kSNcUr4CiDVqkm3pKdHM+cnLW3rdx9wbGO/Os03Lh572u2jrdzY5Wnfgg/bqoQ0cWPVT79F+PI", + "Z3EAj+YYFm8mCnprPJRFmhNLNXdyjGTGf5tOOTSwwMF5vn4YJr0yL90a42p8x9XJsHZcahNcaspC+dOc", + "8nl7yRoakzwNExqQkMV3xj5JMyJHwJLzlMXWOacLUN/6apQfZNt/UD5/Lt9yOMTnati+/nAJheFfZgnd", + "LvE3mzkxEi+fEPNNd2t7Xx7mkGGKB/0jniC9Sz+AM+c7P23Gfd4RaYdO81UcANqnuU63z0ZC3wtv5HNj", + "37W6hHjdYLTnn9BVaae27fGyr61sxefDH7nm0KjpoWEB6GRBkhhIkpEoyVS9KsREryobQp3+1fJNXgut", + "OC2ngeNiEcofpA76Pbn+dgWavsMXwq1Zsntl320yTFqc5TvNo/1dGhi7Lq8HQ2EuzIR9MNsA8loMlEuo", + "VItAowVerfULnjyLx0T2JhMIkweVc0I1oBkQePTDPGjG7doMnieUwx6HmDPB7oHwfKKkEomo8OckiRHy", + "CDinM3VHk0y2QdAAzfx5BayIPp5DPJMH/PAvf91uVLCVHv3z4WqWzl2i9JVYdeVJ0PpfY3w+fIn3GJ8P", + "X7tbXGNiV3dvpfu2Tbe12Mr2+vHt8UoWuf7YEUsbAaKZbe9Col7wUHTEmAyNKHGekZeLKdmwREGMDJIn", + "ryuk5fXx7rdNOseKGsbbF9Ew3r6UhqEBMNzWALJTNjZGsEmYR9Az8xMxrV3mi+LT5o3uaq7B9vYQLWj1", + "1fyJtt8suUe1ccWbCjS52ZO16RupMG52ertPzdSsx3GgjcMddGXStdZxtmNCHVRosaD9b+of/R+RNdOm", + "aqSp87MedrDiZuDp+YKsQhPm9Rit08POctPGlVriyQo8NgaTbXLHD16KvZg0SztiGspcELjs3mx+noXe", + "kTcXIuVH+/s0ZWM4nIxpmnpW/29lXp8yrc23peSs1R8xB5H9N27enpAQVhumbO8OFpXftLu/+LtQSm6f", + "/n8AAAD//zGTQZ8JMgEA", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/packages/api/internal/cfg/model.go b/packages/api/internal/cfg/model.go index d6659f4ca4..af33dc2be3 100644 --- a/packages/api/internal/cfg/model.go +++ b/packages/api/internal/cfg/model.go @@ -2,6 +2,7 @@ package cfg import ( "encoding/base64" + "encoding/json" "errors" "fmt" "reflect" @@ -12,6 +13,7 @@ import ( "github.com/caarlos0/env/v11" "github.com/golang-jwt/jwt/v5" + "github.com/e2b-dev/infra/packages/auth/pkg/auth" "github.com/e2b-dev/infra/packages/shared/pkg/featureflags" ) @@ -62,10 +64,7 @@ type Config struct { VolumesToken VolumesTokenConfig - // SupabaseJWTSecrets is a list of secrets used to verify the Supabase JWT. - // More secrets are possible in the case of JWT secret rotation where we need to accept - // tokens signed with the old secret for some time. - SupabaseJWTSecrets []string `env:"SUPABASE_JWT_SECRETS"` + AuthProvider auth.ProviderConfig `env:"AUTH_PROVIDER_CONFIG"` // Deprecated: Template manager should use its own DefaultKernelVersion string `env:"DEFAULT_KERNEL_VERSION"` @@ -94,6 +93,17 @@ var ( ErrUnknownKeyType = errors.New("unknown JWT signing key type") parserFuncs = map[reflect.Type]env.ParserFunc{ + reflect.TypeFor[auth.ProviderConfig](): func(v string) (any, error) { + var config auth.ProviderConfig + if v == "" || strings.TrimSpace(v) == "null" { + return config, nil + } + if err := json.Unmarshal([]byte(v), &config); err != nil { + return nil, fmt.Errorf("parse AUTH_PROVIDER_CONFIG: %w", err) + } + + return config, nil + }, reflect.TypeFor[JWTSigningKey](): func(v string) (any, error) { keyPieces := strings.SplitN(v, ":", 2) if len(keyPieces) != 2 { diff --git a/packages/api/internal/cfg/model_test.go b/packages/api/internal/cfg/model_test.go index 5950343b5e..6d8c60561c 100644 --- a/packages/api/internal/cfg/model_test.go +++ b/packages/api/internal/cfg/model_test.go @@ -33,11 +33,13 @@ func TestParse(t *testing.T) { assert.ErrorContains(t, err, `environment variable "POSTGRES_CONNECTION_STRING" should not be empty`) }) - t.Run("supabase secrets are comma separated", func(t *testing.T) { - t.Setenv("SUPABASE_JWT_SECRETS", "aaa,bbb") + t.Run("hmac secrets are parsed from auth provider config", func(t *testing.T) { + t.Setenv("AUTH_PROVIDER_CONFIG", `{"bearer":[{"hmac":{"secrets":["aaa","bbb"]}}]}`) result, err := Parse() require.NoError(t, err) - assert.Equal(t, []string{"aaa", "bbb"}, result.SupabaseJWTSecrets) + require.Len(t, result.AuthProvider.Bearer, 1) + require.NotNil(t, result.AuthProvider.Bearer[0].HMAC) + assert.Equal(t, []string{"aaa", "bbb"}, result.AuthProvider.Bearer[0].HMAC.Secrets) }) t.Run("base64 signing key can be parsed", func(t *testing.T) { diff --git a/packages/api/internal/handlers/store.go b/packages/api/internal/handlers/store.go index a219321e5f..dc4addf3e4 100644 --- a/packages/api/internal/handlers/store.go +++ b/packages/api/internal/handlers/store.go @@ -152,7 +152,11 @@ func NewAPIStore(ctx context.Context, tel *telemetry.Client, redisClient redis.U authCache := sharedauth.NewAuthCache[*types.Team](redisClient) authStore := sharedauth.NewAuthStore(authDB) - authService := sharedauth.NewAuthService[*types.Team](authStore, authCache, config.SupabaseJWTSecrets) + authProviderVerifier, err := sharedauth.NewVerifier(ctx, config.AuthProvider) + if err != nil { + logger.L().Fatal(ctx, "Initializing auth provider JWT verifier", zap.Error(err)) + } + authService := sharedauth.NewAuthService[*types.Team](authStore, authCache, authProviderVerifier) templateCache := templatecache.NewTemplateCache(sqlcDB, redisClient) templateSpawnCounter := utils.NewTemplateSpawnCounter(ctx, time.Minute, sqlcDB) @@ -314,11 +318,11 @@ func (a *APIStore) GetUserFromAccessToken(ctx context.Context, ginCtx *gin.Conte return a.authService.ValidateAccessToken(ctx, ginCtx, accessToken) } -func (a *APIStore) GetUserIDFromSupabaseToken(ctx context.Context, ginCtx *gin.Context, supabaseToken string) (uuid.UUID, *api.APIError) { - ctx, span := tracer.Start(ctx, "get user id from supabase token") +func (a *APIStore) GetUserIDFromAuthProviderToken(ctx context.Context, ginCtx *gin.Context, token string) (uuid.UUID, *api.APIError) { + ctx, span := tracer.Start(ctx, "get user id from auth provider token") defer span.End() - return a.authService.ValidateSupabaseToken(ctx, ginCtx, supabaseToken) + return a.authService.ValidateAuthProviderToken(ctx, ginCtx, token) } func (a *APIStore) GetTeamFromSupabaseToken(ctx context.Context, ginCtx *gin.Context, teamID string) (*types.Team, *api.APIError) { @@ -327,3 +331,10 @@ func (a *APIStore) GetTeamFromSupabaseToken(ctx context.Context, ginCtx *gin.Con return a.authService.ValidateSupabaseTeam(ctx, ginCtx, teamID) } + +func (a *APIStore) GetTeamFromAuthProviderToken(ctx context.Context, ginCtx *gin.Context, teamID string) (*types.Team, *api.APIError) { + ctx, span := tracer.Start(ctx, "get team from auth provider token") + defer span.End() + + return a.authService.ValidateSupabaseTeam(ctx, ginCtx, teamID) +} diff --git a/packages/api/main.go b/packages/api/main.go index e8dbd1009d..d1aa96c539 100644 --- a/packages/api/main.go +++ b/packages/api/main.go @@ -152,6 +152,7 @@ func NewGinServer(ctx context.Context, config cfg.Config, tel *telemetry.Client, // Supabase headers auth.HeaderSupabaseToken, auth.HeaderSupabaseTeam, + auth.HeaderTeamID, // Custom headers sent from SDK "browser", "lang", @@ -172,8 +173,10 @@ func NewGinServer(ctx context.Context, config cfg.Config, tel *telemetry.Client, []auth.Authenticator{ auth.NewApiKeyAuthenticator(apiStore.GetTeamFromAPIKey), auth.NewAccessTokenAuthenticator(apiStore.GetUserFromAccessToken), - auth.NewSupabaseTokenAuthenticator(apiStore.GetUserIDFromSupabaseToken), + auth.NewAuthProviderTokenAuthenticator(apiStore.GetUserIDFromAuthProviderToken), + auth.NewSupabaseTokenAuthenticator(apiStore.GetUserIDFromAuthProviderToken), auth.NewSupabaseTeamAuthenticator(apiStore.GetTeamFromSupabaseToken), + auth.NewAuthProviderTeamAuthenticator(apiStore.GetTeamFromAuthProviderToken), auth.NewAdminTokenAuthenticator(config.AdminToken), }, metricsMiddleware.SetProcessingStartTime, diff --git a/packages/auth/go.mod b/packages/auth/go.mod index a0d1e593c4..7cb59ba2a4 100644 --- a/packages/auth/go.mod +++ b/packages/auth/go.mod @@ -7,10 +7,13 @@ replace github.com/e2b-dev/infra/packages/db => ../db replace github.com/e2b-dev/infra/packages/shared => ../shared require ( + github.com/MicahParks/jwkset v0.11.0 + github.com/MicahParks/keyfunc/v3 v3.8.0 github.com/e2b-dev/infra/packages/db v0.0.0 github.com/e2b-dev/infra/packages/shared v0.0.0 github.com/getkin/kin-openapi v0.133.0 github.com/gin-gonic/gin v1.12.0 + github.com/go-jose/go-jose/v4 v4.1.4 github.com/golang-jwt/jwt/v5 v5.3.1 github.com/google/uuid v1.6.0 github.com/oapi-codegen/gin-middleware v1.0.2 @@ -131,6 +134,7 @@ require ( golang.org/x/sync v0.20.0 // indirect golang.org/x/sys v0.43.0 // indirect golang.org/x/text v0.36.0 // indirect + golang.org/x/time v0.14.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20260401024825-9d38bb4040a9 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20260401024825-9d38bb4040a9 // indirect google.golang.org/grpc v1.80.0 // indirect diff --git a/packages/auth/go.sum b/packages/auth/go.sum index 8dc03c2739..2369cb524e 100644 --- a/packages/auth/go.sum +++ b/packages/auth/go.sum @@ -4,6 +4,10 @@ github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6 h1:He8af github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8= github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c h1:udKWzYgxTojEKWjV8V+WSxDXJ4NFATAsZjh8iIbsQIg= github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= +github.com/MicahParks/jwkset v0.11.0 h1:yc0zG+jCvZpWgFDFmvs8/8jqqVBG9oyIbmBtmjOhoyQ= +github.com/MicahParks/jwkset v0.11.0/go.mod h1:U2oRhRaLgDCLjtpGL2GseNKGmZtLs/3O7p+OZaL5vo0= +github.com/MicahParks/keyfunc/v3 v3.8.0 h1:Hx2dgIjAXGk9slakM6rV9BOeaWDPEXXZ4Us8guNBfds= +github.com/MicahParks/keyfunc/v3 v3.8.0/go.mod h1:z66bkCviwqfg2YUp+Jcc/xRE9IXLcMq6DrgV/+Htru0= github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs= @@ -64,6 +68,8 @@ github.com/gin-contrib/sse v1.1.1 h1:uGYpNwTacv5R68bSGMapo62iLTRa9l5zxGCps4hK6ko github.com/gin-contrib/sse v1.1.1/go.mod h1:QXzuVkA0YO7o/gun03UI1Q+FTI8ZV/n5t03kIQAI89s= github.com/gin-gonic/gin v1.12.0 h1:b3YAbrZtnf8N//yjKeU2+MQsh2mY5htkZidOM7O0wG8= github.com/gin-gonic/gin v1.12.0/go.mod h1:VxccKfsSllpKshkBWgVgRniFFAzFb9csfngsqANjnLc= +github.com/go-jose/go-jose/v4 v4.1.4 h1:moDMcTHmvE6Groj34emNPLs/qtYXRVcd6S7NHbHz3kA= +github.com/go-jose/go-jose/v4 v4.1.4/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= @@ -294,6 +300,8 @@ golang.org/x/term v0.42.0 h1:UiKe+zDFmJobeJ5ggPwOshJIVt6/Ft0rcfrXZDLWAWY= golang.org/x/term v0.42.0/go.mod h1:Dq/D+snpsbazcBG5+F9Q1n2rXV8Ma+71xEjTRufARgY= golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg= golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164= +golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI= +golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4= gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E= google.golang.org/genproto/googleapis/api v0.0.0-20260401024825-9d38bb4040a9 h1:VPWxll4HlMw1Vs/qXtN7BvhZqsS9cdAittCNvVENElA= diff --git a/packages/auth/pkg/auth/bearer/bearer.go b/packages/auth/pkg/auth/bearer/bearer.go new file mode 100644 index 0000000000..44df84ef0b --- /dev/null +++ b/packages/auth/pkg/auth/bearer/bearer.go @@ -0,0 +1,85 @@ +package bearer + +import ( + "context" + "errors" + "fmt" + + "github.com/golang-jwt/jwt/v5" + + "github.com/e2b-dev/infra/packages/auth/pkg/auth/jwtutil" +) + +// MinSecretLength is the minimum length of a secret used to verify HMAC-signed +// JWTs. This is a security measure to prevent the use of weak secrets (like +// empty). +const MinSecretLength = 16 + +// Verifier verifies HMAC-signed JWTs against a set of shared secrets. +type Verifier struct { + secrets []string + userIDClaim string + audiences []string + options []jwt.ParserOption +} + +// NewVerifier constructs a Verifier from the supplied Entry. +func NewVerifier(entry Entry) (*Verifier, error) { + options := []jwt.ParserOption{jwt.WithExpirationRequired()} + + var secrets []string + if entry.HMAC != nil { + secrets = make([]string, 0, len(entry.HMAC.Secrets)) + for _, secret := range entry.HMAC.Secrets { + if len(secret) < MinSecretLength { + return nil, fmt.Errorf("jwt secret is too short, minimum length is %d", MinSecretLength) + } + secrets = append(secrets, secret) + } + } + + return &Verifier{ + secrets: secrets, + userIDClaim: entry.ClaimMappings.Username.Claim, + audiences: entry.Audiences, + options: options, + }, nil +} + +// Verify parses and validates the supplied token string against each +// configured secret, returning the first successful verification. +func (v *Verifier) Verify(_ context.Context, tokenString string) (*jwtutil.Identity, error) { + errs := make([]error, 0, len(v.secrets)) + for _, secret := range v.secrets { + claims := jwt.MapClaims{} + token, err := jwt.ParseWithClaims(tokenString, claims, func(token *jwt.Token) (any, error) { + if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { + return nil, fmt.Errorf("unexpected auth provider signing method: %v", token.Header["alg"]) + } + + return []byte(secret), nil + }, v.options...) + if err != nil { + errs = append(errs, fmt.Errorf("failed to verify auth provider bearer token: %w", err)) + + continue + } + if !token.Valid { + continue + } + + if err := jwtutil.ValidateAudience(claims, v.audiences); err != nil { + errs = append(errs, fmt.Errorf("failed to verify auth provider bearer token: %w", err)) + + continue + } + + return jwtutil.IdentityFromClaims(claims, v.userIDClaim), nil + } + + if len(errs) == 0 { + return nil, errors.New("failed to verify auth provider bearer token, no usable secrets found") + } + + return nil, errors.Join(errs...) +} diff --git a/packages/auth/pkg/auth/bearer/bearer_test.go b/packages/auth/pkg/auth/bearer/bearer_test.go new file mode 100644 index 0000000000..cb719fb6fa --- /dev/null +++ b/packages/auth/pkg/auth/bearer/bearer_test.go @@ -0,0 +1,92 @@ +package bearer + +import ( + "testing" + "time" + + "github.com/golang-jwt/jwt/v5" + "github.com/google/uuid" + "github.com/stretchr/testify/require" + + "github.com/e2b-dev/infra/packages/auth/pkg/auth/jwtutil" +) + +func TestVerifier_Verify(t *testing.T) { + t.Parallel() + + const secret = "supabasejwtsecretsupabasejwtsecret" + verifier, err := NewVerifier(Entry{ + HMAC: &HMACConfig{Secrets: []string{"wrong-secret-wrong-secret", secret}}, + ClaimMappings: jwtutil.ClaimMappings{ + Username: jwtutil.ClaimMapping{Claim: "sub"}, + }, + }) + require.NoError(t, err) + + userID := uuid.New() + token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{ + "sub": userID.String(), + "exp": time.Now().Add(time.Hour).Unix(), + }) + + signedToken, err := token.SignedString([]byte(secret)) + require.NoError(t, err) + + identity, err := verifier.Verify(t.Context(), signedToken) + require.NoError(t, err) + require.Equal(t, userID, identity.UserID) +} + +func TestVerifier_AudienceMatch(t *testing.T) { + t.Parallel() + + const secret = "supabasejwtsecretsupabasejwtsecret" + verifier, err := NewVerifier(Entry{ + HMAC: &HMACConfig{Secrets: []string{secret}}, + Audiences: []string{"audience-a", "audience-b"}, + ClaimMappings: jwtutil.ClaimMappings{ + Username: jwtutil.ClaimMapping{Claim: "sub"}, + }, + }) + require.NoError(t, err) + + userID := uuid.New() + token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{ + "sub": userID.String(), + "aud": "audience-b", + "exp": time.Now().Add(time.Hour).Unix(), + }) + + signedToken, err := token.SignedString([]byte(secret)) + require.NoError(t, err) + + identity, err := verifier.Verify(t.Context(), signedToken) + require.NoError(t, err) + require.Equal(t, userID, identity.UserID) +} + +func TestVerifier_AudienceMatchRejectsMismatch(t *testing.T) { + t.Parallel() + + const secret = "supabasejwtsecretsupabasejwtsecret" + verifier, err := NewVerifier(Entry{ + HMAC: &HMACConfig{Secrets: []string{secret}}, + Audiences: []string{"audience-a"}, + ClaimMappings: jwtutil.ClaimMappings{ + Username: jwtutil.ClaimMapping{Claim: "sub"}, + }, + }) + require.NoError(t, err) + + token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{ + "sub": uuid.NewString(), + "aud": "other-audience", + "exp": time.Now().Add(time.Hour).Unix(), + }) + + signedToken, err := token.SignedString([]byte(secret)) + require.NoError(t, err) + + _, err = verifier.Verify(t.Context(), signedToken) + require.Error(t, err) +} diff --git a/packages/auth/pkg/auth/bearer/entry.go b/packages/auth/pkg/auth/bearer/entry.go new file mode 100644 index 0000000000..3cdbfcf96f --- /dev/null +++ b/packages/auth/pkg/auth/bearer/entry.go @@ -0,0 +1,39 @@ +package bearer + +import ( + "errors" + + "github.com/e2b-dev/infra/packages/auth/pkg/auth/jwtutil" +) + +// Entry describes a single HMAC-verified bearer JWT source. +type Entry struct { + HMAC *HMACConfig `json:"hmac"` + Audiences []string `json:"audiences"` + ClaimMappings jwtutil.ClaimMappings `json:"claimMappings"` +} + +// HMACConfig holds HMAC secrets used for symmetric JWT verification. +type HMACConfig struct { + Secrets []string `json:"secrets"` +} + +// Normalized returns a copy with defaults applied. +func (e Entry) Normalized() Entry { + e.ClaimMappings = e.ClaimMappings.Normalized() + + return e +} + +// Validate returns an error if the entry contains invalid configuration. +func (e Entry) Validate() error { + if e.HMAC == nil { + return errors.New("hmac is required") + } + + if len(e.HMAC.Secrets) == 0 { + return errors.New("hmac.secrets must not be empty") + } + + return nil +} diff --git a/packages/auth/pkg/auth/consts.go b/packages/auth/pkg/auth/consts.go index e5010dd7c3..852aa3da71 100644 --- a/packages/auth/pkg/auth/consts.go +++ b/packages/auth/pkg/auth/consts.go @@ -6,6 +6,7 @@ const ( HeaderAuthorization = "Authorization" HeaderSupabaseToken = "X-Supabase-Token" HeaderSupabaseTeam = "X-Supabase-Team" + HeaderTeamID = "X-Team-Id" HeaderAdminToken = "X-Admin-Token" // Token prefixes. diff --git a/packages/auth/pkg/auth/jwt.go b/packages/auth/pkg/auth/jwt.go deleted file mode 100644 index 2df1dca9e8..0000000000 --- a/packages/auth/pkg/auth/jwt.go +++ /dev/null @@ -1,82 +0,0 @@ -package auth - -import ( - "context" - "errors" - "fmt" - - "github.com/golang-jwt/jwt/v5" - "github.com/google/uuid" - "go.uber.org/zap" - - "github.com/e2b-dev/infra/packages/shared/pkg/logger" -) - -const ( - // MinJWTSecretLength is the minimum length of a secret used to verify the Supabase JWT. - // This is a security measure to prevent the use of weak secrets (like empty). - MinJWTSecretLength = 16 -) - -// SupabaseClaims defines the claims we expect from the Supabase JWT. -type SupabaseClaims struct { - jwt.RegisteredClaims -} - -// GetJWTClaims tries each secret to parse and validate the JWT token. -func GetJWTClaims(ctx context.Context, secrets []string, token string) (*SupabaseClaims, error) { - errs := make([]error, 0) - - for _, secret := range secrets { - if len(secret) < MinJWTSecretLength { - logger.L().Warn(ctx, "jwt secret is too short and will be ignored", - zap.Int("min_length", MinJWTSecretLength), - zap.String("secret_start", secret[:min(3, len(secret))])) - - continue - } - - parsed, err := jwt.ParseWithClaims(token, &SupabaseClaims{}, func(token *jwt.Token) (any, error) { - if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { - return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"]) - } - - return []byte(secret), nil - }) - if err != nil { - errs = append(errs, fmt.Errorf("failed to parse supabase token: %w", err)) - - continue - } - - if claims, ok := parsed.Claims.(*SupabaseClaims); ok && parsed.Valid { - return claims, nil - } - } - - if len(errs) == 0 { - return nil, errors.New("failed to parse supabase token, no secrets found") - } - - return nil, errors.Join(errs...) -} - -// ParseUserIDFromToken validates a Supabase JWT and extracts the user ID from the subject claim. -func ParseUserIDFromToken(ctx context.Context, secrets []string, supabaseToken string) (uuid.UUID, error) { - claims, err := GetJWTClaims(ctx, secrets, supabaseToken) - if err != nil { - return uuid.UUID{}, err - } - - userId, err := claims.GetSubject() - if err != nil { - return uuid.UUID{}, fmt.Errorf("failed getting jwt subject: %w", err) - } - - userIDParsed, err := uuid.Parse(userId) - if err != nil { - return uuid.UUID{}, fmt.Errorf("failed parsing user uuid: %w", err) - } - - return userIDParsed, nil -} diff --git a/packages/auth/pkg/auth/jwt_test.go b/packages/auth/pkg/auth/jwt_test.go deleted file mode 100644 index 2163bad993..0000000000 --- a/packages/auth/pkg/auth/jwt_test.go +++ /dev/null @@ -1,83 +0,0 @@ -package auth - -import ( - "testing" - "time" - - "github.com/golang-jwt/jwt/v5" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func signTestToken(t *testing.T, secret string, subject string) string { - t.Helper() - - claims := SupabaseClaims{ - RegisteredClaims: jwt.RegisteredClaims{ - Subject: subject, - ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Hour)), - IssuedAt: jwt.NewNumericDate(time.Now()), - Issuer: "test", - }, - } - - token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) - - signedToken, err := token.SignedString([]byte(secret)) - assert.NoError(t, err) - - return signedToken -} - -func TestGetJWTClaims(t *testing.T) { - t.Parallel() - secret1 := "testsecret1testsecret1" - secret2 := "testsecret2testsecret2" - - ctx := t.Context() - token1 := signTestToken(t, secret1, "1") - token2 := signTestToken(t, secret2, "2") - tokenEmpty := signTestToken(t, "", "3") - - t.Run("valid token for first secret", func(t *testing.T) { - t.Parallel() - claims, err := GetJWTClaims(ctx, []string{secret1, secret2}, token1) - require.NoError(t, err) - assert.Equal(t, "1", claims.Subject) - }) - - t.Run("valid token for second secret", func(t *testing.T) { - t.Parallel() - claims, err := GetJWTClaims(ctx, []string{secret1, secret2}, token2) - require.NoError(t, err) - assert.Equal(t, "2", claims.Subject) - }) - - t.Run("invalid token secret combination", func(t *testing.T) { - t.Parallel() - claims, err := GetJWTClaims(ctx, []string{secret1}, token2) - require.Error(t, err) - assert.Nil(t, claims) - }) - - t.Run("no secrets", func(t *testing.T) { - t.Parallel() - claims, err := GetJWTClaims(ctx, []string{}, token1) - require.Error(t, err) - assert.Nil(t, claims) - }) - - t.Run("empty secret", func(t *testing.T) { - t.Parallel() - claims, err := GetJWTClaims(ctx, []string{""}, tokenEmpty) - require.Error(t, err) - assert.Nil(t, claims) - }) - - t.Run("invalid token for all secrets", func(t *testing.T) { - t.Parallel() - claims, err := GetJWTClaims(ctx, []string{secret1, secret2}, "invalid") - require.Error(t, err) - assert.Nil(t, claims) - }) -} diff --git a/packages/auth/pkg/auth/jwtutil/audience.go b/packages/auth/pkg/auth/jwtutil/audience.go new file mode 100644 index 0000000000..8d7a01919c --- /dev/null +++ b/packages/auth/pkg/auth/jwtutil/audience.go @@ -0,0 +1,104 @@ +package jwtutil + +import ( + "errors" + "fmt" + + "github.com/golang-jwt/jwt/v5" +) + +// AudienceMatchPolicy controls how a token's `aud` claim is checked against +// the configured `audiences` list. +// +// The field exists for forward compatibility with future policies. Today +// the only accepted value is `MatchAny` (or empty, which is treated as +// `MatchAny`). +type AudienceMatchPolicy string + +const ( + // AudienceMatchAny passes verification when at least one configured + // audience is present in the token's `aud` claim. + AudienceMatchAny AudienceMatchPolicy = "MatchAny" +) + +// ValidateAudienceMatchPolicy ensures the policy is allowed for the given +// audiences. It mirrors Kubernetes' apiserver validation: +// +// - audiences must be non-empty. +// - With more than one audience, the policy must be MatchAny. +// - With a single audience, the policy must be empty or MatchAny. +func ValidateAudienceMatchPolicy(policy AudienceMatchPolicy, audiences []string) error { + if len(audiences) == 0 { + return errors.New("audiences must contain at least one entry") + } + + if len(audiences) > 1 && policy != AudienceMatchAny { + return fmt.Errorf("audienceMatchPolicy must be %q for multiple audiences", AudienceMatchAny) + } + + if len(audiences) == 1 && policy != "" && policy != AudienceMatchAny { + return fmt.Errorf("audienceMatchPolicy must be empty or %q for a single audience", AudienceMatchAny) + } + + return nil +} + +// ValidateAudience checks the token's `aud` claim against the configured +// audiences. Verification passes when at least one configured audience is +// present in the token's `aud` claim. +// +// An empty `audiences` slice is treated as "no audience restriction" and +// always passes. +func ValidateAudience(claims jwt.MapClaims, audiences []string) error { + if len(audiences) == 0 { + return nil + } + + tokenAudiences, err := ExtractAudiences(claims) + if err != nil { + return err + } + + tokenSet := make(map[string]struct{}, len(tokenAudiences)) + for _, aud := range tokenAudiences { + tokenSet[aud] = struct{}{} + } + + for _, want := range audiences { + if _, ok := tokenSet[want]; ok { + return nil + } + } + + return errors.New("token audience does not match any configured audience") +} + +// ExtractAudiences returns the list of audiences from the token's `aud` claim. +// It accepts the standard string, []string, and []any encodings. +func ExtractAudiences(claims jwt.MapClaims) ([]string, error) { + value, ok := claims["aud"] + if !ok { + return nil, errors.New("token is missing aud claim") + } + + switch typed := value.(type) { + case string: + return []string{typed}, nil + case []string: + return typed, nil + case []any: + out := make([]string, 0, len(typed)) + for _, item := range typed { + s, ok := item.(string) + if !ok { + return nil, fmt.Errorf("token aud claim contains non-string value %v", item) + } + + out = append(out, s) + } + + return out, nil + default: + return nil, fmt.Errorf("token aud claim has unsupported type %T", value) + } +} diff --git a/packages/auth/pkg/auth/jwtutil/audience_test.go b/packages/auth/pkg/auth/jwtutil/audience_test.go new file mode 100644 index 0000000000..c3612c41bb --- /dev/null +++ b/packages/auth/pkg/auth/jwtutil/audience_test.go @@ -0,0 +1,72 @@ +package jwtutil + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestValidateAudienceMatchPolicy(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + policy AudienceMatchPolicy + audiences []string + wantErr string + }{ + { + name: "empty audiences errors", + policy: "", + audiences: nil, + wantErr: "audiences must contain at least one entry", + }, + { + name: "single audience empty policy ok", + policy: "", + audiences: []string{"a"}, + }, + { + name: "single audience MatchAny ok", + policy: AudienceMatchAny, + audiences: []string{"a"}, + }, + { + name: "single audience invalid policy errors", + policy: "MatchAll", + audiences: []string{"a"}, + wantErr: "audienceMatchPolicy must be empty or", + }, + { + name: "multiple audiences MatchAny ok", + policy: AudienceMatchAny, + audiences: []string{"a", "b"}, + }, + { + name: "multiple audiences empty policy errors", + policy: "", + audiences: []string{"a", "b"}, + wantErr: "audienceMatchPolicy must be", + }, + { + name: "multiple audiences invalid policy errors", + policy: "MatchAll", + audiences: []string{"a", "b"}, + wantErr: "audienceMatchPolicy must be", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + err := ValidateAudienceMatchPolicy(tt.policy, tt.audiences) + if tt.wantErr == "" { + require.NoError(t, err) + return + } + require.Error(t, err) + require.Contains(t, err.Error(), tt.wantErr) + }) + } +} diff --git a/packages/auth/pkg/auth/jwtutil/claims.go b/packages/auth/pkg/auth/jwtutil/claims.go new file mode 100644 index 0000000000..5046d26483 --- /dev/null +++ b/packages/auth/pkg/auth/jwtutil/claims.go @@ -0,0 +1,28 @@ +package jwtutil + +import "strings" + +// DefaultUserIDClaim is the JWT claim name used for the user identifier when +// no explicit mapping is configured. +const DefaultUserIDClaim = "sub" + +// ClaimMappings declares which JWT claim is mapped to which internal identity +// field. +type ClaimMappings struct { + Username ClaimMapping `json:"username"` +} + +// ClaimMapping declares the source claim for a single mapping. +type ClaimMapping struct { + Claim string `json:"claim"` +} + +// Normalized returns a copy with default values applied. +func (m ClaimMappings) Normalized() ClaimMappings { + m.Username.Claim = strings.TrimSpace(m.Username.Claim) + if m.Username.Claim == "" { + m.Username.Claim = DefaultUserIDClaim + } + + return m +} diff --git a/packages/auth/pkg/auth/jwtutil/identity.go b/packages/auth/pkg/auth/jwtutil/identity.go new file mode 100644 index 0000000000..e6280e9f1f --- /dev/null +++ b/packages/auth/pkg/auth/jwtutil/identity.go @@ -0,0 +1,55 @@ +package jwtutil + +import ( + "github.com/golang-jwt/jwt/v5" + "github.com/google/uuid" +) + +// Identity is the normalized identity extracted from a validated JWT. +type Identity struct { + UserID uuid.UUID + Claims jwt.MapClaims +} + +// IdentityFromClaims builds an Identity from the supplied claims. The +// userIDClaim names the claim whose value should be parsed as a UUID and used +// as the user identifier. If the claim is missing or unparseable, the +// returned Identity has a zero UserID but still contains the raw claims. +func IdentityFromClaims(claims jwt.MapClaims, userIDClaim string) *Identity { + identity := &Identity{Claims: claims} + if claimValue, ok := claimString(claims, userIDClaim); ok { + userID, err := uuid.Parse(claimValue) + if err == nil { + identity.UserID = userID + } + } + + return identity +} + +func claimString(claims jwt.MapClaims, name string) (string, bool) { + value, ok := claims[name] + if !ok { + return "", false + } + + switch typed := value.(type) { + case string: + return typed, typed != "" + case []string: + if len(typed) == 0 { + return "", false + } + + return typed[0], typed[0] != "" + case []any: + if len(typed) == 0 { + return "", false + } + first, ok := typed[0].(string) + + return first, ok && first != "" + default: + return "", false + } +} diff --git a/packages/auth/pkg/auth/middleware.go b/packages/auth/pkg/auth/middleware.go index 6d4e9ecc2a..361b83afff 100644 --- a/packages/auth/pkg/auth/middleware.go +++ b/packages/auth/pkg/auth/middleware.go @@ -169,6 +169,20 @@ func NewAccessTokenAuthenticator(validationFunc func(ctx context.Context, ginCtx } } +// NewAuthProviderTokenAuthenticator creates an authenticator for AuthProviderTokenAuth (Authorization Bearer token). +func NewAuthProviderTokenAuthenticator(validationFunc func(ctx context.Context, ginCtx *gin.Context, token string) (uuid.UUID, *APIError)) Authenticator { + return &CommonAuthenticator[uuid.UUID]{ + SchemeName: "AuthProviderTokenAuth", + Header: HeaderKey{ + Name: HeaderAuthorization, + RemovePrefix: PrefixBearer, + }, + ValidationFunc: validationFunc, + SetContextFunc: SetUserID, + ErrorMessage: "Invalid auth provider token.", + } +} + // NewSupabaseTokenAuthenticator creates an authenticator for the Supabase1TokenAuth security scheme (X-Supabase-Token header). func NewSupabaseTokenAuthenticator(validationFunc func(ctx context.Context, ginCtx *gin.Context, token string) (uuid.UUID, *APIError)) Authenticator { return &CommonAuthenticator[uuid.UUID]{ @@ -195,6 +209,19 @@ func NewSupabaseTeamAuthenticator(validationFunc func(ctx context.Context, ginCt } } +// NewAuthProviderTeamAuthenticator creates an authenticator for the AuthProviderTeamAuth security scheme (X-Team-Id header). +func NewAuthProviderTeamAuthenticator(validationFunc func(ctx context.Context, ginCtx *gin.Context, token string) (*types.Team, *APIError)) Authenticator { + return &CommonAuthenticator[*types.Team]{ + SchemeName: "AuthProviderTeamAuth", + Header: HeaderKey{ + Name: HeaderTeamID, + }, + ValidationFunc: validationFunc, + SetContextFunc: SetTeamInfo, + ErrorMessage: "Invalid auth provider token teamID.", + } +} + // NewAdminTokenAuthenticator creates an authenticator for the AdminTokenAuth security scheme (X-Admin-Token header). func NewAdminTokenAuthenticator(adminToken string) Authenticator { return &CommonAuthenticator[struct{}]{ diff --git a/packages/auth/pkg/auth/oidc/entry.go b/packages/auth/pkg/auth/oidc/entry.go new file mode 100644 index 0000000000..154ad91d60 --- /dev/null +++ b/packages/auth/pkg/auth/oidc/entry.go @@ -0,0 +1,104 @@ +package oidc + +import ( + "encoding/json" + "errors" + "fmt" + "strings" + "time" + + "github.com/e2b-dev/infra/packages/auth/pkg/auth/jwtutil" +) + +const ( + // DefaultJWKSCacheDuration is the default JWKS cache duration applied + // when an Entry does not specify one. + DefaultJWKSCacheDuration = 5 * time.Minute + // DefaultDiscoveryPath is the relative path appended to the issuer URL + // to derive the discovery URL when one is not explicitly configured. + DefaultDiscoveryPath = "/.well-known/openid-configuration" +) + +// Entry describes a single OIDC issuer. +type Entry struct { + Issuer Issuer `json:"issuer"` + ClaimMappings jwtutil.ClaimMappings `json:"claimMappings"` + JWKSCacheDuration time.Duration `json:"jwksCacheDuration"` +} + +// Issuer describes an OIDC issuer endpoint plus audience policy. +type Issuer struct { + URL string `json:"url"` + DiscoveryURL string `json:"discoveryURL"` + Audiences []string `json:"audiences"` + AudienceMatchPolicy jwtutil.AudienceMatchPolicy `json:"audienceMatchPolicy"` +} + +// UnmarshalJSON parses `jwksCacheDuration` from a Go duration string. +func (e *Entry) UnmarshalJSON(data []byte) error { + type entryJSON struct { + Issuer Issuer `json:"issuer"` + ClaimMappings jwtutil.ClaimMappings `json:"claimMappings"` + JWKSCacheDuration string `json:"jwksCacheDuration"` + } + + var raw entryJSON + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + e.Issuer = raw.Issuer + e.ClaimMappings = raw.ClaimMappings + + if raw.JWKSCacheDuration != "" { + duration, err := time.ParseDuration(raw.JWKSCacheDuration) + if err != nil { + return fmt.Errorf("parse jwksCacheDuration: %w", err) + } + + e.JWKSCacheDuration = duration + } + + return nil +} + +// Normalized returns a copy with defaults applied. +func (e *Entry) Normalized() Entry { + out := *e + out.Issuer.URL = strings.TrimSpace(out.Issuer.URL) + out.Issuer.DiscoveryURL = strings.TrimSpace(out.Issuer.DiscoveryURL) + out.ClaimMappings = out.ClaimMappings.Normalized() + + if out.JWKSCacheDuration <= 0 { + out.JWKSCacheDuration = DefaultJWKSCacheDuration + } + + return out +} + +// Validate returns an error if the entry contains invalid configuration. +// +// All issues found are joined into a single error to surface as much useful +// feedback as possible in one pass. +func (e *Entry) Validate() error { + var errs []error + + errs = append(errs, validateIssuerURL(e.Issuer.URL)...) + errs = append(errs, validateDiscoveryURL(e.Issuer.URL, e.Issuer.DiscoveryURL)...) + + if err := jwtutil.ValidateAudienceMatchPolicy(e.Issuer.AudienceMatchPolicy, e.Issuer.Audiences); err != nil { + errs = append(errs, fmt.Errorf("issuer: %w", err)) + } + + return errors.Join(errs...) +} + +// DiscoveryURL returns the configured discoveryURL or the default derived +// from the issuer URL. +func (e *Entry) DiscoveryURL() string { + if e.Issuer.DiscoveryURL != "" { + return e.Issuer.DiscoveryURL + } + + return strings.TrimRight(e.Issuer.URL, "/") + DefaultDiscoveryPath +} diff --git a/packages/auth/pkg/auth/oidc/entry_test.go b/packages/auth/pkg/auth/oidc/entry_test.go new file mode 100644 index 0000000000..843babe88b --- /dev/null +++ b/packages/auth/pkg/auth/oidc/entry_test.go @@ -0,0 +1,153 @@ +package oidc + +import ( + "testing" + + "github.com/stretchr/testify/require" + + "github.com/e2b-dev/infra/packages/auth/pkg/auth/jwtutil" +) + +func TestEntry_Validate(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + entry Entry + wantErr string + }{ + { + name: "valid single audience no policy", + entry: Entry{ + Issuer: Issuer{ + URL: "https://issuer.example.com", + Audiences: []string{"dashboard-api"}, + }, + }, + }, + { + name: "valid single audience MatchAny", + entry: Entry{ + Issuer: Issuer{ + URL: "https://issuer.example.com", + Audiences: []string{"dashboard-api"}, + AudienceMatchPolicy: jwtutil.AudienceMatchAny, + }, + }, + }, + { + name: "valid multiple audiences with MatchAny", + entry: Entry{ + Issuer: Issuer{ + URL: "https://issuer.example.com", + Audiences: []string{"a", "b"}, + AudienceMatchPolicy: jwtutil.AudienceMatchAny, + }, + }, + }, + { + name: "missing issuer URL", + entry: Entry{ + Issuer: Issuer{ + Audiences: []string{"a"}, + }, + }, + wantErr: "issuer.url is required", + }, + { + name: "non-https issuer URL", + entry: Entry{ + Issuer: Issuer{ + URL: "http://issuer.example.com", + Audiences: []string{"a"}, + }, + }, + wantErr: "must be https", + }, + { + name: "issuer URL with userinfo", + entry: Entry{ + Issuer: Issuer{ + URL: "https://user:pass@issuer.example.com", + Audiences: []string{"a"}, + }, + }, + wantErr: "must not contain a username or password", + }, + { + name: "issuer URL with query", + entry: Entry{ + Issuer: Issuer{ + URL: "https://issuer.example.com?foo=bar", + Audiences: []string{"a"}, + }, + }, + wantErr: "must not contain a query", + }, + { + name: "issuer URL with fragment", + entry: Entry{ + Issuer: Issuer{ + URL: "https://issuer.example.com#frag", + Audiences: []string{"a"}, + }, + }, + wantErr: "must not contain a fragment", + }, + { + name: "discoveryURL same as issuer URL", + entry: Entry{ + Issuer: Issuer{ + URL: "https://issuer.example.com", + DiscoveryURL: "https://issuer.example.com", + Audiences: []string{"a"}, + }, + }, + wantErr: "discoveryURL must be different", + }, + { + name: "empty audiences", + entry: Entry{ + Issuer: Issuer{ + URL: "https://issuer.example.com", + }, + }, + wantErr: "audiences must contain at least one entry", + }, + { + name: "multiple audiences without MatchAny", + entry: Entry{ + Issuer: Issuer{ + URL: "https://issuer.example.com", + Audiences: []string{"a", "b"}, + }, + }, + wantErr: "audienceMatchPolicy must be", + }, + { + name: "single audience with invalid policy", + entry: Entry{ + Issuer: Issuer{ + URL: "https://issuer.example.com", + Audiences: []string{"a"}, + AudienceMatchPolicy: "MatchAll", + }, + }, + wantErr: "audienceMatchPolicy must be empty or", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + err := tt.entry.Validate() + if tt.wantErr == "" { + require.NoError(t, err) + return + } + require.Error(t, err) + require.Contains(t, err.Error(), tt.wantErr) + }) + } +} diff --git a/packages/auth/pkg/auth/oidc/oidc.go b/packages/auth/pkg/auth/oidc/oidc.go new file mode 100644 index 0000000000..d86aaf5d34 --- /dev/null +++ b/packages/auth/pkg/auth/oidc/oidc.go @@ -0,0 +1,226 @@ +package oidc + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "io" + "net/http" + "net/url" + "strings" + "time" + + "github.com/MicahParks/jwkset" + "github.com/MicahParks/keyfunc/v3" + "github.com/golang-jwt/jwt/v5" + + "github.com/e2b-dev/infra/packages/auth/pkg/auth/jwtutil" +) + +// JWKSHTTPTimeout is the timeout used for OIDC discovery and JWKS HTTP +// requests. +const JWKSHTTPTimeout = 10 * time.Second + +// Verifier verifies JWTs against a single OIDC issuer. +type Verifier struct { + keyfunc keyfunc.Keyfunc + userIDClaim string + audiences []string + parserOptions []jwt.ParserOption +} + +// discoveryDocument is a minimal subset of the OIDC discovery document +// (https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderMetadata). +type discoveryDocument struct { + Issuer string `json:"issuer"` + JWKSURI string `json:"jwks_uri"` +} + +// NewVerifier constructs a Verifier from the supplied Entry. It performs the +// OIDC discovery fetch synchronously and fails fast on configuration or +// network errors. +func NewVerifier(ctx context.Context, entry Entry, httpClient *http.Client) (*Verifier, error) { + if httpClient == nil { + return nil, errors.New("OIDC JWKS HTTP client is required") + } + + if entry.Issuer.URL == "" { + return nil, errors.New("issuer URL is required") + } + + discoveryURL := entry.DiscoveryURL() + if err := validateHTTPSURL(discoveryURL, "discoveryURL"); err != nil { + return nil, err + } + + doc, err := fetchDiscoveryDocument(ctx, httpClient, discoveryURL) + if err != nil { + return nil, fmt.Errorf("fetch OIDC discovery document at %s: %w", discoveryURL, err) + } + + if doc.Issuer != entry.Issuer.URL { + return nil, fmt.Errorf("discovery document issuer %q does not match configured issuer %q", doc.Issuer, entry.Issuer.URL) + } + + if err := validateHTTPSURL(doc.JWKSURI, "discovery jwks_uri"); err != nil { + return nil, err + } + + storage, err := jwkset.NewStorageFromHTTP(doc.JWKSURI, jwkset.HTTPClientStorageOptions{ + Client: httpClient, + Ctx: ctx, + HTTPTimeout: JWKSHTTPTimeout, + RefreshInterval: entry.JWKSCacheDuration, + }) + if err != nil { + return nil, fmt.Errorf("create OIDC JWKS storage: %w", err) + } + + keyFunc, err := keyfunc.New(keyfunc.Options{ + Ctx: ctx, + Storage: storage, + }) + if err != nil { + return nil, fmt.Errorf("create OIDC JWKS keyfunc: %w", err) + } + + parserOptions := []jwt.ParserOption{ + jwt.WithExpirationRequired(), + jwt.WithIssuer(entry.Issuer.URL), + } + + return &Verifier{ + keyfunc: keyFunc, + userIDClaim: entry.ClaimMappings.Username.Claim, + audiences: entry.Issuer.Audiences, + parserOptions: parserOptions, + }, nil +} + +// Verify parses and validates the supplied token string. +func (v *Verifier) Verify(ctx context.Context, tokenString string) (*jwtutil.Identity, error) { + claims := jwt.MapClaims{} + token, err := jwt.ParseWithClaims(tokenString, claims, func(token *jwt.Token) (any, error) { + return v.keyfunc.KeyfuncCtx(ctx)(token) + }, v.parserOptions...) + if err != nil { + return nil, fmt.Errorf("failed to verify auth provider token: %w", err) + } + if !token.Valid { + return nil, errors.New("auth provider token is invalid") + } + + if err := jwtutil.ValidateAudience(claims, v.audiences); err != nil { + return nil, fmt.Errorf("failed to verify auth provider token: %w", err) + } + + return jwtutil.IdentityFromClaims(claims, v.userIDClaim), nil +} + +func fetchDiscoveryDocument(ctx context.Context, httpClient *http.Client, discoveryURL string) (*discoveryDocument, error) { + fetchCtx, cancel := context.WithTimeout(ctx, JWKSHTTPTimeout) + defer cancel() + + req, err := http.NewRequestWithContext(fetchCtx, http.MethodGet, discoveryURL, nil) + if err != nil { + return nil, fmt.Errorf("create discovery request: %w", err) + } + req.Header.Set("Accept", "application/json") + + resp, err := httpClient.Do(req) + if err != nil { + return nil, fmt.Errorf("execute discovery request: %w", err) + } + defer resp.Body.Close() + + if resp.StatusCode < 200 || resp.StatusCode >= 300 { + body, _ := io.ReadAll(io.LimitReader(resp.Body, 1024)) + + return nil, fmt.Errorf("discovery endpoint returned status %d: %s", resp.StatusCode, string(body)) + } + + var doc discoveryDocument + if err := json.NewDecoder(resp.Body).Decode(&doc); err != nil { + return nil, fmt.Errorf("decode discovery document: %w", err) + } + + if doc.Issuer == "" { + return nil, errors.New("discovery document is missing issuer") + } + + if doc.JWKSURI == "" { + return nil, errors.New("discovery document is missing jwks_uri") + } + + return &doc, nil +} + +// validateHTTPSURL applies the same checks as validateURL but returns at the +// first failure. It is intended for runtime validation of URLs derived from +// the OIDC discovery document where one error is enough to fail fast. +func validateHTTPSURL(rawURL string, field string) error { + if errs := validateURL(rawURL, field); len(errs) > 0 { + return errs[0] + } + + return nil +} + +// validateURL enforces the same constraints Kubernetes requires of issuer +// URLs: +// - parseable via url.Parse +// - https scheme +// - no userinfo (username/password) +// - no query string +// - no fragment +// +// The field name is included in error messages to help operators locate the +// offending value. All applicable errors are returned together. +func validateURL(rawURL string, field string) []error { + var errs []error + + u, err := url.Parse(rawURL) + if err != nil { + return []error{fmt.Errorf("invalid %s: %w", field, err)} + } + if u.Scheme != "https" { + errs = append(errs, fmt.Errorf("invalid %s scheme %q (must be https)", field, u.Scheme)) + } + if u.User != nil { + errs = append(errs, fmt.Errorf("invalid %s: must not contain a username or password", field)) + } + if len(u.RawQuery) > 0 { + errs = append(errs, fmt.Errorf("invalid %s: must not contain a query", field)) + } + if len(u.Fragment) > 0 { + errs = append(errs, fmt.Errorf("invalid %s: must not contain a fragment", field)) + } + + return errs +} + +// validateIssuerURL enforces presence + URL constraints on the issuer URL. +func validateIssuerURL(issuerURL string) []error { + if issuerURL == "" { + return []error{errors.New("issuer.url is required")} + } + + return validateURL(issuerURL, "issuer.url") +} + +// validateDiscoveryURL enforces URL constraints on the optional discovery +// URL plus the requirement that it differ from the issuer URL when set. +func validateDiscoveryURL(issuerURL, discoveryURL string) []error { + if discoveryURL == "" { + return nil + } + + var errs []error + if issuerURL != "" && strings.TrimRight(issuerURL, "/") == strings.TrimRight(discoveryURL, "/") { + errs = append(errs, errors.New("issuer.discoveryURL must be different from issuer.url")) + } + errs = append(errs, validateURL(discoveryURL, "issuer.discoveryURL")...) + + return errs +} diff --git a/packages/auth/pkg/auth/oidc/oidc_test.go b/packages/auth/pkg/auth/oidc/oidc_test.go new file mode 100644 index 0000000000..b6bf7e8461 --- /dev/null +++ b/packages/auth/pkg/auth/oidc/oidc_test.go @@ -0,0 +1,113 @@ +package oidc + +import ( + "crypto/rand" + "crypto/rsa" + "testing" + "time" + + "github.com/golang-jwt/jwt/v5" + "github.com/google/uuid" + "github.com/stretchr/testify/require" + + "github.com/e2b-dev/infra/packages/auth/pkg/auth/jwtutil" +) + +const testIssuerURL = "https://issuer.example.com" + +func TestVerifier_Verify(t *testing.T) { + t.Parallel() + + privateKey, err := rsa.GenerateKey(rand.Reader, 2048) + require.NoError(t, err) + + const keyID = "test-key" + server := NewTestServer(t, &privateKey.PublicKey, keyID, testIssuerURL) + + verifier, err := NewVerifier(t.Context(), Entry{ + Issuer: Issuer{ + URL: testIssuerURL, + DiscoveryURL: server.URL + "/.well-known/openid-configuration", + Audiences: []string{"dashboard-api"}, + }, + ClaimMappings: jwtutil.ClaimMappings{ + Username: jwtutil.ClaimMapping{Claim: "sub"}, + }, + JWKSCacheDuration: time.Minute, + }, server.Client()) + require.NoError(t, err) + + userID := uuid.New() + token := jwt.NewWithClaims(jwt.SigningMethodRS256, jwt.MapClaims{ + "iss": testIssuerURL, + "aud": "dashboard-api", + "sub": userID.String(), + "exp": time.Now().Add(time.Hour).Unix(), + }) + token.Header["kid"] = keyID + + signedToken, err := token.SignedString(privateKey) + require.NoError(t, err) + + identity, err := verifier.Verify(t.Context(), signedToken) + require.NoError(t, err) + require.Equal(t, userID, identity.UserID) +} + +func TestVerifier_RejectsWrongAudience(t *testing.T) { + t.Parallel() + + privateKey, err := rsa.GenerateKey(rand.Reader, 2048) + require.NoError(t, err) + + const keyID = "test-key" + server := NewTestServer(t, &privateKey.PublicKey, keyID, testIssuerURL) + + verifier, err := NewVerifier(t.Context(), Entry{ + Issuer: Issuer{ + URL: testIssuerURL, + DiscoveryURL: server.URL + "/.well-known/openid-configuration", + Audiences: []string{"dashboard-api"}, + }, + ClaimMappings: jwtutil.ClaimMappings{ + Username: jwtutil.ClaimMapping{Claim: "sub"}, + }, + }, server.Client()) + require.NoError(t, err) + + token := jwt.NewWithClaims(jwt.SigningMethodRS256, jwt.MapClaims{ + "iss": testIssuerURL, + "aud": "other-audience", + "sub": uuid.NewString(), + "exp": time.Now().Add(time.Hour).Unix(), + }) + token.Header["kid"] = keyID + + signedToken, err := token.SignedString(privateKey) + require.NoError(t, err) + + _, err = verifier.Verify(t.Context(), signedToken) + require.Error(t, err) +} + +func TestNewVerifier_DiscoveryIssuerMismatch(t *testing.T) { + t.Parallel() + + privateKey, err := rsa.GenerateKey(rand.Reader, 2048) + require.NoError(t, err) + + const keyID = "test-key" + server := NewTestServer(t, &privateKey.PublicKey, keyID, "https://different-issuer.example.com") + + _, err = NewVerifier(t.Context(), Entry{ + Issuer: Issuer{ + URL: testIssuerURL, + DiscoveryURL: server.URL + "/.well-known/openid-configuration", + }, + ClaimMappings: jwtutil.ClaimMappings{ + Username: jwtutil.ClaimMapping{Claim: "sub"}, + }, + }, server.Client()) + require.Error(t, err) + require.Contains(t, err.Error(), "issuer") +} diff --git a/packages/auth/pkg/auth/oidc/testserver.go b/packages/auth/pkg/auth/oidc/testserver.go new file mode 100644 index 0000000000..d97ab8ff0b --- /dev/null +++ b/packages/auth/pkg/auth/oidc/testserver.go @@ -0,0 +1,52 @@ +package oidc + +import ( + "crypto/rsa" + "encoding/json" + "net/http" + "net/http/httptest" + "testing" + + jose "github.com/go-jose/go-jose/v4" +) + +// NewTestServer starts a TLS server that exposes both an OIDC discovery +// endpoint and a JWKS endpoint signed with publicKey. The discovery doc +// reports `discoveryIssuer` as its issuer; tests can use this to simulate +// matching or mismatched issuer values. +// +// This helper is exported so tests in sibling packages can construct an OIDC +// fixture without duplicating the boilerplate. +func NewTestServer(t *testing.T, publicKey *rsa.PublicKey, keyID string, discoveryIssuer string) *httptest.Server { + t.Helper() + + mux := http.NewServeMux() + server := httptest.NewTLSServer(mux) + t.Cleanup(server.Close) + + mux.HandleFunc("/.well-known/openid-configuration", func(w http.ResponseWriter, _ *http.Request) { + err := json.NewEncoder(w).Encode(map[string]string{ + "issuer": discoveryIssuer, + "jwks_uri": server.URL + "/jwks", + }) + if err != nil { + t.Errorf("encode discovery response: %v", err) + } + }) + + mux.HandleFunc("/jwks", func(w http.ResponseWriter, _ *http.Request) { + err := json.NewEncoder(w).Encode(jose.JSONWebKeySet{Keys: []jose.JSONWebKey{ + { + Key: publicKey, + KeyID: keyID, + Algorithm: string(jose.RS256), + Use: "sig", + }, + }}) + if err != nil { + t.Errorf("encode JWKS response: %v", err) + } + }) + + return server +} diff --git a/packages/auth/pkg/auth/service.go b/packages/auth/pkg/auth/service.go index 27a71c893d..88847771f9 100644 --- a/packages/auth/pkg/auth/service.go +++ b/packages/auth/pkg/auth/service.go @@ -27,19 +27,19 @@ type AuthStore[T TeamItem] interface { GetTeamAPIKeyHashes(ctx context.Context, teamID uuid.UUID) ([]string, error) } -// AuthService encapsulates the cache, store, and JWT secrets for auth validation. +// AuthService encapsulates the cache, store, and JWT verifier for auth validation. type AuthService[T TeamItem] struct { - store AuthStore[T] - teamCache *AuthCache[T] - jwtSecrets []string + store AuthStore[T] + teamCache *AuthCache[T] + authProviderVerifier *Verifier } -// NewAuthService creates an AuthService with the given store, cache, and JWT secrets. -func NewAuthService[T TeamItem](store AuthStore[T], teamCache *AuthCache[T], jwtSecrets []string) *AuthService[T] { +// NewAuthService creates an AuthService with the given store, cache, and auth provider verifier. +func NewAuthService[T TeamItem](store AuthStore[T], teamCache *AuthCache[T], authProviderVerifier *Verifier) *AuthService[T] { return &AuthService[T]{ - store: store, - teamCache: teamCache, - jwtSecrets: jwtSecrets, + store: store, + teamCache: teamCache, + authProviderVerifier: authProviderVerifier, } } @@ -132,9 +132,13 @@ func (s *AuthService[T]) ValidateAccessToken(ctx context.Context, ginCtx *gin.Co return userID, nil } -// ValidateSupabaseToken parses a Supabase JWT and extracts the user ID. -func (s *AuthService[T]) ValidateSupabaseToken(ctx context.Context, ginCtx *gin.Context, supabaseToken string) (uuid.UUID, *APIError) { - userID, err := ParseUserIDFromToken(ctx, s.jwtSecrets, supabaseToken) +// ValidateAuthProviderToken verifies a JWT against the configured auth provider and resolves an internal user ID. +func (s *AuthService[T]) ValidateAuthProviderToken(ctx context.Context, ginCtx *gin.Context, token string) (uuid.UUID, *APIError) { + return s.validateJWTWithProvider(ctx, ginCtx, s.authProviderVerifier, token, "auth provider") +} + +func (s *AuthService[T]) validateJWTWithProvider(ctx context.Context, ginCtx *gin.Context, verifier *Verifier, token string, tokenSource string) (uuid.UUID, *APIError) { + identity, err := verifier.Verify(ctx, token) if err != nil { return uuid.UUID{}, &APIError{ Err: err, @@ -143,6 +147,15 @@ func (s *AuthService[T]) ValidateSupabaseToken(ctx context.Context, ginCtx *gin. } } + userID := identity.UserID + if userID == uuid.Nil { + return uuid.UUID{}, &APIError{ + Err: fmt.Errorf("%s token user claim is missing or is not an internal UUID", tokenSource), + ClientMsg: "Backend authentication failed", + Code: http.StatusUnauthorized, + } + } + //nolint:contextcheck // We use the gin request context to set attributes on the parent span. telemetry.SetAttributes(ginCtx.Request.Context(), telemetry.WithUserID(userID.String()), diff --git a/packages/auth/pkg/auth/verifier.go b/packages/auth/pkg/auth/verifier.go new file mode 100644 index 0000000000..7c5a351e36 --- /dev/null +++ b/packages/auth/pkg/auth/verifier.go @@ -0,0 +1,131 @@ +package auth + +import ( + "context" + "errors" + "fmt" + "net/http" + + "github.com/e2b-dev/infra/packages/auth/pkg/auth/bearer" + "github.com/e2b-dev/infra/packages/auth/pkg/auth/jwtutil" + "github.com/e2b-dev/infra/packages/auth/pkg/auth/oidc" +) + +// ProviderConfig describes external auth provider verification. +// +// `jwt` entries are OIDC-compliant issuers (asymmetric keys discovered via +// the OIDC discovery document). `bearer` entries are non-OIDC HMAC-signed +// JWTs. +type ProviderConfig struct { + JWT []oidc.Entry `json:"jwt"` + Bearer []bearer.Entry `json:"bearer"` +} + +// Enabled returns true when at least one auth provider entry is configured. +func (c ProviderConfig) Enabled() bool { + return len(c.JWT) > 0 || len(c.Bearer) > 0 +} + +// normalize applies defaults across both arrays and returns a copy. +func (c ProviderConfig) normalize() ProviderConfig { + jwts := make([]oidc.Entry, len(c.JWT)) + for i, entry := range c.JWT { + jwts[i] = entry.Normalized() + } + + bearers := make([]bearer.Entry, len(c.Bearer)) + for i, entry := range c.Bearer { + bearers[i] = entry.Normalized() + } + + return ProviderConfig{JWT: jwts, Bearer: bearers} +} + +// validate runs configuration sanity checks on a (already normalized) config. +func (c ProviderConfig) validate() error { + for i, entry := range c.JWT { + if err := entry.Validate(); err != nil { + return fmt.Errorf("auth provider jwt[%d]: %w", i, err) + } + } + + for i, entry := range c.Bearer { + if err := entry.Validate(); err != nil { + return fmt.Errorf("auth provider bearer[%d]: %w", i, err) + } + } + + return nil +} + +// strategy is the interface satisfied by per-provider JWT verifiers used by +// Verifier. +type strategy interface { + Verify(ctx context.Context, tokenString string) (*jwtutil.Identity, error) +} + +type Verifier struct { + strategies []strategy +} + +func NewVerifier(ctx context.Context, config ProviderConfig) (*Verifier, error) { + return newVerifier(ctx, config, &http.Client{Timeout: oidc.JWKSHTTPTimeout}) +} + +func newVerifier(ctx context.Context, config ProviderConfig, jwksHTTPClient *http.Client) (*Verifier, error) { + normalized := config.normalize() + if err := normalized.validate(); err != nil { + return nil, err + } + if !normalized.Enabled() { + return nil, nil + } + + strategies := make([]strategy, 0, len(normalized.JWT)+len(normalized.Bearer)) + + for i, entry := range normalized.JWT { + s, err := oidc.NewVerifier(ctx, entry, jwksHTTPClient) + if err != nil { + return nil, fmt.Errorf("auth provider jwt[%d]: %w", i, err) + } + strategies = append(strategies, s) + } + + for i, entry := range normalized.Bearer { + s, err := bearer.NewVerifier(entry) + if err != nil { + return nil, fmt.Errorf("auth provider bearer[%d]: %w", i, err) + } + strategies = append(strategies, s) + } + + if len(strategies) == 0 { + return nil, errors.New("auth provider verifier has no configured signing verifier") + } + + return &Verifier{ + strategies: strategies, + }, nil +} + +func (v *Verifier) Verify(ctx context.Context, tokenString string) (*jwtutil.Identity, error) { + if v == nil { + return nil, errors.New("auth provider verifier is not configured") + } + + if len(v.strategies) == 0 { + return nil, errors.New("auth provider verifier strategies are not configured") + } + + errs := make([]error, 0, len(v.strategies)) + for _, strategy := range v.strategies { + identity, err := strategy.Verify(ctx, tokenString) + if err == nil { + return identity, nil + } + + errs = append(errs, err) + } + + return nil, fmt.Errorf("failed to verify auth provider token: %w", errors.Join(errs...)) +} diff --git a/packages/auth/pkg/auth/verifier_test.go b/packages/auth/pkg/auth/verifier_test.go new file mode 100644 index 0000000000..b470469778 --- /dev/null +++ b/packages/auth/pkg/auth/verifier_test.go @@ -0,0 +1,156 @@ +package auth + +import ( + "crypto/rand" + "crypto/rsa" + "crypto/tls" + "crypto/x509" + "net/http" + "net/http/httptest" + "testing" + "time" + + "github.com/golang-jwt/jwt/v5" + "github.com/google/uuid" + "github.com/stretchr/testify/require" + + "github.com/e2b-dev/infra/packages/auth/pkg/auth/bearer" + "github.com/e2b-dev/infra/packages/auth/pkg/auth/oidc" +) + +const testIssuerURL = "https://issuer.example.com" + +// httpClientForServers returns an HTTP client whose root CA pool trusts every +// supplied test server. Required when a single verifier needs to talk to +// multiple httptest TLS servers (each uses its own self-signed cert). +func httpClientForServers(servers ...*httptest.Server) *http.Client { + pool := x509.NewCertPool() + for _, s := range servers { + pool.AddCert(s.Certificate()) + } + + return &http.Client{ + Transport: &http.Transport{ + TLSClientConfig: &tls.Config{ + RootCAs: pool, + MinVersion: tls.VersionTLS12, + }, + }, + } +} + +func TestVerifier_VerifyWithMultipleStrategies(t *testing.T) { + t.Parallel() + + privateKey, err := rsa.GenerateKey(rand.Reader, 2048) + require.NoError(t, err) + + const ( + keyID = "test-key" + hmacSecret = "supabasejwtsecretsupabasejwtsecret" + ) + server := oidc.NewTestServer(t, &privateKey.PublicKey, keyID, testIssuerURL) + + verifier, err := newVerifier(t.Context(), ProviderConfig{ + JWT: []oidc.Entry{ + { + Issuer: oidc.Issuer{ + URL: testIssuerURL, + DiscoveryURL: server.URL + "/.well-known/openid-configuration", + Audiences: []string{"dashboard-api"}, + }, + JWKSCacheDuration: time.Minute, + }, + }, + Bearer: []bearer.Entry{ + { + HMAC: &bearer.HMACConfig{Secrets: []string{hmacSecret}}, + }, + }, + }, server.Client()) + require.NoError(t, err) + + hmacUserID := uuid.New() + hmacToken := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{ + "sub": hmacUserID.String(), + "exp": time.Now().Add(time.Hour).Unix(), + }) + signedHMACToken, err := hmacToken.SignedString([]byte(hmacSecret)) + require.NoError(t, err) + + hmacIdentity, err := verifier.Verify(t.Context(), signedHMACToken) + require.NoError(t, err) + require.Equal(t, hmacUserID, hmacIdentity.UserID) + + jwksUserID := uuid.New() + jwksToken := jwt.NewWithClaims(jwt.SigningMethodRS256, jwt.MapClaims{ + "iss": testIssuerURL, + "aud": "dashboard-api", + "sub": jwksUserID.String(), + "exp": time.Now().Add(time.Hour).Unix(), + }) + jwksToken.Header["kid"] = keyID + signedJWKSToken, err := jwksToken.SignedString(privateKey) + require.NoError(t, err) + + jwksIdentity, err := verifier.Verify(t.Context(), signedJWKSToken) + require.NoError(t, err) + require.Equal(t, jwksUserID, jwksIdentity.UserID) +} + +func TestVerifier_VerifyMultipleJWTIssuers(t *testing.T) { + t.Parallel() + + privateKey1, err := rsa.GenerateKey(rand.Reader, 2048) + require.NoError(t, err) + privateKey2, err := rsa.GenerateKey(rand.Reader, 2048) + require.NoError(t, err) + + const ( + keyID1 = "key-1" + keyID2 = "key-2" + issuer1URL = "https://issuer-one.example.com" + issuer2URL = "https://issuer-two.example.com" + ) + + server1 := oidc.NewTestServer(t, &privateKey1.PublicKey, keyID1, issuer1URL) + server2 := oidc.NewTestServer(t, &privateKey2.PublicKey, keyID2, issuer2URL) + + verifier, err := newVerifier(t.Context(), ProviderConfig{ + JWT: []oidc.Entry{ + { + Issuer: oidc.Issuer{ + URL: issuer1URL, + DiscoveryURL: server1.URL + "/.well-known/openid-configuration", + Audiences: []string{"app-1"}, + }, + }, + { + Issuer: oidc.Issuer{ + URL: issuer2URL, + DiscoveryURL: server2.URL + "/.well-known/openid-configuration", + Audiences: []string{"app-2"}, + }, + }, + }, + }, httpClientForServers(server1, server2)) + require.NoError(t, err) + + // Token from issuer 2 must verify against the second strategy even when + // the first one rejects it. + userID := uuid.New() + token := jwt.NewWithClaims(jwt.SigningMethodRS256, jwt.MapClaims{ + "iss": issuer2URL, + "aud": "app-2", + "sub": userID.String(), + "exp": time.Now().Add(time.Hour).Unix(), + }) + token.Header["kid"] = keyID2 + + signedToken, err := token.SignedString(privateKey2) + require.NoError(t, err) + + identity, err := verifier.Verify(t.Context(), signedToken) + require.NoError(t, err) + require.Equal(t, userID, identity.UserID) +} diff --git a/packages/auth/pkg/tests/sign_token.go b/packages/auth/pkg/tests/sign_token.go index 53d50009b1..cc091cefb5 100644 --- a/packages/auth/pkg/tests/sign_token.go +++ b/packages/auth/pkg/tests/sign_token.go @@ -6,20 +6,16 @@ import ( "github.com/golang-jwt/jwt/v5" "github.com/stretchr/testify/assert" - - "github.com/e2b-dev/infra/packages/auth/pkg/auth" ) func SignTestToken(t *testing.T, secret string, subject string) string { t.Helper() - claims := auth.SupabaseClaims{ - RegisteredClaims: jwt.RegisteredClaims{ - Subject: subject, - ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Hour)), // 1 hour expiry - IssuedAt: jwt.NewNumericDate(time.Now()), - Issuer: "test", - }, + claims := jwt.RegisteredClaims{ + Subject: subject, + ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Hour)), // 1 hour expiry + IssuedAt: jwt.NewNumericDate(time.Now()), + Issuer: "test", } token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) diff --git a/packages/dashboard-api/go.mod b/packages/dashboard-api/go.mod index ac005235ce..dfc2cf2223 100644 --- a/packages/dashboard-api/go.mod +++ b/packages/dashboard-api/go.mod @@ -41,6 +41,8 @@ require ( github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c // indirect github.com/ClickHouse/ch-go v0.67.0 // indirect github.com/ClickHouse/clickhouse-go/v2 v2.40.1 // indirect + github.com/MicahParks/jwkset v0.11.0 // indirect + github.com/MicahParks/keyfunc/v3 v3.8.0 // indirect github.com/Microsoft/go-winio v0.6.2 // indirect github.com/andybalholm/brotli v1.2.0 // indirect github.com/apapsch/go-jsonmerge/v2 v2.0.0 // indirect @@ -69,6 +71,7 @@ require ( github.com/gin-contrib/sse v1.1.1 // indirect github.com/go-faster/city v1.0.1 // indirect github.com/go-faster/errors v0.7.1 // indirect + github.com/go-jose/go-jose/v4 v4.1.4 // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-ole/go-ole v1.3.0 // indirect @@ -169,6 +172,7 @@ require ( golang.org/x/net v0.53.0 // indirect golang.org/x/sys v0.43.0 // indirect golang.org/x/text v0.36.0 // indirect + golang.org/x/time v0.14.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20260401024825-9d38bb4040a9 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20260401024825-9d38bb4040a9 // indirect google.golang.org/grpc v1.80.0 // indirect diff --git a/packages/dashboard-api/go.sum b/packages/dashboard-api/go.sum index eda146d7ef..b6b02945fb 100644 --- a/packages/dashboard-api/go.sum +++ b/packages/dashboard-api/go.sum @@ -8,6 +8,10 @@ github.com/ClickHouse/ch-go v0.67.0 h1:18MQF6vZHj+4/hTRaK7JbS/TIzn4I55wC+QzO24ui github.com/ClickHouse/ch-go v0.67.0/go.mod h1:2MSAeyVmgt+9a2k2SQPPG1b4qbTPzdGDpf1+bcHh+18= github.com/ClickHouse/clickhouse-go/v2 v2.40.1 h1:PbwsHBgqXRydU7jKULD1C8CHmifczffvQqmFvltM2W4= github.com/ClickHouse/clickhouse-go/v2 v2.40.1/go.mod h1:GDzSBLVhladVm8V01aEB36IoBOVLLICfyeuiIp/8Ezc= +github.com/MicahParks/jwkset v0.11.0 h1:yc0zG+jCvZpWgFDFmvs8/8jqqVBG9oyIbmBtmjOhoyQ= +github.com/MicahParks/jwkset v0.11.0/go.mod h1:U2oRhRaLgDCLjtpGL2GseNKGmZtLs/3O7p+OZaL5vo0= +github.com/MicahParks/keyfunc/v3 v3.8.0 h1:Hx2dgIjAXGk9slakM6rV9BOeaWDPEXXZ4Us8guNBfds= +github.com/MicahParks/keyfunc/v3 v3.8.0/go.mod h1:z66bkCviwqfg2YUp+Jcc/xRE9IXLcMq6DrgV/+Htru0= github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= github.com/RaveNoX/go-jsoncommentstrip v1.0.0/go.mod h1:78ihd09MekBnJnxpICcwzCMzGrKSKYe4AqU6PDYYpjk= @@ -86,6 +90,8 @@ github.com/go-faster/city v1.0.1 h1:4WAxSZ3V2Ws4QRDrscLEDcibJY8uf41H6AhXDrNDcGw= github.com/go-faster/city v1.0.1/go.mod h1:jKcUJId49qdW3L1qKHH/3wPeUstCVpVSXTM6vO3VcTw= github.com/go-faster/errors v0.7.1 h1:MkJTnDoEdi9pDabt1dpWf7AA8/BaSYZqibYyhZ20AYg= github.com/go-faster/errors v0.7.1/go.mod h1:5ySTjWFiphBs07IKuiL69nxdfd5+fzh1u7FPGZP2quo= +github.com/go-jose/go-jose/v4 v4.1.4 h1:moDMcTHmvE6Groj34emNPLs/qtYXRVcd6S7NHbHz3kA= +github.com/go-jose/go-jose/v4 v4.1.4/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= @@ -438,6 +444,8 @@ golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg= golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164= +golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI= +golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= diff --git a/packages/dashboard-api/internal/api/api.gen.go b/packages/dashboard-api/internal/api/api.gen.go index f4170c453a..57063887f3 100644 --- a/packages/dashboard-api/internal/api/api.gen.go +++ b/packages/dashboard-api/internal/api/api.gen.go @@ -1,6 +1,6 @@ // Package api provides primitives to interact with the openapi HTTP API. // -// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.5.1 DO NOT EDIT. +// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.6.0 DO NOT EDIT. package api import ( @@ -21,9 +21,11 @@ import ( ) const ( - AdminTokenAuthScopes = "AdminTokenAuth.Scopes" - Supabase1TokenAuthScopes = "Supabase1TokenAuth.Scopes" - Supabase2TeamAuthScopes = "Supabase2TeamAuth.Scopes" + AdminTokenAuthScopes = "AdminTokenAuth.Scopes" + AuthProviderTeamAuthScopes = "AuthProviderTeamAuth.Scopes" + AuthProviderTokenAuthScopes = "AuthProviderTokenAuth.Scopes" + Supabase1TokenAuthScopes = "Supabase1TokenAuth.Scopes" + Supabase2TeamAuthScopes = "Supabase2TeamAuth.Scopes" ) // Defines values for BuildStatus. @@ -33,6 +35,20 @@ const ( Success BuildStatus = "success" ) +// Valid indicates whether the value is a known member of the BuildStatus enum. +func (e BuildStatus) Valid() bool { + switch e { + case Building: + return true + case Failed: + return true + case Success: + return true + default: + return false + } +} + // AddTeamMemberRequest defines model for AddTeamMemberRequest. type AddTeamMemberRequest struct { Email openapi_types.Email `json:"email"` @@ -47,7 +63,7 @@ type BuildInfo struct { FinishedAt *time.Time `json:"finishedAt"` // Names Template names related to this build, if available. - Names *[]string `json:"names"` + Names *[]string `json:"names,omitempty"` // Status Build status mapped for dashboard clients. Status BuildStatus `json:"status"` @@ -102,7 +118,7 @@ type DefaultTemplate struct { BuildCount int32 `json:"buildCount"` BuildId openapi_types.UUID `json:"buildId"` CreatedAt time.Time `json:"createdAt"` - EnvdVersion *string `json:"envdVersion"` + EnvdVersion *string `json:"envdVersion,omitempty"` Id string `json:"id"` Public bool `json:"public"` RamMb int64 `json:"ramMb"` @@ -114,7 +130,7 @@ type DefaultTemplate struct { // DefaultTemplateAlias defines model for DefaultTemplateAlias. type DefaultTemplateAlias struct { Alias string `json:"alias"` - Namespace *string `json:"namespace"` + Namespace *string `json:"namespace,omitempty"` } // DefaultTemplatesResponse defines model for DefaultTemplatesResponse. @@ -179,7 +195,7 @@ type SandboxRecord struct { DiskSizeMB DiskSizeMB `json:"diskSizeMB"` // Domain Base domain where the sandbox traffic is accessible - Domain *string `json:"domain"` + Domain *string `json:"domain,omitempty"` // MemoryMB Memory for the sandbox in MiB MemoryMB MemoryMB `json:"memoryMB"` @@ -191,7 +207,7 @@ type SandboxRecord struct { StartedAt time.Time `json:"startedAt"` // StoppedAt Time when the sandbox was stopped - StoppedAt *time.Time `json:"stoppedAt"` + StoppedAt *time.Time `json:"stoppedAt,omitempty"` // TemplateID Identifier of the template from which is the sandbox created TemplateID string `json:"templateID"` @@ -199,7 +215,7 @@ type SandboxRecord struct { // TeamMember defines model for TeamMember. type TeamMember struct { - AddedBy *openapi_types.UUID `json:"addedBy"` + AddedBy *openapi_types.UUID `json:"addedBy,omitempty"` CreatedAt *time.Time `json:"createdAt"` Email string `json:"email"` Id openapi_types.UUID `json:"id"` @@ -220,14 +236,14 @@ type TeamResolveResponse struct { // UpdateTeamRequest defines model for UpdateTeamRequest. type UpdateTeamRequest struct { Name *string `json:"name,omitempty"` - ProfilePictureUrl *string `json:"profilePictureUrl"` + ProfilePictureUrl *string `json:"profilePictureUrl,omitempty"` } // UpdateTeamResponse defines model for UpdateTeamResponse. type UpdateTeamResponse struct { Id openapi_types.UUID `json:"id"` Name string `json:"name"` - ProfilePictureUrl *string `json:"profilePictureUrl"` + ProfilePictureUrl *string `json:"profilePictureUrl,omitempty"` } // UserTeam defines model for UserTeam. @@ -405,7 +421,7 @@ func (siw *ServerInterfaceWrapper) PostAdminUsersUserIdBootstrap(c *gin.Context) // ------------- Path parameter "userId" ------------- var userId UserId - err = runtime.BindStyledParameterWithOptions("simple", "userId", c.Param("userId"), &userId, runtime.BindStyledParameterOptions{Explode: false, Required: true}) + err = runtime.BindStyledParameterWithOptions("simple", "userId", c.Param("userId"), &userId, runtime.BindStyledParameterOptions{Explode: false, Required: true, Type: "string", Format: "uuid"}) if err != nil { siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter userId: %w", err), http.StatusBadRequest) return @@ -432,12 +448,16 @@ func (siw *ServerInterfaceWrapper) GetBuilds(c *gin.Context) { c.Set(Supabase2TeamAuthScopes, []string{}) + c.Set(AuthProviderTeamAuthScopes, []string{}) + + c.Set(AuthProviderTokenAuthScopes, []string{}) + // Parameter object where we will unmarshal all parameters from the context var params GetBuildsParams // ------------- Optional query parameter "build_id_or_template" ------------- - err = runtime.BindQueryParameter("form", true, false, "build_id_or_template", c.Request.URL.Query(), ¶ms.BuildIdOrTemplate) + err = runtime.BindQueryParameterWithOptions("form", true, false, "build_id_or_template", c.Request.URL.Query(), ¶ms.BuildIdOrTemplate, runtime.BindQueryParameterOptions{Type: "string", Format: ""}) if err != nil { siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter build_id_or_template: %w", err), http.StatusBadRequest) return @@ -445,7 +465,7 @@ func (siw *ServerInterfaceWrapper) GetBuilds(c *gin.Context) { // ------------- Optional query parameter "statuses" ------------- - err = runtime.BindQueryParameter("form", false, false, "statuses", c.Request.URL.Query(), ¶ms.Statuses) + err = runtime.BindQueryParameterWithOptions("form", false, false, "statuses", c.Request.URL.Query(), ¶ms.Statuses, runtime.BindQueryParameterOptions{Type: "array", Format: ""}) if err != nil { siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter statuses: %w", err), http.StatusBadRequest) return @@ -453,7 +473,7 @@ func (siw *ServerInterfaceWrapper) GetBuilds(c *gin.Context) { // ------------- Optional query parameter "limit" ------------- - err = runtime.BindQueryParameter("form", true, false, "limit", c.Request.URL.Query(), ¶ms.Limit) + err = runtime.BindQueryParameterWithOptions("form", true, false, "limit", c.Request.URL.Query(), ¶ms.Limit, runtime.BindQueryParameterOptions{Type: "integer", Format: "int32"}) if err != nil { siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter limit: %w", err), http.StatusBadRequest) return @@ -461,7 +481,7 @@ func (siw *ServerInterfaceWrapper) GetBuilds(c *gin.Context) { // ------------- Optional query parameter "cursor" ------------- - err = runtime.BindQueryParameter("form", true, false, "cursor", c.Request.URL.Query(), ¶ms.Cursor) + err = runtime.BindQueryParameterWithOptions("form", true, false, "cursor", c.Request.URL.Query(), ¶ms.Cursor, runtime.BindQueryParameterOptions{Type: "string", Format: ""}) if err != nil { siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter cursor: %w", err), http.StatusBadRequest) return @@ -486,6 +506,10 @@ func (siw *ServerInterfaceWrapper) GetBuildsStatuses(c *gin.Context) { c.Set(Supabase2TeamAuthScopes, []string{}) + c.Set(AuthProviderTeamAuthScopes, []string{}) + + c.Set(AuthProviderTokenAuthScopes, []string{}) + // Parameter object where we will unmarshal all parameters from the context var params GetBuildsStatusesParams @@ -498,7 +522,7 @@ func (siw *ServerInterfaceWrapper) GetBuildsStatuses(c *gin.Context) { return } - err = runtime.BindQueryParameter("form", false, true, "build_ids", c.Request.URL.Query(), ¶ms.BuildIds) + err = runtime.BindQueryParameterWithOptions("form", false, true, "build_ids", c.Request.URL.Query(), ¶ms.BuildIds, runtime.BindQueryParameterOptions{Type: "array", Format: ""}) if err != nil { siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter build_ids: %w", err), http.StatusBadRequest) return @@ -522,7 +546,7 @@ func (siw *ServerInterfaceWrapper) GetBuildsBuildId(c *gin.Context) { // ------------- Path parameter "build_id" ------------- var buildId BuildId - err = runtime.BindStyledParameterWithOptions("simple", "build_id", c.Param("build_id"), &buildId, runtime.BindStyledParameterOptions{Explode: false, Required: true}) + err = runtime.BindStyledParameterWithOptions("simple", "build_id", c.Param("build_id"), &buildId, runtime.BindStyledParameterOptions{Explode: false, Required: true, Type: "string", Format: "uuid"}) if err != nil { siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter build_id: %w", err), http.StatusBadRequest) return @@ -532,6 +556,10 @@ func (siw *ServerInterfaceWrapper) GetBuildsBuildId(c *gin.Context) { c.Set(Supabase2TeamAuthScopes, []string{}) + c.Set(AuthProviderTeamAuthScopes, []string{}) + + c.Set(AuthProviderTokenAuthScopes, []string{}) + for _, middleware := range siw.HandlerMiddlewares { middleware(c) if c.IsAborted() { @@ -563,7 +591,7 @@ func (siw *ServerInterfaceWrapper) GetSandboxesSandboxIDRecord(c *gin.Context) { // ------------- Path parameter "sandboxID" ------------- var sandboxID SandboxID - err = runtime.BindStyledParameterWithOptions("simple", "sandboxID", c.Param("sandboxID"), &sandboxID, runtime.BindStyledParameterOptions{Explode: false, Required: true}) + err = runtime.BindStyledParameterWithOptions("simple", "sandboxID", c.Param("sandboxID"), &sandboxID, runtime.BindStyledParameterOptions{Explode: false, Required: true, Type: "string", Format: ""}) if err != nil { siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter sandboxID: %w", err), http.StatusBadRequest) return @@ -573,6 +601,10 @@ func (siw *ServerInterfaceWrapper) GetSandboxesSandboxIDRecord(c *gin.Context) { c.Set(Supabase2TeamAuthScopes, []string{}) + c.Set(AuthProviderTeamAuthScopes, []string{}) + + c.Set(AuthProviderTokenAuthScopes, []string{}) + for _, middleware := range siw.HandlerMiddlewares { middleware(c) if c.IsAborted() { @@ -588,6 +620,8 @@ func (siw *ServerInterfaceWrapper) GetTeams(c *gin.Context) { c.Set(Supabase1TokenAuthScopes, []string{}) + c.Set(AuthProviderTokenAuthScopes, []string{}) + for _, middleware := range siw.HandlerMiddlewares { middleware(c) if c.IsAborted() { @@ -603,6 +637,8 @@ func (siw *ServerInterfaceWrapper) PostTeams(c *gin.Context) { c.Set(Supabase1TokenAuthScopes, []string{}) + c.Set(AuthProviderTokenAuthScopes, []string{}) + for _, middleware := range siw.HandlerMiddlewares { middleware(c) if c.IsAborted() { @@ -620,6 +656,8 @@ func (siw *ServerInterfaceWrapper) GetTeamsResolve(c *gin.Context) { c.Set(Supabase1TokenAuthScopes, []string{}) + c.Set(AuthProviderTokenAuthScopes, []string{}) + // Parameter object where we will unmarshal all parameters from the context var params GetTeamsResolveParams @@ -632,7 +670,7 @@ func (siw *ServerInterfaceWrapper) GetTeamsResolve(c *gin.Context) { return } - err = runtime.BindQueryParameter("form", true, true, "slug", c.Request.URL.Query(), ¶ms.Slug) + err = runtime.BindQueryParameterWithOptions("form", true, true, "slug", c.Request.URL.Query(), ¶ms.Slug, runtime.BindQueryParameterOptions{Type: "string", Format: ""}) if err != nil { siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter slug: %w", err), http.StatusBadRequest) return @@ -656,7 +694,7 @@ func (siw *ServerInterfaceWrapper) PatchTeamsTeamID(c *gin.Context) { // ------------- Path parameter "teamID" ------------- var teamID TeamID - err = runtime.BindStyledParameterWithOptions("simple", "teamID", c.Param("teamID"), &teamID, runtime.BindStyledParameterOptions{Explode: false, Required: true}) + err = runtime.BindStyledParameterWithOptions("simple", "teamID", c.Param("teamID"), &teamID, runtime.BindStyledParameterOptions{Explode: false, Required: true, Type: "string", Format: "uuid"}) if err != nil { siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter teamID: %w", err), http.StatusBadRequest) return @@ -666,6 +704,10 @@ func (siw *ServerInterfaceWrapper) PatchTeamsTeamID(c *gin.Context) { c.Set(Supabase2TeamAuthScopes, []string{}) + c.Set(AuthProviderTeamAuthScopes, []string{}) + + c.Set(AuthProviderTokenAuthScopes, []string{}) + for _, middleware := range siw.HandlerMiddlewares { middleware(c) if c.IsAborted() { @@ -684,7 +726,7 @@ func (siw *ServerInterfaceWrapper) GetTeamsTeamIDMembers(c *gin.Context) { // ------------- Path parameter "teamID" ------------- var teamID TeamID - err = runtime.BindStyledParameterWithOptions("simple", "teamID", c.Param("teamID"), &teamID, runtime.BindStyledParameterOptions{Explode: false, Required: true}) + err = runtime.BindStyledParameterWithOptions("simple", "teamID", c.Param("teamID"), &teamID, runtime.BindStyledParameterOptions{Explode: false, Required: true, Type: "string", Format: "uuid"}) if err != nil { siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter teamID: %w", err), http.StatusBadRequest) return @@ -694,6 +736,10 @@ func (siw *ServerInterfaceWrapper) GetTeamsTeamIDMembers(c *gin.Context) { c.Set(Supabase2TeamAuthScopes, []string{}) + c.Set(AuthProviderTeamAuthScopes, []string{}) + + c.Set(AuthProviderTokenAuthScopes, []string{}) + for _, middleware := range siw.HandlerMiddlewares { middleware(c) if c.IsAborted() { @@ -712,7 +758,7 @@ func (siw *ServerInterfaceWrapper) PostTeamsTeamIDMembers(c *gin.Context) { // ------------- Path parameter "teamID" ------------- var teamID TeamID - err = runtime.BindStyledParameterWithOptions("simple", "teamID", c.Param("teamID"), &teamID, runtime.BindStyledParameterOptions{Explode: false, Required: true}) + err = runtime.BindStyledParameterWithOptions("simple", "teamID", c.Param("teamID"), &teamID, runtime.BindStyledParameterOptions{Explode: false, Required: true, Type: "string", Format: "uuid"}) if err != nil { siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter teamID: %w", err), http.StatusBadRequest) return @@ -722,6 +768,10 @@ func (siw *ServerInterfaceWrapper) PostTeamsTeamIDMembers(c *gin.Context) { c.Set(Supabase2TeamAuthScopes, []string{}) + c.Set(AuthProviderTeamAuthScopes, []string{}) + + c.Set(AuthProviderTokenAuthScopes, []string{}) + for _, middleware := range siw.HandlerMiddlewares { middleware(c) if c.IsAborted() { @@ -740,7 +790,7 @@ func (siw *ServerInterfaceWrapper) DeleteTeamsTeamIDMembersUserId(c *gin.Context // ------------- Path parameter "teamID" ------------- var teamID TeamID - err = runtime.BindStyledParameterWithOptions("simple", "teamID", c.Param("teamID"), &teamID, runtime.BindStyledParameterOptions{Explode: false, Required: true}) + err = runtime.BindStyledParameterWithOptions("simple", "teamID", c.Param("teamID"), &teamID, runtime.BindStyledParameterOptions{Explode: false, Required: true, Type: "string", Format: "uuid"}) if err != nil { siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter teamID: %w", err), http.StatusBadRequest) return @@ -749,7 +799,7 @@ func (siw *ServerInterfaceWrapper) DeleteTeamsTeamIDMembersUserId(c *gin.Context // ------------- Path parameter "userId" ------------- var userId UserId - err = runtime.BindStyledParameterWithOptions("simple", "userId", c.Param("userId"), &userId, runtime.BindStyledParameterOptions{Explode: false, Required: true}) + err = runtime.BindStyledParameterWithOptions("simple", "userId", c.Param("userId"), &userId, runtime.BindStyledParameterOptions{Explode: false, Required: true, Type: "string", Format: "uuid"}) if err != nil { siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter userId: %w", err), http.StatusBadRequest) return @@ -759,6 +809,10 @@ func (siw *ServerInterfaceWrapper) DeleteTeamsTeamIDMembersUserId(c *gin.Context c.Set(Supabase2TeamAuthScopes, []string{}) + c.Set(AuthProviderTeamAuthScopes, []string{}) + + c.Set(AuthProviderTokenAuthScopes, []string{}) + for _, middleware := range siw.HandlerMiddlewares { middleware(c) if c.IsAborted() { @@ -774,6 +828,8 @@ func (siw *ServerInterfaceWrapper) GetTemplatesDefaults(c *gin.Context) { c.Set(Supabase1TokenAuthScopes, []string{}) + c.Set(AuthProviderTokenAuthScopes, []string{}) + for _, middleware := range siw.HandlerMiddlewares { middleware(c) if c.IsAborted() { @@ -830,56 +886,57 @@ func RegisterHandlersWithOptions(router gin.IRouter, si ServerInterface, options // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", - "AAAA///Y9cxiL0cAAA==", + "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", + "5l3eLP8fAAD//xVY6+H9SQAA", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/packages/dashboard-api/internal/cfg/model.go b/packages/dashboard-api/internal/cfg/model.go index 331ee794fc..f04d04f246 100644 --- a/packages/dashboard-api/internal/cfg/model.go +++ b/packages/dashboard-api/internal/cfg/model.go @@ -1,17 +1,21 @@ package cfg import ( + "encoding/json" "errors" + "reflect" "github.com/caarlos0/env/v11" + + "github.com/e2b-dev/infra/packages/auth/pkg/auth" ) type Config struct { - Port int `env:"PORT" envDefault:"3010"` - PostgresConnectionString string `env:"POSTGRES_CONNECTION_STRING,required,notEmpty"` - ClickhouseConnectionString string `env:"CLICKHOUSE_CONNECTION_STRING"` - AdminToken string `env:"ADMIN_TOKEN,required,notEmpty"` - SupabaseJWTSecrets []string `env:"SUPABASE_JWT_SECRETS"` + Port int `env:"PORT" envDefault:"3010"` + PostgresConnectionString string `env:"POSTGRES_CONNECTION_STRING,required,notEmpty"` + ClickhouseConnectionString string `env:"CLICKHOUSE_CONNECTION_STRING"` + AdminToken string `env:"ADMIN_TOKEN,required,notEmpty"` + AuthProvider auth.ProviderConfig `env:"AUTH_PROVIDER_CONFIG"` AuthDBConnectionString string `env:"AUTH_DB_CONNECTION_STRING"` AuthDBReadReplicaConnectionString string `env:"AUTH_DB_READ_REPLICA_CONNECTION_STRING"` @@ -29,7 +33,22 @@ type Config struct { func Parse() (Config, error) { var config Config - err := env.Parse(&config) + err := env.ParseWithOptions(&config, env.Options{ + FuncMap: map[reflect.Type]env.ParserFunc{ + reflect.TypeFor[auth.ProviderConfig](): func(v string) (any, error) { + var config auth.ProviderConfig + if v == "" { + return config, nil + } + + if err := json.Unmarshal([]byte(v), &config); err != nil { + return nil, err + } + + return config, nil + }, + }, + }) if config.AuthDBConnectionString == "" { config.AuthDBConnectionString = config.PostgresConnectionString diff --git a/packages/dashboard-api/internal/cfg/model_test.go b/packages/dashboard-api/internal/cfg/model_test.go new file mode 100644 index 0000000000..d3871ee093 --- /dev/null +++ b/packages/dashboard-api/internal/cfg/model_test.go @@ -0,0 +1,62 @@ +package cfg + +import ( + "testing" + "time" + + "github.com/stretchr/testify/require" + + "github.com/e2b-dev/infra/packages/auth/pkg/auth/jwtutil" +) + +func TestParseAuthProviderConfig(t *testing.T) { + t.Setenv("POSTGRES_CONNECTION_STRING", "postgres://example") + t.Setenv("ADMIN_TOKEN", "admin-token") + t.Setenv("REDIS_URL", "redis://example") + t.Setenv("AUTH_PROVIDER_CONFIG", `{ + "jwt": [ + { + "issuer": { + "url": "https://auth.example.com", + "audiences": ["dashboard-api", "other"], + "audienceMatchPolicy": "MatchAny" + }, + "claimMappings": { + "username": { "claim": "https://e2b.dev/user_id" } + }, + "jwksCacheDuration": "30m" + } + ] + }`) + + config, err := Parse() + require.NoError(t, err) + require.Len(t, config.AuthProvider.JWT, 1) + + entry := config.AuthProvider.JWT[0] + require.Equal(t, "https://auth.example.com", entry.Issuer.URL) + require.Equal(t, []string{"dashboard-api", "other"}, entry.Issuer.Audiences) + require.Equal(t, jwtutil.AudienceMatchAny, entry.Issuer.AudienceMatchPolicy) + require.Equal(t, "https://e2b.dev/user_id", entry.ClaimMappings.Username.Claim) + require.Equal(t, 30*time.Minute, entry.JWKSCacheDuration) +} + +func TestParseAuthProviderConfigBearer(t *testing.T) { + t.Setenv("POSTGRES_CONNECTION_STRING", "postgres://example") + t.Setenv("ADMIN_TOKEN", "admin-token") + t.Setenv("REDIS_URL", "redis://example") + t.Setenv("AUTH_PROVIDER_CONFIG", `{ + "bearer": [ + { + "hmac": { "secrets": ["secret-1", "secret-2"] }, + "claimMappings": { "username": { "claim": "sub" } } + } + ] + }`) + + config, err := Parse() + require.NoError(t, err) + require.Len(t, config.AuthProvider.Bearer, 1) + require.Equal(t, []string{"secret-1", "secret-2"}, config.AuthProvider.Bearer[0].HMAC.Secrets) + require.Equal(t, "sub", config.AuthProvider.Bearer[0].ClaimMappings.Username.Claim) +} diff --git a/packages/dashboard-api/internal/handlers/store.go b/packages/dashboard-api/internal/handlers/store.go index f64013f394..dcbc39d672 100644 --- a/packages/dashboard-api/internal/handlers/store.go +++ b/packages/dashboard-api/internal/handlers/store.go @@ -53,8 +53,8 @@ func (s *APIStore) GetHealth(c *gin.Context) { }) } -func (s *APIStore) GetUserIDFromSupabaseToken(ctx context.Context, ginCtx *gin.Context, supabaseToken string) (uuid.UUID, *sharedauth.APIError) { - return s.authService.ValidateSupabaseToken(ctx, ginCtx, supabaseToken) +func (s *APIStore) GetUserIDFromAuthProviderToken(ctx context.Context, ginCtx *gin.Context, token string) (uuid.UUID, *sharedauth.APIError) { + return s.authService.ValidateAuthProviderToken(ctx, ginCtx, token) } func (s *APIStore) GetTeamFromSupabaseToken(ctx context.Context, ginCtx *gin.Context, teamID string) (*types.Team, *sharedauth.APIError) { diff --git a/packages/dashboard-api/main.go b/packages/dashboard-api/main.go index cd1294d8a3..9ac0241eb6 100644 --- a/packages/dashboard-api/main.go +++ b/packages/dashboard-api/main.go @@ -187,7 +187,14 @@ func run() int { authCache := sharedauth.NewAuthCache[*types.Team](redisClient) authStore := sharedauth.NewAuthStore(authDB) - authService := sharedauth.NewAuthService[*types.Team](authStore, authCache, config.SupabaseJWTSecrets) + authProviderVerifier, err := sharedauth.NewVerifier(ctx, config.AuthProvider) + if err != nil { + l.Error(ctx, "Initializing auth provider JWT verifier", zap.Error(err)) + + return 1 + } + + authService := sharedauth.NewAuthService[*types.Team](authStore, authCache, authProviderVerifier) defer authService.Close(ctx) teamProvisionSink, err := internalteamprovision.NewProvisionSink( @@ -216,8 +223,10 @@ func run() int { authenticationFunc := sharedauth.CreateAuthenticationFunc( []sharedauth.Authenticator{ sharedauth.NewAdminTokenAuthenticator(config.AdminToken), - sharedauth.NewSupabaseTokenAuthenticator(apiStore.GetUserIDFromSupabaseToken), + sharedauth.NewAuthProviderTokenAuthenticator(apiStore.GetUserIDFromAuthProviderToken), + sharedauth.NewSupabaseTokenAuthenticator(apiStore.GetUserIDFromAuthProviderToken), sharedauth.NewSupabaseTeamAuthenticator(apiStore.GetTeamFromSupabaseToken), + sharedauth.NewAuthProviderTeamAuthenticator(apiStore.GetTeamFromSupabaseToken), }, nil, ) @@ -290,9 +299,11 @@ func newHTTPServer( "Origin", "Content-Length", "Content-Type", + sharedauth.HeaderAuthorization, sharedauth.HeaderAdminToken, sharedauth.HeaderSupabaseToken, sharedauth.HeaderSupabaseTeam, + sharedauth.HeaderTeamID, } r.Use(cors.New(corsConfig)) diff --git a/spec/openapi-dashboard.yml b/spec/openapi-dashboard.yml index 453c94c8bb..7764788fec 100644 --- a/spec/openapi-dashboard.yml +++ b/spec/openapi-dashboard.yml @@ -18,6 +18,14 @@ components: type: apiKey in: header name: X-Supabase-Team + AuthProviderTokenAuth: + type: http + scheme: bearer + bearerFormat: access_token + AuthProviderTeamAuth: + type: apiKey + in: header + name: X-Team-Id parameters: build_id: @@ -617,6 +625,8 @@ paths: security: - Supabase1TokenAuth: [] Supabase2TeamAuth: [] + - AuthProviderTokenAuth: [] + AuthProviderTeamAuth: [] parameters: - $ref: "#/components/parameters/build_id_or_template" - $ref: "#/components/parameters/build_statuses" @@ -645,6 +655,8 @@ paths: security: - Supabase1TokenAuth: [] Supabase2TeamAuth: [] + - AuthProviderTokenAuth: [] + AuthProviderTeamAuth: [] parameters: - $ref: "#/components/parameters/build_ids" @@ -671,6 +683,8 @@ paths: security: - Supabase1TokenAuth: [] Supabase2TeamAuth: [] + - AuthProviderTokenAuth: [] + AuthProviderTeamAuth: [] parameters: - $ref: "#/components/parameters/build_id" responses: @@ -696,6 +710,8 @@ paths: security: - Supabase1TokenAuth: [] Supabase2TeamAuth: [] + - AuthProviderTokenAuth: [] + AuthProviderTeamAuth: [] parameters: - $ref: "#/components/parameters/sandboxID" responses: @@ -721,6 +737,7 @@ paths: tags: [teams] security: - Supabase1TokenAuth: [] + - AuthProviderTokenAuth: [] responses: "200": description: Successfully returned user teams. @@ -737,6 +754,7 @@ paths: tags: [teams] security: - Supabase1TokenAuth: [] + - AuthProviderTokenAuth: [] requestBody: required: true content: @@ -784,6 +802,7 @@ paths: tags: [teams] security: - Supabase1TokenAuth: [] + - AuthProviderTokenAuth: [] parameters: - $ref: "#/components/parameters/teamSlug" responses: @@ -809,6 +828,8 @@ paths: security: - Supabase1TokenAuth: [] Supabase2TeamAuth: [] + - AuthProviderTokenAuth: [] + AuthProviderTeamAuth: [] parameters: - $ref: "#/components/parameters/teamID" requestBody: @@ -840,6 +861,8 @@ paths: security: - Supabase1TokenAuth: [] Supabase2TeamAuth: [] + - AuthProviderTokenAuth: [] + AuthProviderTeamAuth: [] parameters: - $ref: "#/components/parameters/teamID" responses: @@ -861,6 +884,8 @@ paths: security: - Supabase1TokenAuth: [] Supabase2TeamAuth: [] + - AuthProviderTokenAuth: [] + AuthProviderTeamAuth: [] parameters: - $ref: "#/components/parameters/teamID" requestBody: @@ -890,6 +915,8 @@ paths: security: - Supabase1TokenAuth: [] Supabase2TeamAuth: [] + - AuthProviderTokenAuth: [] + AuthProviderTeamAuth: [] parameters: - $ref: "#/components/parameters/teamID" - $ref: "#/components/parameters/userId" @@ -912,6 +939,7 @@ paths: tags: [templates] security: - Supabase1TokenAuth: [] + - AuthProviderTokenAuth: [] responses: "200": description: Successfully returned default templates. diff --git a/spec/openapi.yml b/spec/openapi.yml index f5c8960ed7..83a4b0aa9b 100644 --- a/spec/openapi.yml +++ b/spec/openapi.yml @@ -27,6 +27,14 @@ components: type: apiKey in: header name: X-Supabase-Team + AuthProviderTokenAuth: + type: http + scheme: bearer + bearerFormat: access_token + AuthProviderTeamAuth: + type: apiKey + in: header + name: X-Team-Id AdminTokenAuth: type: apiKey in: header @@ -1821,6 +1829,7 @@ paths: security: - AccessTokenAuth: [] - Supabase1TokenAuth: [] + - AuthProviderTokenAuth: [] responses: "200": description: Successfully returned all teams @@ -1844,6 +1853,8 @@ paths: - ApiKeyAuth: [] - Supabase1TokenAuth: [] Supabase2TeamAuth: [] + - AuthProviderTokenAuth: [] + AuthProviderTeamAuth: [] parameters: - $ref: "#/components/parameters/teamID" - in: query @@ -1886,6 +1897,8 @@ paths: - ApiKeyAuth: [] - Supabase1TokenAuth: [] Supabase2TeamAuth: [] + - AuthProviderTokenAuth: [] + AuthProviderTeamAuth: [] parameters: - $ref: "#/components/parameters/teamID" - in: query @@ -1933,6 +1946,8 @@ paths: - ApiKeyAuth: [] - Supabase1TokenAuth: [] Supabase2TeamAuth: [] + - AuthProviderTokenAuth: [] + AuthProviderTeamAuth: [] parameters: - name: metadata in: query @@ -1963,6 +1978,8 @@ paths: - ApiKeyAuth: [] - Supabase1TokenAuth: [] Supabase2TeamAuth: [] + - AuthProviderTokenAuth: [] + AuthProviderTeamAuth: [] requestBody: required: true content: @@ -1991,6 +2008,8 @@ paths: - ApiKeyAuth: [] - Supabase1TokenAuth: [] Supabase2TeamAuth: [] + - AuthProviderTokenAuth: [] + AuthProviderTeamAuth: [] parameters: - name: metadata in: query @@ -2035,6 +2054,8 @@ paths: - ApiKeyAuth: [] - Supabase1TokenAuth: [] Supabase2TeamAuth: [] + - AuthProviderTokenAuth: [] + AuthProviderTeamAuth: [] parameters: - name: sandbox_ids in: query @@ -2070,6 +2091,8 @@ paths: - ApiKeyAuth: [] - Supabase1TokenAuth: [] Supabase2TeamAuth: [] + - AuthProviderTokenAuth: [] + AuthProviderTeamAuth: [] parameters: - $ref: "#/components/parameters/sandboxID" - in: query @@ -2109,6 +2132,8 @@ paths: - ApiKeyAuth: [] - Supabase1TokenAuth: [] Supabase2TeamAuth: [] + - AuthProviderTokenAuth: [] + AuthProviderTeamAuth: [] parameters: - $ref: "#/components/parameters/sandboxID" - in: query @@ -2165,6 +2190,8 @@ paths: - ApiKeyAuth: [] - Supabase1TokenAuth: [] Supabase2TeamAuth: [] + - AuthProviderTokenAuth: [] + AuthProviderTeamAuth: [] parameters: - $ref: "#/components/parameters/sandboxID" responses: @@ -2188,6 +2215,8 @@ paths: - ApiKeyAuth: [] - Supabase1TokenAuth: [] Supabase2TeamAuth: [] + - AuthProviderTokenAuth: [] + AuthProviderTeamAuth: [] parameters: - $ref: "#/components/parameters/sandboxID" responses: @@ -2208,6 +2237,8 @@ paths: - ApiKeyAuth: [] - Supabase1TokenAuth: [] Supabase2TeamAuth: [] + - AuthProviderTokenAuth: [] + AuthProviderTeamAuth: [] parameters: - $ref: "#/components/parameters/sandboxID" - in: query @@ -2252,6 +2283,8 @@ paths: - ApiKeyAuth: [] - Supabase1TokenAuth: [] Supabase2TeamAuth: [] + - AuthProviderTokenAuth: [] + AuthProviderTeamAuth: [] parameters: - $ref: "#/components/parameters/sandboxID" responses: @@ -2275,6 +2308,8 @@ paths: - ApiKeyAuth: [] - Supabase1TokenAuth: [] Supabase2TeamAuth: [] + - AuthProviderTokenAuth: [] + AuthProviderTeamAuth: [] parameters: - $ref: "#/components/parameters/sandboxID" requestBody: @@ -2307,6 +2342,8 @@ paths: - ApiKeyAuth: [] - Supabase1TokenAuth: [] Supabase2TeamAuth: [] + - AuthProviderTokenAuth: [] + AuthProviderTeamAuth: [] parameters: - $ref: "#/components/parameters/sandboxID" requestBody: @@ -2344,6 +2381,8 @@ paths: - ApiKeyAuth: [] - Supabase1TokenAuth: [] Supabase2TeamAuth: [] + - AuthProviderTokenAuth: [] + AuthProviderTeamAuth: [] tags: [sandboxes] requestBody: content: @@ -2377,6 +2416,8 @@ paths: - ApiKeyAuth: [] - Supabase1TokenAuth: [] Supabase2TeamAuth: [] + - AuthProviderTokenAuth: [] + AuthProviderTeamAuth: [] tags: [sandboxes] requestBody: required: true @@ -2421,6 +2462,8 @@ paths: - ApiKeyAuth: [] - Supabase1TokenAuth: [] Supabase2TeamAuth: [] + - AuthProviderTokenAuth: [] + AuthProviderTeamAuth: [] tags: [sandboxes] requestBody: content: @@ -2451,6 +2494,8 @@ paths: - ApiKeyAuth: [] - Supabase1TokenAuth: [] Supabase2TeamAuth: [] + - AuthProviderTokenAuth: [] + AuthProviderTeamAuth: [] parameters: - $ref: "#/components/parameters/sandboxID" requestBody: @@ -2487,6 +2532,8 @@ paths: - ApiKeyAuth: [] - Supabase1TokenAuth: [] Supabase2TeamAuth: [] + - AuthProviderTokenAuth: [] + AuthProviderTeamAuth: [] parameters: - name: sandboxID in: query @@ -2518,6 +2565,8 @@ paths: - ApiKeyAuth: [] - Supabase1TokenAuth: [] Supabase2TeamAuth: [] + - AuthProviderTokenAuth: [] + AuthProviderTeamAuth: [] requestBody: required: true content: @@ -2548,6 +2597,8 @@ paths: - ApiKeyAuth: [] - Supabase1TokenAuth: [] Supabase2TeamAuth: [] + - AuthProviderTokenAuth: [] + AuthProviderTeamAuth: [] requestBody: required: true content: @@ -2578,6 +2629,8 @@ paths: - ApiKeyAuth: [] - Supabase1TokenAuth: [] Supabase2TeamAuth: [] + - AuthProviderTokenAuth: [] + AuthProviderTeamAuth: [] parameters: - $ref: "#/components/parameters/templateID" - in: path @@ -2612,6 +2665,8 @@ paths: - AccessTokenAuth: [] - Supabase1TokenAuth: [] Supabase2TeamAuth: [] + - AuthProviderTokenAuth: [] + AuthProviderTeamAuth: [] parameters: - in: query required: false @@ -2641,6 +2696,8 @@ paths: - AccessTokenAuth: [] - Supabase1TokenAuth: [] Supabase2TeamAuth: [] + - AuthProviderTokenAuth: [] + AuthProviderTeamAuth: [] requestBody: required: true content: @@ -2670,6 +2727,8 @@ paths: - ApiKeyAuth: [] - Supabase1TokenAuth: [] Supabase2TeamAuth: [] + - AuthProviderTokenAuth: [] + AuthProviderTeamAuth: [] parameters: - $ref: "#/components/parameters/templateID" - $ref: "#/components/parameters/paginationNextToken" @@ -2693,6 +2752,8 @@ paths: - AccessTokenAuth: [] - Supabase1TokenAuth: [] Supabase2TeamAuth: [] + - AuthProviderTokenAuth: [] + AuthProviderTeamAuth: [] parameters: - $ref: "#/components/parameters/templateID" requestBody: @@ -2721,6 +2782,8 @@ paths: - AccessTokenAuth: [] - Supabase1TokenAuth: [] Supabase2TeamAuth: [] + - AuthProviderTokenAuth: [] + AuthProviderTeamAuth: [] parameters: - $ref: "#/components/parameters/templateID" responses: @@ -2739,6 +2802,8 @@ paths: - AccessTokenAuth: [] - Supabase1TokenAuth: [] Supabase2TeamAuth: [] + - AuthProviderTokenAuth: [] + AuthProviderTeamAuth: [] parameters: - $ref: "#/components/parameters/templateID" requestBody: @@ -2766,6 +2831,8 @@ paths: - AccessTokenAuth: [] - Supabase1TokenAuth: [] Supabase2TeamAuth: [] + - AuthProviderTokenAuth: [] + AuthProviderTeamAuth: [] parameters: - $ref: "#/components/parameters/templateID" - $ref: "#/components/parameters/buildID" @@ -2785,6 +2852,8 @@ paths: - ApiKeyAuth: [] - Supabase1TokenAuth: [] Supabase2TeamAuth: [] + - AuthProviderTokenAuth: [] + AuthProviderTeamAuth: [] parameters: - $ref: "#/components/parameters/templateID" - $ref: "#/components/parameters/buildID" @@ -2811,6 +2880,8 @@ paths: - AccessTokenAuth: [] - Supabase1TokenAuth: [] Supabase2TeamAuth: [] + - AuthProviderTokenAuth: [] + AuthProviderTeamAuth: [] parameters: - $ref: "#/components/parameters/templateID" requestBody: @@ -2842,6 +2913,8 @@ paths: - ApiKeyAuth: [] - Supabase1TokenAuth: [] Supabase2TeamAuth: [] + - AuthProviderTokenAuth: [] + AuthProviderTeamAuth: [] parameters: - $ref: "#/components/parameters/templateID" - $ref: "#/components/parameters/buildID" @@ -2889,6 +2962,8 @@ paths: - ApiKeyAuth: [] - Supabase1TokenAuth: [] Supabase2TeamAuth: [] + - AuthProviderTokenAuth: [] + AuthProviderTeamAuth: [] parameters: - $ref: "#/components/parameters/templateID" - $ref: "#/components/parameters/buildID" @@ -2943,6 +3018,8 @@ paths: - ApiKeyAuth: [] - Supabase1TokenAuth: [] Supabase2TeamAuth: [] + - AuthProviderTokenAuth: [] + AuthProviderTeamAuth: [] requestBody: required: true content: @@ -2971,6 +3048,8 @@ paths: - ApiKeyAuth: [] - Supabase1TokenAuth: [] Supabase2TeamAuth: [] + - AuthProviderTokenAuth: [] + AuthProviderTeamAuth: [] requestBody: required: true content: @@ -2997,6 +3076,8 @@ paths: - ApiKeyAuth: [] - Supabase1TokenAuth: [] Supabase2TeamAuth: [] + - AuthProviderTokenAuth: [] + AuthProviderTeamAuth: [] parameters: - $ref: "#/components/parameters/templateID" responses: @@ -3025,6 +3106,8 @@ paths: - ApiKeyAuth: [] - Supabase1TokenAuth: [] Supabase2TeamAuth: [] + - AuthProviderTokenAuth: [] + AuthProviderTeamAuth: [] parameters: - name: alias in: path @@ -3183,6 +3266,7 @@ paths: tags: [access-tokens] security: - Supabase1TokenAuth: [] + - AuthProviderTokenAuth: [] requestBody: required: true content: @@ -3207,6 +3291,7 @@ paths: tags: [access-tokens] security: - Supabase1TokenAuth: [] + - AuthProviderTokenAuth: [] parameters: - $ref: "#/components/parameters/accessTokenID" responses: @@ -3226,6 +3311,8 @@ paths: security: - Supabase1TokenAuth: [] Supabase2TeamAuth: [] + - AuthProviderTokenAuth: [] + AuthProviderTeamAuth: [] responses: "200": description: Successfully returned all team API keys @@ -3245,6 +3332,8 @@ paths: security: - Supabase1TokenAuth: [] Supabase2TeamAuth: [] + - AuthProviderTokenAuth: [] + AuthProviderTeamAuth: [] requestBody: required: true content: @@ -3270,6 +3359,8 @@ paths: security: - Supabase1TokenAuth: [] Supabase2TeamAuth: [] + - AuthProviderTokenAuth: [] + AuthProviderTeamAuth: [] parameters: - $ref: "#/components/parameters/apiKeyID" requestBody: @@ -3293,6 +3384,8 @@ paths: security: - Supabase1TokenAuth: [] Supabase2TeamAuth: [] + - AuthProviderTokenAuth: [] + AuthProviderTeamAuth: [] parameters: - $ref: "#/components/parameters/apiKeyID" responses: @@ -3313,6 +3406,8 @@ paths: - ApiKeyAuth: [] - Supabase1TokenAuth: [] Supabase2TeamAuth: [] + - AuthProviderTokenAuth: [] + AuthProviderTeamAuth: [] responses: "200": description: Successfully listed all team volumes @@ -3334,6 +3429,8 @@ paths: - ApiKeyAuth: [] - Supabase1TokenAuth: [] Supabase2TeamAuth: [] + - AuthProviderTokenAuth: [] + AuthProviderTeamAuth: [] requestBody: required: true content: @@ -3362,6 +3459,8 @@ paths: - ApiKeyAuth: [] - Supabase1TokenAuth: [] Supabase2TeamAuth: [] + - AuthProviderTokenAuth: [] + AuthProviderTeamAuth: [] parameters: - $ref: "#/components/parameters/volumeID" responses: @@ -3385,6 +3484,8 @@ paths: - ApiKeyAuth: [] - Supabase1TokenAuth: [] Supabase2TeamAuth: [] + - AuthProviderTokenAuth: [] + AuthProviderTeamAuth: [] parameters: - $ref: "#/components/parameters/volumeID" responses: diff --git a/tests/integration/go.mod b/tests/integration/go.mod index 3033c74ede..8734aadb97 100644 --- a/tests/integration/go.mod +++ b/tests/integration/go.mod @@ -34,8 +34,6 @@ require ( ) require ( - dario.cat/mergo v1.0.2 // indirect - github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c // indirect github.com/ClickHouse/ch-go v0.67.0 // indirect github.com/ClickHouse/clickhouse-go/v2 v2.40.1 // indirect github.com/DataDog/datadog-go/v5 v5.2.0 // indirect @@ -43,126 +41,64 @@ require ( github.com/andybalholm/brotli v1.2.0 // indirect github.com/apapsch/go-jsonmerge/v2 v2.0.0 // indirect github.com/bitfield/gotestdox v0.2.2 // indirect - github.com/bsm/redislock v0.9.4 // indirect - github.com/bytedance/gopkg v0.1.4 // indirect - github.com/bytedance/sonic v1.15.0 // indirect - github.com/bytedance/sonic/loader v0.5.1 // indirect - github.com/cenkalti/backoff/v4 v4.3.0 // indirect github.com/cenkalti/backoff/v5 v5.0.3 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect - github.com/cloudwego/base64x v0.1.6 // indirect - github.com/containerd/errdefs v1.0.0 // indirect - github.com/containerd/errdefs/pkg v0.3.0 // indirect - github.com/containerd/log v0.1.0 // indirect - github.com/containerd/platforms v0.2.1 // indirect - github.com/cpuguy83/dockercfg v0.3.2 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/dchest/uniuri v1.2.0 // indirect - github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect - github.com/distribution/reference v0.6.0 // indirect github.com/dnephin/pflag v1.0.7 // indirect - github.com/docker/go-connections v0.6.0 // indirect - github.com/docker/go-units v0.5.0 // indirect github.com/dprotaso/go-yit v0.0.0-20220510233725-9ba8df137936 // indirect - github.com/ebitengine/purego v0.10.0 // indirect github.com/exaring/otelpgx v0.9.3 // indirect github.com/fatih/color v1.18.0 // indirect - github.com/felixge/httpsnoop v1.0.4 // indirect github.com/fsnotify/fsnotify v1.9.0 // indirect - github.com/gabriel-vasile/mimetype v1.4.13 // indirect github.com/gaissmai/extnetip v0.3.3 // indirect github.com/getkin/kin-openapi v0.133.0 // indirect - github.com/gin-contrib/sse v1.1.1 // indirect - github.com/gin-gonic/gin v1.12.0 // indirect github.com/go-faster/city v1.0.1 // indirect github.com/go-faster/errors v0.7.1 // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect - github.com/go-ole/go-ole v1.3.0 // indirect github.com/go-openapi/jsonpointer v0.22.4 // indirect github.com/go-openapi/swag/jsonname v0.25.4 // indirect - github.com/go-playground/locales v0.14.1 // indirect - github.com/go-playground/universal-translator v0.18.1 // indirect - github.com/go-playground/validator/v10 v10.30.2 // indirect - github.com/goccy/go-json v0.10.6 // indirect - github.com/goccy/go-yaml v1.19.2 // indirect github.com/golang-jwt/jwt/v5 v5.3.1 // indirect github.com/google/go-cmp v0.7.0 // indirect github.com/google/nftables v0.3.0 // indirect github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect - github.com/gorilla/mux v1.8.1 // indirect github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.2 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0 // indirect github.com/jackc/pgerrcode v0.0.0-20250907135507-afb5586c32a6 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect github.com/jackc/puddle/v2 v2.2.2 // indirect - github.com/jellydator/ttlcache/v3 v3.4.0 // indirect github.com/josharian/intern v1.0.0 // indirect - github.com/json-iterator/go v1.1.12 // indirect github.com/klauspost/compress v1.18.5 // indirect - github.com/klauspost/cpuid/v2 v2.3.0 // indirect - github.com/leodido/go-urn v1.4.0 // indirect github.com/lib/pq v1.11.2 // indirect - github.com/lufia/plan9stats v0.0.0-20240909124753-873cd0166683 // indirect - github.com/magiconair/properties v1.8.10 // indirect github.com/mailru/easyjson v0.9.1 // indirect github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/mdlayher/netlink v1.7.3-0.20250113171957-fbb4dce95f42 // indirect github.com/mdlayher/socket v0.5.1 // indirect github.com/mfridman/interpolate v0.0.2 // indirect - github.com/moby/docker-image-spec v1.3.1 // indirect - github.com/moby/go-archive v0.2.0 // indirect - github.com/moby/moby/api v1.54.1 // indirect - github.com/moby/moby/client v0.4.0 // indirect - github.com/moby/patternmatcher v0.6.1 // indirect - github.com/moby/sys/sequential v0.6.0 // indirect - github.com/moby/sys/user v0.4.0 // indirect - github.com/moby/sys/userns v0.1.0 // indirect - github.com/moby/term v0.5.2 // indirect - github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect - github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect github.com/ngrok/firewall_toolkit v0.0.18 // indirect - github.com/oapi-codegen/gin-middleware v1.0.2 // indirect github.com/oapi-codegen/oapi-codegen/v2 v2.6.0 // indirect github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037 // indirect github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90 // indirect - github.com/opencontainers/go-digest v1.0.0 // indirect - github.com/opencontainers/image-spec v1.1.1 // indirect github.com/orcaman/concurrent-map/v2 v2.0.1 // indirect github.com/paulmach/orb v0.11.1 // indirect - github.com/pelletier/go-toml/v2 v2.3.0 // indirect github.com/perimeterx/marshmallow v1.1.5 // indirect github.com/pierrec/lz4/v4 v4.1.22 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect - github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect github.com/pressly/goose/v3 v3.26.0 // indirect - github.com/quic-go/qpack v0.6.0 // indirect - github.com/quic-go/quic-go v0.59.0 // indirect - github.com/redis/go-redis/v9 v9.17.3 // indirect github.com/rs/zerolog v1.34.0 // indirect github.com/segmentio/asm v1.2.0 // indirect github.com/sethvargo/go-retry v0.3.0 // indirect - github.com/shirou/gopsutil/v4 v4.26.3 // indirect github.com/shopspring/decimal v1.4.0 // indirect - github.com/sirupsen/logrus v1.9.4 // indirect github.com/speakeasy-api/jsonpath v0.6.0 // indirect github.com/speakeasy-api/openapi-overlay v0.10.2 // indirect - github.com/testcontainers/testcontainers-go v0.42.0 // indirect - github.com/tklauser/go-sysconf v0.3.16 // indirect - github.com/tklauser/numcpus v0.11.0 // indirect - github.com/twitchyliquid64/golang-asm v0.15.1 // indirect - github.com/ugorji/go/codec v1.3.1 // indirect github.com/vmware-labs/yaml-jsonpath v0.3.2 // indirect github.com/woodsbury/decimal128 v1.4.0 // indirect - github.com/yusufpapurcu/wmi v1.2.4 // indirect - go.mongodb.org/mongo-driver/v2 v2.5.0 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect go.opentelemetry.io/contrib/bridges/otelzap v0.14.0 // indirect go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.65.0 // indirect - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.64.0 // indirect go.opentelemetry.io/contrib/instrumentation/runtime v0.66.0 // indirect go.opentelemetry.io/otel v1.43.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.15.0 // indirect @@ -178,8 +114,6 @@ require ( go.opentelemetry.io/proto/otlp v1.10.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.27.1 // indirect - golang.org/x/arch v0.25.0 // indirect - golang.org/x/crypto v0.50.0 // indirect golang.org/x/mod v0.35.0 // indirect golang.org/x/net v0.53.0 // indirect golang.org/x/sys v0.43.0 // indirect diff --git a/tests/integration/go.sum b/tests/integration/go.sum index 3dc530375c..b5ec8413cb 100644 --- a/tests/integration/go.sum +++ b/tests/integration/go.sum @@ -1,11 +1,5 @@ connectrpc.com/connect v1.18.1 h1:PAg7CjSAGvscaf6YZKUefjoih5Z/qYkyaTrBW8xvYPw= connectrpc.com/connect v1.18.1/go.mod h1:0292hj1rnx8oFrStN7cB4jjVBeqs+Yx5yDIC2prWDO8= -dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8= -dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA= -github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6 h1:He8afgbRMd7mFxO99hRNu+6tazq8nFF9lIwo9JFroBk= -github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8= -github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c h1:udKWzYgxTojEKWjV8V+WSxDXJ4NFATAsZjh8iIbsQIg= -github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/ClickHouse/ch-go v0.67.0 h1:18MQF6vZHj+4/hTRaK7JbS/TIzn4I55wC+QzO24uiqc= github.com/ClickHouse/ch-go v0.67.0/go.mod h1:2MSAeyVmgt+9a2k2SQPPG1b4qbTPzdGDpf1+bcHh+18= github.com/ClickHouse/clickhouse-go/v2 v2.40.1 h1:PbwsHBgqXRydU7jKULD1C8CHmifczffvQqmFvltM2W4= @@ -23,20 +17,6 @@ github.com/apapsch/go-jsonmerge/v2 v2.0.0/go.mod h1:lvDnEdqiQrp0O42VQGgmlKpxL1AP github.com/bitfield/gotestdox v0.2.2 h1:x6RcPAbBbErKLnapz1QeAlf3ospg8efBsedU93CDsnE= github.com/bitfield/gotestdox v0.2.2/go.mod h1:D+gwtS0urjBrzguAkTM2wodsTQYFHdpx8eqRJ3N+9pY= github.com/bmatcuk/doublestar v1.1.1/go.mod h1:UD6OnuiIn0yFxxA2le/rnRU1G4RaI4UvFv1sNto9p6w= -github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs= -github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c= -github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA= -github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0= -github.com/bsm/redislock v0.9.4 h1:X/Wse1DPpiQgHbVYRE9zv6m070UcKoOGekgvpNhiSvw= -github.com/bsm/redislock v0.9.4/go.mod h1:Epf7AJLiSFwLCiZcfi6pWFO/8eAYrYpQXFxEDPoDeAk= -github.com/bytedance/gopkg v0.1.4 h1:oZnQwnX82KAIWb7033bEwtxvTqXcYMxDBaQxo5JJHWM= -github.com/bytedance/gopkg v0.1.4/go.mod h1:v1zWfPm21Fb+OsyXN2VAHdL6TBb2L88anLQgdyje6R4= -github.com/bytedance/sonic v1.15.0 h1:/PXeWFaR5ElNcVE84U0dOHjiMHQOwNIx3K4ymzh/uSE= -github.com/bytedance/sonic v1.15.0/go.mod h1:tFkWrPz0/CUCLEF4ri4UkHekCIcdnkqXw9VduqpJh0k= -github.com/bytedance/sonic/loader v0.5.1 h1:Ygpfa9zwRCCKSlrp5bBP/b/Xzc3VxsAW+5NIYXrOOpI= -github.com/bytedance/sonic/loader v0.5.1/go.mod h1:AR4NYCk5DdzZizZ5djGqQ92eEhCCcdf5x77udYiSJRo= -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/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM= github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= @@ -44,64 +24,32 @@ github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XL github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= -github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M= -github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU= -github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI= -github.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M= -github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE= -github.com/containerd/errdefs/pkg v0.3.0/go.mod h1:NJw6s9HwNuRhnjJhM7pylWwMyAkmCQvQ4GpJHEqRLVk= -github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= -github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= -github.com/containerd/platforms v0.2.1 h1:zvwtM3rz2YHPQsF2CHYM8+KtB5dvhISiXh5ZpSBQv6A= -github.com/containerd/platforms v0.2.1/go.mod h1:XHCb+2/hzowdiut9rkudds9bE5yJ7npe7dG/wG+uFPw= github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= -github.com/cpuguy83/dockercfg v0.3.2 h1:DlJTyZGBDlXqUZ2Dk2Q3xHs/FtnooJJVaad2S9GKorA= -github.com/cpuguy83/dockercfg v0.3.2/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc= -github.com/creack/pty v1.1.24 h1:bJrF4RRfyJnbTJqzRLHzcGaZK1NeM5kTC9jGgovnR1s= -github.com/creack/pty v1.1.24/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dchest/uniuri v1.2.0 h1:koIcOUdrTIivZgSLhHQvKgqdWZq5d7KdMEWF1Ud6+5g= github.com/dchest/uniuri v1.2.0/go.mod h1:fSzm4SLHzNZvWLvWJew423PhAzkpNQYq+uNLq4kxhkY= -github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= -github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= -github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= -github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= github.com/dnephin/pflag v1.0.7 h1:oxONGlWxhmUct0YzKTgrpQv9AUA1wtPBn7zuSjJqptk= github.com/dnephin/pflag v1.0.7/go.mod h1:uxE91IoWURlOiTUIA8Mq5ZZkAv3dPUfZNaT80Zm7OQE= -github.com/docker/go-connections v0.6.0 h1:LlMG9azAe1TqfR7sO+NJttz1gy6KO7VJBh+pMmjSD94= -github.com/docker/go-connections v0.6.0/go.mod h1:AahvXYshr6JgfUJGdDCs2b5EZG/vmaMAntpSFH5BFKE= -github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= -github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/dprotaso/go-yit v0.0.0-20191028211022-135eb7262960/go.mod h1:9HQzr9D/0PGwMEbC3d5AB7oi67+h4TsQqItC1GVYG58= github.com/dprotaso/go-yit v0.0.0-20220510233725-9ba8df137936 h1:PRxIJD8XjimM5aTknUK9w6DHLDox2r2M3DI4i2pnd3w= github.com/dprotaso/go-yit v0.0.0-20220510233725-9ba8df137936/go.mod h1:ttYvX5qlB+mlV1okblJqcSMtR4c52UKxDiX9GRBS8+Q= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= -github.com/ebitengine/purego v0.10.0 h1:QIw4xfpWT6GWTzaW5XEKy3HXoqrJGx1ijYHzTF0/ISU= -github.com/ebitengine/purego v0.10.0/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= github.com/exaring/otelpgx v0.9.3 h1:4yO02tXC7ZJZ+hcqcUkfxblYNCIFGVhpUWI0iw1TzPU= github.com/exaring/otelpgx v0.9.3/go.mod h1:R5/M5LWsPPBZc1SrRE5e0DiU48bI78C1/GPTWs6I66U= github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= -github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= -github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= -github.com/gabriel-vasile/mimetype v1.4.13 h1:46nXokslUBsAJE/wMsp5gtO500a4F3Nkz9Ufpk2AcUM= -github.com/gabriel-vasile/mimetype v1.4.13/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s= github.com/gaissmai/extnetip v0.3.3 h1:0nXgaD0/pylkVxCpxEAk43aOFq8ZqlUgB5KCejju7aE= github.com/gaissmai/extnetip v0.3.3/go.mod h1:M3NWlyFKaVosQXWXKKeIPK+5VM4U85DahdIqNYX4TK4= github.com/getkin/kin-openapi v0.133.0 h1:pJdmNohVIJ97r4AUFtEXRXwESr8b0bD721u/Tz6k8PQ= github.com/getkin/kin-openapi v0.133.0/go.mod h1:boAciF6cXk5FhPqe/NQeBTeenbjqU4LhWBf09ILVvWE= -github.com/gin-contrib/sse v1.1.1 h1:uGYpNwTacv5R68bSGMapo62iLTRa9l5zxGCps4hK6ko= -github.com/gin-contrib/sse v1.1.1/go.mod h1:QXzuVkA0YO7o/gun03UI1Q+FTI8ZV/n5t03kIQAI89s= -github.com/gin-gonic/gin v1.12.0 h1:b3YAbrZtnf8N//yjKeU2+MQsh2mY5htkZidOM7O0wG8= -github.com/gin-gonic/gin v1.12.0/go.mod h1:VxccKfsSllpKshkBWgVgRniFFAzFb9csfngsqANjnLc= github.com/go-faster/city v1.0.1 h1:4WAxSZ3V2Ws4QRDrscLEDcibJY8uf41H6AhXDrNDcGw= github.com/go-faster/city v1.0.1/go.mod h1:jKcUJId49qdW3L1qKHH/3wPeUstCVpVSXTM6vO3VcTw= github.com/go-faster/errors v0.7.1 h1:MkJTnDoEdi9pDabt1dpWf7AA8/BaSYZqibYyhZ20AYg= @@ -111,30 +59,15 @@ github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= -github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= -github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= -github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= github.com/go-openapi/jsonpointer v0.22.4 h1:dZtK82WlNpVLDW2jlA1YCiVJFVqkED1MegOUy9kR5T4= github.com/go-openapi/jsonpointer v0.22.4/go.mod h1:elX9+UgznpFhgBuaMQ7iu4lvvX1nvNsesQ3oxmYTw80= github.com/go-openapi/swag/jsonname v0.25.4 h1:bZH0+MsS03MbnwBXYhuTttMOqk+5KcQ9869Vye1bNHI= github.com/go-openapi/swag/jsonname v0.25.4/go.mod h1:GPVEk9CWVhNvWhZgrnvRA6utbAltopbKwDu8mXNUMag= github.com/go-openapi/testify/v2 v2.0.2 h1:X999g3jeLcoY8qctY/c/Z8iBHTbwLz7R2WXd6Ub6wls= github.com/go-openapi/testify/v2 v2.0.2/go.mod h1:HCPmvFFnheKK2BuwSA0TbbdxJ3I16pjwMkYkP4Ywn54= -github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= -github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= -github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= -github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= -github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= -github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= -github.com/go-playground/validator/v10 v10.30.2 h1:JiFIMtSSHb2/XBUbWM4i/MpeQm9ZK2xqPNk8vgvu5JQ= -github.com/go-playground/validator/v10 v10.30.2/go.mod h1:mAf2pIOVXjTEBrwUMGKkCWKKPs9NheYGabeB04txQSc= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/go-test/deep v1.0.8 h1:TDsG77qcSprGbC6vTN8OuXp5g+J+b5Pcguhf7Zt61VM= github.com/go-test/deep v1.0.8/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= -github.com/goccy/go-json v0.10.6 h1:p8HrPJzOakx/mn/bQtjgNjdTcN+/S6FcG2CTtQOrHVU= -github.com/goccy/go-json v0.10.6/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= -github.com/goccy/go-yaml v1.19.2 h1:PmFC1S6h8ljIz6gMRBopkjP1TVT7xuwrButHID66PoM= -github.com/goccy/go-yaml v1.19.2/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang-jwt/jwt/v5 v5.3.1 h1:kYf81DTWFe7t+1VvL7eS+jKFVWaUnK9cB1qbwn63YCY= @@ -157,10 +90,8 @@ github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= -github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/nftables v0.3.0 h1:bkyZ0cbpVeMHXOrtlFc8ISmfVqq5gPJukoYieyVmITg= github.com/google/nftables v0.3.0/go.mod h1:BCp9FsrbF1Fn/Yu6CLUc9GGZFw/+hsxfluNXXmxBfRM= github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= @@ -168,8 +99,6 @@ github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaU github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= -github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.2 h1:sGm2vDRFUrQJO/Veii4h4zG2vvqG6uWNkBHSTqXOZk0= github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.2/go.mod h1:wd1YpapPLivG6nQgbf7ZkG1hhSOXDhhn4MLTknx2aAc= github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0 h1:HWRh5R2+9EifMyIHV7ZV+MIZqgz+PMpZ14Jynv3O2Zs= @@ -186,20 +115,14 @@ github.com/jackc/pgx/v5 v5.9.2 h1:3ZhOzMWnR4yJ+RW1XImIPsD1aNSz4T4fyP7zlQb56hw= github.com/jackc/pgx/v5 v5.9.2/go.mod h1:mal1tBGAFfLHvZzaYh77YS/eC6IX9OWbRV1QIIM0Jn4= github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo= github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= -github.com/jellydator/ttlcache/v3 v3.4.0 h1:YS4P125qQS0tNhtL6aeYkheEaB/m8HCqdMMP4mnWdTY= -github.com/jellydator/ttlcache/v3 v3.4.0/go.mod h1:Hw9EgjymziQD3yGsQdf1FqFdpp7YjFMd4Srg5EJlgD4= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= -github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= -github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/juju/gnuflag v0.0.0-20171113085948-2ce1bb71843d/go.mod h1:2PavIy+JPciBPrBUjwbNvtwB6RQlve+hkpll6QSNmOE= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= github.com/klauspost/compress v1.18.5 h1:/h1gH5Ce+VWNLSWqPzOVn6XBO+vJbCNGvjoaGBFW2IE= github.com/klauspost/compress v1.18.5/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ= -github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y= -github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= @@ -207,14 +130,8 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= -github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= github.com/lib/pq v1.11.2 h1:x6gxUeu39V0BHZiugWe8LXZYZ+Utk7hSJGThs8sdzfs= github.com/lib/pq v1.11.2/go.mod h1:/p+8NSbOcwzAEI7wiMXFlgydTwcgTr3OSKMsD2BitpA= -github.com/lufia/plan9stats v0.0.0-20240909124753-873cd0166683 h1:7UMa6KCCMjZEMDtTVdcGu0B1GmmC7QJKiCCjyTAWQy0= -github.com/lufia/plan9stats v0.0.0-20240909124753-873cd0166683/go.mod h1:ilwx/Dta8jXAgpFYFvSWEMwxmbWXyiUHkd5FwyKhb5k= -github.com/magiconair/properties v1.8.10 h1:s31yESBquKXCV9a/ScB3ESkOjUYYv+X0rg8SYxI99mE= -github.com/magiconair/properties v1.8.10/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= github.com/mailru/easyjson v0.9.1 h1:LbtsOm5WAswyWbvTEOqhypdPeZzHavpZx96/n553mR8= github.com/mailru/easyjson v0.9.1/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= @@ -230,30 +147,6 @@ github.com/mdlayher/socket v0.5.1 h1:VZaqt6RkGkt2OE9l3GcC6nZkqD3xKeQLyfleW/uBcos github.com/mdlayher/socket v0.5.1/go.mod h1:TjPLHI1UgwEv5J1B5q0zTZq12A/6H7nKmtTanQE37IQ= github.com/mfridman/interpolate v0.0.2 h1:pnuTK7MQIxxFz1Gr+rjSIx9u7qVjf5VOoM/u6BbAxPY= github.com/mfridman/interpolate v0.0.2/go.mod h1:p+7uk6oE07mpE/Ik1b8EckO0O4ZXiGAfshKBWLUM9Xg= -github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= -github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= -github.com/moby/go-archive v0.2.0 h1:zg5QDUM2mi0JIM9fdQZWC7U8+2ZfixfTYoHL7rWUcP8= -github.com/moby/go-archive v0.2.0/go.mod h1:mNeivT14o8xU+5q1YnNrkQVpK+dnNe/K6fHqnTg4qPU= -github.com/moby/moby/api v1.54.1 h1:TqVzuJkOLsgLDDwNLmYqACUuTehOHRGKiPhvH8V3Nn4= -github.com/moby/moby/api v1.54.1/go.mod h1:+RQ6wluLwtYaTd1WnPLykIDPekkuyD/ROWQClE83pzs= -github.com/moby/moby/client v0.4.0 h1:S+2XegzHQrrvTCvF6s5HFzcrywWQmuVnhOXe2kiWjIw= -github.com/moby/moby/client v0.4.0/go.mod h1:QWPbvWchQbxBNdaLSpoKpCdf5E+WxFAgNHogCWDoa7g= -github.com/moby/patternmatcher v0.6.1 h1:qlhtafmr6kgMIJjKJMDmMWq7WLkKIo23hsrpR3x084U= -github.com/moby/patternmatcher v0.6.1/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc= -github.com/moby/sys/sequential v0.6.0 h1:qrx7XFUd/5DxtqcoH1h438hF5TmOvzC/lspjy7zgvCU= -github.com/moby/sys/sequential v0.6.0/go.mod h1:uyv8EUTrca5PnDsdMGXhZe6CCe8U/UiTWd+lL+7b/Ko= -github.com/moby/sys/user v0.4.0 h1:jhcMKit7SA80hivmFJcbB1vqmw//wU61Zdui2eQXuMs= -github.com/moby/sys/user v0.4.0/go.mod h1:bG+tYYYJgaMtRKgEmuueC0hJEAZWwtIbZTB+85uoHjs= -github.com/moby/sys/userns v0.1.0 h1:tVLXkFOxVu9A64/yh59slHVv9ahO9UIev4JZusOLG/g= -github.com/moby/sys/userns v0.1.0/go.mod h1:IHUYgu/kao6N8YZlp9Cf444ySSvCmDlmzUcYfDHOl28= -github.com/moby/term v0.5.2 h1:6qk3FJAFDs6i/q3W/pQ97SX192qKfZgGjCQqfCJkgzQ= -github.com/moby/term v0.5.2/go.mod h1:d3djjFCrjnB+fl8NJux+EJzu0msscUP+f8it8hPkFLc= -github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -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/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= -github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8= -github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw= github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc= @@ -264,8 +157,6 @@ github.com/ngrok/firewall_toolkit v0.0.18/go.mod h1:g1yp6uBx0r6A6+lICpZk4PEUCOlB github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= -github.com/oapi-codegen/gin-middleware v1.0.2 h1:/H99UzvHQAUxXK8pzdcGAZgjCVeXdFDAUUWaJT0k0eI= -github.com/oapi-codegen/gin-middleware v1.0.2/go.mod h1:2HJDQjH8jzK2/k/VKcWl+/T41H7ai2bKa6dN3AA2GpA= github.com/oapi-codegen/oapi-codegen/v2 v2.6.0 h1:4i+F2cvwBFZeplxCssNdLy3MhNzUD87mI3HnayHZkAU= github.com/oapi-codegen/oapi-codegen/v2 v2.6.0/go.mod h1:eWHeJSohQJIINJZzzQriVynfGsnlQVh0UkN2UYYcw4Q= github.com/oapi-codegen/runtime v1.4.0 h1:KLOSFOp7UzkbS7Cs1ms6NBEKYr0WmH2wZG0KKbd2er4= @@ -288,17 +179,11 @@ github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAl github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro= github.com/onsi/gomega v1.38.1 h1:FaLA8GlcpXDwsb7m0h2A9ew2aTk3vnZMlzFgg5tz/pk= github.com/onsi/gomega v1.38.1/go.mod h1:LfcV8wZLvwcYRwPiJysphKAEsmcFnLMK/9c+PjvlX8g= -github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= -github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= -github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040= -github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M= github.com/orcaman/concurrent-map/v2 v2.0.1 h1:jOJ5Pg2w1oeB6PeDurIYf6k9PQ+aTITr/6lP/L/zp6c= github.com/orcaman/concurrent-map/v2 v2.0.1/go.mod h1:9Eq3TG2oBe5FirmYWQfYO5iH1q0Jv47PLaNK++uCdOM= github.com/paulmach/orb v0.11.1 h1:3koVegMC4X/WeiXYz9iswopaTwMem53NzTJuTF20JzU= github.com/paulmach/orb v0.11.1/go.mod h1:5mULz1xQfs3bmQm63QEJA6lNGujuRafwA5S/EnuLaLU= github.com/paulmach/protoscan v0.2.1/go.mod h1:SpcSwydNLrxUGSDvXvO0P7g7AuhJ7lcKfDlhJCDw2gY= -github.com/pelletier/go-toml/v2 v2.3.0 h1:k59bC/lIZREW0/iVaQR8nDHxVq8OVlIzYCOJf421CaM= -github.com/pelletier/go-toml/v2 v2.3.0/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= github.com/perimeterx/marshmallow v1.1.5 h1:a2LALqQ1BlHM8PZblsDdidgv1mWi1DgC2UmX50IvK2s= github.com/perimeterx/marshmallow v1.1.5/go.mod h1:dsXbUu8CRzfYP5a87xpp0xq9S3u0Vchtcl8we9tYaXw= github.com/pierrec/lz4/v4 v4.1.22 h1:cKFw6uJDK+/gfw5BcDL0JL5aBsAFdsIT18eRtLj7VIU= @@ -307,16 +192,8 @@ github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU= -github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= github.com/pressly/goose/v3 v3.26.0 h1:KJakav68jdH0WDvoAcj8+n61WqOIaPGgH0bJWS6jpmM= github.com/pressly/goose/v3 v3.26.0/go.mod h1:4hC1KrritdCxtuFsqgs1R4AU5bWtTAf+cnWvfhf2DNY= -github.com/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8= -github.com/quic-go/qpack v0.6.0/go.mod h1:lUpLKChi8njB4ty2bFLX2x4gzDqXwUpaO1DP9qMDZII= -github.com/quic-go/quic-go v0.59.0 h1:OLJkp1Mlm/aS7dpKgTc6cnpynnD2Xg7C1pwL6vy/SAw= -github.com/quic-go/quic-go v0.59.0/go.mod h1:upnsH4Ju1YkqpLXC305eW3yDZ4NfnNbmQRCMWS58IKU= -github.com/redis/go-redis/v9 v9.17.3 h1:fN29NdNrE17KttK5Ndf20buqfDZwGNgoUr9qjl1DQx4= -github.com/redis/go-redis/v9 v9.17.3/go.mod h1:u410H11HMLoB+TP67dz8rL9s6QW2j76l0//kSOd3370= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= @@ -331,13 +208,9 @@ github.com/sergi/go-diff v1.4.0 h1:n/SP9D5ad1fORl+llWyN+D6qoUETXNZARKjyY2/KVCw= github.com/sergi/go-diff v1.4.0/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= github.com/sethvargo/go-retry v0.3.0 h1:EEt31A35QhrcRZtrYFDTBg91cqZVnFL2navjDrah2SE= github.com/sethvargo/go-retry v0.3.0/go.mod h1:mNX17F0C/HguQMyMyJxcnU471gOZGxCLyYaFyAZraas= -github.com/shirou/gopsutil/v4 v4.26.3 h1:2ESdQt90yU3oXF/CdOlRCJxrP+Am1aBYubTMTfxJ1qc= -github.com/shirou/gopsutil/v4 v4.26.3/go.mod h1:LZ6ewCSkBqUpvSOf+LsTGnRinC6iaNUNMGBtDkJBaLQ= github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k= github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME= github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= -github.com/sirupsen/logrus v1.9.4 h1:TsZE7l11zFCLZnZ+teH4Umoq5BhEIfIzfRDZ1Uzql2w= -github.com/sirupsen/logrus v1.9.4/go.mod h1:ftWc9WdOfJ0a92nsE2jF5u5ZwH8Bv2zdeOC42RjbV2g= github.com/speakeasy-api/jsonpath v0.6.0 h1:IhtFOV9EbXplhyRqsVhHoBmmYjblIRh5D1/g8DHMXJ8= github.com/speakeasy-api/jsonpath v0.6.0/go.mod h1:ymb2iSkyOycmzKwbEAYPJV/yi2rSmvBCLZJcyD+VVWw= github.com/speakeasy-api/openapi-overlay v0.10.2 h1:VOdQ03eGKeiHnpb1boZCGm7x8Haj6gST0P3SGTX95GU= @@ -346,7 +219,6 @@ github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad/go.mod h1:qLr4V1qq6nMqFKk github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= -github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/objx v0.5.3 h1:jmXUvGomnU1o3W/V5h2VEradbpJDwGrzugQQvL0POH4= github.com/stretchr/objx v0.5.3/go.mod h1:rDQraq+vQZU7Fde9LOZLr8Tax6zZvy4kuNKF+QYS+U0= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= @@ -358,19 +230,9 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= -github.com/testcontainers/testcontainers-go v0.42.0 h1:He3IhTzTZOygSXLJPMX7n44XtK+qhjat1nI9cneBbUY= -github.com/testcontainers/testcontainers-go v0.42.0/go.mod h1:vZjdY1YmUA1qEForxOIOazfsrdyORJAbhi0bp8plN30= github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= -github.com/tklauser/go-sysconf v0.3.16 h1:frioLaCQSsF5Cy1jgRBrzr6t502KIIwQ0MArYICU0nA= -github.com/tklauser/go-sysconf v0.3.16/go.mod h1:/qNL9xxDhc7tx3HSRsLWNnuzbVfh3e7gh/BmM179nYI= -github.com/tklauser/numcpus v0.11.0 h1:nSTwhKH5e1dMNsCdVBukSZrURJRoHbSEQjdEbY+9RXw= -github.com/tklauser/numcpus v0.11.0/go.mod h1:z+LwcLq54uWZTX0u/bGobaV34u6V7KNlTZejzM6/3MQ= -github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= -github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= github.com/ugorji/go/codec v1.3.1 h1:waO7eEiFDwidsBN6agj1vJQ4AG7lh2yqXyOXqhgQuyY= github.com/ugorji/go/codec v1.3.1/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4= github.com/vishvananda/netns v0.0.5 h1:DfiHV+j8bA32MFM7bfEunvT8IAqQ/NzSJHtcmW5zdEY= @@ -388,19 +250,13 @@ github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7Jul github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= -github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= -github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= go.mongodb.org/mongo-driver v1.11.4/go.mod h1:PTSz5yu21bkT/wXpkS7WR5f0ddqw5quethTUn9WM+2g= -go.mongodb.org/mongo-driver/v2 v2.5.0 h1:yXUhImUjjAInNcpTcAlPHiT7bIXhshCTL3jVBkF3xaE= -go.mongodb.org/mongo-driver/v2 v2.5.0/go.mod h1:yOI9kBsufol30iFsl1slpdq1I0eHPzybRWdyYUs8K/0= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= go.opentelemetry.io/contrib/bridges/otelzap v0.14.0 h1:2nKw2ZXZOC0N8RBsBbYwGwfKR7kJWzzyCZ6QfUGW/es= go.opentelemetry.io/contrib/bridges/otelzap v0.14.0/go.mod h1:kvyVt0WEI5BB6XaIStXPIkCSQ2nSkyd8IZnAHLEXge4= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.65.0 h1:XmiuHzgJt067+a6kwyAzkhXooYVv3/TOw9cM2VfJgUM= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.65.0/go.mod h1:KDgtbWKTQs4bM+VPUr6WlL9m/WXcmkCcBlIzqxPGzmI= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.64.0 h1:ssfIgGNANqpVFCndZvcuyKbl0g+UAVcbBcqGkG28H0Y= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.64.0/go.mod h1:GQ/474YrbE4Jx8gZ4q5I4hrhUzM6UPzyrqJYV2AqPoQ= go.opentelemetry.io/contrib/instrumentation/runtime v0.66.0 h1:JruBNmrPELWjR+PU3fsQBFQRYtsMLQ/zPfbvwDz9I/w= go.opentelemetry.io/contrib/instrumentation/runtime v0.66.0/go.mod h1:vwNrfL6w1uAE3qX48KFii2Qoqf+NEDP5wNjus+RHz8Y= go.opentelemetry.io/otel v1.43.0 h1:mYIM03dnh5zfN7HautFE4ieIig9amkNANT+xcVxAj9I= @@ -433,22 +289,16 @@ go.opentelemetry.io/proto/otlp v1.10.0 h1:IQRWgT5srOCYfiWnpqUYz9CVmbO8bFmKcwYxpu go.opentelemetry.io/proto/otlp v1.10.0/go.mod h1:/CV4QoCR/S9yaPj8utp3lvQPoqMtxXdzn7ozvvozVqk= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= -go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y= -go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc= go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= -golang.org/x/arch v0.25.0 h1:qnk6Ksugpi5Bz32947rkUgDt9/s5qvqDPl/gBKdMJLE= -golang.org/x/arch v0.25.0/go.mod h1:0X+GdSIP+kL5wPmpK7sdkEVTt2XoYP0cSjQSbZBwOi8= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.50.0 h1:zO47/JPrL6vsNkINmLoo/PH1gcxpls50DNogFvB5ZGI= -golang.org/x/crypto v0.50.0/go.mod h1:3muZ7vA7PBCE6xgPX7nkzzjiUq87kRItoJQM1Yo8S+Q= golang.org/x/exp v0.0.0-20260212183809-81e46e3db34a h1:ovFr6Z0MNmU7nH8VaX5xqw+05ST2uO1exVfZPVqRC5o= golang.org/x/exp v0.0.0-20260212183809-81e46e3db34a/go.mod h1:K79w1Vqn7PoiZn+TkNpx3BUWUQksGO3JcVX6qIjytmA= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= @@ -479,7 +329,6 @@ golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -487,17 +336,14 @@ golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI= @@ -573,5 +419,3 @@ modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI= modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw= modernc.org/sqlite v1.38.2 h1:Aclu7+tgjgcQVShZqim41Bbw9Cho0y/7WzYptXqkEek= modernc.org/sqlite v1.38.2/go.mod h1:cPTJYSlgg3Sfg046yBShXENNtPrWrDX8bsbAQBzgQ5E= -pgregory.net/rapid v1.2.0 h1:keKAYRcjm+e1F0oAuU5F5+YPAWcyxNNRK2wud503Gnk= -pgregory.net/rapid v1.2.0/go.mod h1:PY5XlDGj0+V1FCq0o192FdRhpKHGTRIWBgqjDBTrq04= diff --git a/tests/integration/internal/api/generated.go b/tests/integration/internal/api/generated.go index b43f96ac59..063a80418b 100644 --- a/tests/integration/internal/api/generated.go +++ b/tests/integration/internal/api/generated.go @@ -20,11 +20,13 @@ import ( ) const ( - AccessTokenAuthScopes = "AccessTokenAuth.Scopes" - AdminTokenAuthScopes = "AdminTokenAuth.Scopes" - ApiKeyAuthScopes = "ApiKeyAuth.Scopes" - Supabase1TokenAuthScopes = "Supabase1TokenAuth.Scopes" - Supabase2TeamAuthScopes = "Supabase2TeamAuth.Scopes" + AccessTokenAuthScopes = "AccessTokenAuth.Scopes" + AdminTokenAuthScopes = "AdminTokenAuth.Scopes" + ApiKeyAuthScopes = "ApiKeyAuth.Scopes" + AuthProviderTeamAuthScopes = "AuthProviderTeamAuth.Scopes" + AuthProviderTokenAuthScopes = "AuthProviderTokenAuth.Scopes" + Supabase1TokenAuthScopes = "Supabase1TokenAuth.Scopes" + Supabase2TeamAuthScopes = "Supabase2TeamAuth.Scopes" ) // Defines values for AWSRegistryType.