Skip to content

Conversation

@vaibh123540
Copy link

This PR addresses an issue where users often provide URL-encoded passwords leading to authentication failures. The logic waits for a connection failure after which:

  1. It checks for URL-encoding a password using the decodeURIComponentmethod and regex search using /%[0-9A-Fa-f]{2}/.
  2. If it finds URL-encoding in the password, it suggests the user about it and offers a "retry with decoded password" option.
  3. If the user chooses to retry, the connection request is made again with decoded password.
  4. In case of failure user is prompted with the regular error message (no option for URL-encoding), in case of success the connection is established and the decoded password is updated in permanent storage so that the same error doesn't appear again in the next session.

Files Changed:
DocumentDBClusterItem.ts: Updated authenticateAndConnect catch block to include the described logic after a connection failure.

Testing performed:
I manually tested the workflow for an URL encoded password and a non URL encoded password. I tested both for a successful connection and an unsuccessful connection in the "retry with decoded password" logic.

The overall logic seems to have improved the User Experience.

@vaibh123540 vaibh123540 requested a review from a team as a code owner January 20, 2026 03:24
@vaibh123540
Copy link
Author

@microsoft-github-policy-service agree

@tnaum-ms tnaum-ms self-requested a review January 20, 2026 09:21
@tnaum-ms tnaum-ms linked an issue Jan 20, 2026 that may be closed by this pull request
@tnaum-ms
Copy link
Collaborator

Hey @vaibh123540!

Thank you so much for your contribution and for taking on this issue. Please excuse the delay in getting back to you.

You did a lot of things very well here:

  • The core detection for URL-encoded passwords: the regex plus decodeURIComponent.
  • You only offer the retry when the decoded password is actually different.
  • Handling possible errors from decodeURIComponent and persisting the decoded password on success + masking it in context.valuesToMask

I'd suggest improvements here:

  1. It would be good to only show the "retry with decoded password" option when we have a strong hint the failure is password‑related, and skip it for obvious network conditions (server offline, DNS resolution errors, etc.). You can reuse the kind of checks we already have (like the ECONNREFUSED handling in ClustersClient.ts) to distinguish those cases.

It's not easy to catch all "invalid password"-style error messages/codes across platforms (DocumentDB, Atlas, etc.), but we can have a "black list" for errors we are certain are network related. Here a generated example of what it could be:

/**
 * Network error codes where URL-encoding retry is not relevant.
 * These are OS-level error codes that indicate connectivity issues,
 * not authentication problems.
 */
const NETWORK_ERROR_PATTERNS = [
  'ECONNREFUSED', // Connection refused (server not running)
  'ENOTFOUND', // DNS lookup failed
  'ETIMEDOUT', // Connection timed out
  'ENETUNREACH', // Network unreachable
  'EHOSTUNREACH', // Host unreachable
  'EAI_AGAIN', // DNS temporary failure
  'ECONNRESET', // Connection reset by peer
  'EPIPE', // Broken pipe
  'CERT_', // Certificate errors (self-signed, expired, etc.)
  'self-signed certificate', // TLS/SSL cert issue
] as const;

function isNetworkError(errorMessage: string): boolean {
  const upperMessage = errorMessage.toUpperCase();
  return NETWORK_ERROR_PATTERNS.some((pattern) => upperMessage.includes(pattern.toUpperCase()));
}
  1. The retry path is big enough now that it would be worth extracting it into a small helper with a clear set of inputs/outputs (for example, a retryConnectionWithDecodedPassword(...) function). That keeps authenticateAndConnect easier to grasp / easier to work with code maintainers in the future.
  2. When you get a chance, adding some unit tests for the URL-encoding detection and retry flow would really help ensure we don't accidentally break this in the future. LLMs are particularly good at this..

Please let me know whether you've got the time to continue contributing to this PR or whether we should reassign it.

@tnaum-ms tnaum-ms changed the base branch from next to rel/release-0.7.1 January 27, 2026 06:36
@tnaum-ms
Copy link
Collaborator

@vaibh123540 FYI: I merged current next into your branch. Please run npm run l10n to update the dictionary, this will fix failing checks (this isn't urgent).

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR adds URL-encoding detection and retry logic for cluster authentication to improve user experience when users accidentally provide URL-encoded passwords. When a connection fails, the code detects if the password contains URL-encoded characters and offers a one-time retry with the decoded password.

Changes:

  • Added URL-encoding detection using regex pattern /%[0-9A-Fa-f]{2}/ and decodeURIComponent
  • Added conditional error dialog with "Retry with Decoded Password" button when URL-encoding is detected
  • Implemented retry logic that updates credentials in both cache and storage with the decoded password on success
  • Enhanced error messages to inform users about potential URL-encoding issues

Comment on lines +253 to 257
const result = await vscode.window.showErrorMessage(
l10n.t('Failed to connect to "{cluster}"', { cluster: this.cluster.name }),
{
modal: true,
detail:
l10n.t('Revisit connection details and try again.') +
'\n\n' +
l10n.t('Error: {error}', { error: (error as Error).message }),
},
{ modal: true, detail: detailMessage },
...buttons,
);
Copy link

Copilot AI Jan 27, 2026

Choose a reason for hiding this comment

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

The variable name 'result' shadows the outer variable 'result' from the callWithTelemetryAndErrorHandling callback function scope. This can lead to confusion and potential bugs. Consider renaming this variable to something more specific like 'userResponse' or 'dialogResult' to avoid shadowing.

Copilot uses AI. Check for mistakes.
let detailMessage: string =
l10n.t('Revisit connection details and try again.') +
'\n\n' +
l10n.t('Error: {error}', { error: (error as Error).message });
Copy link

Copilot AI Jan 27, 2026

Choose a reason for hiding this comment

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

The error handling uses (error as Error).message without verifying the error is actually an Error instance. While this works in most cases, it could fail if the caught error is not an Error object. For consistency with the retry error handling (line 289 which uses retryErr instanceof Error), consider using the same defensive pattern to ensure robust error message extraction.

Copilot uses AI. Check for mistakes.
l10n.t('Revisit connection details and try again.') +
'\n\n' +
l10n.t('Error: {error}', { error: (error as Error).message });

Copy link

Copilot AI Jan 27, 2026

Choose a reason for hiding this comment

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

The regex pattern /%[0-9A-Fa-f]{2}/ only detects passwords that contain URL-encoded characters, but it doesn't verify that the entire password is URL-encoded or that the encoding is valid. This could result in false positives for passwords that legitimately contain sequences like "%20" as part of their actual password text (not as encoding). Consider adding additional validation to reduce false positives, or document this limitation in a comment explaining that some legitimate passwords may trigger this detection.

Suggested change
// Heuristic: detect presence of URL-encoded-looking sequences in the password.
// This does not guarantee that the entire password is URL-encoded or that the
// encoding is valid; some legitimate passwords may contain sequences like "%20"
// and will still trigger this hint. We rely on a successful decode and a changed
// value (password !== decodedPassword) before offering the retry.

Copilot uses AI. Check for mistakes.
Comment on lines +228 to +304
// URL-Encoding checks
let decodedPassword: string | undefined;
try {
decodedPassword = password ? decodeURIComponent(password) : undefined;
} catch {
decodedPassword = undefined;
}

const retryButton = l10n.t('Retry with Decoded Password');
const buttons: string[] = [];

let detailMessage: string =
l10n.t('Revisit connection details and try again.') +
'\n\n' +
l10n.t('Error: {error}', { error: (error as Error).message });

const shouldOfferRetry =
password && /%[0-9A-Fa-f]{2}/.test(password) && decodedPassword && password !== decodedPassword;

if (shouldOfferRetry) {
detailMessage += '\n\n' + l10n.t('Your password appears to be URL-encoded.');
buttons.push(retryButton);
}

// Offer a one-time retry using the decoded value
const result = await vscode.window.showErrorMessage(
l10n.t('Failed to connect to "{cluster}"', { cluster: this.cluster.name }),
{
modal: true,
detail:
l10n.t('Revisit connection details and try again.') +
'\n\n' +
l10n.t('Error: {error}', { error: (error as Error).message }),
},
{ modal: true, detail: detailMessage },
...buttons,
);

if (result === retryButton && decodedPassword && username && authMethod) {
context.valuesToMask.push(decodedPassword);

CredentialCache.setAuthCredentials(
this.id,
authMethod,
connectionString.toString(),
{ connectionUser: username, connectionPassword: decodedPassword },
this.cluster.emulatorConfiguration,
connectionCredentials.secrets.entraIdAuthConfig,
);

await ClustersClient.deleteClient(this.id);

try {
clustersClient = await ClustersClient.getClient(this.id);

connectionCredentials.secrets.nativeAuthConfig = {
connectionUser: username,
connectionPassword: decodedPassword,
};
await ConnectionStorageService.save(connectionType, connectionCredentials, true);

ext.outputChannel.appendLine(
l10n.t('Connected to the cluster "{cluster}" using decoded password.', {
cluster: this.cluster.name,
}),
);
return clustersClient;
} catch (retryErr: unknown) {
const retryError = retryErr instanceof Error ? retryErr : new Error(String(retryErr));
ext.outputChannel.appendLine(l10n.t('Retry Error: {error}', { error: retryError.message }));

// Show final error screen without the URL hint
void vscode.window.showErrorMessage(
l10n.t('Failed to connect to "{cluster}"', { cluster: this.cluster.name }),
{
modal: true,
detail:
l10n.t('Revisit connection details and try again.') +
'\n\n' +
l10n.t('Error: {error}', { error: retryError.message }),
},
);
}
}
Copy link

Copilot AI Jan 27, 2026

Choose a reason for hiding this comment

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

The URL-encoding detection and retry logic lacks test coverage. This new feature introduces complex conditional logic, user interaction flows, and credential management that should be validated with automated tests. Consider adding tests for: (1) detection of URL-encoded passwords, (2) successful retry with decoded password, (3) failed retry with decoded password, (4) user declining the retry offer, and (5) edge cases like malformed encoding or passwords with legitimate percent sequences.

Copilot uses AI. Check for mistakes.
Comment on lines +245 to +246
password && /%[0-9A-Fa-f]{2}/.test(password) && decodedPassword && password !== decodedPassword;

Copy link

Copilot AI Jan 27, 2026

Choose a reason for hiding this comment

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

The retry logic doesn't verify that authMethod is specifically NativeAuth before offering to decode the password. While the condition on line 259 checks that username and password exist (which implicitly suggests native auth), it's possible for other auth methods to have these values set. Consider adding an explicit check authMethod === AuthMethodId.NativeAuth to the shouldOfferRetry condition on line 244 to ensure this feature only applies to native authentication where passwords are actually used.

Suggested change
password && /%[0-9A-Fa-f]{2}/.test(password) && decodedPassword && password !== decodedPassword;
authMethod === AuthMethodId.NativeAuth &&
password &&
/%[0-9A-Fa-f]{2}/.test(password) &&
decodedPassword &&
password !== decodedPassword;

Copilot uses AI. Check for mistakes.
@tnaum-ms tnaum-ms added this to the 0.7.1 milestone Jan 29, 2026
@vaibh123540
Copy link
Author

Hey @vaibh123540!

Thank you so much for your contribution and for taking on this issue. Please excuse the delay in getting back to you.

You did a lot of things very well here:

* The core detection for URL-encoded passwords: the regex plus `decodeURIComponent`.

* You only offer the retry when the decoded password is actually different.

* Handling possible errors from `decodeURIComponent` and persisting the decoded password on success + masking it in `context.valuesToMask`

I'd suggest improvements here:

1. It would be good to only show the "retry with decoded password" option when we have a strong hint the failure is password‑related, and skip it for obvious network conditions (server offline, DNS resolution errors, etc.). You can reuse the kind of checks we already have (like the `ECONNREFUSED` handling in `ClustersClient.ts`) to distinguish those cases.

It's not easy to catch all "invalid password"-style error messages/codes across platforms (DocumentDB, Atlas, etc.), but we can have a "black list" for errors we are certain are network related. Here a generated example of what it could be:

/**
 * Network error codes where URL-encoding retry is not relevant.
 * These are OS-level error codes that indicate connectivity issues,
 * not authentication problems.
 */
const NETWORK_ERROR_PATTERNS = [
  'ECONNREFUSED', // Connection refused (server not running)
  'ENOTFOUND', // DNS lookup failed
  'ETIMEDOUT', // Connection timed out
  'ENETUNREACH', // Network unreachable
  'EHOSTUNREACH', // Host unreachable
  'EAI_AGAIN', // DNS temporary failure
  'ECONNRESET', // Connection reset by peer
  'EPIPE', // Broken pipe
  'CERT_', // Certificate errors (self-signed, expired, etc.)
  'self-signed certificate', // TLS/SSL cert issue
] as const;

function isNetworkError(errorMessage: string): boolean {
  const upperMessage = errorMessage.toUpperCase();
  return NETWORK_ERROR_PATTERNS.some((pattern) => upperMessage.includes(pattern.toUpperCase()));
}
2. The retry path is big enough now that it would be worth extracting it into a small helper with a clear set of inputs/outputs (for example, a `retryConnectionWithDecodedPassword(...)` function). That keeps `authenticateAndConnect` easier to grasp / easier to work with code maintainers in the future.

3. When you get a chance, adding some unit tests for the URL-encoding detection and retry flow would really help ensure we don't accidentally break this in the future. LLMs are particularly good at this..

Please let me know whether you've got the time to continue contributing to this PR or whether we should reassign it.

@tnaum-ms Thank you for the detailed review! I will try to make the changes you suggested by the end of this week.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Improve: Warn users about an URL Encoded password

2 participants