[READ-ONLY] Mirror of https://github.com/vitest-dev/vitest. Next generation testing framework powered by Vite. vitest.dev
test testing-tools vite
12

Configure Feed

Select the types of activity you want to include in your feed.

fix: backport #7317 to v2 (#7318)

authored by

Hiroshi Ogawa and committed by
GitHub
(Feb 2, 2025, 10:13 AM +0200) e0fe1d81 89786363

+71 -8
-2
.github/workflows/ci.yml
··· 10 10 - main 11 11 12 12 pull_request: 13 - branches: 14 - - main 15 13 16 14 workflow_dispatch: 17 15
+1
packages/vitest/rollup.config.js
··· 72 72 'node:os', 73 73 'node:stream', 74 74 'node:vm', 75 + 'node:http', 75 76 'inspector', 76 77 'vite-node/source-map', 77 78 'vite-node/client',
+1 -1
packages/ui/client/constants.ts
··· 4 4 export const HOST = [location.hostname, PORT].filter(Boolean).join(':') 5 5 export const ENTRY_URL = `${ 6 6 location.protocol === 'https:' ? 'wss:' : 'ws:' 7 - }//${HOST}/__vitest_api__` 7 + }//${HOST}/__vitest_api__?token=${(window as any).VITEST_API_TOKEN}` 8 8 export const isReport = !!window.METADATA_PATH 9 9 export const BASE_PATH = isReport ? import.meta.env.BASE_URL : __BASE_PATH__
+22
packages/ui/node/index.ts
··· 1 1 import type { Plugin } from 'vite' 2 2 import type { Vitest } from 'vitest/node' 3 + import fs from 'node:fs' 3 4 import { fileURLToPath } from 'node:url' 4 5 import { toArray } from '@vitest/utils' 5 6 import { basename, resolve } from 'pathe' ··· 52 53 } 53 54 54 55 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 + 55 77 server.middlewares.use( 56 78 base, 57 79 sirv(clientDist, {
+2
test/config/test/override.test.ts
··· 249 249 expect(c.server.config.server.middlewareMode).toBe(true) 250 250 expect(c.config.api).toEqual({ 251 251 middlewareMode: true, 252 + token: expect.any(String), 252 253 }) 253 254 }) 254 255 ··· 262 263 expect(c.server.config.server.port).toBe(4321) 263 264 expect(c.config.api).toEqual({ 264 265 port: 4321, 266 + token: expect.any(String), 265 267 }) 266 268 }) 267 269 })
+1 -1
packages/browser/src/client/client.ts
··· 14 14 : getBrowserState().testerId 15 15 export const ENTRY_URL = `${ 16 16 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}` 18 18 19 19 let setCancel = (_: CancelReason) => {} 20 20 export const onCancel = new Promise<CancelReason>((resolve) => {
+6 -1
packages/browser/src/node/rpc.ts
··· 8 8 import { createBirpc } from 'birpc' 9 9 import { parse, stringify } from 'flatted' 10 10 import { dirname } from 'pathe' 11 - import { createDebugger, isFileServingAllowed } from 'vitest/node' 11 + import { createDebugger, isFileServingAllowed, isValidApiRequest } from 'vitest/node' 12 12 import { WebSocketServer } from 'ws' 13 13 14 14 const debug = createDebugger('vitest:browser:api') ··· 29 29 30 30 const { pathname, searchParams } = new URL(request.url, 'http://localhost') 31 31 if (pathname !== BROWSER_API_PATH) { 32 + return 33 + } 34 + 35 + if (!isValidApiRequest(ctx.config, request)) { 36 + socket.destroy() 32 37 return 33 38 } 34 39
+1
packages/browser/src/node/serverOrchestrator.ts
··· 32 32 __VITEST_CONTEXT_ID__: JSON.stringify(contextId), 33 33 __VITEST_TESTER_ID__: '"none"', 34 34 __VITEST_PROVIDED_CONTEXT__: '{}', 35 + __VITEST_API_TOKEN__: JSON.stringify(project.ctx.config.api.token), 35 36 }) 36 37 37 38 // disable CSP for the orchestrator as we are the ones controlling it
+1
packages/browser/src/node/serverTester.ts
··· 52 52 __VITEST_CONTEXT_ID__: JSON.stringify(contextId), 53 53 __VITEST_TESTER_ID__: JSON.stringify(crypto.randomUUID()), 54 54 __VITEST_PROVIDED_CONTEXT__: JSON.stringify(stringify(project.getProvidedContext())), 55 + __VITEST_API_TOKEN__: JSON.stringify(project.ctx.config.api.token), 55 56 }) 56 57 57 58 const testerHtml = typeof server.testerHtml === 'string'
+22
packages/vitest/src/api/check.ts
··· 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 + }
+8 -1
packages/vitest/src/api/setup.ts
··· 1 1 import type { File, TaskResultPack } from '@vitest/runner' 2 2 3 + import type { IncomingMessage } from 'node:http' 3 4 import type { ViteDevServer } from 'vite' 4 5 import type { WebSocket } from 'ws' 5 6 import type { Vitest } from '../node/core' ··· 21 22 import { getModuleGraph } from '../utils/graph' 22 23 import { stringifyReplace } from '../utils/serialization' 23 24 import { parseErrorStacktrace } from '../utils/source-map' 25 + import { isValidApiRequest } from './check' 24 26 25 27 export function setup(ctx: Vitest, _server?: ViteDevServer) { 26 28 const wss = new WebSocketServer({ noServer: true }) ··· 29 31 30 32 const server = _server || ctx.server 31 33 32 - server.httpServer?.on('upgrade', (request, socket, head) => { 34 + server.httpServer?.on('upgrade', (request: IncomingMessage, socket, head) => { 33 35 if (!request.url) { 34 36 return 35 37 } 36 38 37 39 const { pathname } = new URL(request.url, 'http://localhost') 38 40 if (pathname !== API_PATH) { 41 + return 42 + } 43 + 44 + if (!isValidApiRequest(ctx.config, request)) { 45 + socket.destroy() 39 46 return 40 47 } 41 48
+1
packages/vitest/src/public/node.ts
··· 2 2 import { createServer as _createServer } from 'vite' 3 3 import { TestModule as _TestFile } from '../node/reporters/reported-tasks' 4 4 5 + export { isValidApiRequest } from '../api/check' 5 6 export { parseCLI } from '../node/cli/cac' 6 7 export { startVitest } from '../node/cli/cli-api' 7 8 export { resolveApiServerConfig, resolveConfig } from '../node/config/resolveConfig'
+1
packages/browser/src/client/public/esm-client-injector.js
··· 29 29 provider: { __VITEST_PROVIDER__ }, 30 30 providedContext: { __VITEST_PROVIDED_CONTEXT__ }, 31 31 }; 32 + window.VITEST_API_TOKEN = { __VITEST_API_TOKEN__ }; 32 33 33 34 const config = __vitest_browser_runner__.config; 34 35
+3 -1
packages/vitest/src/node/config/resolveConfig.ts
··· 9 9 } from '../types/config' 10 10 import type { BaseCoverageOptions, CoverageReporterWithOptions } from '../types/coverage' 11 11 import type { BuiltinPool, ForksOptions, PoolOptions, ThreadsOptions } from '../types/pool-options' 12 + import crypto from 'node:crypto' 12 13 import { toArray } from '@vitest/utils' 13 14 import { resolveModule } from 'local-pkg' 14 15 import { normalize, relative, resolve } from 'pathe' ··· 584 585 } 585 586 586 587 // 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() } 588 590 589 591 if (options.related) { 590 592 resolved.related = toArray(options.related).map(file =>
+1 -1
packages/vitest/src/node/types/config.ts
··· 1001 1001 1002 1002 defines: Record<string, any> 1003 1003 1004 - api?: ApiConfig 1004 + api: ApiConfig & { token: string } 1005 1005 cliExclude?: string[] 1006 1006 1007 1007 benchmark?: Required<