diff --git a/packages/amazonq/.changes/next-release/Bug Fix-54ae3b04-fe8b-40e8-866a-7f9f73f3c7cb.json b/packages/amazonq/.changes/next-release/Bug Fix-54ae3b04-fe8b-40e8-866a-7f9f73f3c7cb.json new file mode 100644 index 00000000000..0493b348d40 --- /dev/null +++ b/packages/amazonq/.changes/next-release/Bug Fix-54ae3b04-fe8b-40e8-866a-7f9f73f3c7cb.json @@ -0,0 +1,4 @@ +{ + "type": "Bug Fix", + "description": "Auth: `SyntaxError` causing unexpected SSO logout" +} diff --git a/packages/core/src/auth/sso/clients.ts b/packages/core/src/auth/sso/clients.ts index ddfc36128b8..79ed0db4799 100644 --- a/packages/core/src/auth/sso/clients.ts +++ b/packages/core/src/auth/sso/clients.ts @@ -28,7 +28,7 @@ import { pageableToCollection, partialClone } from '../../shared/utilities/colle import { assertHasProps, isNonNullable, RequiredProps, selectFrom } from '../../shared/utilities/tsUtils' import { getLogger } from '../../shared/logger' import { SsoAccessTokenProvider } from './ssoAccessTokenProvider' -import { ToolkitError, getHttpStatusCode, getReasonFromSyntaxError, isClientFault } from '../../shared/errors' +import { AwsClientResponseError, isClientFault } from '../../shared/errors' import { DevSettings } from '../../shared/settings' import { SdkError } from '@aws-sdk/types' import { HttpRequest, HttpResponse } from '@smithy/protocol-http' @@ -90,20 +90,8 @@ export class OidcClient { try { response = await this.client.createToken(request as CreateTokenRequest) } catch (err) { - // In rare cases the SDK client may get unexpected data from its API call. - // This will throw a SyntaxError that contains the returned data. - if (err instanceof SyntaxError) { - getLogger().error(`createToken: SSOIDC Client received an unexpected non-JSON response: %O`, err) - throw new ToolkitError( - `SDK Client unexpected error response: data response code: ${getHttpStatusCode(err)}, data reason: ${getReasonFromSyntaxError(err)}`, - { - code: `${getHttpStatusCode(err)}`, - cause: err, - } - ) - } else { - throw err - } + const newError = AwsClientResponseError.instanceIf(err) + throw newError } assertHasProps(response, 'accessToken', 'expiresIn') diff --git a/packages/core/src/codewhispererChat/controllers/chat/controller.ts b/packages/core/src/codewhispererChat/controllers/chat/controller.ts index 77e446e468d..ca3e9313c5b 100644 --- a/packages/core/src/codewhispererChat/controllers/chat/controller.ts +++ b/packages/core/src/codewhispererChat/controllers/chat/controller.ts @@ -48,7 +48,7 @@ import { LspController } from '../../../amazonq/lsp/lspController' import { CodeWhispererSettings } from '../../../codewhisperer/util/codewhispererSettings' import { getSelectedCustomization } from '../../../codewhisperer/util/customizationUtil' import { FeatureConfigProvider } from '../../../shared/featureConfig' -import { getHttpStatusCode, getReasonFromSyntaxError } from '../../../shared/errors' +import { getHttpStatusCode, AwsClientResponseError } from '../../../shared/errors' export interface ChatControllerMessagePublishers { readonly processPromptChatMessage: MessagePublisher @@ -299,7 +299,7 @@ export class ChatController { errorMessage = e.toUpperCase() } else if (e instanceof SyntaxError) { // Workaround to handle case when LB returns web-page with error and our client doesn't return proper exception - errorMessage = getReasonFromSyntaxError(e) ?? defaultMessage + errorMessage = AwsClientResponseError.tryExtractReasonFromSyntaxError(e) ?? defaultMessage } else if (e instanceof CodeWhispererStreamingServiceException) { errorMessage = e.message requestID = e.$metadata.requestId diff --git a/packages/core/src/shared/errors.ts b/packages/core/src/shared/errors.ts index 467673b3179..5494758b28b 100644 --- a/packages/core/src/shared/errors.ts +++ b/packages/core/src/shared/errors.ts @@ -789,11 +789,11 @@ export function isNetworkError(err?: unknown): err is Error & { code: string } { if ( isVSCodeProxyError(err) || isSocketTimeoutError(err) || - isHttpSyntaxError(err) || isEnoentError(err) || isEaccesError(err) || isEbadfError(err) || - isEconnRefusedError(err) + isEconnRefusedError(err) || + err instanceof AwsClientResponseError ) { return true } @@ -847,18 +847,6 @@ function isSocketTimeoutError(err: Error): boolean { return isError(err, 'TimeoutError', 'Connection timed out after') } -/** - * Expected JSON response from HTTP request, but got an error HTML error page instead. - * - * IMPORTANT: - * - * This function is influenced by {@link getReasonFromSyntaxError()} since it modifies the error - * message with the real underlying reason, instead of the default "Unexpected token" message. - */ -function isHttpSyntaxError(err: Error): boolean { - return isError(err, 'SyntaxError', 'SDK Client unexpected error response') -} - /** * We were seeing errors of ENOENT for the oidc FQDN (eg: oidc.us-east-1.amazonaws.com) during the SSO flow. * Our assumption is that this is an intermittent error. @@ -887,30 +875,69 @@ function isError(err: Error, id: string, messageIncludes: string = '') { } /** - * AWS SDK clients may rarely make requests that results in something other than JSON data - * being returned (e.g html). This will cause the client to throw a SyntaxError as a result + * AWS SDK clients make requests with the expected result to be JSON data. + * But in some cases the request may fail and result in an error HTML page being returned instead + * of the JSON. This will cause the client to throw a `SyntaxError` as a result * of attempt to deserialize the non-JSON data. - * While the contents of the response may contain sensitive information, there may be a reason - * for failure embedded. This function attempts to extract that reason. * - * Example error message before extracting: - * "Unexpected token '<', "