From 166baa9a982e678d66391d6b41c76597d4b40c95 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CNaeel=E2=80=9D?= Date: Fri, 14 Aug 2026 08:05:16 +0400 Subject: [PATCH] =?UTF-8?q?fix:=20=D0=B2=D0=B0=D0=BB=D0=B8=D0=B4=D0=B0?= =?UTF-8?q?=D1=86=D0=B8=D1=8F=20JWT=20=D0=BF=D0=B5=D1=80=D0=B5=D0=B1=D0=BE?= =?UTF-8?q?=D1=80=D0=BE=D0=BC=20=D0=B2=D1=81=D0=B5=D1=85=20=D1=81=D1=82?= =?UTF-8?q?=D0=B5=D0=BD=D0=B4=D0=BE=D0=B2=20gateway=20(=D1=82=D0=BE=D0=BA?= =?UTF-8?q?=D0=B5=D0=BD=20=D0=BB=D1=8E=D0=B1=D0=BE=D0=B3=D0=BE=20=D1=81?= =?UTF-8?q?=D1=82=D0=B5=D0=BD=D0=B4=D0=B0=20=D0=BF=D1=80=D0=B8=D0=BD=D0=B8?= =?UTF-8?q?=D0=BC=D0=B0=D0=B5=D1=82=D1=81=D1=8F)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Dockerfile | 3 +-- app/admin/admin.go | 4 +-- app/auth/jwt.go | 63 +++++++++++++++++++++++++++++++++------------- 3 files changed, 48 insertions(+), 22 deletions(-) diff --git a/Dockerfile b/Dockerfile index 2086bfe..20b08c3 100644 --- a/Dockerfile +++ b/Dockerfile @@ -16,8 +16,7 @@ EXPOSE 4100 # Нечувствительные значения зашиты в образ. Секреты (SHARED_SQS_ADMIN_TOKEN, REDIS_PASSWORD) — env'ом контейнера в deck. ENV REDIS_ADDR=redisk8s.4ff5678b-b683-4aef-91da-22d89dec8a25.svc.cluster.local:6379 \ REDIS_USER=default \ - SHARED_SQS_SEED_DEMO=false \ - NUBES_ENDPOINT=https://lk-api-gateway.ngcloud.ru/api/v1/svc + SHARED_SQS_SEED_DEMO=false HEALTHCHECK --interval=10s --timeout=5s --retries=3 \ CMD wget -q -O - http://localhost:4100/health || exit 1 ENTRYPOINT ["shared-sqs", "--config", "/conf/goaws.yaml"] diff --git a/app/admin/admin.go b/app/admin/admin.go index b34b3c1..ca9e5fb 100644 --- a/app/admin/admin.go +++ b/app/admin/admin.go @@ -64,9 +64,7 @@ type Handler struct { // NewHandler — создаёт admin handler func NewHandler(store *tenant.TenantStore, adminToken string) *Handler { nubesEndpoint := os.Getenv("NUBES_ENDPOINT") - if nubesEndpoint == "" { - nubesEndpoint = "https://lk-api-gateway.ngcloud.ru/api/v1/svc" - } + // Если не задан — пустой: PingNubesAPI переберёт все стенды сам. return &Handler{store: store, adminToken: adminToken, nubesEndpoint: nubesEndpoint} } diff --git a/app/auth/jwt.go b/app/auth/jwt.go index f4bca86..7089e24 100644 --- a/app/auth/jwt.go +++ b/app/auth/jwt.go @@ -74,27 +74,56 @@ func TenantIDFromSub(sub string) string { return fmt.Sprintf("sless-%x", hash[:8]) } -// PingNubesAPI — проверяет валидность токена запросом к nubes API. -// endpoint — базовый URL (например "https://deck-api-test.ngcloud.ru/api/v1"). -// Логика: 401/403 → токен отклонён. Ошибка соединения → API недоступен. +// nubesAPIEndpoints — стенды Nubes API Gateway, по которым валидируется токен. +// Токен принимается, если ХОТЯ БЫ ОДИН стенд его принял. +// Юзер может вводить токен от любого стенда — главное, что токен действительный. +var nubesAPIEndpoints = []string{ + "https://lk-api-gateway.ngcloud.ru/api/v1/svc", + "https://lk-api-gateway-test.ngcloud.ru/api/v1/svc", + "https://lk-api-gateway-dev.ngcloud.ru/api/v1/svc", +} + +// PingNubesAPI — проверяет валидность токена перебором стендов Nubes API. +// endpoint (если задан через NUBES_ENDPOINT) пробуется первым. +// Логика: 401/403 → токен отклонён ЭТИМ стендом, пробуем следующий. // Любой другой HTTP статус → токен принят. func PingNubesAPI(ctx context.Context, endpoint, token string) error { - req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint, nil) - if err != nil { - return fmt.Errorf("build nubes ping request: %w", err) + candidates := make([]string, 0, len(nubesAPIEndpoints)+1) + if endpoint != "" { + candidates = append(candidates, endpoint) + } + for _, ep := range nubesAPIEndpoints { + if ep != endpoint { + candidates = append(candidates, ep) + } } - req.Header.Set("Authorization", "Bearer "+token) - req.Header.Set("User-Agent", "Mozilla/5.0") - client := &http.Client{Timeout: 10 * time.Second} - resp, err := client.Do(req) - if err != nil { - return fmt.Errorf("nubes API unreachable at %s: %w", endpoint, err) - } - defer resp.Body.Close() + var lastErr error + for _, ep := range candidates { + req, err := http.NewRequestWithContext(ctx, http.MethodGet, ep, nil) + if err != nil { + lastErr = fmt.Errorf("build nubes ping request: %w", err) + continue + } + req.Header.Set("Authorization", "Bearer "+token) + req.Header.Set("User-Agent", "Mozilla/5.0") - if resp.StatusCode == http.StatusUnauthorized || resp.StatusCode == http.StatusForbidden { - return fmt.Errorf("nubes API rejected token (HTTP %d)", resp.StatusCode) + client := &http.Client{Timeout: 10 * time.Second} + resp, err := client.Do(req) + if err != nil { + lastErr = fmt.Errorf("nubes API unreachable at %s: %w", ep, err) + continue + } + resp.Body.Close() + + if resp.StatusCode == http.StatusUnauthorized || resp.StatusCode == http.StatusForbidden { + lastErr = fmt.Errorf("nubes API rejected token (HTTP %d)", resp.StatusCode) + continue + } + return nil } - return nil + if lastErr == nil { + lastErr = fmt.Errorf("nubes API rejected token") + } + return lastErr }