Skip to content

Commit f2a003b

Browse files
committed
Add JWT create command. Closes #137
Closes #137
1 parent 8203f6c commit f2a003b

File tree

6 files changed

+176
-1
lines changed

6 files changed

+176
-1
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1414
- MCP Server: Dev Proxy
1515
- 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
1616
- Code action: Update single or all plugin paths to DevProxy.Plugins.dll
17+
- Command: `dev-proxy-toolkit.jwt-create` - Generate JWT with guided input for testing purposes
1718

1819
### Changed:
1920

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ The following sections describe the features that the extension contributes to V
3333
- `Dev Proxy Toolkit: Open configuration file`- Only available when Dev Proxy is installed
3434
- `Dev Proxy Toolkit: Create configuration file`- Only available when Dev Proxy is installed
3535
- `Dev Proxy Toolkit: Discover URLs to watch` - Only available when Dev Proxy is not running
36+
- `Dev Proxy Toolkit: Generate JWT` - Only available when Dev Proxy is installed
3637

3738
### Diagnostics
3839

package.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,12 @@
8383
"category": "Dev Proxy Toolkit",
8484
"icon": "$(debug-start)",
8585
"enablement": "!isDevProxyRunning"
86+
},
87+
{
88+
"command": "dev-proxy-toolkit.jwt-create",
89+
"title": "Generate JWT",
90+
"category": "Dev Proxy Toolkit",
91+
"enablement": "isDevProxyInstalled"
8692
}
8793
],
8894
"mcpServerDefinitionProviders": [

src/commands.ts

Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -369,4 +369,163 @@ export const registerCommands = (context: vscode.ExtensionContext, configuration
369369
? terminal.sendText(`${devProxyExe} --discover --watch-process-names ${processNames.trim()}`)
370370
: terminal.sendText(`${devProxyExe} --discover`);
371371
}));
372+
373+
context.subscriptions.push(
374+
vscode.commands.registerCommand('dev-proxy-toolkit.jwt-create', async () => {
375+
try {
376+
// Collect JWT parameters through input dialogs with sensible defaults
377+
const name = await vscode.window.showInputBox({
378+
prompt: 'Enter the name of the user to create the token for',
379+
placeHolder: 'Dev Proxy',
380+
value: 'Dev Proxy',
381+
title: 'JWT Generation - User Name'
382+
});
383+
384+
if (name === undefined) {
385+
return; // User cancelled
386+
}
387+
388+
const issuer = await vscode.window.showInputBox({
389+
prompt: 'Enter the issuer of the token',
390+
placeHolder: 'dev-proxy',
391+
value: 'dev-proxy',
392+
title: 'JWT Generation - Issuer'
393+
});
394+
395+
if (issuer === undefined) {
396+
return; // User cancelled
397+
}
398+
399+
const audiences = await vscode.window.showInputBox({
400+
prompt: 'Enter the audiences (comma-separated for multiple)',
401+
placeHolder: 'https://myserver.com',
402+
value: 'https://myserver.com',
403+
title: 'JWT Generation - Audiences'
404+
});
405+
406+
if (audiences === undefined) {
407+
return; // User cancelled
408+
}
409+
410+
const roles = await vscode.window.showInputBox({
411+
prompt: 'Enter roles (comma-separated, leave empty for none)',
412+
placeHolder: 'admin,user',
413+
value: '',
414+
title: 'JWT Generation - Roles (Optional)'
415+
});
416+
417+
if (roles === undefined) {
418+
return; // User cancelled
419+
}
420+
421+
const scopes = await vscode.window.showInputBox({
422+
prompt: 'Enter scopes (comma-separated, leave empty for none)',
423+
placeHolder: 'read,write',
424+
value: '',
425+
title: 'JWT Generation - Scopes (Optional)'
426+
});
427+
428+
if (scopes === undefined) {
429+
return; // User cancelled
430+
}
431+
432+
const claims = await vscode.window.showInputBox({
433+
prompt: 'Enter custom claims in format name:value (comma-separated, leave empty for none)',
434+
placeHolder: 'custom:claim,department:engineering',
435+
value: '',
436+
title: 'JWT Generation - Custom Claims (Optional)'
437+
});
438+
439+
if (claims === undefined) {
440+
return; // User cancelled
441+
}
442+
443+
const validFor = await vscode.window.showInputBox({
444+
prompt: 'Enter token validity duration in minutes',
445+
placeHolder: '60',
446+
value: '60',
447+
title: 'JWT Generation - Validity Duration',
448+
validateInput: (value: string) => {
449+
const num = parseInt(value);
450+
if (isNaN(num) || num <= 0) {
451+
return 'Please enter a positive number';
452+
}
453+
return undefined;
454+
}
455+
});
456+
457+
if (validFor === undefined) {
458+
return; // User cancelled
459+
}
460+
461+
// Build the command with all parameters
462+
let command = `${devProxyExe} jwt create --name "${name}" --issuer "${issuer}" --valid-for ${validFor}`;
463+
464+
// Add audiences (can have multiple)
465+
const audienceList = audiences.split(',').map(a => a.trim()).filter(a => a);
466+
audienceList.forEach(audience => {
467+
command += ` --audiences "${audience}"`;
468+
});
469+
470+
// Add roles if provided
471+
const roleList = roles.split(',').map(r => r.trim()).filter(r => r);
472+
roleList.forEach(role => {
473+
command += ` --roles "${role}"`;
474+
});
475+
476+
// Add scopes if provided
477+
const scopeList = scopes.split(',').map(s => s.trim()).filter(s => s);
478+
scopeList.forEach(scope => {
479+
command += ` --scopes "${scope}"`;
480+
});
481+
482+
// Add custom claims if provided
483+
const claimList = claims.split(',').map(c => c.trim()).filter(c => c);
484+
claimList.forEach(claim => {
485+
if (claim.includes(':')) {
486+
command += ` --claims "${claim}"`;
487+
}
488+
});
489+
490+
// Show progress and execute the command
491+
await vscode.window.withProgress({
492+
location: vscode.ProgressLocation.Notification,
493+
title: 'Generating JWT...',
494+
cancellable: false
495+
}, async () => {
496+
try {
497+
const result = await executeCommand(command);
498+
499+
// Extract the token from the result (it should be on the last non-empty line)
500+
const lines = result.split('\n').filter(line => line.trim());
501+
const token = lines[lines.length - 1].trim();
502+
503+
// Show the token in a dialog with copy option
504+
const choice = await vscode.window.showInformationMessage(
505+
'JWT generated successfully!',
506+
{ modal: true },
507+
'Copy to Clipboard',
508+
'Show Token'
509+
);
510+
511+
if (choice === 'Copy to Clipboard') {
512+
await vscode.env.clipboard.writeText(token);
513+
vscode.window.showInformationMessage('JWT copied to clipboard');
514+
} else if (choice === 'Show Token') {
515+
// Create a new untitled document to show the token
516+
const document = await vscode.workspace.openTextDocument({
517+
content: `JWT Generated: ${new Date().toISOString()}\n\nToken: ${token}\n\nCommand used:\n${command}`,
518+
language: 'plaintext'
519+
});
520+
await vscode.window.showTextDocument(document);
521+
}
522+
} catch (error) {
523+
vscode.window.showErrorMessage(`Failed to generate JWT token: ${error}`);
524+
}
525+
});
526+
527+
} catch (error) {
528+
vscode.window.showErrorMessage(`Error in JWT generation: ${error}`);
529+
}
530+
}));
372531
};

src/test/extension.test.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -540,3 +540,11 @@ suite('diagnostic ranges', () => {
540540
assert.strictEqual(modifiedText, 'value', 'Should extract just the string content without quotes');
541541
});
542542
});
543+
544+
suite('Commands', () => {
545+
test('JWT create command should be registered', async () => {
546+
const commands = await vscode.commands.getCommands();
547+
const jwtCreateCommand = commands.find(cmd => cmd === 'dev-proxy-toolkit.jwt-create');
548+
assert.ok(jwtCreateCommand, 'JWT create command should be registered');
549+
});
550+
});

webpack.config.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ const extensionConfig = {
5050
patterns: [
5151
{ from: 'src/snippets.json', to: '[name][ext]' },
5252
{ from: 'src/icon.png', to: '[name][ext]' },
53-
{ from: 'LICENCE', to: '[name][ext]' },
53+
{ from: 'LICENSE', to: '[name][ext]' },
5454
],
5555
}),
5656
],

0 commit comments

Comments
 (0)