Skip to content

Commit 2b1f982

Browse files
committed
cli options
1 parent 04fda79 commit 2b1f982

File tree

1 file changed

+135
-22
lines changed

1 file changed

+135
-22
lines changed

bin/test-auth-flow.ts

Lines changed: 135 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,62 @@ import http from "node:http";
66
import https from "node:https";
77
import 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
1066
class 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 {
301388
async 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

Comments
 (0)