diff --git a/CHANGELOG.md b/CHANGELOG.md index ee54b88..0315a21 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - MCP Server: Dev Proxy - Diagnostics: Show error if pluginPath in plugin instance is not correctly set to DevProxy.Plugins.dll when using Dev Proxy v0.29.0 or later - Code action: Update single or all plugin paths to DevProxy.Plugins.dll +- Command: `dev-proxy-toolkit.jwt-create` - Generate JWT with guided input for testing purposes ### Changed: diff --git a/README.md b/README.md index a25d551..2bb1b32 100644 --- a/README.md +++ b/README.md @@ -33,6 +33,7 @@ The following sections describe the features that the extension contributes to V - `Dev Proxy Toolkit: Open configuration file`- Only available when Dev Proxy is installed - `Dev Proxy Toolkit: Create configuration file`- Only available when Dev Proxy is installed - `Dev Proxy Toolkit: Discover URLs to watch` - Only available when Dev Proxy is not running +- `Dev Proxy Toolkit: Generate JWT` - Only available when Dev Proxy is installed ### Diagnostics diff --git a/package.json b/package.json index 15781be..2cd455f 100644 --- a/package.json +++ b/package.json @@ -83,6 +83,12 @@ "category": "Dev Proxy Toolkit", "icon": "$(debug-start)", "enablement": "!isDevProxyRunning" + }, + { + "command": "dev-proxy-toolkit.jwt-create", + "title": "Generate JWT", + "category": "Dev Proxy Toolkit", + "enablement": "isDevProxyInstalled" } ], "mcpServerDefinitionProviders": [ diff --git a/src/commands.ts b/src/commands.ts index 2b18930..1aeb0db 100644 --- a/src/commands.ts +++ b/src/commands.ts @@ -369,4 +369,163 @@ export const registerCommands = (context: vscode.ExtensionContext, configuration ? terminal.sendText(`${devProxyExe} --discover --watch-process-names ${processNames.trim()}`) : terminal.sendText(`${devProxyExe} --discover`); })); + + context.subscriptions.push( + vscode.commands.registerCommand('dev-proxy-toolkit.jwt-create', async () => { + try { + // Collect JWT parameters through input dialogs with sensible defaults + const name = await vscode.window.showInputBox({ + prompt: 'Enter the name of the user to create the token for', + placeHolder: 'Dev Proxy', + value: 'Dev Proxy', + title: 'JWT Generation - User Name' + }); + + if (name === undefined) { + return; // User cancelled + } + + const issuer = await vscode.window.showInputBox({ + prompt: 'Enter the issuer of the token', + placeHolder: 'dev-proxy', + value: 'dev-proxy', + title: 'JWT Generation - Issuer' + }); + + if (issuer === undefined) { + return; // User cancelled + } + + const audiences = await vscode.window.showInputBox({ + prompt: 'Enter the audiences (comma-separated for multiple)', + placeHolder: 'https://myserver.com', + value: 'https://myserver.com', + title: 'JWT Generation - Audiences' + }); + + if (audiences === undefined) { + return; // User cancelled + } + + const roles = await vscode.window.showInputBox({ + prompt: 'Enter roles (comma-separated, leave empty for none)', + placeHolder: 'admin,user', + value: '', + title: 'JWT Generation - Roles (Optional)' + }); + + if (roles === undefined) { + return; // User cancelled + } + + const scopes = await vscode.window.showInputBox({ + prompt: 'Enter scopes (comma-separated, leave empty for none)', + placeHolder: 'read,write', + value: '', + title: 'JWT Generation - Scopes (Optional)' + }); + + if (scopes === undefined) { + return; // User cancelled + } + + const claims = await vscode.window.showInputBox({ + prompt: 'Enter custom claims in format name:value (comma-separated, leave empty for none)', + placeHolder: 'custom:claim,department:engineering', + value: '', + title: 'JWT Generation - Custom Claims (Optional)' + }); + + if (claims === undefined) { + return; // User cancelled + } + + const validFor = await vscode.window.showInputBox({ + prompt: 'Enter token validity duration in minutes', + placeHolder: '60', + value: '60', + title: 'JWT Generation - Validity Duration', + validateInput: (value: string) => { + const num = parseInt(value); + if (isNaN(num) || num <= 0) { + return 'Please enter a positive number'; + } + return undefined; + } + }); + + if (validFor === undefined) { + return; // User cancelled + } + + // Build the command with all parameters + let command = `${devProxyExe} jwt create --name "${name}" --issuer "${issuer}" --valid-for ${validFor}`; + + // Add audiences (can have multiple) + const audienceList = audiences.split(',').map(a => a.trim()).filter(a => a); + audienceList.forEach(audience => { + command += ` --audiences "${audience}"`; + }); + + // Add roles if provided + const roleList = roles.split(',').map(r => r.trim()).filter(r => r); + roleList.forEach(role => { + command += ` --roles "${role}"`; + }); + + // Add scopes if provided + const scopeList = scopes.split(',').map(s => s.trim()).filter(s => s); + scopeList.forEach(scope => { + command += ` --scopes "${scope}"`; + }); + + // Add custom claims if provided + const claimList = claims.split(',').map(c => c.trim()).filter(c => c); + claimList.forEach(claim => { + if (claim.includes(':')) { + command += ` --claims "${claim}"`; + } + }); + + // Show progress and execute the command + await vscode.window.withProgress({ + location: vscode.ProgressLocation.Notification, + title: 'Generating JWT...', + cancellable: false + }, async () => { + try { + const result = await executeCommand(command); + + // Extract the token from the result (it should be on the last non-empty line) + const lines = result.split('\n').filter(line => line.trim()); + const token = lines[lines.length - 1].trim(); + + // Show the token in a dialog with copy option + const choice = await vscode.window.showInformationMessage( + 'JWT generated successfully!', + { modal: true }, + 'Copy to Clipboard', + 'Show Token' + ); + + if (choice === 'Copy to Clipboard') { + await vscode.env.clipboard.writeText(token); + vscode.window.showInformationMessage('JWT copied to clipboard'); + } else if (choice === 'Show Token') { + // Create a new untitled document to show the token + const document = await vscode.workspace.openTextDocument({ + content: `JWT Generated: ${new Date().toISOString()}\n\nToken: ${token}\n\nCommand used:\n${command}`, + language: 'plaintext' + }); + await vscode.window.showTextDocument(document); + } + } catch (error) { + vscode.window.showErrorMessage(`Failed to generate JWT token: ${error}`); + } + }); + + } catch (error) { + vscode.window.showErrorMessage(`Error in JWT generation: ${error}`); + } + })); }; diff --git a/src/test/extension.test.ts b/src/test/extension.test.ts index 9916879..3bb23f3 100644 --- a/src/test/extension.test.ts +++ b/src/test/extension.test.ts @@ -540,3 +540,11 @@ suite('diagnostic ranges', () => { assert.strictEqual(modifiedText, 'value', 'Should extract just the string content without quotes'); }); }); + +suite('Commands', () => { + test('JWT create command should be registered', async () => { + const commands = await vscode.commands.getCommands(); + const jwtCreateCommand = commands.find(cmd => cmd === 'dev-proxy-toolkit.jwt-create'); + assert.ok(jwtCreateCommand, 'JWT create command should be registered'); + }); +}); diff --git a/webpack.config.js b/webpack.config.js index d004aea..f916bf8 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -50,7 +50,7 @@ const extensionConfig = { patterns: [ { from: 'src/snippets.json', to: '[name][ext]' }, { from: 'src/icon.png', to: '[name][ext]' }, - { from: 'LICENCE', to: '[name][ext]' }, + { from: 'LICENSE', to: '[name][ext]' }, ], }), ],