Skip to content

Commit 5196f40

Browse files
committed
fix(middleware-signing): memoize temporary credentials
1 parent 49e2d61 commit 5196f40

File tree

3 files changed

+78
-6
lines changed

3 files changed

+78
-6
lines changed

packages/middleware-signing/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
"typescript": "~4.1.2"
2525
},
2626
"dependencies": {
27+
"@aws-sdk/property-provider": "3.8.0",
2728
"@aws-sdk/protocol-http": "3.6.1",
2829
"@aws-sdk/signature-v4": "3.6.1",
2930
"@aws-sdk/types": "3.6.1",
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import { HttpRequest } from "@aws-sdk/protocol-http";
2+
3+
import { resolveAwsAuthConfig } from "./configurations";
4+
5+
describe("resolveAwsAuthConfig", () => {
6+
const inputParams = {
7+
credentialDefaultProvider: () => () => Promise.resolve({ accessKeyId: "key", secretAccessKey: "secret" }),
8+
region: jest.fn().mockImplementation(() => Promise.resolve("us-foo-1")),
9+
regionInfoProvider: () => Promise.resolve({ hostname: "foo.com", partition: "aws" }),
10+
serviceId: "foo",
11+
sha256: jest.fn().mockReturnValue({
12+
update: jest.fn(),
13+
digest: jest.fn().mockReturnValue("SHA256 hash"),
14+
}),
15+
credentials: jest.fn().mockResolvedValue({ accessKeyId: "key", secretAccessKey: "secret" }),
16+
};
17+
18+
beforeEach(() => {
19+
jest.clearAllMocks();
20+
});
21+
22+
it("should memoize custom credential provider", async () => {
23+
const { signer: signerProvider } = resolveAwsAuthConfig(inputParams);
24+
const signer = await signerProvider();
25+
const request = new HttpRequest({});
26+
const repeats = 10;
27+
for (let i = 0; i < repeats; i++) {
28+
await signer.sign(request);
29+
}
30+
expect(inputParams.credentials).toBeCalledTimes(1);
31+
});
32+
33+
it("should refresh custom credential provider if expired", async () => {
34+
const FOUR_MINUTES_AND_59_SEC = 299 * 1000;
35+
const input = {
36+
...inputParams,
37+
credentials: jest
38+
.fn()
39+
.mockResolvedValueOnce({
40+
accessKeyId: "key",
41+
secretAccessKey: "secret",
42+
expiration: new Date(Date.now() + FOUR_MINUTES_AND_59_SEC),
43+
})
44+
.mockResolvedValue({ accessKeyId: "key", secretAccessKey: "secret" }),
45+
};
46+
const { signer: signerProvider } = resolveAwsAuthConfig(input);
47+
const signer = await signerProvider();
48+
const request = new HttpRequest({});
49+
const repeats = 10;
50+
for (let i = 0; i < repeats; i++) {
51+
await signer.sign(request);
52+
}
53+
expect(input.credentials).toBeCalledTimes(2);
54+
});
55+
});

packages/middleware-signing/src/configurations.ts

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { memoize } from "@aws-sdk/property-provider";
12
import { SignatureV4 } from "@aws-sdk/signature-v4";
23
import { Credentials, HashConstructor, Provider, RegionInfo, RegionInfoProvider, RequestSigner } from "@aws-sdk/types";
34

@@ -42,9 +43,13 @@ export interface AwsAuthResolvedConfig {
4243
signingEscapePath: boolean;
4344
systemClockOffset: number;
4445
}
45-
export function resolveAwsAuthConfig<T>(input: T & AwsAuthInputConfig & PreviouslyResolved): T & AwsAuthResolvedConfig {
46-
const credentials = input.credentials || input.credentialDefaultProvider(input as any);
47-
const normalizedCreds = normalizeProvider(credentials);
46+
47+
export const resolveAwsAuthConfig = <T>(
48+
input: T & AwsAuthInputConfig & PreviouslyResolved
49+
): T & AwsAuthResolvedConfig => {
50+
const normalizedCreds = input.credentials
51+
? normalizeCredentialProvider(input.credentials)
52+
: input.credentialDefaultProvider(input as any);
4853
const { signingEscapePath = true, systemClockOffset = input.systemClockOffset || 0, sha256 } = input;
4954
let signer: Provider<RequestSigner>;
5055
if (input.signer) {
@@ -81,12 +86,23 @@ export function resolveAwsAuthConfig<T>(input: T & AwsAuthInputConfig & Previous
8186
credentials: normalizedCreds,
8287
signer,
8388
};
84-
}
89+
};
8590

86-
function normalizeProvider<T>(input: T | Provider<T>): Provider<T> {
91+
const normalizeProvider = <T>(input: T | Provider<T>): Provider<T> => {
8792
if (typeof input === "object") {
8893
const promisified = Promise.resolve(input);
8994
return () => promisified;
9095
}
9196
return input as Provider<T>;
92-
}
97+
};
98+
99+
const normalizeCredentialProvider = (credentials: Credentials | Provider<Credentials>): Provider<Credentials> => {
100+
if (typeof credentials === "function") {
101+
return memoize(
102+
credentials,
103+
(credentials) => credentials.expiration !== undefined && credentials.expiration.getTime() - Date.now() < 300000,
104+
(credentials) => credentials.expiration !== undefined
105+
);
106+
}
107+
return normalizeProvider(credentials);
108+
};

0 commit comments

Comments
 (0)