Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore(shared-ini-file-loader): add utility getSSOTokenFilepath #3435

Merged
merged 2 commits into from Mar 16, 2022
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
78 changes: 78 additions & 0 deletions packages/shared-ini-file-loader/src/getSSOTokenFilepath.spec.ts
@@ -0,0 +1,78 @@
import { createHash } from "crypto";
import { join } from "path";

import { getHomeDir } from "./getHomeDir";
import { getSSOTokenFilepath } from "./getSSOTokenFilepath";

jest.mock("crypto");
jest.mock("./getHomeDir");

describe(getSSOTokenFilepath.name, () => {
const mockCacheName = "mockCacheName";
const mockDigest = jest.fn().mockReturnValue(mockCacheName);
const mockUpdate = jest.fn().mockReturnValue({ digest: mockDigest });
const mockHomeDir = "/home/dir";
const mockSsoStartUrl = "mock_sso_start_url";

beforeEach(() => {
(createHash as jest.Mock).mockReturnValue({ update: mockUpdate });
(getHomeDir as jest.Mock).mockReturnValue(mockHomeDir);
});

afterEach(() => {
expect(createHash).toHaveBeenCalledWith("sha1");
jest.clearAllMocks();
});

describe("re-throws error", () => {
const mockError = new Error("error");

it("when createHash throws error", () => {
(createHash as jest.Mock).mockImplementationOnce(() => {
throw mockError;
});
expect(() => getSSOTokenFilepath(mockSsoStartUrl)).toThrow(mockError);
expect(mockUpdate).not.toHaveBeenCalled();
expect(mockDigest).not.toHaveBeenCalled();
expect(getHomeDir).not.toHaveBeenCalled();
});

it("when hash.update() throws error", () => {
mockUpdate.mockImplementationOnce(() => {
throw mockError;
});
expect(() => getSSOTokenFilepath(mockSsoStartUrl)).toThrow(mockError);
expect(mockUpdate).toHaveBeenCalledWith(mockSsoStartUrl);
expect(mockDigest).not.toHaveBeenCalled();
expect(getHomeDir).not.toHaveBeenCalled();
});

it("when hash.digest() throws error", () => {
mockDigest.mockImplementationOnce(() => {
throw mockError;
});
expect(() => getSSOTokenFilepath(mockSsoStartUrl)).toThrow(mockError);
expect(mockUpdate).toHaveBeenCalledWith(mockSsoStartUrl);
expect(mockDigest).toHaveBeenCalledWith("hex");
expect(getHomeDir).not.toHaveBeenCalled();
});

it("when getHomeDir() throws error", () => {
(getHomeDir as jest.Mock).mockImplementationOnce(() => {
throw mockError;
});
expect(() => getSSOTokenFilepath(mockSsoStartUrl)).toThrow(mockError);
expect(mockUpdate).toHaveBeenCalledWith(mockSsoStartUrl);
expect(mockDigest).toHaveBeenCalledWith("hex");
expect(getHomeDir).toHaveBeenCalled();
});
});

it("returns token filepath", () => {
const ssoTokenFilepath = getSSOTokenFilepath(mockSsoStartUrl);
expect(ssoTokenFilepath).toStrictEqual(join(mockHomeDir, ".aws", "sso", "cache", `${mockCacheName}.json`));
expect(mockUpdate).toHaveBeenCalledWith(mockSsoStartUrl);
expect(mockDigest).toHaveBeenCalledWith("hex");
expect(getHomeDir).toHaveBeenCalled();
});
});
13 changes: 13 additions & 0 deletions packages/shared-ini-file-loader/src/getSSOTokenFilepath.ts
@@ -0,0 +1,13 @@
import { createHash } from "crypto";
import { join } from "path";

import { getHomeDir } from "./getHomeDir";

/**
* Returns the filepath of the file where SSO token is stored.
*/
export const getSSOTokenFilepath = (ssoStartUrl: string) => {
const hasher = createHash("sha1");
const cacheName = hasher.update(ssoStartUrl).digest("hex");
return join(getHomeDir(), ".aws", "sso", "cache", `${cacheName}.json`);
};
38 changes: 22 additions & 16 deletions packages/shared-ini-file-loader/src/getSSOTokenFromFile.spec.ts
@@ -1,42 +1,45 @@
import { createHash } from "crypto";
// ToDo: Change to "fs/promises" when supporting nodejs>=14
import { promises } from "fs";
import { join } from "path";

import { getHomeDir } from "./getHomeDir";
import { getSSOTokenFilepath } from "./getSSOTokenFilepath";
import { getSSOTokenFromFile } from "./getSSOTokenFromFile";

jest.mock("crypto");
jest.mock("fs", () => ({ promises: { readFile: jest.fn() } }));
jest.mock("./getHomeDir");
jest.mock("./getSSOTokenFilepath");

describe(getSSOTokenFromFile.name, () => {
const mockCacheName = "mockCacheName";
const mockDigest = jest.fn().mockReturnValue(mockCacheName);
const mockUpdate = jest.fn().mockReturnValue({ digest: mockDigest });
const mockHomeDir = "/home/dir";
const mockSsoStartUrl = "mock_sso_start_url";
const mockSsoTokenFilepath = "/home/dir/.aws/sso/cache/mockCacheName.json";

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

beforeEach(() => {
(createHash as jest.Mock).mockReturnValue({ update: mockUpdate });
(getHomeDir as jest.Mock).mockReturnValue(mockHomeDir);
(getSSOTokenFilepath as jest.Mock).mockReturnValue(mockSsoTokenFilepath);
(promises.readFile as jest.Mock).mockResolvedValue(JSON.stringify(mockToken));
});

afterEach(() => {
expect(createHash).toHaveBeenCalledWith("sha1");
expect(promises.readFile).toHaveBeenCalledWith(
join(mockHomeDir, ".aws", "sso", "cache", `${mockCacheName}.json`),
"utf8"
);
jest.clearAllMocks();
});

it("re-throws if getting SSO Token filepath fails", async () => {
const expectedError = new Error("error");
(getSSOTokenFilepath as jest.Mock).mockImplementationOnce(() => {
throw expectedError;
});

try {
await getSSOTokenFromFile(mockSsoStartUrl);
fail(`expected ${expectedError}`);
} catch (error) {
expect(error).toStrictEqual(expectedError);
}
expect(promises.readFile).not.toHaveBeenCalled();
});

it("re-throws if readFile fails", async () => {
const expectedError = new Error("error");
(promises.readFile as jest.Mock).mockRejectedValue(expectedError);
Expand All @@ -47,6 +50,7 @@ describe(getSSOTokenFromFile.name, () => {
} catch (error) {
expect(error).toStrictEqual(expectedError);
}
expect(promises.readFile).toHaveBeenCalledWith(mockSsoTokenFilepath, "utf8");
});

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

it("returns token when it's valid", async () => {
const token = await getSSOTokenFromFile(mockSsoStartUrl);
expect(token).toStrictEqual(mockToken);
expect(promises.readFile).toHaveBeenCalledWith(mockSsoTokenFilepath, "utf8");
});
});
20 changes: 9 additions & 11 deletions packages/shared-ini-file-loader/src/getSSOTokenFromFile.ts
@@ -1,9 +1,9 @@
import { createHash } from "crypto";
// ToDo: Change to "fs/promises" when supporting nodejs>=14
import { promises as fsPromises } from "fs";
import { join } from "path";

import { getHomeDir } from "./getHomeDir";
import { getSSOTokenFilepath } from "./getSSOTokenFilepath";

const { readFile } = fsPromises;

/**
* Cached SSO token retrieved from SSO login flow.
Expand Down Expand Up @@ -52,13 +52,11 @@ export interface SSOToken {
startUrl?: string;
}

const { readFile } = fsPromises;

/**
* Returns the SSO token from the file system.
*/
export const getSSOTokenFromFile = async (ssoStartUrl: string) => {
const hasher = createHash("sha1");
const cacheName = hasher.update(ssoStartUrl).digest("hex");
const tokenFile = join(getHomeDir(), ".aws", "sso", "cache", `${cacheName}.json`);

const tokenText = await readFile(tokenFile, "utf8");
return JSON.parse(tokenText) as SSOToken;
const ssoTokenFilepath = getSSOTokenFilepath(ssoStartUrl);
const ssoTokenText = await readFile(ssoTokenFilepath, "utf8");
return JSON.parse(ssoTokenText) as SSOToken;
};
1 change: 1 addition & 0 deletions packages/shared-ini-file-loader/src/index.ts
@@ -1,5 +1,6 @@
export * from "./getHomeDir";
export * from "./getProfileName";
export * from "./getSSOTokenFilepath";
export * from "./getSSOTokenFromFile";
export * from "./loadSharedConfigFiles";
export * from "./parseKnownFiles";
Expand Down