Skip to content

Commit e0fe1d8

Browse files
authored
fix: backport #7317 to v2 (#7318)
1 parent 8978636 commit e0fe1d8

File tree

15 files changed

+71
-8
lines changed

15 files changed

+71
-8
lines changed

.github/workflows/ci.yml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,6 @@ on:
1010
- main
1111

1212
pull_request:
13-
branches:
14-
- main
1513

1614
workflow_dispatch:
1715

packages/browser/src/client/client.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ export const SESSION_ID
1414
: getBrowserState().testerId
1515
export const ENTRY_URL = `${
1616
location.protocol === 'https:' ? 'wss:' : 'ws:'
17-
}//${HOST}/__vitest_browser_api__?type=${PAGE_TYPE}&sessionId=${SESSION_ID}`
17+
}//${HOST}/__vitest_browser_api__?type=${PAGE_TYPE}&sessionId=${SESSION_ID}&token=${(window as any).VITEST_API_TOKEN}`
1818

1919
let setCancel = (_: CancelReason) => {}
2020
export const onCancel = new Promise<CancelReason>((resolve) => {

packages/browser/src/client/public/esm-client-injector.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
provider: { __VITEST_PROVIDER__ },
3030
providedContext: { __VITEST_PROVIDED_CONTEXT__ },
3131
};
32+
window.VITEST_API_TOKEN = { __VITEST_API_TOKEN__ };
3233

3334
const config = __vitest_browser_runner__.config;
3435

packages/browser/src/node/rpc.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { ServerMockResolver } from '@vitest/mocker/node'
88
import { createBirpc } from 'birpc'
99
import { parse, stringify } from 'flatted'
1010
import { dirname } from 'pathe'
11-
import { createDebugger, isFileServingAllowed } from 'vitest/node'
11+
import { createDebugger, isFileServingAllowed, isValidApiRequest } from 'vitest/node'
1212
import { WebSocketServer } from 'ws'
1313

1414
const debug = createDebugger('vitest:browser:api')
@@ -32,6 +32,11 @@ export function setupBrowserRpc(server: BrowserServer) {
3232
return
3333
}
3434

35+
if (!isValidApiRequest(ctx.config, request)) {
36+
socket.destroy()
37+
return
38+
}
39+
3540
const type = searchParams.get('type') ?? 'tester'
3641
const sessionId = searchParams.get('sessionId') ?? '0'
3742

packages/browser/src/node/serverOrchestrator.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ export async function resolveOrchestrator(
3232
__VITEST_CONTEXT_ID__: JSON.stringify(contextId),
3333
__VITEST_TESTER_ID__: '"none"',
3434
__VITEST_PROVIDED_CONTEXT__: '{}',
35+
__VITEST_API_TOKEN__: JSON.stringify(project.ctx.config.api.token),
3536
})
3637

3738
// disable CSP for the orchestrator as we are the ones controlling it

packages/browser/src/node/serverTester.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ export async function resolveTester(
5252
__VITEST_CONTEXT_ID__: JSON.stringify(contextId),
5353
__VITEST_TESTER_ID__: JSON.stringify(crypto.randomUUID()),
5454
__VITEST_PROVIDED_CONTEXT__: JSON.stringify(stringify(project.getProvidedContext())),
55+
__VITEST_API_TOKEN__: JSON.stringify(project.ctx.config.api.token),
5556
})
5657

5758
const testerHtml = typeof server.testerHtml === 'string'

packages/ui/client/constants.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,6 @@ export const PORT = import.meta.hot && !browserState ? '51204' : location.port
44
export const HOST = [location.hostname, PORT].filter(Boolean).join(':')
55
export const ENTRY_URL = `${
66
location.protocol === 'https:' ? 'wss:' : 'ws:'
7-
}//${HOST}/__vitest_api__`
7+
}//${HOST}/__vitest_api__?token=${(window as any).VITEST_API_TOKEN}`
88
export const isReport = !!window.METADATA_PATH
99
export const BASE_PATH = isReport ? import.meta.env.BASE_URL : __BASE_PATH__

packages/ui/node/index.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import type { Plugin } from 'vite'
22
import type { Vitest } from 'vitest/node'
3+
import fs from 'node:fs'
34
import { fileURLToPath } from 'node:url'
45
import { toArray } from '@vitest/utils'
56
import { basename, resolve } from 'pathe'
@@ -52,6 +53,27 @@ export default (ctx: Vitest): Plugin => {
5253
}
5354

5455
const clientDist = resolve(fileURLToPath(import.meta.url), '../client')
56+
const clientIndexHtml = fs.readFileSync(resolve(clientDist, 'index.html'), 'utf-8')
57+
58+
// serve index.html with api token
59+
server.middlewares.use((req, res, next) => {
60+
if (req.url) {
61+
const url = new URL(req.url, 'http://localhost')
62+
if (url.pathname === base) {
63+
const html = clientIndexHtml.replace(
64+
'<!-- !LOAD_METADATA! -->',
65+
`<script>window.VITEST_API_TOKEN = ${JSON.stringify(ctx.config.api.token)}</script>`,
66+
)
67+
res.setHeader('Cache-Control', 'no-cache, max-age=0, must-revalidate')
68+
res.setHeader('Content-Type', 'text/html; charset=utf-8')
69+
res.write(html)
70+
res.end()
71+
return
72+
}
73+
}
74+
next()
75+
})
76+
5577
server.middlewares.use(
5678
base,
5779
sirv(clientDist, {

packages/vitest/rollup.config.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ const external = [
7272
'node:os',
7373
'node:stream',
7474
'node:vm',
75+
'node:http',
7576
'inspector',
7677
'vite-node/source-map',
7778
'vite-node/client',

packages/vitest/src/api/check.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import type { IncomingMessage } from 'node:http'
2+
import type { ResolvedConfig } from '../node/types/config'
3+
import crypto from 'node:crypto'
4+
5+
export function isValidApiRequest(config: ResolvedConfig, req: IncomingMessage): boolean {
6+
const url = new URL(req.url ?? '', 'http://localhost')
7+
8+
// validate token. token is injected in ui/tester/orchestrator html, which is cross origin proteced.
9+
try {
10+
const token = url.searchParams.get('token')
11+
if (token && crypto.timingSafeEqual(
12+
Buffer.from(token),
13+
Buffer.from(config.api.token),
14+
)) {
15+
return true
16+
}
17+
}
18+
// an error is thrown when the length is incorrect
19+
catch {}
20+
21+
return false
22+
}

0 commit comments

Comments
 (0)