@@ -6,6 +6,62 @@ import http from "node:http";
6
6
import https from "node:https" ;
7
7
import readline from "node:readline" ;
8
8
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
+
9
65
// Simple in-memory storage implementation for CLI usage
10
66
class MemoryStorage implements StorageInterface {
11
67
private storage : Map < string , string > ;
@@ -33,41 +89,72 @@ class CLIAuthFlowTester {
33
89
storage : MemoryStorage ;
34
90
stateMachine : OAuthStateMachine ;
35
91
autoRedirect : boolean ;
92
+ logOptions : LogOptions ;
93
+ stepsCompleted : string [ ] = [ ] ;
36
94
37
- constructor ( serverUrl : string , autoRedirect : boolean = false ) {
95
+ constructor ( serverUrl : string , options : { autoRedirect ? : boolean ; verbose ?: boolean ; noRedact ?: boolean } = { } ) {
38
96
this . serverUrl = serverUrl ;
39
97
this . state = { ...EMPTY_DEBUGGER_STATE } ;
40
98
this . storage = new MemoryStorage ( ) ;
41
99
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
+ } ;
43
105
}
44
106
45
107
updateState ( updates : Partial < AuthDebuggerState > ) {
46
108
this. state = { ...this . state , ...updates } ;
47
109
}
48
110
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
+ ) ;
54
140
}
55
141
}
56
142
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
+
57
151
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 ) ;
63
153
}
64
154
65
155
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 ) ;
71
158
}
72
159
73
160
async executeStep ( stepName : OAuthStep ) {
@@ -301,15 +388,36 @@ class CLIAuthFlowTester {
301
388
async function main ( ) : Promise < void > {
302
389
const args = process . argv . slice ( 2 ) ;
303
390
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 ]" ) ;
306
393
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]" ) ;
308
414
process . exit ( 1 ) ;
309
415
}
310
416
311
- const serverUrl = args [ 0 ] ;
417
+ // Parse flags
312
418
const autoRedirect = args . includes ( '--auto-redirect' ) ;
419
+ const verbose = args . includes ( '--verbose' ) ;
420
+ const noRedact = args . includes ( '--no-redact' ) ;
313
421
314
422
// Validate URL
315
423
try {
@@ -319,7 +427,12 @@ async function main(): Promise<void> {
319
427
process . exit ( 1 ) ;
320
428
}
321
429
322
- const tester = new CLIAuthFlowTester ( serverUrl , autoRedirect ) ;
430
+ const tester = new CLIAuthFlowTester ( serverUrl , {
431
+ autoRedirect,
432
+ verbose,
433
+ noRedact
434
+ } ) ;
435
+
323
436
const success = await tester . runFullFlow ( ) ;
324
437
325
438
process . exit ( success ? 0 : 1 ) ;
0 commit comments