Skip to content

Commit 68fab16

Browse files
committed
fix(credential-provider-node): update cache on forceRefresh in memoizeChain
Previously, calling the memoized provider with forceRefresh: true would fetch fresh credentials but not update the internal cache. Subsequent calls without forceRefresh would still return the stale cached value. This caused issues for consumers like RDS Signer that force-refresh near-expiry credentials — the refreshed credentials were used once but lost on the next invocation.
1 parent 4b03542 commit 68fab16

3 files changed

Lines changed: 43 additions & 1 deletion

File tree

packages-internal/credential-provider-node/src/runtime/memoize-chain.spec.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,23 @@ describe("memoize runtime config aware AWS credential chain", () => {
158158
expect(expiringCredentials).toHaveBeenCalledTimes(5);
159159
});
160160

161+
it("should update cache on forceRefresh so subsequent calls return refreshed credentials", async () => {
162+
const provider = memoizeChain([staticCredentials], credentialsWillNeedRefresh);
163+
164+
// Initial call populates cache.
165+
await provider();
166+
expect(staticCredentials).toHaveBeenCalledTimes(1);
167+
168+
// Force refresh updates cache.
169+
const refreshed = await provider({ forceRefresh: true });
170+
expect(staticCredentials).toHaveBeenCalledTimes(2);
171+
172+
// Subsequent call without forceRefresh returns cached (refreshed) value.
173+
const cached = await provider();
174+
expect(staticCredentials).toHaveBeenCalledTimes(2);
175+
expect(cached).toBe(refreshed);
176+
});
177+
161178
it("should release locks on credential resolution failure at the end of the chain", async () => {
162179
const neverAvailableCredentialProvider: () => RuntimeConfigAwsCredentialIdentityProvider = () => async () => {
163180
throw new CredentialsProviderError("never available", { tryNextLink: true });

packages-internal/credential-provider-node/src/runtime/memoize-chain.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,8 @@ export function memoizeChain(
3232

3333
const provider = async (options?: AwsIdentityProperties & { forceRefresh?: boolean }) => {
3434
if (options?.forceRefresh) {
35-
return await chain(options);
35+
credentials = await chain(options);
36+
return credentials;
3637
}
3738
if (credentials?.expiration) {
3839
if (credentials?.expiration?.getTime() < Date.now()) {

packages-internal/credential-provider-node/tests/credential-provider-node.integ.spec.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1327,6 +1327,30 @@ describe("credential-provider-node integration test", () => {
13271327
});
13281328
});
13291329

1330+
describe("forceRefresh should update credentials returned by the cache", () => {
1331+
it("subsequent calls without forceRefresh return the refreshed credentials", async () => {
1332+
process.env.AWS_ACCESS_KEY_ID = "ORIGINAL_KEY";
1333+
process.env.AWS_SECRET_ACCESS_KEY = "ORIGINAL_SECRET";
1334+
1335+
const provider = defaultProvider();
1336+
1337+
const credentials1 = await provider();
1338+
expect(credentials1.accessKeyId).toBe("ORIGINAL_KEY");
1339+
1340+
process.env.AWS_ACCESS_KEY_ID = "REFRESHED_KEY";
1341+
process.env.AWS_SECRET_ACCESS_KEY = "REFRESHED_SECRET";
1342+
1343+
const credentials2 = await provider();
1344+
expect(credentials2.accessKeyId).toBe("ORIGINAL_KEY");
1345+
1346+
const credentials3 = await provider({ forceRefresh: true });
1347+
expect(credentials3.accessKeyId).toBe("REFRESHED_KEY");
1348+
1349+
const credentials4 = await provider();
1350+
expect(credentials4.accessKeyId).toBe("REFRESHED_KEY");
1351+
});
1352+
});
1353+
13301354
describe("No credentials available", () => {
13311355
it("should throw CredentialsProviderError", async () => {
13321356
process.env.AWS_EC2_METADATA_DISABLED = "true";

0 commit comments

Comments
 (0)