Skip to content

Commit 24d0032

Browse files
committed
feat(core): do ws auth eagerly
1 parent 7b220ab commit 24d0032

File tree

5 files changed

+41
-18
lines changed

5 files changed

+41
-18
lines changed

packages/core/src/node/rpc/anonymous/auth.ts

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import process from 'node:process'
21
import * as p from '@clack/prompts'
32
import { defineRpcFunction } from '@vitejs/devtools-kit'
43
import c from 'ansis'
@@ -20,19 +19,13 @@ export const anonymousAuth = defineRpcFunction({
2019
setup: (context) => {
2120
const internal = getInternalContext(context)
2221
const storage = internal.storage.auth
23-
const isClientAuthDisabled = context.mode === 'build' || context.viteConfig.devtools?.clientAuth === false || process.env.VITE_DEVTOOLS_DISABLE_CLIENT_AUTH === 'true'
24-
25-
if (isClientAuthDisabled) {
26-
console.warn('[Vite DevTools] Client authentication is disabled. Any browser can connect to the devtools and access to your server and filesystem.')
27-
}
28-
2922
return {
3023
handler: async (query: DevToolsAuthInput): Promise<DevToolsAuthReturn> => {
3124
const session = context.rpc.getCurrentRpcSession()
3225
if (!session)
3326
throw new Error('Failed to retrieve the current RPC session')
3427

35-
if (isClientAuthDisabled || storage.get().trusted[query.authId]) {
28+
if (session.meta.isTrusted || storage.get().trusted[query.authId]) {
3629
session.meta.clientAuthId = query.authId
3730
session.meta.isTrusted = true
3831
return {

packages/core/src/node/ws.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ import type { ConnectionMeta, DevToolsNodeContext, DevToolsNodeRpcSession, DevTo
33
import type { WebSocket } from 'ws'
44
import type { RpcFunctionsHost } from './host-functions'
55
import { AsyncLocalStorage } from 'node:async_hooks'
6-
import process from 'node:process'
76
import { createRpcServer } from '@vitejs/devtools-rpc'
87
import { createWsRpcPreset } from '@vitejs/devtools-rpc/presets/ws/server'
98
import c from 'ansis'
@@ -30,6 +29,7 @@ export async function createWsServer(options: CreateWsServerOptions) {
3029
const preset = createWsRpcPreset({
3130
port,
3231
host,
32+
context: options.context,
3333
onConnected: (ws, meta) => {
3434
wsClients.add(ws)
3535
console.log(c.green`${MARK_CHECK} Websocket client [${meta.id}] connected`)
@@ -42,8 +42,6 @@ export async function createWsServer(options: CreateWsServerOptions) {
4242

4343
const asyncStorage = new AsyncLocalStorage<DevToolsNodeRpcSession>()
4444

45-
const isClientAuthDisabled = options.context.mode === 'build' || options.context.viteConfig.devtools?.clientAuth === false || process.env.VITE_DEVTOOLS_DISABLE_CLIENT_AUTH === 'true'
46-
4745
const rpcGroup = createRpcServer<DevToolsRpcClientFunctions, DevToolsRpcServerFunctions>(
4846
rpcHost.functions,
4947
{
@@ -62,7 +60,7 @@ export async function createWsServer(options: CreateWsServerOptions) {
6260
const rpc = this
6361

6462
// Block unauthorized access to non-anonymous methods
65-
if (!name.startsWith(ANONYMOUS_SCOPE) && !rpc.$meta.isTrusted && !isClientAuthDisabled) {
63+
if (!name.startsWith(ANONYMOUS_SCOPE) && !rpc.$meta.isTrusted) {
6664
return () => {
6765
throw new Error(`Unauthorized access to method ${JSON.stringify(name)} from client [${rpc.$meta.id}]`)
6866
}

packages/kit/src/client/rpc.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,7 @@ export async function getDevToolsRpcClient(
172172
{
173173
preset: createWsRpcPreset({
174174
url,
175+
authId,
175176
...options.wsOptions,
176177
}),
177178
rpcOptions,

packages/rpc/src/presets/ws/client.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,17 @@ export interface WebSocketRpcClientOptions {
88
onConnected?: (e: Event) => void
99
onError?: (e: Error) => void
1010
onDisconnected?: (e: CloseEvent) => void
11+
authId?: string
1112
}
1213

1314
function NOOP() {}
1415

1516
export const createWsRpcPreset: RpcClientPreset<(options: WebSocketRpcClientOptions) => ChannelOptions> = defineRpcClientPreset((options: WebSocketRpcClientOptions) => {
16-
const ws = new WebSocket(options.url)
17+
let url = options.url
18+
if (options.authId) {
19+
url = `${url}?vite_devtools_auth_id=${encodeURIComponent(options.authId)}`
20+
}
21+
const ws = new WebSocket(url)
1722
const {
1823
onConnected = NOOP,
1924
onError = NOOP,

packages/rpc/src/presets/ws/server.ts

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,17 @@
1-
import type { DevToolsNodeRpcSessionMeta } from '@vitejs/devtools-kit'
1+
import type { DevToolsNodeContext, DevToolsNodeRpcSessionMeta } from '@vitejs/devtools-kit'
22
import type { BirpcGroup, BirpcOptions, ChannelOptions } from 'birpc'
33
import type { WebSocket } from 'ws'
44
import type { RpcServerPreset } from '..'
5+
import process from 'node:process'
56
import { parse, stringify } from 'structured-clone-es'
67
import { WebSocketServer } from 'ws'
78
import { defineRpcServerPreset } from '..'
9+
import { getInternalContext } from '../../../../core/src/node/context-internal'
810

911
export interface WebSocketRpcServerOptions {
1012
port: number
1113
host?: string
14+
context: DevToolsNodeContext
1215
onConnected?: (ws: WebSocket, meta: DevToolsNodeRpcSessionMeta) => void
1316
onDisconnected?: (ws: WebSocket, meta: DevToolsNodeRpcSessionMeta) => void
1417
}
@@ -32,27 +35,48 @@ export const createWsRpcPreset: RpcServerPreset<
3235
host = 'localhost',
3336
onConnected = NOOP,
3437
onDisconnected = NOOP,
38+
context,
3539
} = options
3640

41+
const isClientAuthDisabled = context.mode === 'build' || context.viteConfig.devtools?.clientAuth === false || process.env.VITE_DEVTOOLS_DISABLE_CLIENT_AUTH === 'true'
42+
if (isClientAuthDisabled) {
43+
console.warn('[Vite DevTools] Client authentication is disabled. Any browser can connect to the devtools and access to your server and filesystem.')
44+
}
45+
46+
const internal = getInternalContext(context)
47+
3748
const wss = new WebSocketServer({
3849
port,
3950
host,
4051
})
4152

4253
return <ClientFunctions extends object, ServerFunctions extends object>(
43-
rpc: BirpcGroup<ClientFunctions, ServerFunctions, false>,
54+
rpcGroup: BirpcGroup<ClientFunctions, ServerFunctions, false>,
4455
options?: Pick<BirpcOptions<ClientFunctions, ServerFunctions, false>, 'serialize' | 'deserialize'>,
4556
) => {
4657
const {
4758
serialize = stringify,
4859
deserialize = parse,
4960
} = options ?? {}
5061

51-
wss.on('connection', (ws) => {
62+
wss.on('connection', (ws, req) => {
63+
const url = new URL(req.url ?? '', 'http://localhost')
64+
const authId = url.searchParams.get('vite_devtools_auth_id') ?? undefined
65+
let isTrusted = false
66+
if (isClientAuthDisabled) {
67+
isTrusted = true
68+
}
69+
else if (authId && internal.storage.auth.get().trusted[authId]) {
70+
isTrusted = true
71+
}
72+
5273
const meta: DevToolsNodeRpcSessionMeta = {
5374
id: id++,
5475
ws,
76+
isTrusted,
77+
clientAuthId: authId,
5578
}
79+
5680
const channel: ChannelOptions = {
5781
post: (data) => {
5882
ws.send(data)
@@ -67,12 +91,14 @@ export const createWsRpcPreset: RpcServerPreset<
6791
meta,
6892
}
6993

70-
rpc.updateChannels((channels) => {
94+
rpcGroup.updateChannels((channels) => {
7195
channels.push(channel)
7296
})
7397

98+
// const rpc = rpcGroup.clients.find(client => client.$meta.id === meta.id)
99+
74100
ws.on('close', () => {
75-
rpc.updateChannels((channels) => {
101+
rpcGroup.updateChannels((channels) => {
76102
const index = channels.indexOf(channel)
77103
if (index >= 0)
78104
channels.splice(index, 1)

0 commit comments

Comments
 (0)