From 94bb56ae4656c1fc55bbb89d62d7955c39ac51d7 Mon Sep 17 00:00:00 2001 From: "E. Cooper" Date: Mon, 13 Jan 2025 13:34:01 -0800 Subject: [PATCH 1/4] Move command tests into a commands dir in test/ (#557) --- test/{general-cli.mjs => cli.mjs} | 0 test/{ => commands}/database/create.mjs | 8 ++++---- test/{ => commands}/database/database.mjs | 4 ++-- test/{ => commands}/database/delete.mjs | 8 ++++---- test/{ => commands}/database/list.mjs | 10 +++++----- test/{ => commands}/local.mjs | 8 ++++---- test/{ => commands}/login.mjs | 6 +++--- test/{ => commands}/query.mjs | 12 ++++++------ test/{ => commands}/schema/abandon.mjs | 6 +++--- test/{ => commands}/schema/commit.mjs | 6 +++--- test/{ => commands}/schema/diff.mjs | 8 ++++---- test/{ => commands}/schema/pull.mjs | 8 ++++---- test/{ => commands}/schema/push.mjs | 8 ++++---- test/{ => commands}/schema/schema.mjs | 6 +++--- test/{ => commands}/schema/status.mjs | 8 ++++---- test/{ => commands}/shell.mjs | 19 +++++++++++-------- 16 files changed, 64 insertions(+), 61 deletions(-) rename test/{general-cli.mjs => cli.mjs} (100%) rename test/{ => commands}/database/create.mjs (96%) rename test/{ => commands}/database/database.mjs (89%) rename test/{ => commands}/database/delete.mjs (94%) rename test/{ => commands}/database/list.mjs (94%) rename test/{ => commands}/local.mjs (98%) rename test/{ => commands}/login.mjs (96%) rename test/{ => commands}/query.mjs (97%) rename test/{ => commands}/schema/abandon.mjs (95%) rename test/{ => commands}/schema/commit.mjs (95%) rename test/{ => commands}/schema/diff.mjs (94%) rename test/{ => commands}/schema/pull.mjs (97%) rename test/{ => commands}/schema/push.mjs (96%) rename test/{ => commands}/schema/schema.mjs (92%) rename test/{ => commands}/schema/status.mjs (97%) rename test/{ => commands}/shell.mjs (96%) diff --git a/test/general-cli.mjs b/test/cli.mjs similarity index 100% rename from test/general-cli.mjs rename to test/cli.mjs diff --git a/test/database/create.mjs b/test/commands/database/create.mjs similarity index 96% rename from test/database/create.mjs rename to test/commands/database/create.mjs index a7500992..06058ead 100644 --- a/test/database/create.mjs +++ b/test/commands/database/create.mjs @@ -4,10 +4,10 @@ import { expect } from "chai"; import { fql, ServiceError } from "fauna"; import sinon from "sinon"; -import { run } from "../../src/cli.mjs"; -import { setupTestContainer as setupContainer } from "../../src/config/setup-test-container.mjs"; -import { AUTHENTICATION_ERROR_MESSAGE } from "../../src/lib/errors.mjs"; -import { mockAccessKeysFile } from "../helpers.mjs"; +import { run } from "../../../src/cli.mjs"; +import { setupTestContainer as setupContainer } from "../../../src/config/setup-test-container.mjs"; +import { AUTHENTICATION_ERROR_MESSAGE } from "../../../src/lib/errors.mjs"; +import { mockAccessKeysFile } from "../../helpers.mjs"; describe("database create", () => { let container, logger, runQuery, accountAPI; diff --git a/test/database/database.mjs b/test/commands/database/database.mjs similarity index 89% rename from test/database/database.mjs rename to test/commands/database/database.mjs index f04075fb..6ac4027d 100644 --- a/test/database/database.mjs +++ b/test/commands/database/database.mjs @@ -3,8 +3,8 @@ import { expect } from "chai"; import chalk from "chalk"; -import { builtYargs, run } from "../../src/cli.mjs"; -import { setupTestContainer as setupContainer } from "../../src/config/setup-test-container.mjs"; +import { builtYargs, run } from "../../../src/cli.mjs"; +import { setupTestContainer as setupContainer } from "../../../src/config/setup-test-container.mjs"; describe("database", () => { let container, logger; diff --git a/test/database/delete.mjs b/test/commands/database/delete.mjs similarity index 94% rename from test/database/delete.mjs rename to test/commands/database/delete.mjs index a2591eab..f6adf59d 100644 --- a/test/database/delete.mjs +++ b/test/commands/database/delete.mjs @@ -4,10 +4,10 @@ import { expect } from "chai"; import { fql, ServiceError } from "fauna"; import sinon from "sinon"; -import { run } from "../../src/cli.mjs"; -import { setupTestContainer as setupContainer } from "../../src/config/setup-test-container.mjs"; -import { AUTHENTICATION_ERROR_MESSAGE } from "../../src/lib/errors.mjs"; -import { mockAccessKeysFile } from "../helpers.mjs"; +import { run } from "../../../src/cli.mjs"; +import { setupTestContainer as setupContainer } from "../../../src/config/setup-test-container.mjs"; +import { AUTHENTICATION_ERROR_MESSAGE } from "../../../src/lib/errors.mjs"; +import { mockAccessKeysFile } from "../../helpers.mjs"; describe("database delete", () => { let container, logger, runQuery, accountAPI; diff --git a/test/database/list.mjs b/test/commands/database/list.mjs similarity index 94% rename from test/database/list.mjs rename to test/commands/database/list.mjs index ac8511a4..b708c36f 100644 --- a/test/database/list.mjs +++ b/test/commands/database/list.mjs @@ -4,11 +4,11 @@ import { expect } from "chai"; import { ServiceError } from "fauna"; import sinon from "sinon"; -import { run } from "../../src/cli.mjs"; -import { setupTestContainer as setupContainer } from "../../src/config/setup-test-container.mjs"; -import { AUTHENTICATION_ERROR_MESSAGE } from "../../src/lib/errors.mjs"; -import { colorize } from "../../src/lib/formatting/colorize.mjs"; -import { mockAccessKeysFile } from "../helpers.mjs"; +import { run } from "../../../src/cli.mjs"; +import { setupTestContainer as setupContainer } from "../../../src/config/setup-test-container.mjs"; +import { AUTHENTICATION_ERROR_MESSAGE } from "../../../src/lib/errors.mjs"; +import { colorize } from "../../../src/lib/formatting/colorize.mjs"; +import { mockAccessKeysFile } from "../../helpers.mjs"; describe("database list", () => { let container, fs, logger, stdout, runQueryFromString, accountAPI; diff --git a/test/local.mjs b/test/commands/local.mjs similarity index 98% rename from test/local.mjs rename to test/commands/local.mjs index f894b662..15562ebb 100644 --- a/test/local.mjs +++ b/test/commands/local.mjs @@ -5,10 +5,10 @@ import { expect } from "chai"; import { AbortError } from "fauna"; import sinon, { stub } from "sinon"; -import { run } from "../src/cli.mjs"; -import { setupTestContainer } from "../src/config/setup-test-container.mjs"; -import { reformatFSL } from "../src/lib/schema.mjs"; -import { f } from "./helpers.mjs"; +import { run } from "../../src/cli.mjs"; +import { setupTestContainer } from "../../src/config/setup-test-container.mjs"; +import { reformatFSL } from "../../src/lib/schema.mjs"; +import { f } from "../helpers.mjs"; describe("local command", () => { let container, diff --git a/test/login.mjs b/test/commands/login.mjs similarity index 96% rename from test/login.mjs rename to test/commands/login.mjs index 27ea5779..b6f6c018 100644 --- a/test/login.mjs +++ b/test/commands/login.mjs @@ -4,9 +4,9 @@ import * as awilix from "awilix"; import { expect } from "chai"; import sinon, { spy } from "sinon"; -import { run } from "../src/cli.mjs"; -import { setupTestContainer as setupContainer } from "../src/config/setup-test-container.mjs"; -import { f } from "./helpers.mjs"; +import { run } from "../../src/cli.mjs"; +import { setupTestContainer as setupContainer } from "../../src/config/setup-test-container.mjs"; +import { f } from "../helpers.mjs"; describe("login", function () { let container, fs, fetch, getSession; diff --git a/test/query.mjs b/test/commands/query.mjs similarity index 97% rename from test/query.mjs rename to test/commands/query.mjs index 56af5a71..45cba6df 100644 --- a/test/query.mjs +++ b/test/commands/query.mjs @@ -4,17 +4,17 @@ import { expect } from "chai"; import { NetworkError, ServiceError } from "fauna"; import sinon from "sinon"; -import { run } from "../src/cli.mjs"; -import { setupTestContainer as setupContainer } from "../src/config/setup-test-container.mjs"; -import { NETWORK_ERROR_MESSAGE } from "../src/lib/errors.mjs"; -import { colorize } from "../src/lib/formatting/colorize.mjs"; -import { QUERY_INFO_CHOICES } from "../src/lib/options.mjs"; +import { run } from "../../src/cli.mjs"; +import { setupTestContainer as setupContainer } from "../../src/config/setup-test-container.mjs"; +import { NETWORK_ERROR_MESSAGE } from "../../src/lib/errors.mjs"; +import { colorize } from "../../src/lib/formatting/colorize.mjs"; +import { QUERY_INFO_CHOICES } from "../../src/lib/options.mjs"; import { createV4QueryFailure, createV4QuerySuccess, createV10QueryFailure, createV10QuerySuccess, -} from "./helpers.mjs"; +} from "../helpers.mjs"; describe("query", function () { let container, logger, runQueryFromString; diff --git a/test/schema/abandon.mjs b/test/commands/schema/abandon.mjs similarity index 95% rename from test/schema/abandon.mjs rename to test/commands/schema/abandon.mjs index 07a5f3f3..dcc5102b 100644 --- a/test/schema/abandon.mjs +++ b/test/commands/schema/abandon.mjs @@ -5,9 +5,9 @@ import chalk from "chalk"; import sinon from "sinon"; import tryToCatch from "try-to-catch"; -import { run } from "../../src/cli.mjs"; -import { setupTestContainer as setupContainer } from "../../src/config/setup-test-container.mjs"; -import { buildUrl, commonFetchParams, f } from "../helpers.mjs"; +import { run } from "../../../src/cli.mjs"; +import { setupTestContainer as setupContainer } from "../../../src/config/setup-test-container.mjs"; +import { buildUrl, commonFetchParams, f } from "../../helpers.mjs"; describe("schema abandon", function () { let diff = diff --git a/test/schema/commit.mjs b/test/commands/schema/commit.mjs similarity index 95% rename from test/schema/commit.mjs rename to test/commands/schema/commit.mjs index 30f8e2c8..8cded0bf 100644 --- a/test/schema/commit.mjs +++ b/test/commands/schema/commit.mjs @@ -5,9 +5,9 @@ import chalk from "chalk"; import sinon from "sinon"; import tryToCatch from "try-to-catch"; -import { run } from "../../src/cli.mjs"; -import { setupTestContainer as setupContainer } from "../../src/config/setup-test-container.mjs"; -import { buildUrl, commonFetchParams, f } from "../helpers.mjs"; +import { run } from "../../../src/cli.mjs"; +import { setupTestContainer as setupContainer } from "../../../src/config/setup-test-container.mjs"; +import { buildUrl, commonFetchParams, f } from "../../helpers.mjs"; describe("schema commit", function () { const textDiff = diff --git a/test/schema/diff.mjs b/test/commands/schema/diff.mjs similarity index 94% rename from test/schema/diff.mjs rename to test/commands/schema/diff.mjs index 1b98be1a..709c511c 100644 --- a/test/schema/diff.mjs +++ b/test/commands/schema/diff.mjs @@ -2,10 +2,10 @@ import { expect } from "chai"; -import { run } from "../../src/cli.mjs"; -import { setupTestContainer as setupContainer } from "../../src/config/setup-test-container.mjs"; -import { reformatFSL } from "../../src/lib/schema.mjs"; -import { buildUrl, commonFetchParams, f } from "../helpers.mjs"; +import { run } from "../../../src/cli.mjs"; +import { setupTestContainer as setupContainer } from "../../../src/config/setup-test-container.mjs"; +import { reformatFSL } from "../../../src/lib/schema.mjs"; +import { buildUrl, commonFetchParams, f } from "../../helpers.mjs"; describe("schema diff", function () { const colorDiffString = diff --git a/test/schema/pull.mjs b/test/commands/schema/pull.mjs similarity index 97% rename from test/schema/pull.mjs rename to test/commands/schema/pull.mjs index 566a8125..1762e3d6 100644 --- a/test/schema/pull.mjs +++ b/test/commands/schema/pull.mjs @@ -4,14 +4,14 @@ import * as awilix from "awilix"; import { expect } from "chai"; import sinon from "sinon"; -import { run } from "../../src/cli.mjs"; -import { setupTestContainer as setupContainer } from "../../src/config/setup-test-container.mjs"; +import { run } from "../../../src/cli.mjs"; +import { setupTestContainer as setupContainer } from "../../../src/config/setup-test-container.mjs"; import { deleteUnusedSchemaFiles, getAllSchemaFileContents, writeSchemaFiles, -} from "../../src/lib/schema.mjs"; -import { buildUrl, commonFetchParams, f } from "../helpers.mjs"; +} from "../../../src/lib/schema.mjs"; +import { buildUrl, commonFetchParams, f } from "../../helpers.mjs"; describe("schema pull", function () { let container, logger, confirm, fetch, fs, fsp, gatherFSL; diff --git a/test/schema/push.mjs b/test/commands/schema/push.mjs similarity index 96% rename from test/schema/push.mjs rename to test/commands/schema/push.mjs index eee7324b..85ff2c8e 100644 --- a/test/schema/push.mjs +++ b/test/commands/schema/push.mjs @@ -5,10 +5,10 @@ import chalk from "chalk"; import path from "path"; import sinon from "sinon"; -import { builtYargs, run } from "../../src/cli.mjs"; -import { setupTestContainer as setupContainer } from "../../src/config/setup-test-container.mjs"; -import { reformatFSL } from "../../src/lib/schema.mjs"; -import { buildUrl, f } from "../helpers.mjs"; +import { builtYargs, run } from "../../../src/cli.mjs"; +import { setupTestContainer as setupContainer } from "../../../src/config/setup-test-container.mjs"; +import { reformatFSL } from "../../../src/lib/schema.mjs"; +import { buildUrl, f } from "../../helpers.mjs"; describe("schema push", function () { const diffString = diff --git a/test/schema/schema.mjs b/test/commands/schema/schema.mjs similarity index 92% rename from test/schema/schema.mjs rename to test/commands/schema/schema.mjs index ff8980b9..b8123909 100644 --- a/test/schema/schema.mjs +++ b/test/commands/schema/schema.mjs @@ -4,12 +4,12 @@ import { expect } from "chai"; import chalk from "chalk"; import sinon from "sinon"; -import { builtYargs, run } from "../../src/cli.mjs"; -import { setupTestContainer as setupContainer } from "../../src/config/setup-test-container.mjs"; +import { builtYargs, run } from "../../../src/cli.mjs"; +import { setupTestContainer as setupContainer } from "../../../src/config/setup-test-container.mjs"; import { AUTHENTICATION_ERROR_MESSAGE, NETWORK_ERROR_MESSAGE, -} from "../../src/lib/errors.mjs"; +} from "../../../src/lib/errors.mjs"; describe("schema", function () { let container, logger, stderr; diff --git a/test/schema/status.mjs b/test/commands/schema/status.mjs similarity index 97% rename from test/schema/status.mjs rename to test/commands/schema/status.mjs index b100f949..4fbbd76b 100644 --- a/test/schema/status.mjs +++ b/test/commands/schema/status.mjs @@ -4,10 +4,10 @@ import { expect } from "chai"; import chalk from "chalk"; import sinon from "sinon"; -import { run } from "../../src/cli.mjs"; -import { setupTestContainer as setupContainer } from "../../src/config/setup-test-container.mjs"; -import { reformatFSL } from "../../src/lib/schema.mjs"; -import { buildUrl, commonFetchParams, f } from "../helpers.mjs"; +import { run } from "../../../src/cli.mjs"; +import { setupTestContainer as setupContainer } from "../../../src/config/setup-test-container.mjs"; +import { reformatFSL } from "../../../src/lib/schema.mjs"; +import { buildUrl, commonFetchParams, f } from "../../helpers.mjs"; describe("schema status", function () { let container, fetch, logger, gatherFSL; diff --git a/test/shell.mjs b/test/commands/shell.mjs similarity index 96% rename from test/shell.mjs rename to test/commands/shell.mjs index 22acfc17..f82185bf 100644 --- a/test/shell.mjs +++ b/test/commands/shell.mjs @@ -9,13 +9,16 @@ import { expect } from "chai"; import { NetworkError } from "fauna"; import sinon, { stub } from "sinon"; -import { run } from "../src/cli.mjs"; -import { setupTestContainer as setupContainer } from "../src/config/setup-test-container.mjs"; -import { NETWORK_ERROR_MESSAGE, ValidationError } from "../src/lib/errors.mjs"; -import { isQueryable } from "../src/lib/fauna-client.mjs"; -import { dirExists } from "../src/lib/file-util.mjs"; -import { colorize } from "../src/lib/formatting/colorize.mjs"; -import { createV4QuerySuccess, createV10QuerySuccess } from "./helpers.mjs"; +import { run } from "../../src/cli.mjs"; +import { setupTestContainer as setupContainer } from "../../src/config/setup-test-container.mjs"; +import { + NETWORK_ERROR_MESSAGE, + ValidationError, +} from "../../src/lib/errors.mjs"; +import { isQueryable } from "../../src/lib/fauna-client.mjs"; +import { dirExists } from "../../src/lib/file-util.mjs"; +import { colorize } from "../../src/lib/formatting/colorize.mjs"; +import { createV4QuerySuccess, createV10QuerySuccess } from "../helpers.mjs"; // this is defined up here so the indentation doesn't make it harder to use :( const v10Object1 = createV10QuerySuccess({ @@ -132,7 +135,7 @@ describe("shell", function () { const registerHomedir = (container, subdir = "") => { const __dirname = import.meta.dirname; - const homedir = path.join(__dirname, "../test/test-homedir", subdir); + const homedir = path.join(__dirname, "../../test/test-homedir", subdir); container.register({ homedir: awilix.asValue(stub().returns(homedir)), From 6db388a17b02da9d5258777f5def39d74b37058d Mon Sep 17 00:00:00 2001 From: Matthew Wilde Date: Tue, 14 Jan 2025 15:29:02 -0500 Subject: [PATCH 2/4] Alternate login method (#555) * no browser login option * update name of flag * add a test * get dashboard url from account url * test fix --- src/commands/login.mjs | 56 +++++++++++++++++++++++++++++----- src/config/setup-container.mjs | 3 +- src/lib/account-api.mjs | 20 ++++++++++++ src/lib/auth/oauth-client.mjs | 42 ++++++++++++++++--------- test/commands/login.mjs | 29 ++++++++++++++++-- 5 files changed, 124 insertions(+), 26 deletions(-) diff --git a/src/commands/login.mjs b/src/commands/login.mjs index 45757738..17c5f7d3 100644 --- a/src/commands/login.mjs +++ b/src/commands/login.mjs @@ -12,13 +12,9 @@ async function doLogin(argv) { const open = container.resolve("open"); const credentials = container.resolve("credentials"); const oAuth = container.resolve("oauthClient"); - oAuth.server.on("ready", async () => { - const authCodeParams = oAuth.getOAuthParams({ clientId: argv.clientId }); - const dashboardOAuthURL = await startOAuthRequest(authCodeParams); - open(dashboardOAuthURL); - logger.stdout(`To login, open your browser to:\n${dashboardOAuthURL}`); - }); - oAuth.server.on("auth_code_received", async () => { + const input = container.resolve("input"); + + const loginWithToken = async () => { try { const { clientId, clientSecret, authCode, redirectURI, codeVerifier } = oAuth.getTokenParams({ @@ -37,11 +33,48 @@ async function doLogin(argv) { /* eslint-enable camelcase */ await credentials.login(accessToken); + logger.stdout("Login successful."); } catch (err) { logger.stderr(err); } + }; + const authCodeParams = oAuth.getOAuthParams({ + clientId: argv.clientId, + noRedirect: argv.noRedirect, }); - await oAuth.start(); + const dashboardOAuthURL = await startOAuthRequest(authCodeParams); + logger.stdout(`To login, open a browser to:\n${dashboardOAuthURL}`); + if (!argv.noRedirect) { + oAuth.server.on("ready", async () => { + open(dashboardOAuthURL); + }); + oAuth.server.on("auth_code_received", async () => { + await loginWithToken(); + }); + await oAuth.start(); + logger.stdout("Waiting for authentication in browser to complete..."); + } else { + try { + const userCode = await input({ + message: "Authorization Code:", + }); + try { + const jsonString = atob(userCode); + const parsed = JSON.parse(jsonString); + const { code, state } = parsed; + oAuth.validateAuthorizationCode(code, state); + await loginWithToken(); + } catch (err) { + logger.stderr( + `Error during login: ${err.message}\nPlease restart login.`, + ); + } + } catch (err) { + if (err.name === "ExitPromptError") { + logger.stdout("Login canceled."); + } + } + } } /** @@ -70,6 +103,13 @@ function buildLoginCommand(yargs) { required: false, hidden: true, }, + "no-redirect": { + alias: "n", + type: "boolean", + description: + "Login without redirecting to a local callback server. Use this option if you are unable to open a browser on your local machine.", + default: false, + }, user: { alias: "u", type: "string", diff --git a/src/config/setup-container.mjs b/src/config/setup-container.mjs index d318529e..ac518145 100644 --- a/src/config/setup-container.mjs +++ b/src/config/setup-container.mjs @@ -5,7 +5,7 @@ import os from "node:os"; import path from "node:path"; import { exit } from "node:process"; -import { confirm } from "@inquirer/prompts"; +import { confirm, input } from "@inquirer/prompts"; import * as awilix from "awilix"; import { Lifetime } from "awilix"; import Docker from "dockerode"; @@ -69,6 +69,7 @@ export const injectables = { // third-party libraries confirm: awilix.asValue(confirm), + input: awilix.asValue(input), open: awilix.asValue(open), updateNotifier: awilix.asValue(updateNotifier), fauna: awilix.asValue(fauna), diff --git a/src/lib/account-api.mjs b/src/lib/account-api.mjs index 1b76f211..9fbeca0e 100644 --- a/src/lib/account-api.mjs +++ b/src/lib/account-api.mjs @@ -31,6 +31,26 @@ export function setAccountUrl(url) { accountUrl = url; } +/** + * Infer the dashboard URL to use for login redirect URI + * @returns {string} The dashboard URL + */ +export function getDashboardUrl() { + if (process.env.FAUNA_DASHBOARD_URL) { + return process.env.FAUNA_DASHBOARD_URL; + } + switch (accountUrl) { + case "https://account.fauna-dev.com": + return "https://dashboard.fauna-dev.com"; + case "https://account.fauna-preview.com": + return "https://dashboard.fauna-preview.com"; + case "http://localhost:8000": + return "http://localhost:3005"; + default: + return "https://dashboard.fauna.com"; + } +} + /** * Builds a URL for the account API * diff --git a/src/lib/auth/oauth-client.mjs b/src/lib/auth/oauth-client.mjs index 898f8a5e..aabb302f 100644 --- a/src/lib/auth/oauth-client.mjs +++ b/src/lib/auth/oauth-client.mjs @@ -4,6 +4,7 @@ import url from "url"; import util from "util"; import { container } from "../../config/container.mjs"; +import { getDashboardUrl } from "../account-api.mjs"; import SuccessPage from "./successPage.mjs"; const ALLOWED_ORIGINS = [ @@ -39,13 +40,18 @@ class OAuthClient { * Gets the OAuth parameters for the OAuth request. * @param {Object} [overrides] - The parameters for the OAuth request * @param {string} [overrides.clientId] - The client ID + * @param {boolean} [overrides.noRedirect] - Whether to disable the redirect * @returns {Object} The OAuth parameters */ - getOAuthParams({ clientId }) { + getOAuthParams({ clientId, noRedirect }) { + const redirectURI = noRedirect + ? `${getDashboardUrl()}/auth/oauth/callback/cli` + : `${REDIRECT_URI}:${this.port}`; + return { /* eslint-disable camelcase */ client_id: clientId ?? CLIENT_ID, - redirect_uri: `${REDIRECT_URI}:${this.port}`, + redirect_uri: redirectURI, code_challenge: this.codeChallenge, code_challenge_method: "S256", response_type: "code", @@ -72,6 +78,17 @@ class OAuthClient { }; } + validateAuthorizationCode(authCode, state) { + if (!authCode || typeof authCode !== "string") { + throw new Error("Invalid authorization code received"); + } else { + this.authCode = authCode; + if (state !== this.state) { + throw new Error("Invalid state received"); + } + } + } + static _generateCSRFToken() { return Buffer.from(randomBytes(20)).toString("base64url"); } @@ -88,17 +105,10 @@ class OAuthClient { } _handleCode({ authCode, state, res }) { - if (!authCode || typeof authCode !== "string") { - throw new Error("Invalid authorization code received"); - } else { - this.authCode = authCode; - if (state !== this.state) { - throw new Error("Invalid state received"); - } - res.writeHead(302, { Location: "/success" }); - res.end(); - this.server.emit("auth_code_received"); - } + this.validateAuthorizationCode(authCode, state); + res.writeHead(302, { Location: "/success" }); + res.end(); + this.server.emit("auth_code_received"); } // req: IncomingMessage, res: ServerResponse @@ -162,8 +172,10 @@ class OAuthClient { } closeServer() { - this.server.closeAllConnections(); - this.server.close(); + if (this.server.listening) { + this.server.closeAllConnections(); + this.server.close(); + } } } diff --git a/test/commands/login.mjs b/test/commands/login.mjs index b6f6c018..e76c4449 100644 --- a/test/commands/login.mjs +++ b/test/commands/login.mjs @@ -2,7 +2,7 @@ import * as awilix from "awilix"; import { expect } from "chai"; -import sinon, { spy } from "sinon"; +import sinon, { spy, stub } from "sinon"; import { run } from "../../src/cli.mjs"; import { setupTestContainer as setupContainer } from "../../src/config/setup-test-container.mjs"; @@ -40,6 +40,7 @@ describe("login", function () { codeVerifier: "code-verifier", }; }, + validateAuthorizationCode: stub(), server: { on: (eventName, handler) => { handlers[eventName] = handler; @@ -113,7 +114,7 @@ describe("login", function () { // We open auth url in the browser and prompt user expect(container.resolve("open").calledWith("http://dashboard-url.com")); expect(logger.stdout).to.have.been.calledWith( - "To login, open your browser to:\nhttp://dashboard-url.com", + "To login, open a browser to:\nhttp://dashboard-url.com", ); // Trigger server event with mocked auth code @@ -162,4 +163,28 @@ describe("login", function () { "Using a local Fauna container does not require login.\n", ); }); + + it("doesn't run loopback server with --no-redirect flag", async function () { + const input = container.resolve("input"); + const sampleCreds = btoa(JSON.stringify({ code: "asdf", state: "state" })); + input.resolves(sampleCreds); + + await run(`login --no-redirect=true`, container); + const oauthClient = container.resolve("oauthClient"); + const logger = container.resolve("logger"); + const credentials = container.resolve("credentials"); + expect(oauthClient.start.called).to.be.false; + expect(container.resolve("open").called).to.be.false; + expect(logger.stdout).to.have.been.calledWith( + "To login, open a browser to:\nhttp://dashboard-url.com", + ); + expect(input).to.have.been.calledWith({ + message: "Authorization Code:", + }); + expect(oauthClient.validateAuthorizationCode).to.have.been.calledWith( + "asdf", + "state", + ); + expect(credentials.accountKeys.key).to.equal("login-account-key"); + }); }); From de82557895ccbd374214b81f4a2c2ff9f13a3989 Mon Sep 17 00:00:00 2001 From: Cleve Stuart <90649124+cleve-fauna@users.noreply.github.com> Date: Tue, 14 Jan 2025 14:02:35 -0800 Subject: [PATCH 3/4] Pick up rename of @fauna/typescript -> @fauna/ts-dev-utils (#560) --- eslint.config.mjs | 2 +- package-lock.json | 19 ++++++------------- package.json | 2 +- prettier.config.js | 2 +- 4 files changed, 9 insertions(+), 16 deletions(-) diff --git a/eslint.config.mjs b/eslint.config.mjs index 8f12798f..0530f575 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -1,4 +1,4 @@ -import { config as defaultConfig } from "@fauna/typescript/config/js/eslint.config.js"; +import { config as defaultConfig } from "@fauna/ts-dev-utils/config/js/eslint.config.js"; import * as espree from "espree"; import globals from "globals"; diff --git a/package-lock.json b/package-lock.json index 5a55abe0..07428d0a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -33,7 +33,7 @@ "devDependencies": { "@eslint/eslintrc": "^3.1.0", "@eslint/js": "^9.16.0", - "@fauna/typescript": "^0.0.12", + "@fauna/ts-dev-utils": "^0.0.16", "@inquirer/testing": "^2.1.7", "@types/chai": "^5.0.0", "@types/mocha": "^10.0.1", @@ -238,10 +238,10 @@ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, - "node_modules/@fauna/typescript": { - "version": "0.0.12", - "resolved": "https://registry.npmjs.org/@fauna/typescript/-/typescript-0.0.12.tgz", - "integrity": "sha512-8eAq6RKaKDnUAY5AjoSgfZ6zT4TTDrNG/JkmrhQHrQjj6ScosqMEnCbPBkRhUWzhzIITDQwx5Rcs6VRGjjKhqw==", + "node_modules/@fauna/ts-dev-utils": { + "version": "0.0.16", + "resolved": "https://registry.npmjs.org/@fauna/ts-dev-utils/-/ts-dev-utils-0.0.16.tgz", + "integrity": "sha512-/s+fxVVXPep7XezSlYfEe9fvhJAaEhpeHeeDArAAztMEboxApeTzbblta/yG2U9MTGR6psavQ3SrMTdAep5MWw==", "dev": true, "license": "MPL-2.0", "dependencies": { @@ -1444,14 +1444,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/collect-mentions/-/collect-mentions-2.0.1.tgz", "integrity": "sha512-3/MkmIZDerSapDxlRLGJ2M38Zs2+GMJ6i3X4d9ilyB82PcSFVzA5VVH6A6SKXPIuzcyeE+xAS+4XdY0O1MVAxQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "mentions-regex": "^2.0.3" - }, - "engines": { - "node": ">=20" - } + "dev": true }, "node_modules/color-convert": { "version": "2.0.1", diff --git a/package.json b/package.json index dada86a5..a3f39d90 100644 --- a/package.json +++ b/package.json @@ -30,7 +30,7 @@ "devDependencies": { "@eslint/eslintrc": "^3.1.0", "@eslint/js": "^9.16.0", - "@fauna/typescript": "^0.0.12", + "@fauna/ts-dev-utils": "^0.0.16", "@inquirer/testing": "^2.1.7", "@types/chai": "^5.0.0", "@types/mocha": "^10.0.1", diff --git a/prettier.config.js b/prettier.config.js index 9660df94..e47603a9 100644 --- a/prettier.config.js +++ b/prettier.config.js @@ -1,4 +1,4 @@ -import basePrettierConfig from "@fauna/typescript/config/prettierrc.js"; +import basePrettierConfig from "@fauna/ts-dev-utils/config/prettierrc.js"; /** * @type {import("prettier").Config} From bfb125b22a061a405a43aa344081fd2e4b7a66a4 Mon Sep 17 00:00:00 2001 From: Cleve Stuart <90649124+cleve-fauna@users.noreply.github.com> Date: Wed, 15 Jan 2025 07:01:55 -0800 Subject: [PATCH 4/4] Align fauna local error messaging casing with command argument casing for 'host-port' (#561) --- src/lib/docker-containers.mjs | 6 +++--- test/commands/local.mjs | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/lib/docker-containers.mjs b/src/lib/docker-containers.mjs index 2c11a231..7c2cb0e1 100644 --- a/src/lib/docker-containers.mjs +++ b/src/lib/docker-containers.mjs @@ -165,8 +165,8 @@ async function findContainer({ containerName, hostPort }) { if (diffPort) { throw new CommandError( `[FindContainer] Container '${containerName}' is already \ -in use on hostPort '${diffPort.PublicPort}'. Please use a new name via \ -arguments --name --hostPort ${hostPort} to start the container.`, +in use on host-port '${diffPort.PublicPort}'. Please use a new name via \ +arguments --name --host-port ${hostPort} to start the container.`, ); } return result; @@ -222,7 +222,7 @@ async function createContainer({ const occupied = await isPortOccupied({ hostIp, hostPort }); if (occupied) { throw new CommandError( - `[StartContainer] The hostPort '${hostPort}' on IP '${hostIp}' is already occupied. \ + `[StartContainer] The host-port '${hostPort}' on IP '${hostIp}' is already occupied. \ Please pass a --host-port other than '${hostPort}'.`, ); } diff --git a/test/commands/local.mjs b/test/commands/local.mjs index 15562ebb..4892750e 100644 --- a/test/commands/local.mjs +++ b/test/commands/local.mjs @@ -130,7 +130,7 @@ describe("local command", () => { // Assertions expect(written).to.contain( - "[StartContainer] The hostPort '8443' on IP '0.0.0.0' is already occupied. \ + "[StartContainer] The host-port '8443' on IP '0.0.0.0' is already occupied. \ Please pass a --host-port other than '8443'.", ); expect(written).not.to.contain("fauna local"); @@ -567,8 +567,8 @@ https://support.fauna.com/hc/en-us/requests/new`, expect(logsStub).not.to.have.been.called; const written = stderrStream.getWritten(); expect(written).to.contain( - `[FindContainer] Container 'faunadb' is already in use on hostPort '9999'. \ -Please use a new name via arguments --name --hostPort ${desiredPort} \ + `[FindContainer] Container 'faunadb' is already in use on host-port '9999'. \ +Please use a new name via arguments --name --host-port ${desiredPort} \ to start the container.`, ); expect(written).not.to.contain("An unexpected");