Skip to content

Commit 50867ab

Browse files
feat(smithy-typescript-codegen): allow deferred resolution for api key config (#588)
Co-authored-by: Eduardo Rodrigues <[email protected]>
1 parent 42ca023 commit 50867ab

File tree

4 files changed

+47
-40
lines changed

4 files changed

+47
-40
lines changed

smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/TypeScriptDependency.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,9 @@ public enum TypeScriptDependency implements SymbolDependencyContainer {
8585
AWS_SDK_FETCH_HTTP_HANDLER("dependencies", "@aws-sdk/fetch-http-handler", false),
8686
AWS_SDK_NODE_HTTP_HANDLER("dependencies", "@aws-sdk/node-http-handler", false),
8787

88+
// Conditionally added when setting the auth middleware.
89+
AWS_SDK_UTIL_MIDDLEWARE("dependencies", "@aws-sdk/util-middleware", false),
90+
8891
// Conditionally added if a event stream shape is found anywhere in the model
8992
AWS_SDK_EVENTSTREAM_SERDE_CONFIG_RESOLVER("dependencies", "@aws-sdk/eventstream-serde-config-resolver",
9093
false),

smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/integration/AddHttpApiKeyAuthPlugin.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
import software.amazon.smithy.model.traits.OptionalAuthTrait;
3333
import software.amazon.smithy.typescript.codegen.CodegenUtils;
3434
import software.amazon.smithy.typescript.codegen.TypeScriptCodegenContext;
35+
import software.amazon.smithy.typescript.codegen.TypeScriptDependency;
3536
import software.amazon.smithy.typescript.codegen.TypeScriptSettings;
3637
import software.amazon.smithy.typescript.codegen.TypeScriptWriter;
3738
import software.amazon.smithy.utils.IoUtils;
@@ -134,6 +135,7 @@ private void writeAdditionalFiles(
134135
writerFactory.accept(
135136
Paths.get(CodegenUtils.SOURCE_FOLDER, "middleware", INTEGRATION_NAME, "index.ts").toString(),
136137
writer -> {
138+
writer.addDependency(TypeScriptDependency.AWS_SDK_UTIL_MIDDLEWARE);
137139
String source = IoUtils.readUtf8Resource(getClass(), "http-api-key-auth.ts");
138140
writer.write("$L$L", noTouchNoticePrefix, "http-api-key-auth.ts");
139141
writer.write("$L", source);

smithy-typescript-codegen/src/main/resources/software/amazon/smithy/typescript/codegen/integration/http-api-key-auth.spec.ts

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,16 @@
1+
import { HttpRequest } from "@aws-sdk/protocol-http";
2+
import { MiddlewareStack } from "@aws-sdk/types";
3+
14
import {
25
getHttpApiKeyAuthPlugin,
36
httpApiKeyAuthMiddleware,
47
resolveHttpApiKeyAuthConfig,
58
} from "./index";
6-
import { HttpRequest } from "@aws-sdk/protocol-http";
79

810
describe("resolveHttpApiKeyAuthConfig", () => {
911
it("should return the input unchanged", () => {
1012
const config = {
11-
apiKey: "exampleApiKey",
13+
apiKey: () => Promise.resolve("example-api-key"),
1214
};
1315
expect(resolveHttpApiKeyAuthConfig(config)).toEqual(config);
1416
});
@@ -18,32 +20,32 @@ describe("getHttpApiKeyAuthPlugin", () => {
1820
it("should apply the middleware to the stack", () => {
1921
const plugin = getHttpApiKeyAuthPlugin(
2022
{
21-
apiKey: "exampleApiKey",
23+
apiKey: () => Promise.resolve("example-api-key"),
2224
},
2325
{
2426
in: "query",
2527
name: "key",
2628
}
2729
);
2830

29-
const mockAdd = jest.fn();
31+
const mockApplied = jest.fn();
3032
const mockOther = jest.fn();
3133

3234
// TODO there's got to be a better way to do this mocking
3335
plugin.applyToStack({
34-
add: mockAdd,
36+
addRelativeTo: mockApplied,
3537
// We don't expect any of these others to be called.
36-
addRelativeTo: mockOther,
38+
add: mockOther,
3739
concat: mockOther,
3840
resolve: mockOther,
3941
applyToStack: mockOther,
4042
use: mockOther,
4143
clone: mockOther,
4244
remove: mockOther,
4345
removeByTag: mockOther,
44-
});
46+
} as unknown as MiddlewareStack<any, any>);
4547

46-
expect(mockAdd.mock.calls.length).toEqual(1);
48+
expect(mockApplied.mock.calls.length).toEqual(1);
4749
expect(mockOther.mock.calls.length).toEqual(0);
4850
});
4951
});
@@ -59,7 +61,7 @@ describe("httpApiKeyAuthMiddleware", () => {
5961
it("should set the query parameter if the location is `query`", async () => {
6062
const middleware = httpApiKeyAuthMiddleware(
6163
{
62-
apiKey: "exampleApiKey",
64+
apiKey: () => Promise.resolve("example-api-key"),
6365
},
6466
{
6567
in: "query",
@@ -77,7 +79,7 @@ describe("httpApiKeyAuthMiddleware", () => {
7779
expect(mockNextHandler.mock.calls.length).toEqual(1);
7880
expect(
7981
mockNextHandler.mock.calls[0][0].request.query.key
80-
).toBe("exampleApiKey");
82+
).toBe("example-api-key");
8183
});
8284

8385
it("should skip if the api key has not been set", async () => {
@@ -122,7 +124,7 @@ describe("httpApiKeyAuthMiddleware", () => {
122124
it("should set the API key in the lower-cased named header", async () => {
123125
const middleware = httpApiKeyAuthMiddleware(
124126
{
125-
apiKey: "exampleApiKey",
127+
apiKey: () => Promise.resolve("example-api-key"),
126128
},
127129
{
128130
in: "header",
@@ -140,13 +142,13 @@ describe("httpApiKeyAuthMiddleware", () => {
140142
expect(mockNextHandler.mock.calls.length).toEqual(1);
141143
expect(
142144
mockNextHandler.mock.calls[0][0].request.headers.authorization
143-
).toBe("exampleApiKey");
145+
).toBe("example-api-key");
144146
});
145147

146148
it("should set the API key in the named header with the provided scheme", async () => {
147149
const middleware = httpApiKeyAuthMiddleware(
148150
{
149-
apiKey: "exampleApiKey",
151+
apiKey: () => Promise.resolve("example-api-key"),
150152
},
151153
{
152154
in: "header",
@@ -164,7 +166,7 @@ describe("httpApiKeyAuthMiddleware", () => {
164166
expect(mockNextHandler.mock.calls.length).toEqual(1);
165167
expect(
166168
mockNextHandler.mock.calls[0][0].request.headers.authorization
167-
).toBe("exampleScheme exampleApiKey");
169+
).toBe("exampleScheme example-api-key");
168170
});
169171
});
170172
});

smithy-typescript-codegen/src/main/resources/software/amazon/smithy/typescript/codegen/integration/http-api-key-auth.ts

Lines changed: 26 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,8 @@
11
// derived from https://github.com/aws/aws-sdk-js-v3/blob/e35f78c97fa6710ff9c444351893f0f06755e771/packages/middleware-endpoint-discovery/src/endpointDiscoveryMiddleware.ts
22

33
import { HttpRequest } from "@aws-sdk/protocol-http";
4-
import {
5-
AbsoluteLocation,
6-
BuildHandlerOptions,
7-
BuildMiddleware,
8-
Pluggable,
9-
} from "@aws-sdk/types";
4+
import { BuildMiddleware, Pluggable, Provider, RelativeMiddlewareOptions } from "@aws-sdk/types";
5+
import { normalizeProvider } from "@aws-sdk/util-middleware";
106

117
interface HttpApiKeyAuthMiddlewareConfig {
128
/**
@@ -36,28 +32,31 @@ export interface HttpApiKeyAuthInputConfig {
3632
*
3733
* This is optional because some operations may not require an API key.
3834
*/
39-
apiKey?: string;
35+
apiKey?: string | Provider<string>;
4036
}
4137

42-
interface PreviouslyResolved {}
38+
export interface ApiKeyPreviouslyResolved {}
4339

4440
export interface HttpApiKeyAuthResolvedConfig {
4541
/**
4642
* The API key to use when making requests.
4743
*
4844
* This is optional because some operations may not require an API key.
4945
*/
50-
apiKey?: string;
46+
apiKey?: Provider<string>;
5147
}
5248

5349
// We have to provide a resolve function when we have config, even if it doesn't
5450
// actually do anything to the input value. "If any of inputConfig, resolvedConfig,
5551
// or resolveFunction are set, then all of inputConfig, resolvedConfig, and
5652
// resolveFunction must be set."
5753
export function resolveHttpApiKeyAuthConfig<T>(
58-
input: T & PreviouslyResolved & HttpApiKeyAuthInputConfig
54+
input: T & ApiKeyPreviouslyResolved & HttpApiKeyAuthInputConfig,
5955
): T & HttpApiKeyAuthResolvedConfig {
60-
return input;
56+
return {
57+
...input,
58+
apiKey: input.apiKey ? normalizeProvider(input.apiKey) : undefined,
59+
};
6160
}
6261

6362
/**
@@ -69,23 +68,25 @@ export function resolveHttpApiKeyAuthConfig<T>(
6968
* prefixed with a scheme. If the trait says to put the API key into a named
7069
* query parameter, that query parameter will be used.
7170
*
72-
* @param resolvedConfig the client configuration. Must include the API key value.
73-
* @param options the plugin options (location of the parameter, name, and optional scheme)
71+
* @param pluginConfig the client configuration. Includes the function that will return the API key value.
72+
* @param middlewareConfig the plugin options (location of the parameter, name, and optional scheme)
7473
* @returns a function that processes the HTTP request and passes it on to the next handler
7574
*/
7675
export const httpApiKeyAuthMiddleware =
7776
<Input extends object, Output extends object>(
7877
pluginConfig: HttpApiKeyAuthResolvedConfig,
79-
middlewareConfig: HttpApiKeyAuthMiddlewareConfig
78+
middlewareConfig: HttpApiKeyAuthMiddlewareConfig,
8079
): BuildMiddleware<Input, Output> =>
8180
(next) =>
8281
async (args) => {
8382
if (!HttpRequest.isInstance(args.request)) return next(args);
8483

84+
const apiKey = pluginConfig.apiKey && (await pluginConfig.apiKey());
85+
8586
// This middleware will not be injected if the operation has the @optionalAuth trait.
8687
// We don't know if we're the only auth middleware, so let the service deal with the
8788
// absence of the API key (or let other middleware do its job).
88-
if (!pluginConfig.apiKey) {
89+
if (!apiKey) {
8990
return next(args);
9091
}
9192

@@ -98,36 +99,35 @@ export const httpApiKeyAuthMiddleware =
9899
...(middlewareConfig.in === "header" && {
99100
// Set the header, even if it's already been set.
100101
[middlewareConfig.name.toLowerCase()]: middlewareConfig.scheme
101-
? `${middlewareConfig.scheme} ${pluginConfig.apiKey}`
102-
: pluginConfig.apiKey,
102+
? `${middlewareConfig.scheme} ${apiKey}`
103+
: apiKey,
103104
}),
104105
},
105106
query: {
106107
...args.request.query,
107108
// Set the query parameter, even if it's already been set.
108-
...(middlewareConfig.in === "query" && { [middlewareConfig.name]: pluginConfig.apiKey }),
109+
...(middlewareConfig.in === "query" && { [middlewareConfig.name]: apiKey }),
109110
},
110111
},
111112
});
112113
};
113114

114-
export const httpApiKeyAuthMiddlewareOptions: BuildHandlerOptions &
115-
AbsoluteLocation = {
115+
export const httpApiKeyAuthMiddlewareOptions: RelativeMiddlewareOptions = {
116116
name: "httpApiKeyAuthMiddleware",
117-
step: "build",
118-
priority: "low",
119-
tags: ["AUTHORIZATION"],
117+
tags: ["APIKEY", "AUTH"],
118+
relation: "after",
119+
toMiddleware: "retryMiddleware",
120120
override: true,
121121
};
122122

123123
export const getHttpApiKeyAuthPlugin = (
124124
pluginConfig: HttpApiKeyAuthResolvedConfig,
125-
middlewareConfig: HttpApiKeyAuthMiddlewareConfig
125+
middlewareConfig: HttpApiKeyAuthMiddlewareConfig,
126126
): Pluggable<any, any> => ({
127127
applyToStack: (clientStack) => {
128-
clientStack.add(
128+
clientStack.addRelativeTo(
129129
httpApiKeyAuthMiddleware(pluginConfig, middlewareConfig),
130-
httpApiKeyAuthMiddlewareOptions
130+
httpApiKeyAuthMiddlewareOptions,
131131
);
132132
},
133133
});

0 commit comments

Comments
 (0)