Skip to content
Draft
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 packages/core/package.nls.json
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@
"AWS.command.codecatalyst.login": "Connect to CodeCatalyst",
"AWS.command.codecatalyst.logout": "Sign out of CodeCatalyst",
"AWS.command.codecatalyst.signout": "Sign Out",
"AWS.command.codecatalyst.showUserInfo": "Show User Information",

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The localization string is missing additional entries for the error messages used in the showUserInfo function. This could cause localization issues.

"AWS.command.manageSubscription": "Manage Q Developer Pro Subscription",
"AWS.command.amazonq.explainCode": "Explain",
"AWS.command.amazonq.refactorCode": "Refactor",
Expand Down
40 changes: 40 additions & 0 deletions packages/core/src/codecatalyst/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,41 @@ export async function listCommands(): Promise<void> {
await vscode.commands.executeCommand('workbench.action.quickOpen', '> CodeCatalyst')
}

/** "Show User Info" command - displays current CodeCatalyst user details. */
export async function showUserInfo(client: CodeCatalystClient): Promise<void> {
try {
const userDetails = await client.verifySession()

const userInfoMessage = [
`**User Information**`,
``,
`**User ID:** ${userDetails.userId}`,
`**Username:** ${userDetails.userName}`,
`**Display Name:** ${userDetails.displayName}`,
`**Email:** ${userDetails.primaryEmail}`,
Comment on lines +40 to +46

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛑 Security Vulnerability: The user information displayed in the modal dialog may contain sensitive data that could be exposed to unauthorized users. The userDetails.userId and userDetails.primaryEmail fields should be handled with care to prevent information disclosure.

Suggested change
const userInfoMessage = [
`**User Information**`,
``,
`**User ID:** ${userDetails.userId}`,
`**Username:** ${userDetails.userName}`,
`**Display Name:** ${userDetails.displayName}`,
`**Email:** ${userDetails.primaryEmail}`,
const userInfoMessage = [
`**User Information**`,
``,
`**User ID:** ${userDetails.userId.substring(0, 8)}...`,
`**Username:** ${userDetails.userName}`,
`**Display Name:** ${userDetails.displayName}`,
`**Email:** ${userDetails.primaryEmail.replace(/(.{2})(.*)(@.*)/, '$1***$3')}`,
].join('\n')

].join('\n')

await vscode.window.showInformationMessage(
localize('aws.codecatalyst.userInfo.title', 'CodeCatalyst User Information'),
{
modal: true,
detail: userInfoMessage
}
)
} catch (error) {
if (error instanceof ToolkitError) {
await vscode.window.showErrorMessage(
localize('aws.codecatalyst.userInfo.error', 'Failed to get user information: {0}', error.message)
)
} else {
await vscode.window.showErrorMessage(
localize('aws.codecatalyst.userInfo.errorGeneric', 'Failed to get user information. Please ensure you are authenticated with CodeCatalyst.')
)
}
throw error
}
Comment on lines +57 to +67

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The error handling could be improved by providing more specific error messages based on the error type. Consider checking for authentication-specific errors and providing clearer guidance to users.

Suggested change
if (error instanceof ToolkitError) {
await vscode.window.showErrorMessage(
localize('aws.codecatalyst.userInfo.error', 'Failed to get user information: {0}', error.message)
)
} else {
await vscode.window.showErrorMessage(
localize('aws.codecatalyst.userInfo.errorGeneric', 'Failed to get user information. Please ensure you are authenticated with CodeCatalyst.')
)
}
throw error
}
} catch (error) {
if (error instanceof ToolkitError) {
if (error.code === 'NoConnectionBadState') {
await vscode.window.showErrorMessage(
localize('aws.codecatalyst.userInfo.notConnected', 'Not connected to CodeCatalyst. Please connect first.')
)
} else {
await vscode.window.showErrorMessage(
localize('aws.codecatalyst.userInfo.error', 'Failed to get user information: {0}', error.message)
)
}
} else {
await vscode.window.showErrorMessage(
localize('aws.codecatalyst.userInfo.errorGeneric', 'Failed to get user information. Please ensure you are authenticated with CodeCatalyst.')
)
}
throw error
}

}

/** "Clone CodeCatalyst Repository" command. */
export async function cloneCodeCatalystRepo(client: CodeCatalystClient, url?: vscode.Uri): Promise<void> {
let resource: { name: string; project: string; org: string }
Expand Down Expand Up @@ -239,6 +274,10 @@ export class CodeCatalystCommands {
return listCommands()
}

public showUserInfo(_?: VsCodeCommandArg) {
return this.withClient(showUserInfo)
}

public cloneRepo(_?: VsCodeCommandArg, ...args: WithClient<typeof cloneCodeCatalystRepo>) {
return this.withClient(cloneCodeCatalystRepo, ...args)
}
Expand Down Expand Up @@ -344,6 +383,7 @@ export class CodeCatalystCommands {
public static readonly declared = {
openResource: Commands.from(this).declareOpenResource('aws.codecatalyst.openResource'),
listCommands: Commands.from(this).declareListCommands('aws.codecatalyst.listCommands'),
showUserInfo: Commands.declare('aws.codecatalyst.showUserInfo', (commands: CodeCatalystCommands) => () => commands.showUserInfo()),
openSpace: Commands.from(this).declareOpenSpace('aws.codecatalyst.openOrg'),
openProject: Commands.from(this).declareOpenProject('aws.codecatalyst.openProject'),
openRepository: Commands.from(this).declareOpenRepository('aws.codecatalyst.openRepo'),
Expand Down
85 changes: 85 additions & 0 deletions packages/core/src/test/codecatalyst/commands.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
/*!
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/

import assert from 'assert'
import sinon from 'sinon'
import * as vscode from 'vscode'
import { showUserInfo } from '../../codecatalyst/commands'
import { CodeCatalystClient } from '../../shared/clients/codecatalystClient'

describe('CodeCatalyst Commands', function () {
let sandbox: sinon.SinonSandbox
let mockClient: sinon.SinonStubbedInstance<CodeCatalystClient>
let showInformationMessageStub: sinon.SinonStub
let showErrorMessageStub: sinon.SinonStub

beforeEach(function () {
sandbox = sinon.createSandbox()
mockClient = sandbox.createStubInstance(Object as any)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The mock client creation is incorrect. Object as any doesn't provide the proper type structure for CodeCatalystClient. This could lead to runtime errors in tests.

showInformationMessageStub = sandbox.stub(vscode.window, 'showInformationMessage')
showErrorMessageStub = sandbox.stub(vscode.window, 'showErrorMessage')
})

afterEach(function () {
sandbox.restore()
})

describe('showUserInfo', function () {
it('displays user information when client returns valid data', async function () {
const mockUserDetails = {
userId: 'user-123',
userName: 'testuser',
displayName: 'Test User',
primaryEmail: '[email protected]'
}

mockClient.verifySession.resolves(mockUserDetails)

await showUserInfo(mockClient as any)

assert(showInformationMessageStub.calledOnce)
const [message, options] = showInformationMessageStub.firstCall.args
assert.strictEqual(message, 'CodeCatalyst User Information')
assert(options.modal)
assert(options.detail.includes('Test User'))
assert(options.detail.includes('[email protected]'))
})
it('displays error message when client fails with ToolkitError', async function () {
const toolkitError = new ToolkitError('Authentication failed', { code: 'NoConnectionBadState' })
mockClient.verifySession.rejects(toolkitError)

try {
await showUserInfo(mockClient as any)
assert.fail('Expected error to be thrown')
} catch (error) {
assert(showErrorMessageStub.calledOnce)
const errorMessage = showErrorMessageStub.firstCall.args[0]
assert(errorMessage.includes('Authentication failed'))
}
})

it('displays generic error message when client fails with generic error', async function () {
mockClient.verifySession.rejects(new Error('Network error'))

try {
await showUserInfo(mockClient as any)
assert.fail('Expected error to be thrown')
} catch (error) {
assert(showErrorMessageStub.calledOnce)
const errorMessage = showErrorMessageStub.firstCall.args[0]
assert(errorMessage.includes('Please ensure you are authenticated'))
}
})
mockClient.verifySession.rejects(new Error('Authentication failed'))

try {
await showUserInfo(mockClient as any)
assert.fail('Expected error to be thrown')
} catch (error) {
assert(showErrorMessageStub.calledOnce)
}
})
})
})

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The test file is missing a newline at the end, which violates common coding standards and may cause issues with some tools.

13 changes: 13 additions & 0 deletions packages/toolkit/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -1424,6 +1424,13 @@
"when": "view == aws.codecatalyst && !isCloud9 && aws.codecatalyst.connected",
"group": "1_codeCatalyst@1"
},
{
"command": "aws.codecatalyst.showUserInfo",
"when": "view == aws.codecatalyst && !isCloud9 && aws.codecatalyst.connected",
"group": "1_codeCatalyst@3"
},
"group": "1_codeCatalyst@2"
},
{
"command": "aws.codecatalyst.manageConnections",
"when": "view == aws.codecatalyst && !isCloud9 && !aws.codecatalyst.connected",
Expand Down Expand Up @@ -2540,6 +2547,12 @@
"icon": "$(debug-disconnect)",
"enablement": "isCloud9 || !aws.isWebExtHost"
},
{
"command": "aws.codecatalyst.showUserInfo",
"title": "%AWS.command.codecatalyst.showUserInfo%",
"category": "AWS",
"enablement": "isCloud9 || !aws.isWebExtHost"
},
{
"command": "aws.toolkit.auth.addConnection",
"title": "%AWS.command.auth.addConnection%",
Expand Down
Loading