From 55b42d7dbe4f16665964d07ae3e45eab95f7ae94 Mon Sep 17 00:00:00 2001 From: Sharang Parnerkar <30073382+mighty840@users.noreply.github.com> Date: Sun, 30 Aug 2026 23:16:10 +0200 Subject: [PATCH] =?UTF-8?q?fix(ci):=20make=20main=20deployable=20again=20?= =?UTF-8?q?=E2=80=94=20coverage,=20CVEs,=20and=20the=20image=20job?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Found while preparing the tenant-registry enforcement flip: the deployed portal image is from June 10, so the service-token code merged in #21 never shipped — flipping tenant-registry would have broken every portal page. Three independent defects: 1. The image job's job-level hashFiles('Dockerfile') condition evaluates BEFORE checkout against an empty workspace — always false, job silently skipped on every main push. Same bug just fixed in tenant-registry (#16). 2. The test job was red on #21's merge: this repo pins 100% function/branch coverage and my local run skipped --coverage. Closed for real, not lowered: two dead json() arrows removed from mocks the code never reads (it throws on !ok before touching the body), and a new test exercising the expires_in ?? 300 default-lifetime branch. 80 tests, 100/100/100/100. 3. The shared job's trivy gate was red on 14 real findings (3 CRITICAL): @auth/core 0.37.2 -> 0.41.3 (CVE-2026-73420, Unicode-normalization email homoglyph bypass — in the auth library this rollout depends on), next 16.2.6 -> 16.2.11, nanoid + postcss via pnpm overrides. next-auth 5.0.0-beta.25 -> beta.30; typecheck, lint and build clean on the new versions. Co-Authored-By: Claude Fable 5 --- .gitea/workflows/ci.yaml | 5 +- package.json | 13 ++- pnpm-lock.yaml | 178 ++++++++++++++++------------------ src/lib/service-token.test.ts | 27 +++++- 4 files changed, 121 insertions(+), 102 deletions(-) diff --git a/.gitea/workflows/ci.yaml b/.gitea/workflows/ci.yaml index 8758e54..d3f1254 100644 --- a/.gitea/workflows/ci.yaml +++ b/.gitea/workflows/ci.yaml @@ -122,7 +122,10 @@ jobs: # --service breakpilot-portal --branch main # ) accepts unsigned payloads — orca matches on repo + branch. needs: [shared, test] - if: github.event_name == 'push' && github.ref == 'refs/heads/main' && hashFiles('Dockerfile') != '' + # NOTE: no hashFiles() here — at job level it evaluates BEFORE checkout + # against an empty workspace, so the old condition was always false and + # this job silently never ran (deployment sat on a June-10 image). + if: github.event_name == 'push' && github.ref == 'refs/heads/main' runs-on: docker steps: - uses: actions/checkout@v4 diff --git a/package.json b/package.json index 13de8c1..95c9341 100644 --- a/package.json +++ b/package.json @@ -23,8 +23,8 @@ "clsx": "^2.1.1", "geist": "^1.7.2", "lucide-react": "^1.17.0", - "next": "16.2.6", - "next-auth": "5.0.0-beta.25", + "next": "16.2.11", + "next-auth": "5.0.0-beta.30", "react": "19.0.0", "react-dom": "19.0.0", "tailwind-merge": "^3.6.0" @@ -48,5 +48,12 @@ "workerDirectory": [ "public" ] + }, + "pnpm": { + "overrides": { + "nanoid@<3.3.18": ">=3.3.18", + "postcss@<8.4.31": ">=8.4.31", + "@auth/core@<0.41.3": ">=0.41.3" + } } -} \ No newline at end of file +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 601f08a..2e6795e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -4,6 +4,11 @@ settings: autoInstallPeers: true excludeLinksFromLockfile: false +overrides: + nanoid@<3.3.18: '>=3.3.18' + postcss@<8.4.31: '>=8.4.31' + '@auth/core@<0.41.3': '>=0.41.3' + importers: .: @@ -16,16 +21,16 @@ importers: version: 2.1.1 geist: specifier: ^1.7.2 - version: 1.7.2(next@16.2.6(@babel/core@7.29.0)(@playwright/test@1.60.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)) + version: 1.7.2(next@16.2.11(@babel/core@7.29.0)(@playwright/test@1.60.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)) lucide-react: specifier: ^1.17.0 version: 1.17.0(react@19.0.0) next: - specifier: 16.2.6 - version: 16.2.6(@babel/core@7.29.0)(@playwright/test@1.60.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + specifier: 16.2.11 + version: 16.2.11(@babel/core@7.29.0)(@playwright/test@1.60.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) next-auth: - specifier: 5.0.0-beta.25 - version: 5.0.0-beta.25(next@16.2.6(@babel/core@7.29.0)(@playwright/test@1.60.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react@19.0.0) + specifier: 5.0.0-beta.30 + version: 5.0.0-beta.30(next@16.2.11(@babel/core@7.29.0)(@playwright/test@1.60.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react@19.0.0) react: specifier: 19.0.0 version: 19.0.0 @@ -86,12 +91,12 @@ packages: resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} engines: {node: '>=6.0.0'} - '@auth/core@0.37.2': - resolution: {integrity: sha512-kUvzyvkcd6h1vpeMAojK2y7+PAV5H+0Cc9+ZlKYDFhDY31AlvsB+GW5vNO4qE3Y07KeQgvNO9U0QUx/fN62kBw==} + '@auth/core@0.41.3': + resolution: {integrity: sha512-sJ3JMHHkXMD3aOjopv7mOBTO1Ocw4b0fAEXJBz6k7YHLpYQI6C40jCUPc5fNvUKxXRXNE1/sRISA15UrwWJBTw==} peerDependencies: '@simplewebauthn/browser': ^9.0.1 '@simplewebauthn/server': ^9.0.2 - nodemailer: ^6.8.0 + nodemailer: ^7.0.7 || ^8.0.5 peerDependenciesMeta: '@simplewebauthn/browser': optional: true @@ -581,56 +586,56 @@ packages: '@emnapi/core': ^1.7.1 '@emnapi/runtime': ^1.7.1 - '@next/env@16.2.6': - resolution: {integrity: sha512-gd8HoHN4ufj73WmR3JmVolrpJR47ILK6LouP5xElPglaVxir6e1a7VzvTvDWkOoPXT9rkkTzyCxBu4yeZfZwcw==} + '@next/env@16.2.11': + resolution: {integrity: sha512-0do5A3BJ2gxWr0ZCMcD6BhW+e595jyxdTl3rXTS6lOtD8ektMiW6CO+EPwt1Eca1DBnm90r/7GdiKWBKxH++DA==} '@next/eslint-plugin-next@16.2.6': resolution: {integrity: sha512-Z8l6o4JWKUl755x4R+wogD86KPeU+Ckw4K+SYG4kHeOJtRenDeK+OSbGcqZpDtbwn9DsJVdir2UxmwXuinUbUw==} - '@next/swc-darwin-arm64@16.2.6': - resolution: {integrity: sha512-ZJGkkcNfYgrrMkqOdZ7zoLa1TOy0qpcMfk/z4Mh/FKUz40gVO+HNQWqmLxf67Z5WB64DRp0dhEbyHfel+6sJUg==} + '@next/swc-darwin-arm64@16.2.11': + resolution: {integrity: sha512-wryL4pjKmDwGv2ox6+GZDFxvmtSRLqApBR8kL1j4+vhB7Z5vJC/zAnXpiR9Xkfzl0AS8WLMnsuGV/UKI67/rrw==} engines: {node: '>= 10'} cpu: [arm64] os: [darwin] - '@next/swc-darwin-x64@16.2.6': - resolution: {integrity: sha512-v/YLBHIY132Ced3puBJ7YJKw1lqsCrgcNo2aRJlCEyQrrCeRJlvGlnmxhPxNQI3KE3N1DN5r9TPNPvka3nq5RQ==} + '@next/swc-darwin-x64@16.2.11': + resolution: {integrity: sha512-aZl2j4f/fLyjQvOhv0Oe9UaMAQHolYpKhctsoYzplSumKJKPUmgjcf6545aBtysLTcu994TREd0+pSgNE4ohmg==} engines: {node: '>= 10'} cpu: [x64] os: [darwin] - '@next/swc-linux-arm64-gnu@16.2.6': - resolution: {integrity: sha512-RPOvqlYBbcQjkz9VQQDZ2T2bARIjXZV1KFlt+V2Mr6SW/e4I9fcKsaA0hdyf2FHoTlsV2xnBd5Y912rP/1Ce6w==} + '@next/swc-linux-arm64-gnu@16.2.11': + resolution: {integrity: sha512-5jEriyEnH/LWFy27L2ZG0XaLlyEJIjhsImEsiS9P563PKEVp2BVups/xfOucIrsvVntp11oNcZwjHvaDPYVB5g==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] - '@next/swc-linux-arm64-musl@16.2.6': - resolution: {integrity: sha512-URUTu1+dMkxJsPFgm+OeEvq9wf5sujw0EvgYy80TDGHTSLTnIHeqb0Eu8A3sC95IRgjejQL+kC4mw+4yPxiAXA==} + '@next/swc-linux-arm64-musl@16.2.11': + resolution: {integrity: sha512-eIjcpx2fnnFSSkZDbTxy74KnokUXDjfoLClpWelfgHLf621aTqswhwXQ7GkD5K5rplrS6LZ/Bj+mVuvzluBOEg==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] - '@next/swc-linux-x64-gnu@16.2.6': - resolution: {integrity: sha512-DOj182mPV8G3UkrayLoREM5YEYI+Dk5wv7Ox9xl1fFibAELEsFD0lDPfHIeILlutMMfdyhlzYPELG3peuKaurw==} + '@next/swc-linux-x64-gnu@16.2.11': + resolution: {integrity: sha512-8WgzpaWMs46qJT9kiV47cje86L0x/Mu9t8/Gwj+pnbgW3rETVfCnaScPjlYUwNScpOozdcIMHWmAvuZJUonR2w==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - '@next/swc-linux-x64-musl@16.2.6': - resolution: {integrity: sha512-HKQ5SP/V/ub73UvF7n/zeJlxk2kLmtL7Wzrg4WfmkjmNos5onJ2tKu7yZOPdL18A6Svfn3max29ym+ry7NkK4g==} + '@next/swc-linux-x64-musl@16.2.11': + resolution: {integrity: sha512-I3UgPds7G4ZYnTb/H+5GBGuUT2DhAk6j0mL6A4s63RjFs74wB2hOWP0vaxsK+3NJraExt3eYEPQ/UtT0x/64Nw==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - '@next/swc-win32-arm64-msvc@16.2.6': - resolution: {integrity: sha512-LZXpTlPyS5v7HhSmnvsLGP3iIYgYOBnc8r8ArlT55sGHV89bR2HlDdBjWQ+PY6SJMmk8TuVGFuxalnP3k/0Dwg==} + '@next/swc-win32-arm64-msvc@16.2.11': + resolution: {integrity: sha512-n89CjtcThnjrwgJMAiI5xbqwLY51zvwC9tSlArmVndAJLYVl9T9UAdlkXTmZvE++idoXe8KdglQlhNRdUp1c6g==} engines: {node: '>= 10'} cpu: [arm64] os: [win32] - '@next/swc-win32-x64-msvc@16.2.6': - resolution: {integrity: sha512-F0+4i0h9J6C4eE3EAPWsoCk7UW/dbzOjyzxY0qnDUOYFu6FFmdZ6l97/XdV3/Nz3VYyO7UWjyEJUXkGqcoXfMA==} + '@next/swc-win32-x64-msvc@16.2.11': + resolution: {integrity: sha512-md8CLNggS1Dx9pUgApzps5uAf+N8GN9xywzmNx9vHAWo94HtBwCCqkSnhIrdfQe83Dhz8Lfo/20Nb1Zxal092w==} engines: {node: '>= 10'} cpu: [x64] os: [win32] @@ -897,9 +902,6 @@ packages: '@tybys/wasm-util@0.10.2': resolution: {integrity: sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==} - '@types/cookie@0.6.0': - resolution: {integrity: sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==} - '@types/estree@1.0.8': resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} @@ -1315,10 +1317,6 @@ packages: convert-source-map@2.0.0: resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} - cookie@0.7.1: - resolution: {integrity: sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==} - engines: {node: '>= 0.6'} - cookie@1.1.1: resolution: {integrity: sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==} engines: {node: '>=18'} @@ -1931,8 +1929,8 @@ packages: resolution: {integrity: sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==} hasBin: true - jose@5.10.0: - resolution: {integrity: sha512-s+3Al/p9g32Iq+oqXxkW//7jk2Vig6FF1CFqzVXoTUXt2qz89YWbL+OwS17NFYEvxC35n0FKeGO2LGYSxeM2Gg==} + jose@6.2.10: + resolution: {integrity: sha512-iiW7J9qRFlGxvCOIBDBDxFePQSn7ZMAnrYGhrrOo6siO/MIqwfyilLR27pkfDgUk+raLuzADS8A3S/KLBisc0g==} js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} @@ -2134,9 +2132,9 @@ packages: resolution: {integrity: sha512-dkEJPVvun4FryqBmZ5KhDo0K9iDXAwn08tMLDinNdRBNPcYEDiWYysLcc6k3mjTMlbP9KyylvRpd4wFtwrT9rw==} engines: {node: ^20.17.0 || >=22.9.0} - nanoid@3.3.12: - resolution: {integrity: sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==} - engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + nanoid@6.0.1: + resolution: {integrity: sha512-3wVS3i51pE2pi1k5FFL/95BGfVS0kSsvDVuGXHOtxox/TywUmtgq+3qiTOTbs9J7KfHaXPiN171k/A6dBnaXFw==} + engines: {node: ^22 || ^24 || >=26} hasBin: true napi-postinstall@0.3.4: @@ -2147,14 +2145,14 @@ packages: natural-compare@1.4.0: resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} - next-auth@5.0.0-beta.25: - resolution: {integrity: sha512-2dJJw1sHQl2qxCrRk+KTQbeH+izFbGFPuJj5eGgBZFYyiYYtvlrBeUw1E/OJJxTRjuxbSYGnCTkUIRsIIW0bog==} + next-auth@5.0.0-beta.30: + resolution: {integrity: sha512-+c51gquM3F6nMVmoAusRJ7RIoY0K4Ts9HCCwyy/BRoe4mp3msZpOzYMyb5LAYc1wSo74PMQkGDcaghIO7W6Xjg==} peerDependencies: '@simplewebauthn/browser': ^9.0.1 '@simplewebauthn/server': ^9.0.2 - next: ^14.0.0-0 || ^15.0.0-0 - nodemailer: ^6.6.5 - react: ^18.2.0 || ^19.0.0-0 + next: ^14.0.0-0 || ^15.0.0 || ^16.0.0 + nodemailer: ^7.0.7 + react: ^18.2.0 || ^19.0.0 peerDependenciesMeta: '@simplewebauthn/browser': optional: true @@ -2163,8 +2161,8 @@ packages: nodemailer: optional: true - next@16.2.6: - resolution: {integrity: sha512-qOVgKJg1+At15NpeUP+eJgCHvTCgXsogweq87Ri/Ix7PkqQHg4sdaXmSFqKlgaIXE4kW0g25LE68W87UANlHtw==} + next@16.2.11: + resolution: {integrity: sha512-B339zaqbyK8cmxhoAvLrcwoabwCP1wz21zSzfqxqXAemTu2BXnH7tQnfcglKv1vnMUIDBc+Hth7XODQriTZiRQ==} engines: {node: '>=20.9.0'} hasBin: true peerDependencies: @@ -2310,21 +2308,18 @@ packages: resolution: {integrity: sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==} engines: {node: ^10 || ^12 || >=14} - preact-render-to-string@5.2.3: - resolution: {integrity: sha512-aPDxUn5o3GhWdtJtW0svRC2SS/l8D9MAgo2+AWml+BhDImb27ALf04Q2d+AHqUUOc6RdSXFIBVa2gxzgMKgtZA==} + preact-render-to-string@6.5.11: + resolution: {integrity: sha512-ubnauqoGczeGISiOh6RjX0/cdaF8v/oDXIjO85XALCQjwQP+SB4RDXXtvZ6yTYSjG+PC1QRP2AhPgCEsM2EvUw==} peerDependencies: preact: '>=10' - preact@10.11.3: - resolution: {integrity: sha512-eY93IVpod/zG3uMF22Unl8h9KkrcKIRs2EGar8hwLZZDU1lkjph303V9HZBwufh2s736U6VXuhD109LYqPoffg==} + preact@10.24.3: + resolution: {integrity: sha512-Z2dPnBnMUfyQfSQ+GBdsGa16hz35YmLmtTLhM169uW944hYL6xzTYkJjC07j+Wosz733pMWx0fgON3JNw1jJQA==} prelude-ls@1.2.1: resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} engines: {node: '>= 0.8.0'} - pretty-format@3.8.0: - resolution: {integrity: sha512-WuxUnVtlWL1OfZFQFuqvnvs6MiAGk9UNsBostyBOB0Is9wb5uRESevA6rnl/rkksXaGX3GzZhPup5d6Vp1nFew==} - prop-types@15.8.1: resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} @@ -2813,15 +2808,13 @@ snapshots: '@jridgewell/gen-mapping': 0.3.13 '@jridgewell/trace-mapping': 0.3.31 - '@auth/core@0.37.2': + '@auth/core@0.41.3': dependencies: '@panva/hkdf': 1.2.1 - '@types/cookie': 0.6.0 - cookie: 0.7.1 - jose: 5.10.0 + jose: 6.2.10 oauth4webapi: 3.8.6 - preact: 10.11.3 - preact-render-to-string: 5.2.3(preact@10.11.3) + preact: 10.24.3 + preact-render-to-string: 6.5.11(preact@10.24.3) '@babel/code-frame@7.29.0': dependencies: @@ -3242,34 +3235,34 @@ snapshots: '@tybys/wasm-util': 0.10.2 optional: true - '@next/env@16.2.6': {} + '@next/env@16.2.11': {} '@next/eslint-plugin-next@16.2.6': dependencies: fast-glob: 3.3.1 - '@next/swc-darwin-arm64@16.2.6': + '@next/swc-darwin-arm64@16.2.11': optional: true - '@next/swc-darwin-x64@16.2.6': + '@next/swc-darwin-x64@16.2.11': optional: true - '@next/swc-linux-arm64-gnu@16.2.6': + '@next/swc-linux-arm64-gnu@16.2.11': optional: true - '@next/swc-linux-arm64-musl@16.2.6': + '@next/swc-linux-arm64-musl@16.2.11': optional: true - '@next/swc-linux-x64-gnu@16.2.6': + '@next/swc-linux-x64-gnu@16.2.11': optional: true - '@next/swc-linux-x64-musl@16.2.6': + '@next/swc-linux-x64-musl@16.2.11': optional: true - '@next/swc-win32-arm64-msvc@16.2.6': + '@next/swc-win32-arm64-msvc@16.2.11': optional: true - '@next/swc-win32-x64-msvc@16.2.6': + '@next/swc-win32-x64-msvc@16.2.11': optional: true '@nodelib/fs.scandir@2.1.5': @@ -3461,8 +3454,6 @@ snapshots: tslib: 2.8.1 optional: true - '@types/cookie@0.6.0': {} - '@types/estree@1.0.8': {} '@types/estree@1.0.9': {} @@ -3910,8 +3901,6 @@ snapshots: convert-source-map@2.0.0: {} - cookie@0.7.1: {} - cookie@1.1.1: {} cross-spawn@7.0.6: @@ -4415,9 +4404,9 @@ snapshots: functions-have-names@1.2.3: {} - geist@1.7.2(next@16.2.6(@babel/core@7.29.0)(@playwright/test@1.60.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)): + geist@1.7.2(next@16.2.11(@babel/core@7.29.0)(@playwright/test@1.60.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)): dependencies: - next: 16.2.6(@babel/core@7.29.0)(@playwright/test@1.60.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + next: 16.2.11(@babel/core@7.29.0)(@playwright/test@1.60.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) generator-function@2.0.1: {} @@ -4695,7 +4684,7 @@ snapshots: jiti@2.7.0: {} - jose@5.10.0: {} + jose@6.2.10: {} js-tokens@4.0.0: {} @@ -4878,21 +4867,21 @@ snapshots: mute-stream@3.0.0: {} - nanoid@3.3.12: {} + nanoid@6.0.1: {} napi-postinstall@0.3.4: {} natural-compare@1.4.0: {} - next-auth@5.0.0-beta.25(next@16.2.6(@babel/core@7.29.0)(@playwright/test@1.60.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react@19.0.0): + next-auth@5.0.0-beta.30(next@16.2.11(@babel/core@7.29.0)(@playwright/test@1.60.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react@19.0.0): dependencies: - '@auth/core': 0.37.2 - next: 16.2.6(@babel/core@7.29.0)(@playwright/test@1.60.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@auth/core': 0.41.3 + next: 16.2.11(@babel/core@7.29.0)(@playwright/test@1.60.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) react: 19.0.0 - next@16.2.6(@babel/core@7.29.0)(@playwright/test@1.60.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0): + next@16.2.11(@babel/core@7.29.0)(@playwright/test@1.60.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0): dependencies: - '@next/env': 16.2.6 + '@next/env': 16.2.11 '@swc/helpers': 0.5.15 baseline-browser-mapping: 2.10.31 caniuse-lite: 1.0.30001793 @@ -4901,14 +4890,14 @@ snapshots: react-dom: 19.0.0(react@19.0.0) styled-jsx: 5.1.6(@babel/core@7.29.0)(react@19.0.0) optionalDependencies: - '@next/swc-darwin-arm64': 16.2.6 - '@next/swc-darwin-x64': 16.2.6 - '@next/swc-linux-arm64-gnu': 16.2.6 - '@next/swc-linux-arm64-musl': 16.2.6 - '@next/swc-linux-x64-gnu': 16.2.6 - '@next/swc-linux-x64-musl': 16.2.6 - '@next/swc-win32-arm64-msvc': 16.2.6 - '@next/swc-win32-x64-msvc': 16.2.6 + '@next/swc-darwin-arm64': 16.2.11 + '@next/swc-darwin-x64': 16.2.11 + '@next/swc-linux-arm64-gnu': 16.2.11 + '@next/swc-linux-arm64-musl': 16.2.11 + '@next/swc-linux-x64-gnu': 16.2.11 + '@next/swc-linux-x64-musl': 16.2.11 + '@next/swc-win32-arm64-msvc': 16.2.11 + '@next/swc-win32-x64-msvc': 16.2.11 '@playwright/test': 1.60.0 sharp: 0.34.5 transitivePeerDependencies: @@ -5034,27 +5023,24 @@ snapshots: postcss@8.4.31: dependencies: - nanoid: 3.3.12 + nanoid: 6.0.1 picocolors: 1.1.1 source-map-js: 1.2.1 postcss@8.5.15: dependencies: - nanoid: 3.3.12 + nanoid: 6.0.1 picocolors: 1.1.1 source-map-js: 1.2.1 - preact-render-to-string@5.2.3(preact@10.11.3): + preact-render-to-string@6.5.11(preact@10.24.3): dependencies: - preact: 10.11.3 - pretty-format: 3.8.0 + preact: 10.24.3 - preact@10.11.3: {} + preact@10.24.3: {} prelude-ls@1.2.1: {} - pretty-format@3.8.0: {} - prop-types@15.8.1: dependencies: loose-envify: 1.4.0 diff --git a/src/lib/service-token.test.ts b/src/lib/service-token.test.ts index 82c7967..a16950d 100644 --- a/src/lib/service-token.test.ts +++ b/src/lib/service-token.test.ts @@ -107,10 +107,10 @@ describe("serviceToken", () => { test("surfaces a rejected token request instead of calling unauthenticated", async () => { configure(); + // no json() on the mock: fetchToken throws on !ok before reading the body vi.spyOn(globalThis, "fetch").mockResolvedValue({ ok: false, status: 401, - json: async () => ({}), } as Response); await expect(serviceToken()).rejects.toThrow("service token request failed: 401"); @@ -130,13 +130,36 @@ describe("serviceToken", () => { test("a failed fetch does not poison the cache", async () => { configure(); vi.spyOn(globalThis, "fetch") - .mockResolvedValueOnce({ ok: false, status: 503, json: async () => ({}) } as Response) + .mockResolvedValueOnce({ ok: false, status: 503 } as Response) .mockResolvedValueOnce(tokenResponse("tok-ok")); await expect(serviceToken()).rejects.toThrow(); expect(await serviceToken()).toBe("tok-ok"); }); + test("a response without expires_in gets the 300s default lifetime", async () => { + configure(); + vi.useFakeTimers(); + vi.setSystemTime(new Date("2026-08-30T10:00:00Z")); + const fetchSpy = vi + .spyOn(globalThis, "fetch") + .mockResolvedValueOnce({ + ok: true, + status: 200, + json: async () => ({ access_token: "tok-default" }), + } as Response) + .mockResolvedValueOnce(tokenResponse("tok-next")); + + expect(await serviceToken()).toBe("tok-default"); + // 4 minutes in: still inside the defaulted 300s window + vi.setSystemTime(new Date("2026-08-30T10:04:00Z")); + expect(await serviceToken()).toBe("tok-default"); + // past the default expiry + vi.setSystemTime(new Date("2026-08-30T10:05:01Z")); + expect(await serviceToken()).toBe("tok-next"); + expect(fetchSpy).toHaveBeenCalledTimes(2); + }); + test("serviceAuthHeader carries the bearer token when configured", async () => { configure(); vi.spyOn(globalThis, "fetch").mockResolvedValue(tokenResponse("tok-1"));