Skip to content

Commit

Permalink
Merge branch 'main' into fix-redact-fetch
Browse files Browse the repository at this point in the history
  • Loading branch information
ecooper authored Jan 15, 2025
2 parents 3a7c26a + bfb125b commit c0e106d
Show file tree
Hide file tree
Showing 25 changed files with 203 additions and 109 deletions.
2 changes: 1 addition & 1 deletion eslint.config.mjs
Original file line number Diff line number Diff line change
@@ -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";

Expand Down
19 changes: 6 additions & 13 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
2 changes: 1 addition & 1 deletion prettier.config.js
Original file line number Diff line number Diff line change
@@ -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}
Expand Down
56 changes: 48 additions & 8 deletions src/commands/login.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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({
Expand All @@ -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.");
}
}
}
}

/**
Expand Down Expand Up @@ -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",
Expand Down
3 changes: 2 additions & 1 deletion src/config/setup-container.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -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),
Expand Down
20 changes: 20 additions & 0 deletions src/lib/account-api.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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
*
Expand Down
42 changes: 27 additions & 15 deletions src/lib/auth/oauth-client.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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 = [
Expand Down Expand Up @@ -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",
Expand All @@ -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");
}
Expand All @@ -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
Expand Down Expand Up @@ -162,8 +172,10 @@ class OAuthClient {
}

closeServer() {
this.server.closeAllConnections();
this.server.close();
if (this.server.listening) {
this.server.closeAllConnections();
this.server.close();
}
}
}

Expand Down
6 changes: 3 additions & 3 deletions src/lib/docker-containers.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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 <newName> --hostPort ${hostPort} to start the container.`,
in use on host-port '${diffPort.PublicPort}'. Please use a new name via \
arguments --name <newName> --host-port ${hostPort} to start the container.`,
);
}
return result;
Expand Down Expand Up @@ -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}'.`,
);
}
Expand Down
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
10 changes: 5 additions & 5 deletions test/database/list.mjs → test/commands/database/list.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
14 changes: 7 additions & 7 deletions test/local.mjs → test/commands/local.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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");
Expand Down Expand Up @@ -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 <newName> --hostPort ${desiredPort} \
`[FindContainer] Container 'faunadb' is already in use on host-port '9999'. \
Please use a new name via arguments --name <newName> --host-port ${desiredPort} \
to start the container.`,
);
expect(written).not.to.contain("An unexpected");
Expand Down
Loading

0 comments on commit c0e106d

Please sign in to comment.