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+
}

packages/vitest/src/api/setup.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import type { File, TaskResultPack } from '@vitest/runner'
22

3+
import type { IncomingMessage } from 'node:http'
34
import type { ViteDevServer } from 'vite'
45
import type { WebSocket } from 'ws'
56
import type { Vitest } from '../node/core'
@@ -21,6 +22,7 @@ import { API_PATH } from '../constants'
2122
import { getModuleGraph } from '../utils/graph'
2223
import { stringifyReplace } from '../utils/serialization'
2324
import { parseErrorStacktrace } from '../utils/source-map'
25+
import { isValidApiRequest } from './check'
2426

2527
export function setup(ctx: Vitest, _server?: ViteDevServer) {
2628
const wss = new WebSocketServer({ noServer: true })
@@ -29,7 +31,7 @@ export function setup(ctx: Vitest, _server?: ViteDevServer) {
2931

3032
const server = _server || ctx.server
3133

32-
server.httpServer?.on('upgrade', (request, socket, head) => {
34+
server.httpServer?.on('upgrade', (request: IncomingMessage, socket, head) => {
3335
if (!request.url) {
3436
return
3537
}
@@ -39,6 +41,11 @@ export function setup(ctx: Vitest, _server?: ViteDevServer) {
3941
return
4042
}
4143

44+
if (!isValidApiRequest(ctx.config, request)) {
45+
socket.destroy()
46+
return
47+
}
48+
4249
wss.handleUpgrade(request, socket, head, (ws) => {
4350
wss.emit('connection', ws, request)
4451
setupClient(ws)

packages/vitest/src/node/config/resolveConfig.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import type {
99
} from '../types/config'
1010
import type { BaseCoverageOptions, CoverageReporterWithOptions } from '../types/coverage'
1111
import type { BuiltinPool, ForksOptions, PoolOptions, ThreadsOptions } from '../types/pool-options'
12+
import crypto from 'node:crypto'
1213
import { toArray } from '@vitest/utils'
1314
import { resolveModule } from 'local-pkg'
1415
import { normalize, relative, resolve } from 'pathe'
@@ -584,7 +585,8 @@ export function resolveConfig(
584585
}
585586

586587
// the server has been created, we don't need to override vite.server options
587-
resolved.api = resolveApiServerConfig(options, defaultPort)
588+
const api = resolveApiServerConfig(options, defaultPort)
589+
resolved.api = { ...api, token: crypto.randomUUID() }
588590

589591
if (options.related) {
590592
resolved.related = toArray(options.related).map(file =>

packages/vitest/src/node/types/config.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1001,7 +1001,7 @@ export interface ResolvedConfig
10011001

10021002
defines: Record<string, any>
10031003

1004-
api?: ApiConfig
1004+
api: ApiConfig & { token: string }
10051005
cliExclude?: string[]
10061006

10071007
benchmark?: Required<

packages/vitest/src/public/node.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import type { ModuleDiagnostic as _FileDiagnostic } from '../node/reporters/repo
22
import { createServer as _createServer } from 'vite'
33
import { TestModule as _TestFile } from '../node/reporters/reported-tasks'
44

5+
export { isValidApiRequest } from '../api/check'
56
export { parseCLI } from '../node/cli/cac'
67
export { startVitest } from '../node/cli/cli-api'
78
export { resolveApiServerConfig, resolveConfig } from '../node/config/resolveConfig'

test/config/test/override.test.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -249,6 +249,7 @@ describe('correctly defines api flag', () => {
249249
expect(c.server.config.server.middlewareMode).toBe(true)
250250
expect(c.config.api).toEqual({
251251
middlewareMode: true,
252+
token: expect.any(String),
252253
})
253254
})
254255

@@ -262,6 +263,7 @@ describe('correctly defines api flag', () => {
262263
expect(c.server.config.server.port).toBe(4321)
263264
expect(c.config.api).toEqual({
264265
port: 4321,
266+
token: expect.any(String),
265267
})
266268
})
267269
})

0 commit comments

Comments
 (0)