11import type { Server , ServerWebSocket } from 'bun' ;
2+ import crypto from 'crypto' ;
3+ import fs from 'fs' ;
4+ import path from 'path' ;
25import { RPCHandler } from '@orpc/server/fetch' ;
3- import { loadAgentConfig , getConfigDir , ensureConfigDir } from '../config/loader' ;
6+ import { loadAgentConfig , saveAgentConfig , getConfigDir , ensureConfigDir } from '../config/loader' ;
47import type { AgentConfig } from '../shared/types' ;
8+ import { CONFIG_FILE } from '../shared/types' ;
59import { HOST_WORKSPACE_NAME } from '../shared/client-types' ;
610import { DEFAULT_AGENT_PORT } from '../shared/constants' ;
711import { WorkspaceManager } from '../workspace/manager' ;
@@ -14,6 +18,7 @@ import { serveStaticBun } from './static';
1418import { SessionsCacheManager } from '../sessions/cache' ;
1519import { ModelCacheManager } from '../models/cache' ;
1620import { FileWatcher } from './file-watcher' ;
21+ import { checkAuth , unauthorizedResponse } from './auth' ;
1722import {
1823 getTailscaleStatus ,
1924 getTailscaleIdentity ,
@@ -125,13 +130,23 @@ function createAgentServer(
125130 const corsHeaders = {
126131 'Access-Control-Allow-Origin' : '*' ,
127132 'Access-Control-Allow-Methods' : 'GET, POST, OPTIONS' ,
128- 'Access-Control-Allow-Headers' : 'Content-Type' ,
133+ 'Access-Control-Allow-Headers' : 'Content-Type, Authorization ' ,
129134 } ;
130135
131136 if ( method === 'OPTIONS' ) {
132137 return new Response ( null , { status : 204 , headers : corsHeaders } ) ;
133138 }
134139
140+ const staticResponse = await serveStaticBun ( pathname ) ;
141+ if ( staticResponse ) {
142+ return staticResponse ;
143+ }
144+
145+ const authResult = checkAuth ( req , currentConfig ) ;
146+ if ( ! authResult . ok ) {
147+ return unauthorizedResponse ( ) ;
148+ }
149+
135150 const terminalMatch = pathname . match ( / ^ \/ r p c \/ t e r m i n a l \/ ( [ ^ / ] + ) $ / ) ;
136151
137152 if ( terminalMatch ) {
@@ -177,11 +192,6 @@ function createAgentServer(
177192 }
178193 }
179194
180- const staticResponse = await serveStaticBun ( pathname ) ;
181- if ( staticResponse ) {
182- return staticResponse ;
183- }
184-
185195 return Response . json ( { error : 'Not found' } , { status : 404 , headers : corsHeaders } ) ;
186196 } ,
187197
@@ -252,12 +262,31 @@ const BANNER = `
252262 |_| |_____|_| \\_\\_| \\_\\|_|
253263` ;
254264
265+ async function ensureAuthForNewInstalls ( configDir : string ) : Promise < AgentConfig > {
266+ const configPath = path . join ( configDir , CONFIG_FILE ) ;
267+ const configExists = fs . existsSync ( configPath ) ;
268+
269+ const config = await loadAgentConfig ( configDir ) ;
270+
271+ if ( ! configExists && ! config . auth ?. token ) {
272+ const token = `perry-${ crypto . randomBytes ( 16 ) . toString ( 'hex' ) } ` ;
273+ config . auth = { ...config . auth , token } ;
274+ await saveAgentConfig ( config , configDir ) ;
275+
276+ console . log ( `[agent] New install detected - auth enabled by default` ) ;
277+ console . log ( `[agent] Auth token generated: ${ token } ` ) ;
278+ console . log ( `[agent] Configure clients with: perry config token ${ token } ` ) ;
279+ }
280+
281+ return config ;
282+ }
283+
255284export async function startAgent ( options : StartAgentOptions = { } ) : Promise < void > {
256285 const configDir = options . configDir || getConfigDir ( ) ;
257286
258287 await ensureConfigDir ( configDir ) ;
259288
260- const config = await loadAgentConfig ( configDir ) ;
289+ const config = await ensureAuthForNewInstalls ( configDir ) ;
261290
262291 if ( options . noHostAccess || process . env . PERRY_NO_HOST_ACCESS === 'true' ) {
263292 config . allowHostAccess = false ;
0 commit comments