Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:

Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
6 changes: 6 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": [
Expand Down
159 changes: 159 additions & 0 deletions src/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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}`);
}
}));
};
8 changes: 8 additions & 0 deletions src/test/extension.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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');
});
});
2 changes: 1 addition & 1 deletion webpack.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -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]' },
],
}),
],
Expand Down