diff --git a/.changeset/fluffy-ducks-grin.md b/.changeset/fluffy-ducks-grin.md new file mode 100644 index 00000000000..44f78383ef7 --- /dev/null +++ b/.changeset/fluffy-ducks-grin.md @@ -0,0 +1,5 @@ +--- +"@smithy/core": minor +--- + +Resolve auth schemes based on the preference list diff --git a/packages/core/src/middleware-http-auth-scheme/httpAuthSchemeMiddleware.ts b/packages/core/src/middleware-http-auth-scheme/httpAuthSchemeMiddleware.ts index 80251e7abfc..b66024f1359 100644 --- a/packages/core/src/middleware-http-auth-scheme/httpAuthSchemeMiddleware.ts +++ b/packages/core/src/middleware-http-auth-scheme/httpAuthSchemeMiddleware.ts @@ -6,6 +6,7 @@ import { HttpAuthSchemeParametersProvider, HttpAuthSchemeProvider, IdentityProviderConfig, + Provider, SelectedHttpAuthScheme, SerializeHandler, SerializeHandlerArguments, @@ -15,10 +16,13 @@ import { } from "@smithy/types"; import { getSmithyContext } from "@smithy/util-middleware"; +import { resolveAuthOptions } from "./resolveAuthOptions"; + /** * @internal */ export interface PreviouslyResolved { + authSchemePreference?: Provider; httpAuthSchemes: HttpAuthScheme[]; httpAuthSchemeProvider: HttpAuthSchemeProvider; } @@ -84,10 +88,14 @@ export const httpAuthSchemeMiddleware = const options = config.httpAuthSchemeProvider( await mwOptions.httpAuthSchemeParametersProvider(config, context as TContext, args.input) ); + + const authSchemePreference = config.authSchemePreference ? await config.authSchemePreference() : []; + const resolvedOptions = resolveAuthOptions(options, authSchemePreference); + const authSchemes = convertHttpAuthSchemesToMap(config.httpAuthSchemes); const smithyContext: HttpAuthSchemeMiddlewareSmithyContext = getSmithyContext(context); const failureReasons = []; - for (const option of options) { + for (const option of resolvedOptions) { const scheme = authSchemes.get(option.schemeId); if (!scheme) { failureReasons.push(`HttpAuthScheme \`${option.schemeId}\` was not enabled for this service.`); diff --git a/packages/core/src/middleware-http-auth-scheme/resolveAuthOptions.spec.ts b/packages/core/src/middleware-http-auth-scheme/resolveAuthOptions.spec.ts new file mode 100644 index 00000000000..4dd5f8f7ba1 --- /dev/null +++ b/packages/core/src/middleware-http-auth-scheme/resolveAuthOptions.spec.ts @@ -0,0 +1,42 @@ +import { HttpAuthOption } from "@smithy/types"; +import { describe, expect, it } from "vitest"; + +import { resolveAuthOptions } from "./resolveAuthOptions"; + +describe("resolveAuthSchemes", () => { + const sigv4 = "sigv4"; + const sigv4a = "sigv4a"; + + const mockSigV4AuthScheme = { schemeId: `aws.auth#${sigv4}` } as HttpAuthOption; + const mockSigV4aAuthScheme = { schemeId: `aws.auth#${sigv4a}` } as HttpAuthOption; + + it("should return candidate auth schemes is preference list is not available", () => { + const candidateAuthSchemes = [mockSigV4AuthScheme, mockSigV4aAuthScheme]; + expect(resolveAuthOptions(candidateAuthSchemes, [])).toEqual(candidateAuthSchemes); + + // @ts-expect-error case where callee incorrectly passes undefined + expect(resolveAuthOptions(candidateAuthSchemes)).toEqual(candidateAuthSchemes); + }); + + it("should return auth scheme from preference if it's available", () => { + expect(resolveAuthOptions([mockSigV4AuthScheme, mockSigV4aAuthScheme], [sigv4a])).toEqual([ + mockSigV4aAuthScheme, + mockSigV4AuthScheme, + ]); + + expect(resolveAuthOptions([mockSigV4AuthScheme, mockSigV4aAuthScheme], [sigv4a, sigv4])).toEqual([ + mockSigV4aAuthScheme, + mockSigV4AuthScheme, + ]); + + expect(resolveAuthOptions([mockSigV4AuthScheme, mockSigV4aAuthScheme], [sigv4, sigv4a])).toEqual([ + mockSigV4AuthScheme, + mockSigV4aAuthScheme, + ]); + }); + + it("should ignore auth scheme from preference if it's not available", () => { + expect(resolveAuthOptions([mockSigV4AuthScheme], [sigv4a])).toEqual([mockSigV4AuthScheme]); + expect(resolveAuthOptions([mockSigV4AuthScheme], ["sigv3"])).toEqual([mockSigV4AuthScheme]); + }); +}); diff --git a/packages/core/src/middleware-http-auth-scheme/resolveAuthOptions.ts b/packages/core/src/middleware-http-auth-scheme/resolveAuthOptions.ts new file mode 100644 index 00000000000..1fd3d88e129 --- /dev/null +++ b/packages/core/src/middleware-http-auth-scheme/resolveAuthOptions.ts @@ -0,0 +1,39 @@ +import { HttpAuthOption } from "@smithy/types"; + +/** + * Resolves list of auth options based on the supported ones, vs the preference list. + * + * @param candidateAuthOptions list of supported auth options selected by the standard + * resolution process (model-based, endpoints 2.0, etc.) + * @param authSchemePreference list of auth schemes preferred by user. + * @returns + */ +export const resolveAuthOptions = ( + candidateAuthOptions: HttpAuthOption[], + authSchemePreference: string[] +): HttpAuthOption[] => { + if (!authSchemePreference || authSchemePreference.length === 0) { + return candidateAuthOptions; + } + + // reprioritize candidates based on user's preference + const preferredAuthOptions = []; + + for (const preferredSchemeName of authSchemePreference) { + for (const candidateAuthOption of candidateAuthOptions) { + const candidateAuthSchemeName = candidateAuthOption.schemeId.split("#")[1]; + if (candidateAuthSchemeName === preferredSchemeName) { + preferredAuthOptions.push(candidateAuthOption); + } + } + } + + // add any remaining candidates that weren't in the preference list + for (const candidateAuthOption of candidateAuthOptions) { + if (!preferredAuthOptions.find(({ schemeId }) => schemeId === candidateAuthOption.schemeId)) { + preferredAuthOptions.push(candidateAuthOption); + } + } + + return preferredAuthOptions; +};