Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/credential-provider-ini/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
"@aws-sdk/core": "*",
"@aws-sdk/credential-provider-env": "*",
"@aws-sdk/credential-provider-http": "*",
"@aws-sdk/credential-provider-login": "*",
"@aws-sdk/credential-provider-process": "*",
"@aws-sdk/credential-provider-sso": "*",
"@aws-sdk/credential-provider-web-identity": "*",
Expand Down
7 changes: 4 additions & 3 deletions packages/credential-provider-ini/src/fromIni.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type { FromLoginCredentialsInit } from "@aws-sdk/credential-provider-login";
import type { AssumeRoleWithWebIdentityParams } from "@aws-sdk/credential-provider-web-identity";
import type { CredentialProviderOptions } from "@aws-sdk/types";
import type { RuntimeConfigAwsCredentialIdentityProvider } from "@aws-sdk/types";
Expand All @@ -10,7 +11,7 @@ import { resolveProfileData } from "./resolveProfileData";
/**
* @public
*/
export interface FromIniInit extends SourceProfileInit, CredentialProviderOptions {
export interface FromIniInit extends SourceProfileInit, CredentialProviderOptions, FromLoginCredentialsInit {
/**
* A function that returns a promise fulfilled with an MFA token code for
* the provided MFA Serial code. If a profile requires an MFA code and
Expand Down Expand Up @@ -40,8 +41,8 @@ export interface FromIniInit extends SourceProfileInit, CredentialProviderOption
roleAssumerWithWebIdentity?: (params: AssumeRoleWithWebIdentityParams) => Promise<AwsCredentialIdentity>;

/**
* STSClientConfig or SSOClientConfig to be used for creating inner client
* for auth operations.
* AWS SDK Client configuration to be used for creating inner client
* for auth operations. Inner clients include STS, SSO, and Signin clients.
* @internal
*/
clientConfig?: any;
Expand Down
110 changes: 110 additions & 0 deletions packages/credential-provider-ini/src/resolveLoginCredentials.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
import { setCredentialFeature } from "@aws-sdk/core/client";
import { fromLoginCredentials } from "@aws-sdk/credential-provider-login";
import type { AwsCredentialIdentity } from "@smithy/types";
import { afterEach, describe, expect, test as it, vi } from "vitest";

import { isLoginProfile, resolveLoginCredentials } from "./resolveLoginCredentials";

vi.mock("@aws-sdk/credential-provider-login", () => ({
fromLoginCredentials: vi.fn(),
}));

vi.mock("@aws-sdk/core/client", () => ({
setCredentialFeature: vi.fn(),
}));

describe(isLoginProfile.name, () => {
it("returns false for empty profile", () => {
expect(isLoginProfile({})).toEqual(false);
});

it("returns false for profile without login_session", () => {
expect(isLoginProfile({ region: "us-west-2" })).toEqual(false);
});

it("returns true for profile with login_session", () => {
expect(isLoginProfile({ login_session: "arn:aws:sts::123456789101:assumed-role/MyRole/user" })).toEqual(true);
});
});

describe(resolveLoginCredentials.name, () => {
const mockCreds: AwsCredentialIdentity = {
accessKeyId: "mockAccessKeyId",
secretAccessKey: "mockSecretAccessKey",
sessionToken: "mockSessionToken",
accountId: "123456789101",
};

const mockCredsWithFeature = {
...mockCreds,
$source: { CREDENTIALS_PROFILE_LOGIN: "AC" as const },
};

afterEach(() => {
vi.clearAllMocks();
});

it("calls fromLoginCredentials and adds profile feature", async () => {
const mockProfileName = "mockProfileName";
const mockOptions = {
logger: {
debug: vi.fn(),
info: vi.fn(),
warn: vi.fn(),
error: vi.fn(),
},
};

vi.mocked(fromLoginCredentials).mockReturnValue(() => Promise.resolve(mockCreds));
vi.mocked(setCredentialFeature).mockReturnValue(mockCredsWithFeature);

const receivedCreds = await resolveLoginCredentials(mockProfileName, mockOptions);

expect(receivedCreds).toStrictEqual(mockCredsWithFeature);
expect(fromLoginCredentials).toHaveBeenCalledWith({
...mockOptions,
profile: mockProfileName,
});
expect(setCredentialFeature).toHaveBeenCalledWith(mockCreds, "CREDENTIALS_PROFILE_LOGIN", "AC");
});

it("throws error when fromLoginCredentials throws error", async () => {
const mockProfileName = "mockProfileName";
const expectedError = new Error("error from fromLoginCredentials");

vi.mocked(fromLoginCredentials).mockReturnValue(() => Promise.reject(expectedError));

try {
await resolveLoginCredentials(mockProfileName, {});
fail(`expected ${expectedError}`);
} catch (error) {
expect(error).toStrictEqual(expectedError);
}
expect(fromLoginCredentials).toHaveBeenCalledWith({
profile: mockProfileName,
});
});

it("passes options to fromLoginCredentials", async () => {
const mockProfileName = "mockProfileName";
const mockOptions = {
logger: {
debug: vi.fn(),
info: vi.fn(),
warn: vi.fn(),
error: vi.fn(),
},
clientConfig: { region: "us-east-1" },
};

vi.mocked(fromLoginCredentials).mockReturnValue(() => Promise.resolve(mockCreds));
vi.mocked(setCredentialFeature).mockReturnValue(mockCredsWithFeature);

await resolveLoginCredentials(mockProfileName, mockOptions);

expect(fromLoginCredentials).toHaveBeenCalledWith({
...mockOptions,
profile: mockProfileName,
});
});
});
27 changes: 27 additions & 0 deletions packages/credential-provider-ini/src/resolveLoginCredentials.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { setCredentialFeature } from "@aws-sdk/core/client";
import { fromLoginCredentials } from "@aws-sdk/credential-provider-login";
import type { AwsCredentialIdentity, ParsedIniData } from "@smithy/types";

import type { FromIniInit } from "./fromIni";

/**
* @internal
*/
export const isLoginProfile = (data: ParsedIniData[string]): boolean => {
return Boolean(data && data.login_session);
};

/**
* @internal
*/
export const resolveLoginCredentials = async (
profileName: string,
options: FromIniInit
): Promise<AwsCredentialIdentity> => {
const credentials = await fromLoginCredentials({
...options,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

does options pass through callerClientConfig and providerConfig.clientConfig?

profile: profileName,
})();

return setCredentialFeature(credentials, "CREDENTIALS_PROFILE_LOGIN", "AC");
};
29 changes: 23 additions & 6 deletions packages/credential-provider-ini/src/resolveProfileData.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@ import { CredentialsProviderError } from "@smithy/property-provider";
import { afterEach, beforeEach, describe, expect, test as it, vi } from "vitest";

import { isAssumeRoleProfile, resolveAssumeRoleCredentials } from "./resolveAssumeRoleCredentials";
import { isLoginProfile, resolveLoginCredentials } from "./resolveLoginCredentials";
import { resolveProfileData } from "./resolveProfileData";
import { isSsoProfile, resolveSsoCredentials } from "./resolveSsoCredentials";
import { isStaticCredsProfile, resolveStaticCredentials } from "./resolveStaticCredentials";
import { isWebIdentityProfile, resolveWebIdentityCredentials } from "./resolveWebIdentityCredentials";

vi.mock("./resolveAssumeRoleCredentials");
vi.mock("./resolveLoginCredentials");
vi.mock("./resolveSsoCredentials");
vi.mock("./resolveStaticCredentials");
vi.mock("./resolveWebIdentityCredentials");
Expand Down Expand Up @@ -37,6 +39,7 @@ describe(resolveProfileData.name, () => {
beforeEach(() => {
[
resolveAssumeRoleCredentials,
resolveLoginCredentials,
resolveSsoCredentials,
resolveStaticCredentials,
resolveWebIdentityCredentials,
Expand All @@ -46,19 +49,23 @@ describe(resolveProfileData.name, () => {
});

beforeEach(() => {
[isAssumeRoleProfile, isSsoProfile, isStaticCredsProfile, isWebIdentityProfile].forEach((isProfileFn) => {
vi.mocked(isProfileFn).mockReturnValue(true);
});
[isAssumeRoleProfile, isLoginProfile, isSsoProfile, isStaticCredsProfile, isWebIdentityProfile].forEach(
(isProfileFn) => {
vi.mocked(isProfileFn).mockReturnValue(true);
}
);
});

afterEach(() => {
vi.clearAllMocks();
});

it("throws error if all profile checks fail", async () => {
[isAssumeRoleProfile, isSsoProfile, isStaticCredsProfile, isWebIdentityProfile].forEach((isProfileFn) => {
vi.mocked(isProfileFn).mockReturnValue(false);
});
[isAssumeRoleProfile, isLoginProfile, isSsoProfile, isStaticCredsProfile, isWebIdentityProfile].forEach(
(isProfileFn) => {
vi.mocked(isProfileFn).mockReturnValue(false);
}
);
try {
await resolveProfileData(mockProfileName, mockProfiles, mockOptions);
fail(`expected ${mockError}`);
Expand Down Expand Up @@ -132,4 +139,14 @@ describe(resolveProfileData.name, () => {
expect(receivedCreds).toStrictEqual(mockCreds);
expect(resolveSsoCredentials).toHaveBeenCalledWith(mockProfileName, {}, mockOptions);
});

it("resolves with login profile, when it's not static or assume role or web identity or sso", async () => {
[isAssumeRoleProfile, isStaticCredsProfile, isWebIdentityProfile, isSsoProfile].forEach((isProfileFn) => {
vi.mocked(isProfileFn).mockReturnValue(false);
});
vi.mocked(resolveLoginCredentials).mockImplementation(() => Promise.resolve(mockCreds));
const receivedCreds = await resolveProfileData(mockProfileName, mockProfiles, mockOptions);
expect(receivedCreds).toStrictEqual(mockCreds);
expect(resolveLoginCredentials).toHaveBeenCalledWith(mockProfileName, mockOptions);
});
});
5 changes: 5 additions & 0 deletions packages/credential-provider-ini/src/resolveProfileData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import type { AwsCredentialIdentity, ParsedIniData } from "@smithy/types";

import { FromIniInit } from "./fromIni";
import { isAssumeRoleProfile, resolveAssumeRoleCredentials } from "./resolveAssumeRoleCredentials";
import { isLoginProfile, resolveLoginCredentials } from "./resolveLoginCredentials";
import { isProcessProfile, resolveProcessCredentials } from "./resolveProcessCredentials";
import { isSsoProfile, resolveSsoCredentials } from "./resolveSsoCredentials";
import { isStaticCredsProfile, resolveStaticCredentials } from "./resolveStaticCredentials";
Expand Down Expand Up @@ -67,6 +68,10 @@ export const resolveProfileData = async (
return await resolveSsoCredentials(profileName, data, options);
}

if (isLoginProfile(data)) {
return resolveLoginCredentials(profileName, options);
}

// If the profile cannot be parsed or contains neither static credentials
// nor role assumption metadata, throw an error. This should be considered a
// terminal resolution error if a profile has been specified by the user
Expand Down
7 changes: 7 additions & 0 deletions packages/credential-provider-login/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# @aws-sdk/credential-provider-login

> An internal package
## Usage

You probably shouldn't, at least directly. Please use [@aws-sdk/credential-providers](https://www.npmjs.com/package/@aws-sdk/credential-providers) instead.
68 changes: 68 additions & 0 deletions packages/credential-provider-login/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
{
"name": "@aws-sdk/credential-provider-login",
"version": "3.0.0",
"description": "AWS credential provider that sources credentials from aws login cached tokens",
"main": "./dist-cjs/index.js",
"module": "./dist-es/index.js",
"scripts": {
"build": "concurrently 'yarn:build:cjs' 'yarn:build:es' 'yarn:build:types'",
"build:cjs": "node ../../scripts/compilation/inline credential-provider-login",
"build:es": "tsc -p tsconfig.es.json",
"build:include:deps": "lerna run --scope $npm_package_name --include-dependencies build",
"build:types": "tsc -p tsconfig.types.json",
"build:types:downlevel": "downlevel-dts dist-types dist-types/ts3.4",
"clean": "rimraf ./dist-* && rimraf *.tsbuildinfo",
"test": "yarn g:vitest run",
"test:watch": "yarn g:vitest watch"
},
"keywords": [
"aws",
"credentials",
"signin",
"login"
],
"sideEffects": false,
"author": {
"name": "AWS SDK for JavaScript Team",
"url": "https://aws.amazon.com/javascript/"
},
"license": "Apache-2.0",
"dependencies": {
"@aws-sdk/core": "*",
"@aws-sdk/nested-clients": "*",
"@aws-sdk/types": "*",
"@smithy/property-provider": "^4.2.5",
"@smithy/protocol-http": "^5.3.5",
"@smithy/shared-ini-file-loader": "^4.4.0",
"@smithy/types": "^4.9.0",
"tslib": "^2.6.2"
},
"devDependencies": {
"@tsconfig/recommended": "1.0.1",
"@types/node": "^18.19.69",
"concurrently": "7.0.0",
"downlevel-dts": "0.10.1",
"rimraf": "3.0.2",
"typescript": "~5.8.3"
},
"types": "./dist-types/index.d.ts",
"engines": {
"node": ">=18.0.0"
},
"typesVersions": {
"<4.0": {
"dist-types/*": [
"dist-types/ts3.4/*"
]
}
},
"files": [
"dist-*/**"
],
"homepage": "https://github.com/aws/aws-sdk-js-v3/tree/main/packages/credential-provider-login",
"repository": {
"type": "git",
"url": "https://github.com/aws/aws-sdk-js-v3.git",
"directory": "packages/credential-provider-login"
}
}
Loading
Loading