Skip to content

Commit b14a9c2

Browse files
authored
chore(shared-ini-file-loader): add utility getSSOTokenFilepath (#3435)
1 parent fbd06a8 commit b14a9c2

File tree

5 files changed

+123
-27
lines changed

5 files changed

+123
-27
lines changed
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
import { createHash } from "crypto";
2+
import { join } from "path";
3+
4+
import { getHomeDir } from "./getHomeDir";
5+
import { getSSOTokenFilepath } from "./getSSOTokenFilepath";
6+
7+
jest.mock("crypto");
8+
jest.mock("./getHomeDir");
9+
10+
describe(getSSOTokenFilepath.name, () => {
11+
const mockCacheName = "mockCacheName";
12+
const mockDigest = jest.fn().mockReturnValue(mockCacheName);
13+
const mockUpdate = jest.fn().mockReturnValue({ digest: mockDigest });
14+
const mockHomeDir = "/home/dir";
15+
const mockSsoStartUrl = "mock_sso_start_url";
16+
17+
beforeEach(() => {
18+
(createHash as jest.Mock).mockReturnValue({ update: mockUpdate });
19+
(getHomeDir as jest.Mock).mockReturnValue(mockHomeDir);
20+
});
21+
22+
afterEach(() => {
23+
expect(createHash).toHaveBeenCalledWith("sha1");
24+
jest.clearAllMocks();
25+
});
26+
27+
describe("re-throws error", () => {
28+
const mockError = new Error("error");
29+
30+
it("when createHash throws error", () => {
31+
(createHash as jest.Mock).mockImplementationOnce(() => {
32+
throw mockError;
33+
});
34+
expect(() => getSSOTokenFilepath(mockSsoStartUrl)).toThrow(mockError);
35+
expect(mockUpdate).not.toHaveBeenCalled();
36+
expect(mockDigest).not.toHaveBeenCalled();
37+
expect(getHomeDir).not.toHaveBeenCalled();
38+
});
39+
40+
it("when hash.update() throws error", () => {
41+
mockUpdate.mockImplementationOnce(() => {
42+
throw mockError;
43+
});
44+
expect(() => getSSOTokenFilepath(mockSsoStartUrl)).toThrow(mockError);
45+
expect(mockUpdate).toHaveBeenCalledWith(mockSsoStartUrl);
46+
expect(mockDigest).not.toHaveBeenCalled();
47+
expect(getHomeDir).not.toHaveBeenCalled();
48+
});
49+
50+
it("when hash.digest() throws error", () => {
51+
mockDigest.mockImplementationOnce(() => {
52+
throw mockError;
53+
});
54+
expect(() => getSSOTokenFilepath(mockSsoStartUrl)).toThrow(mockError);
55+
expect(mockUpdate).toHaveBeenCalledWith(mockSsoStartUrl);
56+
expect(mockDigest).toHaveBeenCalledWith("hex");
57+
expect(getHomeDir).not.toHaveBeenCalled();
58+
});
59+
60+
it("when getHomeDir() throws error", () => {
61+
(getHomeDir as jest.Mock).mockImplementationOnce(() => {
62+
throw mockError;
63+
});
64+
expect(() => getSSOTokenFilepath(mockSsoStartUrl)).toThrow(mockError);
65+
expect(mockUpdate).toHaveBeenCalledWith(mockSsoStartUrl);
66+
expect(mockDigest).toHaveBeenCalledWith("hex");
67+
expect(getHomeDir).toHaveBeenCalled();
68+
});
69+
});
70+
71+
it("returns token filepath", () => {
72+
const ssoTokenFilepath = getSSOTokenFilepath(mockSsoStartUrl);
73+
expect(ssoTokenFilepath).toStrictEqual(join(mockHomeDir, ".aws", "sso", "cache", `${mockCacheName}.json`));
74+
expect(mockUpdate).toHaveBeenCalledWith(mockSsoStartUrl);
75+
expect(mockDigest).toHaveBeenCalledWith("hex");
76+
expect(getHomeDir).toHaveBeenCalled();
77+
});
78+
});
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { createHash } from "crypto";
2+
import { join } from "path";
3+
4+
import { getHomeDir } from "./getHomeDir";
5+
6+
/**
7+
* Returns the filepath of the file where SSO token is stored.
8+
*/
9+
export const getSSOTokenFilepath = (ssoStartUrl: string) => {
10+
const hasher = createHash("sha1");
11+
const cacheName = hasher.update(ssoStartUrl).digest("hex");
12+
return join(getHomeDir(), ".aws", "sso", "cache", `${cacheName}.json`);
13+
};
Lines changed: 22 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,42 +1,45 @@
1-
import { createHash } from "crypto";
21
// ToDo: Change to "fs/promises" when supporting nodejs>=14
32
import { promises } from "fs";
4-
import { join } from "path";
53

6-
import { getHomeDir } from "./getHomeDir";
4+
import { getSSOTokenFilepath } from "./getSSOTokenFilepath";
75
import { getSSOTokenFromFile } from "./getSSOTokenFromFile";
86

9-
jest.mock("crypto");
107
jest.mock("fs", () => ({ promises: { readFile: jest.fn() } }));
11-
jest.mock("./getHomeDir");
8+
jest.mock("./getSSOTokenFilepath");
129

1310
describe(getSSOTokenFromFile.name, () => {
14-
const mockCacheName = "mockCacheName";
15-
const mockDigest = jest.fn().mockReturnValue(mockCacheName);
16-
const mockUpdate = jest.fn().mockReturnValue({ digest: mockDigest });
17-
const mockHomeDir = "/home/dir";
1811
const mockSsoStartUrl = "mock_sso_start_url";
12+
const mockSsoTokenFilepath = "/home/dir/.aws/sso/cache/mockCacheName.json";
1913

2014
const mockToken = {
2115
accessToken: "mockAccessToken",
2216
expiresAt: new Date(Date.now() + 24 * 60 * 60 * 1000).toISOString(),
2317
};
2418

2519
beforeEach(() => {
26-
(createHash as jest.Mock).mockReturnValue({ update: mockUpdate });
27-
(getHomeDir as jest.Mock).mockReturnValue(mockHomeDir);
20+
(getSSOTokenFilepath as jest.Mock).mockReturnValue(mockSsoTokenFilepath);
2821
(promises.readFile as jest.Mock).mockResolvedValue(JSON.stringify(mockToken));
2922
});
3023

3124
afterEach(() => {
32-
expect(createHash).toHaveBeenCalledWith("sha1");
33-
expect(promises.readFile).toHaveBeenCalledWith(
34-
join(mockHomeDir, ".aws", "sso", "cache", `${mockCacheName}.json`),
35-
"utf8"
36-
);
3725
jest.clearAllMocks();
3826
});
3927

28+
it("re-throws if getting SSO Token filepath fails", async () => {
29+
const expectedError = new Error("error");
30+
(getSSOTokenFilepath as jest.Mock).mockImplementationOnce(() => {
31+
throw expectedError;
32+
});
33+
34+
try {
35+
await getSSOTokenFromFile(mockSsoStartUrl);
36+
fail(`expected ${expectedError}`);
37+
} catch (error) {
38+
expect(error).toStrictEqual(expectedError);
39+
}
40+
expect(promises.readFile).not.toHaveBeenCalled();
41+
});
42+
4043
it("re-throws if readFile fails", async () => {
4144
const expectedError = new Error("error");
4245
(promises.readFile as jest.Mock).mockRejectedValue(expectedError);
@@ -47,6 +50,7 @@ describe(getSSOTokenFromFile.name, () => {
4750
} catch (error) {
4851
expect(error).toStrictEqual(expectedError);
4952
}
53+
expect(promises.readFile).toHaveBeenCalledWith(mockSsoTokenFilepath, "utf8");
5054
});
5155

5256
it("re-throws if token is not a valid JSON", async () => {
@@ -59,10 +63,12 @@ describe(getSSOTokenFromFile.name, () => {
5963
} catch (error) {
6064
expect(error.message).toContain(errMsg);
6165
}
66+
expect(promises.readFile).toHaveBeenCalledWith(mockSsoTokenFilepath, "utf8");
6267
});
6368

6469
it("returns token when it's valid", async () => {
6570
const token = await getSSOTokenFromFile(mockSsoStartUrl);
6671
expect(token).toStrictEqual(mockToken);
72+
expect(promises.readFile).toHaveBeenCalledWith(mockSsoTokenFilepath, "utf8");
6773
});
6874
});

packages/shared-ini-file-loader/src/getSSOTokenFromFile.ts

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
import { createHash } from "crypto";
21
// ToDo: Change to "fs/promises" when supporting nodejs>=14
32
import { promises as fsPromises } from "fs";
4-
import { join } from "path";
53

6-
import { getHomeDir } from "./getHomeDir";
4+
import { getSSOTokenFilepath } from "./getSSOTokenFilepath";
5+
6+
const { readFile } = fsPromises;
77

88
/**
99
* Cached SSO token retrieved from SSO login flow.
@@ -52,13 +52,11 @@ export interface SSOToken {
5252
startUrl?: string;
5353
}
5454

55-
const { readFile } = fsPromises;
56-
55+
/**
56+
* Returns the SSO token from the file system.
57+
*/
5758
export const getSSOTokenFromFile = async (ssoStartUrl: string) => {
58-
const hasher = createHash("sha1");
59-
const cacheName = hasher.update(ssoStartUrl).digest("hex");
60-
const tokenFile = join(getHomeDir(), ".aws", "sso", "cache", `${cacheName}.json`);
61-
62-
const tokenText = await readFile(tokenFile, "utf8");
63-
return JSON.parse(tokenText) as SSOToken;
59+
const ssoTokenFilepath = getSSOTokenFilepath(ssoStartUrl);
60+
const ssoTokenText = await readFile(ssoTokenFilepath, "utf8");
61+
return JSON.parse(ssoTokenText) as SSOToken;
6462
};

packages/shared-ini-file-loader/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
export * from "./getHomeDir";
22
export * from "./getProfileName";
3+
export * from "./getSSOTokenFilepath";
34
export * from "./getSSOTokenFromFile";
45
export * from "./loadSharedConfigFiles";
56
export * from "./parseKnownFiles";

0 commit comments

Comments
 (0)