@aws-sdk/credential-provider-sso 3.21.0 → 3.25.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/index.spec.ts CHANGED
@@ -2,10 +2,10 @@ jest.useFakeTimers("modern");
2
2
  const now = 1613699814645;
3
3
  jest.setSystemTime(now);
4
4
 
5
- const mockParseKnowFiles = jest.fn();
5
+ const mockParseKnownFiles = jest.fn();
6
6
  const mockGetMasterProfileName = jest.fn();
7
- jest.mock("@aws-sdk/credential-provider-ini", () => ({
8
- parseKnownFiles: mockParseKnowFiles,
7
+ jest.mock("@aws-sdk/util-credentials", () => ({
8
+ parseKnownFiles: mockParseKnownFiles,
9
9
  getMasterProfileName: mockGetMasterProfileName,
10
10
  }));
11
11
 
@@ -36,170 +36,232 @@ const toRFC3339String = (date: number): string => {
36
36
  return timestamp.replace(/\.[\d]+Z$/, "Z");
37
37
  };
38
38
 
39
- describe("fromSSO", () => {
40
- const ssoConfig = {
41
- sso_start_url: "https:some-url/start",
42
- sso_account_id: "1234567890",
43
- sso_region: "us-foo-1",
44
- sso_role_name: "some-role",
45
- };
39
+ describe("fromSSO()", () => {
40
+ const ssoStartUrl = "https:some-url/start";
41
+ const ssoAccountId = "1234567890";
42
+ const ssoRegion = "us-foo-1";
43
+ const ssoRoleName = "some-role";
46
44
  const token = {
47
- startUrl: ssoConfig.sso_start_url,
48
- region: ssoConfig.sso_region,
45
+ startUrl: ssoStartUrl,
46
+ region: ssoRegion,
49
47
  accessToken: "base64 encoded string",
50
48
  expiresAt: toRFC3339String(now + 60 * 60 * 1000),
51
49
  };
50
+
52
51
  beforeEach(() => {
53
- mockParseKnowFiles.mockClear();
52
+ mockParseKnownFiles.mockClear();
54
53
  mockGetMasterProfileName.mockClear();
55
54
  mockReadFileSync.mockClear();
56
55
  mockSSOSend.mockClear();
57
56
  });
58
57
 
59
- it("should fetch credentials from resolved token file", async () => {
60
- mockParseKnowFiles.mockReturnValue(Promise.resolve({ default: ssoConfig }));
61
- mockGetMasterProfileName.mockReturnValue("default");
62
- mockReadFileSync.mockReturnValue(JSON.stringify(token));
63
- const { roleCredentials } = mockRoleCredentials;
64
- expect(await fromSSO()()).toEqual({ ...roleCredentials, expiration: new Date(roleCredentials.expiration) });
65
- expect(mockReadFileSync.mock.calls[0][0]).toEqual(
66
- expect.stringMatching(/fcab95d6966151d97d9ee7776a90d895b5e5fbe6.json$/)
67
- );
68
- expect(mockReadFileSync.mock.calls[0][1]).toMatchObject({ encoding: "utf-8" });
69
- expect(mockGetRoleCredentialsCommand).toHaveBeenCalledWith({
70
- accountId: ssoConfig.sso_account_id,
71
- roleName: ssoConfig.sso_role_name,
72
- accessToken: token.accessToken,
58
+ describe("load from shared config file", () => {
59
+ const ssoConfig = {
60
+ sso_start_url: ssoStartUrl,
61
+ sso_account_id: ssoAccountId,
62
+ sso_region: ssoRegion,
63
+ sso_role_name: ssoRoleName,
64
+ };
65
+
66
+ it("should fetch credentials from resolved token file", async () => {
67
+ mockParseKnownFiles.mockReturnValue(Promise.resolve({ default: ssoConfig }));
68
+ mockGetMasterProfileName.mockReturnValue("default");
69
+ mockReadFileSync.mockReturnValue(JSON.stringify(token));
70
+ const { roleCredentials } = mockRoleCredentials;
71
+ expect(await fromSSO()()).toEqual({ ...roleCredentials, expiration: new Date(roleCredentials.expiration) });
72
+ expect(mockReadFileSync.mock.calls[0][0]).toEqual(
73
+ expect.stringMatching(/fcab95d6966151d97d9ee7776a90d895b5e5fbe6.json$/)
74
+ );
75
+ expect(mockReadFileSync.mock.calls[0][1]).toMatchObject({ encoding: "utf-8" });
76
+ expect(mockGetRoleCredentialsCommand).toHaveBeenCalledWith({
77
+ accountId: ssoConfig.sso_account_id,
78
+ roleName: ssoConfig.sso_role_name,
79
+ accessToken: token.accessToken,
80
+ });
73
81
  });
74
- });
75
82
 
76
- it("should allow supplying custom client", async () => {
77
- mockParseKnowFiles.mockReturnValue(Promise.resolve({ default: ssoConfig }));
78
- mockGetMasterProfileName.mockReturnValue("default");
79
- mockReadFileSync.mockReturnValue(JSON.stringify(token));
80
- const newSSOClient = { send: jest.fn().mockReturnValue(Promise.resolve(mockRoleCredentials)) };
81
- //@ts-expect-error
82
- await fromSSO({ ssoClient: newSSOClient })();
83
- expect(newSSOClient.send).toHaveBeenCalled();
84
- expect(mockSSOSend).not.toHaveBeenCalled();
85
- });
83
+ it("should allow supplying custom client", async () => {
84
+ mockParseKnownFiles.mockReturnValue(Promise.resolve({ default: ssoConfig }));
85
+ mockGetMasterProfileName.mockReturnValue("default");
86
+ mockReadFileSync.mockReturnValue(JSON.stringify(token));
87
+ const newSSOClient = { send: jest.fn().mockReturnValue(Promise.resolve(mockRoleCredentials)) };
88
+ //@ts-expect-error
89
+ await fromSSO({ ssoClient: newSSOClient })();
90
+ expect(newSSOClient.send).toHaveBeenCalled();
91
+ expect(mockSSOSend).not.toHaveBeenCalled();
92
+ });
86
93
 
87
- it("should throw if profile doesn't exist in the config files", () => {
88
- const profile = "exist";
89
- mockParseKnowFiles.mockReturnValue(Promise.resolve({ non_exist: { foo: "bar" } }));
90
- mockGetMasterProfileName.mockReturnValue(profile);
91
- return expect(async () => {
92
- await fromSSO()();
93
- }).rejects.toMatchObject({
94
- message: `Profile ${profile} could not be found in shared credentials file.`,
95
- tryNextLink: true,
94
+ it("should throw if profile doesn't exist in the config files", () => {
95
+ const profile = "exist";
96
+ mockParseKnownFiles.mockReturnValue(Promise.resolve({ non_exist: { foo: "bar" } }));
97
+ mockGetMasterProfileName.mockReturnValue(profile);
98
+ return expect(async () => {
99
+ await fromSSO()();
100
+ }).rejects.toMatchObject({
101
+ name: "CredentialsProviderError",
102
+ message: expect.stringContaining("Profile exist is not configured with SSO credentials"),
103
+ tryNextLink: true,
104
+ });
96
105
  });
97
- });
98
106
 
99
- it("should throw if profile is not configured with SSO credential", () => {
100
- const profile = "exist";
101
- mockParseKnowFiles.mockReturnValue(Promise.resolve({ [profile]: { foo: "bar" } }));
102
- mockGetMasterProfileName.mockReturnValue(profile);
103
- return expect(async () => {
104
- await fromSSO()();
105
- }).rejects.toMatchObject({
106
- message: `Profile ${profile} is not configured with SSO credentials.`,
107
- tryNextLink: true,
107
+ it("should throw if profile is not configured with SSO credential", () => {
108
+ const profile = "exist";
109
+ mockParseKnownFiles.mockReturnValue(Promise.resolve({ [profile]: { foo: "bar" } }));
110
+ mockGetMasterProfileName.mockReturnValue(profile);
111
+ return expect(async () => {
112
+ await fromSSO()();
113
+ }).rejects.toMatchObject({
114
+ message: `Profile ${profile} is not configured with SSO credentials.`,
115
+ tryNextLink: true,
116
+ });
108
117
  });
109
- });
110
118
 
111
- for (let i = 0; i < Object.keys(ssoConfig).length; i++) {
112
- const keyToRemove = Object.keys(ssoConfig)[i];
113
- it(`should throw if sso configuration is missing ${keyToRemove}`, async () => {
119
+ it.each(Object.keys(ssoConfig))("should throw if sso configuration is missing %s", async (keyToRemove) => {
114
120
  expect.assertions(2);
115
121
  const config = { ...ssoConfig };
116
122
  //@ts-ignore
117
123
  delete config[keyToRemove];
118
- mockParseKnowFiles.mockReturnValue(Promise.resolve({ default: config }));
124
+ mockParseKnownFiles.mockReturnValue(Promise.resolve({ default: config }));
119
125
  mockGetMasterProfileName.mockReturnValue("default");
120
126
  try {
121
127
  await fromSSO()();
122
128
  } catch (e) {
123
- expect(e.message).toContain("Profile default does not have valid SSO credentials.");
129
+ expect(e.message).toContain("Profile is configured with invalid SSO credentials.");
124
130
  expect(e.tryNextLink).toBeFalsy();
125
131
  }
126
132
  });
127
- }
128
133
 
129
- it("should throw if token cache file is not found", async () => {
130
- expect.assertions(2);
131
- mockReadFileSync.mockImplementation(() => {
132
- throw new Error("File not found.");
134
+ it("should throw if token cache file is not found", async () => {
135
+ expect.assertions(2);
136
+ mockReadFileSync.mockImplementation(() => {
137
+ throw new Error("File not found.");
138
+ });
139
+ mockParseKnownFiles.mockReturnValue(Promise.resolve({ default: ssoConfig }));
140
+ mockGetMasterProfileName.mockReturnValue("default");
141
+ try {
142
+ await fromSSO()();
143
+ } catch (e) {
144
+ expect(e.message).toContain(
145
+ "The SSO session associated with this profile has expired or is otherwise invalid."
146
+ );
147
+ expect(e.tryNextLink).toBeFalsy();
148
+ }
133
149
  });
134
- mockParseKnowFiles.mockReturnValue(Promise.resolve({ default: ssoConfig }));
135
- mockGetMasterProfileName.mockReturnValue("default");
136
- try {
137
- await fromSSO()();
138
- } catch (e) {
139
- expect(e.message).toContain("The SSO session associated with this profile has expired or is otherwise invalid.");
140
- expect(e.tryNextLink).toBeFalsy();
141
- }
142
- });
143
150
 
144
- it("should throw if token cache file is invalid", async () => {
145
- expect.assertions(2);
146
- mockReadFileSync.mockReturnValue("invalid JSON content");
147
- mockParseKnowFiles.mockReturnValue(Promise.resolve({ default: ssoConfig }));
148
- mockGetMasterProfileName.mockReturnValue("default");
149
- try {
150
- await fromSSO()();
151
- } catch (e) {
152
- expect(e.message).toContain("The SSO session associated with this profile has expired or is otherwise invalid.");
153
- expect(e.tryNextLink).toBeFalsy();
154
- }
155
- });
151
+ it("should throw if token cache file is invalid", async () => {
152
+ expect.assertions(2);
153
+ mockReadFileSync.mockReturnValue("invalid JSON content");
154
+ mockParseKnownFiles.mockReturnValue(Promise.resolve({ default: ssoConfig }));
155
+ mockGetMasterProfileName.mockReturnValue("default");
156
+ try {
157
+ await fromSSO()();
158
+ } catch (e) {
159
+ expect(e.message).toContain(
160
+ "The SSO session associated with this profile has expired or is otherwise invalid."
161
+ );
162
+ expect(e.tryNextLink).toBeFalsy();
163
+ }
164
+ });
156
165
 
157
- it("should throw if token cache is expired", async () => {
158
- expect.assertions(2);
159
- mockReadFileSync.mockReturnValue({ ...token, expiration: toRFC3339String(now + EXPIRE_WINDOW_MS - 2) });
160
- mockParseKnowFiles.mockReturnValue(Promise.resolve({ default: ssoConfig }));
161
- mockGetMasterProfileName.mockReturnValue("default");
162
- try {
163
- await fromSSO()();
164
- } catch (e) {
165
- expect(e.message).toContain("The SSO session associated with this profile has expired or is otherwise invalid.");
166
- expect(e.tryNextLink).toBeFalsy();
167
- }
168
- });
166
+ it("should throw if token cache is expired", async () => {
167
+ expect.assertions(2);
168
+ mockReadFileSync.mockReturnValue({ ...token, expiration: toRFC3339String(now + EXPIRE_WINDOW_MS - 2) });
169
+ mockParseKnownFiles.mockReturnValue(Promise.resolve({ default: ssoConfig }));
170
+ mockGetMasterProfileName.mockReturnValue("default");
171
+ try {
172
+ await fromSSO()();
173
+ } catch (e) {
174
+ expect(e.message).toContain(
175
+ "The SSO session associated with this profile has expired or is otherwise invalid."
176
+ );
177
+ expect(e.tryNextLink).toBeFalsy();
178
+ }
179
+ });
169
180
 
170
- it("should throw if SSO client throws", async () => {
171
- expect.assertions(3);
172
- mockParseKnowFiles.mockReturnValue(Promise.resolve({ default: ssoConfig }));
173
- mockGetMasterProfileName.mockReturnValue("default");
174
- mockReadFileSync.mockReturnValue(JSON.stringify(token));
175
- const clientError = new Error("No account is found for the user");
176
- //@ts-ignore
177
- clientError.$fault = "client";
178
- mockSSOSend.mockImplementation(async () => {
179
- throw clientError;
181
+ it("should throw if SSO client throws", async () => {
182
+ expect.assertions(3);
183
+ mockParseKnownFiles.mockReturnValue(Promise.resolve({ default: ssoConfig }));
184
+ mockGetMasterProfileName.mockReturnValue("default");
185
+ mockReadFileSync.mockReturnValue(JSON.stringify(token));
186
+ const clientError = new Error("No account is found for the user");
187
+ //@ts-ignore
188
+ clientError.$fault = "client";
189
+ mockSSOSend.mockImplementation(async () => {
190
+ throw clientError;
191
+ });
192
+ try {
193
+ await fromSSO()();
194
+ } catch (e) {
195
+ expect(e.message).toContain(clientError.message);
196
+ expect(e.tryNextLink).toBeFalsy();
197
+ expect(e.$fault).toBe("client");
198
+ }
199
+ });
200
+
201
+ it("should throw if credentials from SSO client is invalid", async () => {
202
+ expect.assertions(2);
203
+ mockReadFileSync.mockReturnValue(JSON.stringify(token));
204
+ mockParseKnownFiles.mockReturnValue(Promise.resolve({ default: ssoConfig }));
205
+ mockGetMasterProfileName.mockReturnValue("default");
206
+ mockSSOSend.mockResolvedValue({
207
+ roleCredentials: { ...mockRoleCredentials.roleCredentials, accessKeyId: undefined },
208
+ });
209
+ try {
210
+ await fromSSO()();
211
+ } catch (e) {
212
+ expect(e.message).toContain("SSO returns an invalid temporary credential.");
213
+ expect(e.tryNextLink).toBeFalsy();
214
+ } finally {
215
+ mockSSOSend.mockResolvedValue(mockRoleCredentials);
216
+ }
180
217
  });
181
- try {
182
- await fromSSO()();
183
- } catch (e) {
184
- expect(e.message).toContain(clientError.message);
185
- expect(e.tryNextLink).toBeFalsy();
186
- expect(e.$fault).toBe("client");
187
- }
188
218
  });
189
219
 
190
- it("should throw if credentials from SSO client is invalid", async () => {
191
- expect.assertions(2);
192
- mockReadFileSync.mockReturnValue(JSON.stringify(token));
193
- mockParseKnowFiles.mockReturnValue(Promise.resolve({ default: ssoConfig }));
194
- mockGetMasterProfileName.mockReturnValue("default");
195
- mockSSOSend.mockResolvedValue({
196
- roleCredentials: { ...mockRoleCredentials.roleCredentials, accessKeyId: undefined },
220
+ describe("load with sso parameters", () => {
221
+ it("should fetch credentials from resolved token file without reading shared config file", async () => {
222
+ mockParseKnownFiles.mockRejectedValue("Should not call parseKnownFiles()");
223
+ mockGetMasterProfileName.mockRejectedValue("Should not call getMasterProfileName()");
224
+ mockReadFileSync.mockReturnValue(JSON.stringify(token));
225
+ const { roleCredentials } = mockRoleCredentials;
226
+ expect(
227
+ await fromSSO({
228
+ ssoStartUrl,
229
+ ssoAccountId,
230
+ ssoRegion,
231
+ ssoRoleName,
232
+ })()
233
+ ).toEqual({ ...roleCredentials, expiration: new Date(roleCredentials.expiration) });
234
+ expect(mockReadFileSync.mock.calls[0][0]).toEqual(
235
+ expect.stringMatching(/fcab95d6966151d97d9ee7776a90d895b5e5fbe6.json$/)
236
+ );
237
+ expect(mockReadFileSync.mock.calls[0][1]).toMatchObject({ encoding: "utf-8" });
238
+ expect(mockGetRoleCredentialsCommand).toHaveBeenCalledWith({
239
+ accountId: ssoAccountId,
240
+ roleName: ssoRoleName,
241
+ accessToken: token.accessToken,
242
+ });
197
243
  });
198
- try {
199
- await fromSSO()();
200
- } catch (e) {
201
- expect(e.message).toContain("SSO returns an invalid temporary credential.");
202
- expect(e.tryNextLink).toBeFalsy();
203
- }
244
+
245
+ it.each(["ssoStartUrl", "ssoAccountId", "ssoRegion", "ssoRoleName"])(
246
+ "should throw for incomplete sso parameters(missing %s)",
247
+ (undefinedKey) => {
248
+ mockParseKnownFiles.mockRejectedValue("Should not call parseKnownFiles()");
249
+ mockGetMasterProfileName.mockRejectedValue("Should not call getMasterProfileName()");
250
+ mockReadFileSync.mockReturnValue(JSON.stringify(token));
251
+ return expect(
252
+ async () =>
253
+ await fromSSO({
254
+ ssoStartUrl,
255
+ ssoAccountId,
256
+ ssoRegion,
257
+ ssoRoleName,
258
+ ...{ [undefinedKey]: undefined },
259
+ } as any)()
260
+ ).rejects.toMatchObject({
261
+ name: "CredentialsProviderError",
262
+ message: expect.stringMatching("Incomplete configuration"),
263
+ });
264
+ }
265
+ );
204
266
  });
205
267
  });
package/src/index.ts CHANGED
@@ -1,8 +1,8 @@
1
1
  import { GetRoleCredentialsCommand, GetRoleCredentialsCommandOutput, SSOClient } from "@aws-sdk/client-sso";
2
- import { getMasterProfileName, parseKnownFiles, SourceProfileInit } from "@aws-sdk/credential-provider-ini";
3
2
  import { CredentialsProviderError } from "@aws-sdk/property-provider";
4
- import { getHomeDir, ParsedIniData } from "@aws-sdk/shared-ini-file-loader";
3
+ import { getHomeDir, Profile } from "@aws-sdk/shared-ini-file-loader";
5
4
  import { CredentialProvider, Credentials } from "@aws-sdk/types";
5
+ import { getMasterProfileName, parseKnownFiles, SourceProfileInit } from "@aws-sdk/util-credentials";
6
6
  import { createHash } from "crypto";
7
7
  import { readFileSync } from "fs";
8
8
  import { join } from "path";
@@ -29,6 +29,27 @@ interface SSOToken {
29
29
  startUrl?: string;
30
30
  }
31
31
 
32
+ export interface SsoCredentialsParameters {
33
+ /**
34
+ * The URL to the AWS SSO service.
35
+ */
36
+ ssoStartUrl: string;
37
+
38
+ /**
39
+ * The ID of the AWS account to use for temporary credentials.
40
+ */
41
+ ssoAccountId: string;
42
+
43
+ /**
44
+ * The AWS region to use for temporary credentials.
45
+ */
46
+ ssoRegion: string;
47
+
48
+ /**
49
+ * The name of the AWS role to assume.
50
+ */
51
+ ssoRoleName: string;
52
+ }
32
53
  export interface FromSSOInit extends SourceProfileInit {
33
54
  ssoClient?: SSOClient;
34
55
  }
@@ -38,34 +59,44 @@ export interface FromSSOInit extends SourceProfileInit {
38
59
  * in ini files.
39
60
  */
40
61
  export const fromSSO =
41
- (init: FromSSOInit = {}): CredentialProvider =>
62
+ (init: FromSSOInit & Partial<SsoCredentialsParameters> = {} as any): CredentialProvider =>
42
63
  async () => {
43
- const profiles = await parseKnownFiles(init);
44
- return resolveSSOCredentials(getMasterProfileName(init), profiles, init);
64
+ const { ssoStartUrl, ssoAccountId, ssoRegion, ssoRoleName, ssoClient } = init;
65
+ if (!ssoStartUrl && !ssoAccountId && !ssoRegion && !ssoRoleName) {
66
+ // Load the SSO config from shared AWS config file.
67
+ const profiles = await parseKnownFiles(init);
68
+ const profileName = getMasterProfileName(init);
69
+ const profile = profiles[profileName];
70
+ if (!isSsoProfile(profile)) {
71
+ throw new CredentialsProviderError(`Profile ${profileName} is not configured with SSO credentials.`);
72
+ }
73
+ const { sso_start_url, sso_account_id, sso_region, sso_role_name } = validateSsoProfile(profile);
74
+ return resolveSSOCredentials({
75
+ ssoStartUrl: sso_start_url,
76
+ ssoAccountId: sso_account_id,
77
+ ssoRegion: sso_region,
78
+ ssoRoleName: sso_role_name,
79
+ ssoClient: ssoClient,
80
+ });
81
+ } else if (!ssoStartUrl || !ssoAccountId || !ssoRegion || !ssoRoleName) {
82
+ throw new CredentialsProviderError(
83
+ 'Incomplete configuration. The fromSSO() argument hash must include "ssoStartUrl",' +
84
+ ' "ssoAccountId", "ssoRegion", "ssoRoleName"'
85
+ );
86
+ } else {
87
+ return resolveSSOCredentials({ ssoStartUrl, ssoAccountId, ssoRegion, ssoRoleName, ssoClient });
88
+ }
45
89
  };
46
90
 
47
- const resolveSSOCredentials = async (
48
- profileName: string,
49
- profiles: ParsedIniData,
50
- options: FromSSOInit
51
- ): Promise<Credentials> => {
52
- const profile = profiles[profileName];
53
- if (!profile) {
54
- throw new CredentialsProviderError(`Profile ${profileName} could not be found in shared credentials file.`);
55
- }
56
- const { sso_start_url: startUrl, sso_account_id: accountId, sso_region: region, sso_role_name: roleName } = profile;
57
- if (!startUrl && !accountId && !region && !roleName) {
58
- throw new CredentialsProviderError(`Profile ${profileName} is not configured with SSO credentials.`);
59
- }
60
- if (!startUrl || !accountId || !region || !roleName) {
61
- throw new CredentialsProviderError(
62
- `Profile ${profileName} does not have valid SSO credentials. Required parameters "sso_account_id", "sso_region", ` +
63
- `"sso_role_name", "sso_start_url". Reference: https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-sso.html`,
64
- SHOULD_FAIL_CREDENTIAL_CHAIN
65
- );
66
- }
91
+ const resolveSSOCredentials = async ({
92
+ ssoStartUrl,
93
+ ssoAccountId,
94
+ ssoRegion,
95
+ ssoRoleName,
96
+ ssoClient,
97
+ }: FromSSOInit & SsoCredentialsParameters): Promise<Credentials> => {
67
98
  const hasher = createHash("sha1");
68
- const cacheName = hasher.update(startUrl).digest("hex");
99
+ const cacheName = hasher.update(ssoStartUrl).digest("hex");
69
100
  const tokenFile = join(getHomeDir(), ".aws", "sso", "cache", `${cacheName}.json`);
70
101
  let token: SSOToken;
71
102
  try {
@@ -81,13 +112,13 @@ const resolveSSOCredentials = async (
81
112
  );
82
113
  }
83
114
  const { accessToken } = token;
84
- const sso = options.ssoClient || new SSOClient({ region });
115
+ const sso = ssoClient || new SSOClient({ region: ssoRegion });
85
116
  let ssoResp: GetRoleCredentialsCommandOutput;
86
117
  try {
87
118
  ssoResp = await sso.send(
88
119
  new GetRoleCredentialsCommand({
89
- accountId,
90
- roleName,
120
+ accountId: ssoAccountId,
121
+ roleName: ssoRoleName,
91
122
  accessToken,
92
123
  })
93
124
  );
@@ -100,3 +131,40 @@ const resolveSSOCredentials = async (
100
131
  }
101
132
  return { accessKeyId, secretAccessKey, sessionToken, expiration: new Date(expiration) };
102
133
  };
134
+
135
+ /**
136
+ * @internal
137
+ */
138
+ export interface SsoProfile extends Profile {
139
+ sso_start_url: string;
140
+ sso_account_id: string;
141
+ sso_region: string;
142
+ sso_role_name: string;
143
+ }
144
+
145
+ /**
146
+ * @internal
147
+ */
148
+ export const validateSsoProfile = (profile: Partial<SsoProfile>): SsoProfile => {
149
+ const { sso_start_url, sso_account_id, sso_region, sso_role_name } = profile;
150
+ if (!sso_start_url || !sso_account_id || !sso_region || !sso_role_name) {
151
+ throw new CredentialsProviderError(
152
+ `Profile is configured with invalid SSO credentials. Required parameters "sso_account_id", "sso_region", ` +
153
+ `"sso_role_name", "sso_start_url". Got ${Object.keys(profile).join(
154
+ ", "
155
+ )}\nReference: https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-sso.html`,
156
+ SHOULD_FAIL_CREDENTIAL_CHAIN
157
+ );
158
+ }
159
+ return profile as SsoProfile;
160
+ };
161
+
162
+ /**
163
+ * @internal
164
+ */
165
+ export const isSsoProfile = (arg: Profile): arg is Partial<SsoProfile> =>
166
+ arg &&
167
+ (typeof arg.sso_start_url === "string" ||
168
+ typeof arg.sso_account_id === "string" ||
169
+ typeof arg.sso_region === "string" ||
170
+ typeof arg.sso_role_name === "string");