From bc1ff2b62f6f09aad890f1fc035522d6deaee082 Mon Sep 17 00:00:00 2001 From: chengcheng Date: Tue, 21 Jul 2026 10:57:51 +0800 Subject: [PATCH] =?UTF-8?q?fix(auth):=20=E4=B8=BA=E7=99=BB=E5=BD=95?= =?UTF-8?q?=E9=93=BE=E8=B7=AF=E5=A2=9E=E5=8A=A0=E6=9C=89=E7=95=8C=E8=B6=85?= =?UTF-8?q?=E6=97=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 为 PostgreSQL 连接、就绪检查和本地登录设置分层超时,数据库不可用时返回稳定 503 错误码并记录无凭据的连接池统计。 前端登录在 10 秒后取消请求,Nginx 登录精确路由限制上游为 15 秒,同时更新 OpenAPI 和回归测试。 补充开发工具链安全 override,使高危依赖审计保持为零。 --- apps/api/docs/swagger.json | 6 + apps/api/docs/swagger.yaml | 4 + .../availability_timeout_integration_test.go | 111 ++++++++++++++++++ apps/api/internal/httpapi/handlers.go | 41 ++++++- apps/api/internal/store/postgres.go | 44 ++++++- .../internal/store/postgres_config_test.go | 49 ++++++++ apps/web/src/api.test.ts | 27 +++++ apps/web/src/api.ts | 41 ++++--- docker/nginx.conf | 14 +++ pnpm-lock.yaml | 66 +++++++---- pnpm-workspace.yaml | 5 +- 11 files changed, 366 insertions(+), 42 deletions(-) create mode 100644 apps/api/internal/httpapi/availability_timeout_integration_test.go create mode 100644 apps/api/internal/store/postgres_config_test.go diff --git a/apps/api/docs/swagger.json b/apps/api/docs/swagger.json index 0c4f779..93c7f7a 100644 --- a/apps/api/docs/swagger.json +++ b/apps/api/docs/swagger.json @@ -5054,6 +5054,12 @@ "schema": { "$ref": "#/definitions/httpapi.ErrorEnvelope" } + }, + "503": { + "description": "Service Unavailable", + "schema": { + "$ref": "#/definitions/httpapi.ErrorEnvelope" + } } } } diff --git a/apps/api/docs/swagger.yaml b/apps/api/docs/swagger.yaml index 93e7f6f..4d39e62 100644 --- a/apps/api/docs/swagger.yaml +++ b/apps/api/docs/swagger.yaml @@ -6219,6 +6219,10 @@ paths: description: Internal Server Error schema: $ref: '#/definitions/httpapi.ErrorEnvelope' + "503": + description: Service Unavailable + schema: + $ref: '#/definitions/httpapi.ErrorEnvelope' summary: 本地登录 tags: - auth diff --git a/apps/api/internal/httpapi/availability_timeout_integration_test.go b/apps/api/internal/httpapi/availability_timeout_integration_test.go new file mode 100644 index 0000000..964627c --- /dev/null +++ b/apps/api/internal/httpapi/availability_timeout_integration_test.go @@ -0,0 +1,111 @@ +package httpapi + +import ( + "bytes" + "context" + "encoding/json" + "io" + "log/slog" + "net/http" + "net/http/httptest" + "net/url" + "os" + "strings" + "testing" + "time" + + "github.com/easyai/easyai-ai-gateway/apps/api/internal/store" +) + +func TestReadyReturnsPostgresUnavailableWithinTwoSeconds(t *testing.T) { + db := newExhaustedPostgresStore(t) + server := &Server{store: db, logger: slog.New(slog.NewJSONHandler(io.Discard, nil))} + requestContext, cancel := context.WithTimeout(context.Background(), 4*time.Second) + defer cancel() + request := httptest.NewRequest(http.MethodGet, "/readyz", nil).WithContext(requestContext) + recorder := httptest.NewRecorder() + + startedAt := time.Now() + server.ready(recorder, request) + elapsed := time.Since(startedAt) + + assertUnavailableResponse(t, recorder, "POSTGRES_UNAVAILABLE", "postgres unavailable") + if elapsed > 3*time.Second { + t.Fatalf("readiness timeout took %s, want no more than 3s", elapsed) + } +} + +func TestLoginReturnsAuthStoreUnavailableWithinFiveSeconds(t *testing.T) { + db := newExhaustedPostgresStore(t) + var logs bytes.Buffer + server := &Server{store: db, logger: slog.New(slog.NewJSONHandler(&logs, nil))} + requestContext, cancel := context.WithTimeout(context.Background(), 7*time.Second) + defer cancel() + request := httptest.NewRequest(http.MethodPost, "/api/v1/auth/login", strings.NewReader(`{"account":"timeout-test-account","password":"timeout-test-password"}`)).WithContext(requestContext) + recorder := httptest.NewRecorder() + + startedAt := time.Now() + server.login(recorder, request) + elapsed := time.Since(startedAt) + + assertUnavailableResponse(t, recorder, "AUTH_STORE_UNAVAILABLE", "authentication service temporarily unavailable") + if elapsed > 6*time.Second { + t.Fatalf("login timeout took %s, want no more than 6s", elapsed) + } + logOutput := logs.String() + for _, field := range []string{"postgres_pool_max_connections", "postgres_pool_acquired_connections", "postgres_pool_idle_connections", "postgres_pool_empty_acquire_count", "postgres_pool_canceled_acquire_count"} { + if !strings.Contains(logOutput, field) { + t.Fatalf("login failure log did not include %q: %s", field, logOutput) + } + } + if strings.Contains(logOutput, "timeout-test-account") || strings.Contains(logOutput, "timeout-test-password") { + t.Fatalf("login failure log exposed credentials: %s", logOutput) + } +} + +func newExhaustedPostgresStore(t *testing.T) *store.Store { + t.Helper() + databaseURL := strings.TrimSpace(os.Getenv("AI_GATEWAY_TEST_DATABASE_URL")) + if databaseURL == "" { + t.Skip("set AI_GATEWAY_TEST_DATABASE_URL to run PostgreSQL availability timeout tests") + } + parsed, err := url.Parse(databaseURL) + if err != nil { + t.Fatalf("parse test database URL: %v", err) + } + query := parsed.Query() + query.Set("pool_max_conns", "1") + query.Set("pool_min_conns", "0") + parsed.RawQuery = query.Encode() + + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + db, err := store.Connect(ctx, parsed.String()) + if err != nil { + t.Fatalf("connect timeout test store: %v", err) + } + t.Cleanup(db.Close) + connection, err := db.Pool().Acquire(ctx) + if err != nil { + t.Fatalf("exhaust timeout test pool: %v", err) + } + t.Cleanup(connection.Release) + return db +} + +func assertUnavailableResponse(t *testing.T, recorder *httptest.ResponseRecorder, expectedCode, expectedMessage string) { + t.Helper() + if recorder.Code != http.StatusServiceUnavailable { + t.Fatalf("status = %d, want 503; body=%s", recorder.Code, recorder.Body.String()) + } + var envelope ErrorEnvelope + if err := json.Unmarshal(recorder.Body.Bytes(), &envelope); err != nil { + t.Fatalf("decode unavailable response: %v", err) + } + if envelope.Error.Code != expectedCode { + t.Fatalf("error code = %q, want %q; body=%s", envelope.Error.Code, expectedCode, recorder.Body.String()) + } + if envelope.Error.Message != expectedMessage { + t.Fatalf("error message = %q, want %q; body=%s", envelope.Error.Message, expectedMessage, recorder.Body.String()) + } +} diff --git a/apps/api/internal/httpapi/handlers.go b/apps/api/internal/httpapi/handlers.go index 82bb8cf..da7e91f 100644 --- a/apps/api/internal/httpapi/handlers.go +++ b/apps/api/internal/httpapi/handlers.go @@ -17,6 +17,14 @@ import ( "github.com/easyai/easyai-ai-gateway/apps/api/internal/store" ) +const ( + postgresReadinessTimeout = 2 * time.Second + localLoginStoreTimeout = 5 * time.Second + errorCodePostgresDown = "POSTGRES_UNAVAILABLE" + errorCodeAuthStoreDown = "AUTH_STORE_UNAVAILABLE" + authStoreUnavailableError = "authentication service temporarily unavailable" +) + // health godoc // @Summary 健康检查 // @Description 返回服务进程、运行环境和身份模式,供负载均衡或人工排障使用。 @@ -42,8 +50,11 @@ func (s *Server) health(w http.ResponseWriter, r *http.Request) { // @Failure 503 {object} ErrorEnvelope // @Router /readyz [get] func (s *Server) ready(w http.ResponseWriter, r *http.Request) { - if err := s.store.Ping(r.Context()); err != nil { - writeError(w, http.StatusServiceUnavailable, "postgres unavailable") + ctx, cancel := context.WithTimeout(r.Context(), postgresReadinessTimeout) + defer cancel() + if err := s.store.Ping(ctx); err != nil { + s.logPostgresUnavailable("postgres readiness check failed") + writeError(w, http.StatusServiceUnavailable, "postgres unavailable", errorCodePostgresDown) return } writeJSON(w, http.StatusOK, map[string]any{"ok": true}) @@ -121,6 +132,7 @@ func (s *Server) register(w http.ResponseWriter, r *http.Request) { // @Failure 401 {object} ErrorEnvelope // @Failure 403 {object} ErrorEnvelope // @Failure 500 {object} ErrorEnvelope +// @Failure 503 {object} ErrorEnvelope // @Router /api/v1/auth/login [post] func (s *Server) login(w http.ResponseWriter, r *http.Request) { var input store.LocalLoginInput @@ -128,12 +140,19 @@ func (s *Server) login(w http.ResponseWriter, r *http.Request) { writeError(w, http.StatusBadRequest, "invalid json body") return } - user, err := s.store.AuthenticateLocalUser(r.Context(), input) + ctx, cancel := context.WithTimeout(r.Context(), localLoginStoreTimeout) + defer cancel() + user, err := s.store.AuthenticateLocalUser(ctx, input) if err != nil { if errors.Is(err, store.ErrInvalidCredentials) { writeError(w, http.StatusUnauthorized, "invalid account or password") return } + if store.IsPostgresUnavailable(err) { + s.logPostgresUnavailable("login authentication store unavailable") + writeError(w, http.StatusServiceUnavailable, authStoreUnavailableError, errorCodeAuthStoreDown) + return + } s.logger.Error("login local user failed", "error", err) writeError(w, http.StatusInternalServerError, "login failed") return @@ -145,6 +164,22 @@ func (s *Server) login(w http.ResponseWriter, r *http.Request) { s.writeAuthResponse(w, http.StatusOK, user) } +func (s *Server) logPostgresUnavailable(message string) { + if s.logger == nil || s.store == nil || s.store.Pool() == nil { + return + } + statistics := s.store.Pool().Stat() + s.logger.Error(message, + "error_category", "postgres_unavailable", + "postgres_pool_max_connections", statistics.MaxConns(), + "postgres_pool_total_connections", statistics.TotalConns(), + "postgres_pool_acquired_connections", statistics.AcquiredConns(), + "postgres_pool_idle_connections", statistics.IdleConns(), + "postgres_pool_empty_acquire_count", statistics.EmptyAcquireCount(), + "postgres_pool_canceled_acquire_count", statistics.CanceledAcquireCount(), + ) +} + func (s *Server) localIdentityEnabled() bool { mode := strings.ToLower(strings.TrimSpace(s.cfg.IdentityMode)) return mode == "" || mode == "standalone" || mode == "hybrid" diff --git a/apps/api/internal/store/postgres.go b/apps/api/internal/store/postgres.go index 444cba4..231d95c 100644 --- a/apps/api/internal/store/postgres.go +++ b/apps/api/internal/store/postgres.go @@ -7,6 +7,7 @@ import ( "encoding/base64" "encoding/json" "errors" + "net" "strings" "time" "unicode" @@ -22,6 +23,11 @@ type Store struct { pool *pgxpool.Pool } +const ( + postgresApplicationName = "easyai-ai-gateway" + postgresConnectTimeout = 5 * time.Second +) + func defaultAPIKeyScopes() []string { return []string{"chat", "embedding", "rerank", "image", "video", "music", "audio", "voice_clone"} } @@ -60,7 +66,11 @@ var ( ) func Connect(ctx context.Context, databaseURL string) (*Store, error) { - pool, err := pgxpool.New(ctx, databaseURL) + config, err := postgresPoolConfig(databaseURL) + if err != nil { + return nil, err + } + pool, err := pgxpool.NewWithConfig(ctx, config) if err != nil { return nil, err } @@ -71,6 +81,38 @@ func Connect(ctx context.Context, databaseURL string) (*Store, error) { return &Store{pool: pool}, nil } +func postgresPoolConfig(databaseURL string) (*pgxpool.Config, error) { + config, err := pgxpool.ParseConfig(databaseURL) + if err != nil { + return nil, err + } + config.ConnConfig.ConnectTimeout = postgresConnectTimeout + config.ConnConfig.RuntimeParams["application_name"] = postgresApplicationName + return config, nil +} + +func IsPostgresUnavailable(err error) bool { + if err == nil { + return false + } + if errors.Is(err, context.DeadlineExceeded) { + return true + } + var connectError *pgconn.ConnectError + if errors.As(err, &connectError) { + return true + } + var networkError net.Error + if errors.As(err, &networkError) { + return true + } + var postgresError *pgconn.PgError + if errors.As(err, &postgresError) { + return strings.HasPrefix(postgresError.Code, "08") || postgresError.Code == "53300" || strings.HasPrefix(postgresError.Code, "57P0") + } + return pgconn.SafeToRetry(err) +} + func (s *Store) Close() { s.pool.Close() } diff --git a/apps/api/internal/store/postgres_config_test.go b/apps/api/internal/store/postgres_config_test.go new file mode 100644 index 0000000..622e055 --- /dev/null +++ b/apps/api/internal/store/postgres_config_test.go @@ -0,0 +1,49 @@ +package store + +import ( + "context" + "testing" + "time" + + "github.com/jackc/pgx/v5/pgconn" +) + +func TestPostgresPoolConfigSetsDiagnosticAndConnectTimeout(t *testing.T) { + config, err := postgresPoolConfig("postgresql://gateway:password@localhost:5432/gateway?sslmode=disable") + if err != nil { + t.Fatalf("parse PostgreSQL pool config: %v", err) + } + if config.ConnConfig.ConnectTimeout != 5*time.Second { + t.Fatalf("connect timeout = %s, want 5s", config.ConnConfig.ConnectTimeout) + } + if applicationName := config.ConnConfig.RuntimeParams["application_name"]; applicationName != "easyai-ai-gateway" { + t.Fatalf("application_name = %q, want easyai-ai-gateway", applicationName) + } +} + +func TestPostgresPoolConfigRejectsMalformedURL(t *testing.T) { + if _, err := postgresPoolConfig("://malformed"); err == nil { + t.Fatal("expected malformed PostgreSQL URL to fail") + } +} + +func TestIsPostgresUnavailableClassifiesConnectivityFailures(t *testing.T) { + for _, testCase := range []struct { + name string + err error + }{ + {name: "deadline", err: context.DeadlineExceeded}, + {name: "connection exception", err: &pgconn.PgError{Code: "08006"}}, + {name: "too many connections", err: &pgconn.PgError{Code: "53300"}}, + {name: "cannot connect now", err: &pgconn.PgError{Code: "57P03"}}, + } { + t.Run(testCase.name, func(t *testing.T) { + if !IsPostgresUnavailable(testCase.err) { + t.Fatalf("error %v was not classified as PostgreSQL unavailable", testCase.err) + } + }) + } + if IsPostgresUnavailable(&pgconn.PgError{Code: "42601"}) { + t.Fatal("SQL syntax error was incorrectly classified as PostgreSQL unavailable") + } +} diff --git a/apps/web/src/api.test.ts b/apps/web/src/api.test.ts index bdf8210..470cc3b 100644 --- a/apps/web/src/api.test.ts +++ b/apps/web/src/api.test.ts @@ -9,12 +9,39 @@ import { getAPITask, getCurrentUser, getOpsManagementSkillMetadata, + loginLocalAccount, OIDC_BROWSER_SESSION_CREDENTIAL, startIdentityPairing, retireIdentityPairingSecurityEventConflict, validateIdentityRevision, } from './api'; +describe('local login transport', () => { + afterEach(() => { + vi.useRealTimers(); + vi.unstubAllGlobals(); + }); + + it('aborts after ten seconds and returns a stable login timeout message', async () => { + vi.useFakeTimers(); + const fetchMock = vi.fn((_url: string, init?: RequestInit) => new Promise((_resolve, reject) => { + init?.signal?.addEventListener('abort', () => reject(new DOMException('aborted', 'AbortError'))); + })); + vi.stubGlobal('fetch', fetchMock); + + const login = loginLocalAccount({ account: 'timeout-test-account', password: 'timeout-test-password' }); + const rejection = expect(login).rejects.toMatchObject({ + message: '登录请求超时,请稍后重试', + }); + await vi.advanceTimersByTimeAsync(10_000); + await rejection; + + const [, init] = fetchMock.mock.calls[0] as [string, RequestInit]; + expect(init.signal).toBeInstanceOf(AbortSignal); + expect(init.signal?.aborted).toBe(true); + }); +}); + describe('Gateway provisioning errors', () => { const cases = [ ['GATEWAY_USER_NOT_PROVISIONED', '该账号尚未开通 EasyAI Gateway'], diff --git a/apps/web/src/api.ts b/apps/web/src/api.ts index 0946295..dc41a93 100644 --- a/apps/web/src/api.ts +++ b/apps/web/src/api.ts @@ -119,6 +119,7 @@ export async function loginLocalAccount(input: { account: string; password: stri auth: false, body: input, method: 'POST', + timeoutMs: 10_000, }); } @@ -1180,7 +1181,7 @@ export async function deleteFileStorageChannel(token: string, channelId: string) async function request( path: string, - options: { token?: string; auth?: boolean; method?: string; body?: unknown; headers?: Record } = {}, + options: { token?: string; auth?: boolean; method?: string; body?: unknown; headers?: Record; timeoutMs?: number } = {}, ): Promise { const headers: Record = { ...(options.headers ?? {}) }; if (options.auth !== false && options.token && options.token !== OIDC_BROWSER_SESSION_CREDENTIAL) { @@ -1189,20 +1190,32 @@ async function request( if (options.body !== undefined) { headers['Content-Type'] = 'application/json'; } - const response = await fetch(`${API_BASE}${path}`, { - method: options.method ?? 'GET', - headers, - body: options.body === undefined ? undefined : JSON.stringify(options.body), - credentials: 'include', - }); - if (!response.ok) { - const body = await response.text(); - throw new GatewayApiError(parseErrorDetails(body, response.status, `Request failed: ${response.status}`)); + const controller = options.timeoutMs ? new AbortController() : undefined; + const timeout = controller ? globalThis.setTimeout(() => controller.abort(), options.timeoutMs) : undefined; + try { + const response = await fetch(`${API_BASE}${path}`, { + method: options.method ?? 'GET', + headers, + body: options.body === undefined ? undefined : JSON.stringify(options.body), + credentials: 'include', + signal: controller?.signal, + }); + if (!response.ok) { + const body = await response.text(); + throw new GatewayApiError(parseErrorDetails(body, response.status, `Request failed: ${response.status}`)); + } + if (response.status === 204) { + return undefined as T; + } + return response.json() as Promise; + } catch (error) { + if (controller?.signal.aborted) { + throw new GatewayApiError('登录请求超时,请稍后重试'); + } + throw error; + } finally { + if (timeout !== undefined) globalThis.clearTimeout(timeout); } - if (response.status === 204) { - return undefined as T; - } - return response.json() as Promise; } function authorizationHeader(token: string): Record { diff --git a/docker/nginx.conf b/docker/nginx.conf index 510bd73..a7ee56a 100644 --- a/docker/nginx.conf +++ b/docker/nginx.conf @@ -33,6 +33,20 @@ server { return 404; } + location = /gateway-api/api/v1/auth/login { + proxy_http_version 1.1; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header Connection ""; + proxy_connect_timeout 3s; + proxy_read_timeout 15s; + proxy_send_timeout 15s; + proxy_redirect off; + proxy_pass http://api:8088/api/v1/auth/login; + } + location /gateway-api/ { proxy_http_version 1.1; proxy_set_header Host $host; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0918eae..dd95339 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -6,10 +6,13 @@ settings: overrides: '@babel/core@<=7.29.0': 7.29.6 + axios@>=1.0.0 <1.18.0: 1.18.0 + brace-expansion@>=2.0.0 <2.1.2: 2.1.2 dompurify@<=3.4.10: 3.4.11 esbuild@>=0.27.3 <0.28.1: 0.28.1 form-data@>=4.0.0 <4.0.6: 4.0.6 - js-yaml@<3.15.0: 4.1.1 + js-yaml@<3.15.0: 4.3.0 + js-yaml@>=4.0.0 <4.3.0: 4.3.0 mermaid@>=11.0.0-alpha.1 <=11.14.0: 11.15.0 minimatch@>=9.0.0 <9.0.7: 9.0.7 '@nx/js>picomatch': 4.0.5 @@ -2443,6 +2446,10 @@ packages: resolution: {integrity: sha512-4B/qKCfeE/ODUaAUpSwfzazo5x29WD4r3vXiWsB7I2mSDAihwEqKO+g8GELZUQSSAo5e1XTYh3ZVfLyxBc12nA==} engines: {node: '>= 10.0.0'} + agent-base@6.0.2: + resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==} + engines: {node: '>= 6.0.0'} + ajv@8.20.0: resolution: {integrity: sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==} @@ -2491,8 +2498,8 @@ packages: asynckit@0.4.0: resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} - axios@1.16.0: - resolution: {integrity: sha512-6hp5CwvTPlN2A31g5dxnwAX0orzM7pmCRDLnZSX772mv8WDqICwFjowHuPs04Mc8deIld1+ejhtaMn5vp6b+1w==} + axios@1.18.0: + resolution: {integrity: sha512-E32NzpYKp++W7XRe52rHiXV2ehxmh3wbdgO7MHeFM+vqxLBYHzt0ElkiImtOBxtOmyp0yoC8C6uESVV84Y2/hw==} babel-plugin-const-enum@1.2.0: resolution: {integrity: sha512-o1m/6iyyFnp9MRsK1dHF3bneqyf3AlM2q3A/YbgQr2pCat6B6XJVDv2TXqzfY2RYUi4mak6WAksSBPlyYGx9dg==} @@ -2553,8 +2560,8 @@ packages: bl@4.1.0: resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==} - brace-expansion@2.1.0: - resolution: {integrity: sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==} + brace-expansion@2.1.2: + resolution: {integrity: sha512-w5JZcKgdhDOgOwm8H+KgbosopHMuGcl6qbulwjtz3SM7I7P3yW1eAjzMPLrIE+NQ9vjgANKHWeMHnrT0OXW1oA==} brace-expansion@5.0.7: resolution: {integrity: sha512-7oFy703dxfY3/NLxC1fh2SUCQ0H9rmAY+5EpDVfXjUTTs+HEwR2nYaqLv+GWcTsumwxPfiz6CzCNkwXwBUwqCA==} @@ -3124,10 +3131,6 @@ packages: resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} engines: {node: '>= 0.4'} - hasown@2.0.3: - resolution: {integrity: sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==} - engines: {node: '>= 0.4'} - hasown@2.0.4: resolution: {integrity: sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==} engines: {node: '>= 0.4'} @@ -3184,6 +3187,10 @@ packages: html-void-elements@3.0.0: resolution: {integrity: sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==} + https-proxy-agent@5.0.1: + resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==} + engines: {node: '>= 6'} + iconv-lite@0.6.3: resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} engines: {node: '>=0.10.0'} @@ -3281,8 +3288,8 @@ packages: js-tokens@9.0.1: resolution: {integrity: sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==} - js-yaml@4.1.1: - resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==} + js-yaml@4.3.0: + resolution: {integrity: sha512-1td788aAnnZ5qs7V2QIRl1owjtYpbKt749Y3xauqQgwIIGF/xXWz1wMTEBx5O3LK3lXLVuqXPdPxj2BoFHaW9Q==} hasBin: true jsesc@3.1.0: @@ -5820,6 +5827,7 @@ snapshots: - '@swc-node/register' - '@swc/core' - debug + - supports-color '@phenomnomnominal/tsquery@5.0.1(typescript@5.9.3)': dependencies: @@ -7152,7 +7160,7 @@ snapshots: '@yarnpkg/parsers@3.0.2': dependencies: - js-yaml: 4.1.1 + js-yaml: 4.3.0 tslib: 2.8.1 '@zkochan/js-yaml@0.0.7': @@ -7161,6 +7169,12 @@ snapshots: address@1.2.2: {} + agent-base@6.0.2: + dependencies: + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + ajv@8.20.0: dependencies: fast-deep-equal: 3.1.3 @@ -7258,13 +7272,15 @@ snapshots: asynckit@0.4.0: {} - axios@1.16.0: + axios@1.18.0: dependencies: follow-redirects: 1.16.0 form-data: 4.0.6 + https-proxy-agent: 5.0.1 proxy-from-env: 2.1.0 transitivePeerDependencies: - debug + - supports-color babel-plugin-const-enum@1.2.0(@babel/core@7.29.6): dependencies: @@ -7336,7 +7352,7 @@ snapshots: inherits: 2.0.4 readable-stream: 3.6.2 - brace-expansion@2.1.0: + brace-expansion@2.1.2: dependencies: balanced-match: 1.0.2 @@ -7863,7 +7879,7 @@ snapshots: front-matter@4.0.2: dependencies: - js-yaml: 4.1.1 + js-yaml: 4.3.0 fs-constants@1.0.0: {} @@ -7912,10 +7928,6 @@ snapshots: dependencies: has-symbols: 1.1.0 - hasown@2.0.3: - dependencies: - function-bind: 1.1.2 - hasown@2.0.4: dependencies: function-bind: 1.1.2 @@ -8054,6 +8066,13 @@ snapshots: html-void-elements@3.0.0: {} + https-proxy-agent@5.0.1: + dependencies: + agent-base: 6.0.2 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + iconv-lite@0.6.3: dependencies: safer-buffer: 2.1.2 @@ -8088,7 +8107,7 @@ snapshots: is-core-module@2.16.2: dependencies: - hasown: 2.0.3 + hasown: 2.0.4 is-decimal@2.0.1: {} @@ -8129,7 +8148,7 @@ snapshots: js-tokens@9.0.1: {} - js-yaml@4.1.1: + js-yaml@4.3.0: dependencies: argparse: 2.0.1 @@ -8677,7 +8696,7 @@ snapshots: minimatch@5.1.9: dependencies: - brace-expansion: 2.1.0 + brace-expansion: 2.1.2 minimatch@9.0.7: dependencies: @@ -8712,7 +8731,7 @@ snapshots: '@yarnpkg/lockfile': 1.1.0 '@yarnpkg/parsers': 3.0.2 '@zkochan/js-yaml': 0.0.7 - axios: 1.16.0 + axios: 1.18.0 chalk: 4.1.2 cli-cursor: 3.1.0 cli-spinners: 2.6.1 @@ -8756,6 +8775,7 @@ snapshots: '@nx/nx-win32-x64-msvc': 21.6.11 transitivePeerDependencies: - debug + - supports-color once@1.4.0: dependencies: diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index fea2db6..f787276 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -8,10 +8,13 @@ allowBuilds: overrides: '@babel/core@<=7.29.0': 7.29.6 + 'axios@>=1.0.0 <1.18.0': 1.18.0 + 'brace-expansion@>=2.0.0 <2.1.2': 2.1.2 'dompurify@<=3.4.10': 3.4.11 'esbuild@>=0.27.3 <0.28.1': 0.28.1 'form-data@>=4.0.0 <4.0.6': 4.0.6 - 'js-yaml@<3.15.0': 4.1.1 + 'js-yaml@<3.15.0': 4.3.0 + 'js-yaml@>=4.0.0 <4.3.0': 4.3.0 'mermaid@>=11.0.0-alpha.1 <=11.14.0': 11.15.0 'minimatch@>=9.0.0 <9.0.7': 9.0.7 '@nx/js>picomatch': 4.0.5