From 605bf37138eb9ef1be4c70324ad6ad04aad7a508 Mon Sep 17 00:00:00 2001 From: fabgo Date: Mon, 28 Oct 2024 09:13:23 -0500 Subject: [PATCH] ENG-2931 Fix error when no identity file is present (#134) Fixes the error that is shown when no identity file is present. The CLI now again shows a message asking the user to login: ./p0 ls gcloud role oauth Please run `p0 login ` to use the P0 CLI. Added unit test. --- src/commands/__tests__/login.test.ts | 56 ++++++++++++++++++++-------- src/drivers/auth.ts | 12 ++---- src/drivers/config.ts | 3 +- src/drivers/firestore.ts | 13 +++++-- 4 files changed, 56 insertions(+), 28 deletions(-) diff --git a/src/commands/__tests__/login.test.ts b/src/commands/__tests__/login.test.ts index 698f565..cee8fda 100644 --- a/src/commands/__tests__/login.test.ts +++ b/src/commands/__tests__/login.test.ts @@ -24,7 +24,7 @@ jest.mock("../../drivers/auth", () => ({ jest.mock("../../drivers/config", () => ({ ...jest.requireActual("../../drivers/config"), saveConfig: jest.fn(), - getTenantConfig: jest.fn(() => bootstrapConfig), + loadConfig: jest.fn(() => bootstrapConfig), })); jest.mock("../../drivers/stdio"); jest.mock("../../plugins/login"); @@ -54,25 +54,26 @@ describe("login", () => { describe("organization exists", () => { let credentialData: string = ""; - mockReadFile.mockImplementation(async () => - Buffer.from(credentialData, "utf-8") - ); - mockWriteFile.mockImplementation(async (_path, data) => { - credentialData = data; - }); - mockSignInWithCredential.mockImplementation( - async (_auth, _firebaseCredential) => - Promise.resolve({ - user: { - email: "user@p0.dev", - }, - }) - ); beforeEach(() => { credentialData = ""; jest.clearAllMocks(); + mockReadFile.mockImplementation(async () => + Buffer.from(credentialData, "utf-8") + ); + mockWriteFile.mockImplementation(async (_path, data) => { + credentialData = data; + }); + mockSignInWithCredential.mockImplementation( + async (_auth, _firebaseCredential) => + Promise.resolve({ + user: { + email: "user@p0.dev", + }, + }) + ); + mockGetDoc({ slug: "test-org", tenantId: "test-tenant", @@ -105,4 +106,29 @@ Please contact support@p0.dev to enable this user." `); }); }); + + describe("identity file does not exist", () => { + beforeEach(() => { + jest.clearAllMocks(); + + // Mock `readFile` to throw an "ENOENT" error + mockReadFile.mockImplementation(() => { + const error = new Error("File not found"); + (error as any).code = "ENOENT"; + return Promise.reject(error); + }); + + mockGetDoc({ + slug: "test-org", + tenantId: "test-tenant", + ssoProvider: "google", + }); + }); + + it("it should ask user to log in", async () => { + await expect(login({ org: "test-org" })).rejects.toMatchInlineSnapshot( + `"Please run \`p0 login \` to use the P0 CLI."` + ); + }); + }); }); diff --git a/src/drivers/auth.ts b/src/drivers/auth.ts index 0e40b21..8700030 100644 --- a/src/drivers/auth.ts +++ b/src/drivers/auth.ts @@ -11,8 +11,7 @@ You should have received a copy of the GNU General Public License along with @p0 import { login } from "../commands/login"; import { Authn, Identity } from "../types/identity"; import { P0_PATH } from "../util"; -import { loadConfig } from "./config"; -import { authenticateToFirebase, initializeFirebase } from "./firestore"; +import { authenticateToFirebase } from "./firestore"; import { print2 } from "./stdio"; import * as fs from "fs/promises"; import * as path from "path"; @@ -66,7 +65,7 @@ export const cached = async ( } }; -export const loadCredentials = async (options?: { +const loadCredentialsWithAutoLogin = async (options?: { noRefresh?: boolean; }): Promise => { try { @@ -78,7 +77,7 @@ export const loadCredentials = async (options?: { ) { await login({ org: identity.org.slug }, { skipAuthenticate: true }); print2("\u200B"); // Force a new line - return loadCredentials({ noRefresh: true }); + return loadCredentialsWithAutoLogin({ noRefresh: true }); } return identity; } catch (error: any) { @@ -92,10 +91,7 @@ export const loadCredentials = async (options?: { export const authenticate = async (options?: { noRefresh?: boolean; }): Promise => { - await loadConfig(); - initializeFirebase(); - - const identity = await loadCredentials(options); + const identity = await loadCredentialsWithAutoLogin(options); const userCredential = await authenticateToFirebase(identity); return { userCredential, identity }; diff --git a/src/drivers/config.ts b/src/drivers/config.ts index cd29159..0cf5525 100644 --- a/src/drivers/config.ts +++ b/src/drivers/config.ts @@ -30,7 +30,8 @@ export async function saveConfig(config: Config) { tenantConfig = config; } -export async function loadConfig() { +export async function loadConfig(): Promise { const buffer = await fs.readFile(CONFIG_FILE_PATH); tenantConfig = JSON.parse(buffer.toString()); + return tenantConfig; } diff --git a/src/drivers/firestore.ts b/src/drivers/firestore.ts index f196738..0b89ece 100644 --- a/src/drivers/firestore.ts +++ b/src/drivers/firestore.ts @@ -9,7 +9,7 @@ This file is part of @p0security/cli You should have received a copy of the GNU General Public License along with @p0security/cli. If not, see . **/ import { Identity } from "../types/identity"; -import { getTenantConfig } from "./config"; +import { loadConfig } from "./config"; import { bootstrapConfig } from "./env"; import { FirebaseApp, initializeApp } from "firebase/app"; import { @@ -17,6 +17,7 @@ import { OAuthProvider, SignInMethod, signInWithCredential, + UserCredential, } from "firebase/auth"; import { collection as fsCollection, @@ -34,16 +35,20 @@ const bootstrapFirestore = getFirestore(bootstrapApp); let app: FirebaseApp; let firestore: Firestore; -export function initializeFirebase() { - const tenantConfig = getTenantConfig(); +async function initializeFirebase() { + const tenantConfig = await loadConfig(); app = initializeApp(tenantConfig.fs, "authFirebase"); firestore = getFirestore(app); } -export async function authenticateToFirebase(identity: Identity) { +export async function authenticateToFirebase( + identity: Identity +): Promise { const { credential } = identity; const tenantId = identity.org.tenantId; + await initializeFirebase(); + // TODO: Move to map lookup const provider = new OAuthProvider( identity.org.ssoProvider === "google"