@@ -27,8 +27,6 @@ export class ApiKeyService implements OnModuleInit {
2727 private readonly logger = new Logger ( ApiKeyService . name ) ;
2828 protected readonly basePath : string ;
2929 protected memoryApiKeys : Array < ApiKeyWithSecret > = [ ] ;
30- private persistentKeys = new Map < string , ApiKeyWithSecret > ( ) ;
31- private ephemeralKeys = new Map < string , ApiKeyWithSecret > ( ) ;
3230 private static readonly validRoles : Set < Role > = new Set ( Object . values ( Role ) ) ;
3331
3432 constructor ( ) {
@@ -37,20 +35,10 @@ export class ApiKeyService implements OnModuleInit {
3735 }
3836
3937 async onModuleInit ( ) {
40- // Load persistent keys once at startup
41- const diskKeys = await this . loadAllFromDisk ( ) ;
42- for ( const key of diskKeys ) {
43- this . persistentKeys . set ( key . id , key ) ;
38+ this . memoryApiKeys = await this . loadAllFromDisk ( ) ;
39+ if ( environment . IS_MAIN_PROCESS ) {
40+ this . setupWatch ( ) ;
4441 }
45-
46- // Clean up legacy internal keys
47- await this . cleanupLegacyInternalKeys ( ) ;
48-
49- // Update memoryApiKeys for backwards compatibility
50- this . updateMemoryApiKeys ( ) ;
51-
52- // NO file watching - manage in memory
53- this . logger . log ( `Loaded ${ this . persistentKeys . size } persistent API keys` ) ;
5442 }
5543
5644 public convertApiKeyWithSecretToApiKey ( key : ApiKeyWithSecret ) : ApiKey {
@@ -59,43 +47,20 @@ export class ApiKeyService implements OnModuleInit {
5947 }
6048
6149 public async findAll ( ) : Promise < ApiKey [ ] > {
62- // Only return persistent keys, not ephemeral ones
63- return Array . from ( this . persistentKeys . values ( ) ) . map ( ( key ) =>
64- this . convertApiKeyWithSecretToApiKey ( key )
50+ return Promise . all (
51+ this . memoryApiKeys . map ( async ( key ) => {
52+ const keyWithoutSecret = this . convertApiKeyWithSecretToApiKey ( key ) ;
53+ return keyWithoutSecret ;
54+ } )
6555 ) ;
6656 }
6757
68- private updateMemoryApiKeys ( ) {
69- // Combine persistent and ephemeral keys for backwards compatibility
70- this . memoryApiKeys = [
71- ...Array . from ( this . persistentKeys . values ( ) ) ,
72- ...Array . from ( this . ephemeralKeys . values ( ) ) ,
73- ] ;
74- }
75-
76- private async cleanupLegacyInternalKeys ( ) {
77- const legacyNames = [ 'CliInternal' , 'ConnectInternal' , 'CLI' , 'Connect' , 'CliAdmin' , 'Internal' ] ;
78- const keysToDelete : string [ ] = [ ] ;
79-
80- for ( const [ id , key ] of this . persistentKeys ) {
81- if ( legacyNames . includes ( key . name ) ) {
82- keysToDelete . push ( id ) ;
83- this . logger . log ( `Removing legacy internal key: ${ key . name } ` ) ;
84- }
85- }
86-
87- if ( keysToDelete . length > 0 ) {
88- // Remove from disk
89- for ( const id of keysToDelete ) {
90- try {
91- await unlink ( join ( this . basePath , `${ id } .json` ) ) ;
92- } catch ( error ) {
93- // File might not exist, that's ok
94- }
95- this . persistentKeys . delete ( id ) ;
96- }
97- this . logger . log ( `Cleaned up ${ keysToDelete . length } legacy internal keys` ) ;
98- }
58+ private setupWatch ( ) {
59+ watch ( this . basePath , { ignoreInitial : false } ) . on ( 'all' , async ( path ) => {
60+ this . logger . debug ( `API key changed: ${ path } ` ) ;
61+ this . memoryApiKeys = [ ] ;
62+ this . memoryApiKeys = await this . loadAllFromDisk ( ) ;
63+ } ) ;
9964 }
10065
10166 private sanitizeName ( name : string ) : string {
@@ -189,10 +154,6 @@ export class ApiKeyService implements OnModuleInit {
189154
190155 await this . saveApiKey ( apiKey as ApiKeyWithSecret ) ;
191156
192- // Update persistent keys in memory
193- this . persistentKeys . set ( apiKey . id as string , apiKey as ApiKeyWithSecret ) ;
194- this . updateMemoryApiKeys ( ) ;
195-
196157 return apiKey as ApiKeyWithSecret ;
197158 }
198159
@@ -277,97 +238,17 @@ export class ApiKeyService implements OnModuleInit {
277238 public findByField ( field : keyof ApiKeyWithSecret , value : string ) : ApiKeyWithSecret | null {
278239 if ( ! value ) return null ;
279240
280- // Check ephemeral keys first
281- for ( const keyData of this . ephemeralKeys . values ( ) ) {
282- if ( keyData [ field ] === value ) {
283- return keyData ;
284- }
285- }
286-
287- // Then check persistent keys
288- for ( const keyData of this . persistentKeys . values ( ) ) {
289- if ( keyData [ field ] === value ) {
290- return keyData ;
291- }
292- }
293-
294- return null ;
241+ return this . memoryApiKeys . find ( ( k ) => k [ field ] === value ) ?? null ;
295242 }
296243
297244 findByKey ( key : string ) : ApiKeyWithSecret | null {
298- if ( ! key ) return null ;
299-
300- // Check ephemeral keys first (faster, in-memory)
301- for ( const keyData of this . ephemeralKeys . values ( ) ) {
302- if ( keyData . key === key ) {
303- return keyData ;
304- }
305- }
306-
307- // Then check persistent keys
308- for ( const keyData of this . persistentKeys . values ( ) ) {
309- if ( keyData . key === key ) {
310- return keyData ;
311- }
312- }
313-
314- return null ;
245+ return this . findByField ( 'key' , key ) ;
315246 }
316247
317248 private generateApiKey ( ) : string {
318249 return crypto . randomBytes ( 32 ) . toString ( 'hex' ) ;
319250 }
320251
321- /**
322- * Register an in-process module with an ephemeral token.
323- * Used by Connect and other modules running in the same process.
324- */
325- public async registerModule ( moduleId : string , roles : Role [ ] ) : Promise < string > {
326- // Generate 256-bit cryptographically secure token
327- const token = crypto . randomBytes ( 32 ) . toString ( 'base64url' ) ;
328-
329- const keyData : ApiKeyWithSecret = {
330- id : `module-${ moduleId } ` ,
331- key : token ,
332- name : `Module-${ moduleId } ` ,
333- description : `Ephemeral token for ${ moduleId } module` ,
334- roles,
335- permissions : [ ] ,
336- createdAt : new Date ( ) . toISOString ( ) ,
337- } ;
338-
339- this . ephemeralKeys . set ( keyData . id , keyData ) ;
340- this . updateMemoryApiKeys ( ) ;
341- this . logger . debug ( `Registered module ${ moduleId } with ephemeral token` ) ;
342-
343- return token ;
344- }
345-
346- /**
347- * Register an ephemeral key for external processes like CLI.
348- * The key is provided by the caller and registered in memory.
349- */
350- public async registerEphemeralKey ( config : {
351- key : string ;
352- name : string ;
353- roles : Role [ ] ;
354- type : string ;
355- } ) : Promise < void > {
356- const keyData : ApiKeyWithSecret = {
357- id : `ephemeral-${ config . type } ` ,
358- key : config . key ,
359- name : config . name ,
360- description : `Ephemeral key for ${ config . type } ` ,
361- roles : config . roles ,
362- permissions : [ ] ,
363- createdAt : new Date ( ) . toISOString ( ) ,
364- } ;
365-
366- this . ephemeralKeys . set ( keyData . id , keyData ) ;
367- this . updateMemoryApiKeys ( ) ;
368- this . logger . debug ( `Registered ephemeral key for ${ config . type } ` ) ;
369- }
370-
371252 private logApiKeyValidationError ( file : string , error : ValidationError ) : void {
372253 this . logger . error ( `Invalid API key structure in file ${ file } .
373254 Errors: ${ JSON . stringify ( error . constraints , null , 2 ) } ` ) ;
@@ -428,30 +309,17 @@ export class ApiKeyService implements OnModuleInit {
428309 throw new Error ( `API keys not found: ${ missingKeys . join ( ', ' ) } ` ) ;
429310 }
430311
431- // Separate persistent and ephemeral keys
432- const persistentIds = ids . filter ( ( id ) => this . persistentKeys . has ( id ) ) ;
433- const ephemeralIds = ids . filter ( ( id ) => this . ephemeralKeys . has ( id ) ) ;
434-
435- // Delete persistent keys from disk
436- if ( persistentIds . length > 0 ) {
437- const { errors } = await batchProcess ( persistentIds , async ( id ) => {
438- await unlink ( join ( this . basePath , `${ id } .json` ) ) ;
439- this . persistentKeys . delete ( id ) ;
440- return id ;
441- } ) ;
442-
443- if ( errors . length > 0 ) {
444- throw errors ;
445- }
446- }
312+ // Delete all files in parallel
313+ const { errors, data : deletedIds } = await batchProcess ( ids , async ( id ) => {
314+ await unlink ( join ( this . basePath , `${ id } .json` ) ) ;
315+ return id ;
316+ } ) ;
447317
448- // Remove ephemeral keys from memory
449- for ( const id of ephemeralIds ) {
450- this . ephemeralKeys . delete ( id ) ;
318+ const deletedSet = new Set ( deletedIds ) ;
319+ this . memoryApiKeys = this . memoryApiKeys . filter ( ( key ) => ! deletedSet . has ( key . id ) ) ;
320+ if ( errors . length > 0 ) {
321+ throw errors ;
451322 }
452-
453- // Update memory cache
454- this . updateMemoryApiKeys ( ) ;
455323 }
456324
457325 async update ( {
@@ -486,15 +354,7 @@ export class ApiKeyService implements OnModuleInit {
486354 if ( permissions ) {
487355 apiKey . permissions = permissions ;
488356 }
489-
490- // Only save to disk if it's a persistent key
491- if ( this . persistentKeys . has ( id ) ) {
492- await this . saveApiKey ( apiKey ) ;
493- this . persistentKeys . set ( id , apiKey ) ;
494- }
495-
496- // Update memory cache
497- this . updateMemoryApiKeys ( ) ;
357+ await this . saveApiKey ( apiKey ) ;
498358 return apiKey ;
499359 }
500360
0 commit comments