@@ -6,6 +6,62 @@ import http from "node:http";
66import https from "node:https" ;
77import readline from "node:readline" ;
88
9+ // Configuration for logging
10+ type LogLevel = 'info' | 'success' | 'error' | 'debug' ;
11+ interface LogOptions {
12+ verbose: boolean ;
13+ redactSensitiveData: boolean ;
14+ }
15+
16+ // Default options
17+ const DEFAULT_LOG_OPTIONS : LogOptions = {
18+ verbose : false ,
19+ redactSensitiveData : true ,
20+ } ;
21+
22+ /**
23+ * Helper function to redact sensitive information in logs
24+ *
25+ * @param data The data object or string to redact sensitive info from
26+ * @param redact Whether to redact sensitive information
27+ * @returns Redacted data object or string
28+ */
29+ function redactSensitiveData ( data : any , redact : boolean = true ) : any {
30+ if ( ! redact || ! data ) return data ;
31+
32+ // For strings that look like tokens
33+ if ( typeof data === 'string' ) {
34+ if ( data . length > 8 && data . includes ( '-' ) ) {
35+ return data . substring ( 0 , 4 ) + '...' + data . substring ( data . length - 4 ) ;
36+ }
37+ return data ;
38+ }
39+
40+ // For objects containing sensitive keys
41+ if ( typeof data === 'object' && data !== null ) {
42+ const sensitiveKeys = [
43+ 'access_token' , 'refresh_token' , 'id_token' , 'token' ,
44+ 'client_secret' , 'code' , 'authorization_code'
45+ ] ;
46+
47+ const result = Array . isArray ( data ) ? [ ...data ] : { ...data } ;
48+
49+ for ( const key in result ) {
50+ if ( sensitiveKeys . includes ( key ) ) {
51+ if ( typeof result [ key ] === 'string' && result [ key ] . length > 0 ) {
52+ result [ key ] = result [ key ] . substring ( 0 , 4 ) + '...' + result [ key ] . substring ( result [ key ] . length - 4 ) ;
53+ }
54+ } else if ( typeof result [ key ] === 'object' && result [ key ] !== null ) {
55+ result [ key ] = redactSensitiveData ( result [ key ] , redact ) ;
56+ }
57+ }
58+
59+ return result ;
60+ }
61+
62+ return data ;
63+ }
64+
965// Simple in-memory storage implementation for CLI usage
1066class MemoryStorage implements StorageInterface {
1167 private storage : Map < string , string > ;
@@ -33,41 +89,72 @@ class CLIAuthFlowTester {
3389 storage : MemoryStorage ;
3490 stateMachine : OAuthStateMachine ;
3591 autoRedirect : boolean ;
92+ logOptions : LogOptions ;
93+ stepsCompleted : string [ ] = [ ] ;
3694
37- constructor ( serverUrl : string , autoRedirect : boolean = false ) {
95+ constructor ( serverUrl : string , options : { autoRedirect ? : boolean ; verbose ?: boolean ; noRedact ?: boolean } = { } ) {
3896 this . serverUrl = serverUrl ;
3997 this . state = { ...EMPTY_DEBUGGER_STATE } ;
4098 this . storage = new MemoryStorage ( ) ;
4199 this . stateMachine = new OAuthStateMachine ( serverUrl , this . updateState . bind ( this ) ) ;
42- this . autoRedirect = autoRedirect ;
100+ this . autoRedirect = options . autoRedirect || false ;
101+ this . logOptions = {
102+ verbose : options . verbose || DEFAULT_LOG_OPTIONS . verbose ,
103+ redactSensitiveData : ! options . noRedact ,
104+ } ;
43105 }
44106
45107 updateState ( updates : Partial < AuthDebuggerState > ) {
46108 this. state = { ...this . state , ...updates } ;
47109 }
48110
49- log ( step : string , message : string , data : any = null ) {
50- const timestamp = new Date ( ) . toISOString ( ) ;
51- console . log ( `[${ timestamp } ] ${ step } : ${ message } ` ) ;
52- if ( data ) {
53- console . log ( ` └─ Details:` , JSON . stringify ( data , null , 2 ) ) ;
111+ log ( level : LogLevel , step : string , message : string , data : any = null ) {
112+ // Only show debug logs in verbose mode
113+ if ( level === 'debug' && ! this . logOptions . verbose ) return ;
114+
115+ const timestamp = this . logOptions . verbose ? `[${ new Date ( ) . toISOString ( ) } ] ` : '' ;
116+ const processedData = data ? redactSensitiveData ( data , this . logOptions . redactSensitiveData ) : null ;
117+
118+ switch ( level ) {
119+ case 'info ':
120+ console . log ( `${ timestamp } ${ step } : ${ message } ` ) ;
121+ break ;
122+ case 'success' :
123+ const icon = step ? '✅ ' : '' ;
124+ console . log ( `${ timestamp } ${ icon } ${ step ? step + ': ' : '' } ${ message } ` ) ;
125+ break ;
126+ case 'error' :
127+ console . error ( `${ timestamp } ❌ ${ step } : ${ message } ` ) ;
128+ break ;
129+ case 'debug ':
130+ console . log ( `${ timestamp } 🔍 ${ step } : ${ message } ` ) ;
131+ break ;
132+ }
133+
134+ if ( processedData && ( this . logOptions . verbose || level === 'error' ) ) {
135+ console . log ( ` └─ ${ level === 'error' ? 'Error' : 'Data' } :` ,
136+ typeof processedData === 'string'
137+ ? processedData
138+ : JSON . stringify ( processedData , null , 2 )
139+ ) ;
54140 }
55141 }
56142
143+ debug ( step : string , message : string , data : any = null ) {
144+ this . log ( 'debug' , step , message , data ) ;
145+ }
146+
147+ info ( step : string , message : string , data : any = null ) {
148+ this . log ( 'info' , step , message , data ) ;
149+ }
150+
57151 error ( step : string , message : string , error : Error | null = null ) {
58- const timestamp = new Date ( ) . toISOString ( ) ;
59- console . error ( `[${ timestamp } ] ❌ ${ step } : ${ message } ` ) ;
60- if ( error ) {
61- console . error ( ` └─ Error:` , error . message ) ;
62- }
152+ this . log ( 'error' , step , message , error ) ;
63153 }
64154
65155 success ( step : string , message : string , data : any = null ) {
66- const timestamp = new Date ( ) . toISOString ( ) ;
67- console . log ( `[${ timestamp } ] ✅ ${ step } : ${ message } ` ) ;
68- if ( data ) {
69- console . log ( ` └─ Data:` , JSON . stringify ( data , null , 2 ) ) ;
70- }
156+ this . log ( 'success' , step , message , data ) ;
157+ this . stepsCompleted . push ( step ) ;
71158 }
72159
73160 async executeStep ( stepName : OAuthStep ) {
@@ -301,15 +388,36 @@ class CLIAuthFlowTester {
301388async function main ( ) : Promise < void > {
302389 const args = process . argv . slice ( 2 ) ;
303390
304- if ( args . length === 0 ) {
305- console . error ( "Usage: npx tsx test-auth-flow.ts <server-url> [--auto-redirect ]" ) ;
391+ if ( args . length === 0 || args [ 0 ] === "--help" || args [ 0 ] === "-h" ) {
392+ console . error ( "Usage: npx tsx test-auth-flow.ts <server-url> [OPTIONS ]" ) ;
306393 console . error ( "Example: npx tsx test-auth-flow.ts https://example.com/mcp" ) ;
307- console . error ( "Add --auto-redirect flag for servers that redirect immediately without user interaction" ) ;
394+ console . error ( "\nOptions:" ) ;
395+ console . error ( " --auto-redirect Enable automatic redirect handling without user interaction" ) ;
396+ console . error ( " --verbose Enable verbose output with timestamps and detailed data" ) ;
397+ console . error ( " --no-redact Show tokens and sensitive data in full (not recommended)" ) ;
398+ console . error ( " --help, -h Show this help message" ) ;
399+ process . exit ( args [ 0 ] === "--help" || args [ 0 ] === "-h" ? 0 : 1 ) ;
400+ }
401+
402+ // Extract the server URL (the first non-flag argument)
403+ let serverUrl = "" ;
404+ for ( const arg of args ) {
405+ if ( ! arg . startsWith ( "--" ) && ! arg . startsWith ( "-" ) ) {
406+ serverUrl = arg ;
407+ break ;
408+ }
409+ }
410+
411+ if ( ! serverUrl ) {
412+ console . error ( "❌ Server URL is required" ) ;
413+ console . error ( "Usage: npx tsx test-auth-flow.ts <server-url> [OPTIONS]" ) ;
308414 process . exit ( 1 ) ;
309415 }
310416
311- const serverUrl = args [ 0 ] ;
417+ // Parse flags
312418 const autoRedirect = args . includes ( '--auto-redirect' ) ;
419+ const verbose = args . includes ( '--verbose' ) ;
420+ const noRedact = args . includes ( '--no-redact' ) ;
313421
314422 // Validate URL
315423 try {
@@ -319,7 +427,12 @@ async function main(): Promise<void> {
319427 process . exit ( 1 ) ;
320428 }
321429
322- const tester = new CLIAuthFlowTester ( serverUrl , autoRedirect ) ;
430+ const tester = new CLIAuthFlowTester ( serverUrl , {
431+ autoRedirect,
432+ verbose,
433+ noRedact
434+ } ) ;
435+
323436 const success = await tester . runFullFlow ( ) ;
324437
325438 process . exit ( success ? 0 : 1 ) ;
0 commit comments