Skip to content

Commit

Permalink
feat(credential-providers): update docs and minimum duration validation
Browse files Browse the repository at this point in the history
  • Loading branch information
kuhe committed Aug 12, 2024
1 parent bfa9b4e commit f191457
Show file tree
Hide file tree
Showing 3 changed files with 57 additions and 33 deletions.
24 changes: 16 additions & 8 deletions packages/credential-providers/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -790,27 +790,35 @@ const credentialProvider = fromNodeProviderChain({
You can use this helper to create a credential chain of your own.

```ts
import { fromEnv, fromIni, chain } from "@aws-sdk/credential-providers";
import { fromEnv, fromIni, createCredentialChain } from "@aws-sdk/credential-providers";
import { S3 } from "@aws-sdk/client-s3";

// You can mix existing AWS SDK credential providers
// and custom async functions returning credential objects.
new S3({
credentials: chain(fromEnv(), fromIni(), async () => {
return myCredentialsFromSomewhereElse;
}),
credentials: createCredentialChain(
fromEnv(),
async () => {
// credentials customized by your code...
return credentials;
},
fromIni()
),
});

// Set a max duration on the credentials (client side only).
// A set expiration will cause the credentials function to be called again
// after the given duration.
// when the time left is less than 5 minutes.
new S3({
credentials: chain(fromEnv(), fromIni()).expireAfter(15 * 60_000), // 15 minutes in milliseconds.
// expire after 15 minutes (in milliseconds).
credentials: createCredentialChain(fromEnv(), fromIni()).expireAfter(15 * 60_000),
});

// apply shared init properties.
// Apply shared init properties.
const init = { logger: console };

new S3({
credentials: chain(...[fromEnv, fromIni].map((p) => p({ logger: console }))),
credentials: createCredentialChain(fromEnv(init), fromIni(init)),
});
```

Expand Down
26 changes: 17 additions & 9 deletions packages/credential-providers/src/customCredentialChain.spec.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { ProviderError } from "@smithy/property-provider";
import { AwsCredentialIdentity, AwsCredentialIdentityProvider } from "@smithy/types";

import { chain } from "./customCredentialChain";
import { createCredentialChain } from "./customCredentialChain";

describe(chain.name, () => {
describe(createCredentialChain.name, () => {
const mockCredentials: AwsCredentialIdentity = {
accessKeyId: "AKI",
secretAccessKey: "SAK",
Expand All @@ -14,7 +14,7 @@ describe(chain.name, () => {
};

it("should throw an error if zero providers are chained", async () => {
const credentialProvider = chain();
const credentialProvider = createCredentialChain();

try {
await credentialProvider();
Expand All @@ -26,23 +26,23 @@ describe(chain.name, () => {
});

it("should create a custom chain", async () => {
const credentialProvider = chain(async () => mockCredentials);
const credentialProvider = createCredentialChain(async () => mockCredentials);

const credentials = await credentialProvider();

expect(credentials).toEqual(mockCredentials);
});

it("should resolve a successful provider function", async () => {
const credentialProvider = chain(failure, failure, async () => mockCredentials, failure);
const credentialProvider = createCredentialChain(failure, failure, async () => mockCredentials, failure);

const credentials = await credentialProvider();

expect(credentials).toEqual(mockCredentials);
});

it("should resolve the first successful provider function", async () => {
const credentialProvider = chain(
const credentialProvider = createCredentialChain(
failure,
failure,
async () => ({ ...mockCredentials, order: "1st" }),
Expand All @@ -56,18 +56,26 @@ describe(chain.name, () => {
});

it("should allow setting a duration", async () => {
const credentialProvider: AwsCredentialIdentityProvider = chain(
const credentialProvider: AwsCredentialIdentityProvider = createCredentialChain(
failure,
failure,
async () => ({ ...mockCredentials, order: "1st" }),
failure,
async () => ({ ...mockCredentials, order: "2nd" })
).expireAfter(15_000);
).expireAfter(6 * 60_000);

const credentials = await credentialProvider();

expect(credentials.expiration).toBeDefined();
expect(credentials.expiration?.getTime()).toBeGreaterThan(Date.now());
expect(credentials.expiration?.getTime()).toBeLessThan(Date.now() + 30_000);
expect(credentials.expiration?.getTime()).toBeLessThan(Date.now() + 375_000);
});

it("it should throw an error for durations less than 5 minutes", async () => {
expect(() => {
createCredentialChain(async () => mockCredentials).expireAfter(299_999);
}).toThrow(
"@aws-sdk/credential-providers - createCredentialChain(...).expireAfter(ms) may not be called with a duration lower than five minutes."
);
});
});
40 changes: 24 additions & 16 deletions packages/credential-providers/src/customCredentialChain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,40 +15,43 @@ type Mutable<Type> = {
/**
* @example
* ```js
* import { fromEnv, fromIni, chain } from "@aws-sdk/credential-providers";
* import { fromEnv, fromIni, createCredentialChain } from '@aws-sdk/credential-providers';
* import { S3 } from '@aws-sdk/client-s3';
*
* // basic chain.
* // You can mix existing AWS SDK credential providers
* // and custom async functions returning credential objects.
* new S3({
* credentials: chain(
* credentials: createCredentialChain(
* fromEnv(),
* async () => {
* // credentials customized by your code...
* return credentials;
* },
* fromIni()
* )
* ),
* });
*
* // set a max duration on the credentials (client side only).
* // Set a max duration on the credentials (client side only).
* // A set expiration will cause the credentials function to be called again
* // when the time left is less than 5 minutes.
* new S3({
* credentials: chain(
* fromEnv(),
* fromIni()
* ).expireAfter(15 * 60_000) // 15 minutes in milliseconds.
* // expire after 15 minutes (in milliseconds).
* credentials: createCredentialChain(fromEnv(), fromIni()).expireAfter(15 * 60_000),
* });
*
* // apply shared init properties.
* // Apply shared init properties.
* const init = { logger: console };
*
* new S3({
* credentials: chain(...[
* fromEnv,
* fromIni
* ].map(p => p({ logger: console })))
* credentials: createCredentialChain(fromEnv(init), fromIni(init)),
* });
*
* ```
*
* @param credentialProviders - one or more credential providers.
* @returns a single AwsCredentialIdentityProvider that calls the given
* providers in sequence until one succeeds or all fail.
*/
export const chain = (
export const createCredentialChain = (
...credentialProviders: AwsCredentialIdentityProvider[]
): AwsCredentialIdentityProvider & CustomCredentialChainOptions => {
let expireAfter = -1;
Expand All @@ -61,6 +64,11 @@ export const chain = (
};
const withOptions = Object.assign(baseFunction, {
expireAfter(milliseconds: number) {
if (milliseconds < 5 * 60_000) {
throw new Error(
"@aws-sdk/credential-providers - createCredentialChain(...).expireAfter(ms) may not be called with a duration lower than five minutes."
);
}
expireAfter = milliseconds;
return withOptions;
},
Expand Down

0 comments on commit f191457

Please sign in to comment.