Skip to content

Commit

Permalink
Create Key (#468)
Browse files Browse the repository at this point in the history
Create a key
---------

Co-authored-by: Ashton Eby <[email protected]>
Co-authored-by: Ashton Eby <[email protected]>
  • Loading branch information
3 people authored Dec 5, 2024
1 parent 4fd660a commit a7227a3
Show file tree
Hide file tree
Showing 13 changed files with 254 additions and 90 deletions.
10 changes: 10 additions & 0 deletions package-lock.json

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

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
"faunadb": "^4.5.4",
"inquirer": "^12.0.0",
"json-colorizer": "^3.0.1",
"luxon": "^3.5.0",
"open": "10.1.0",
"update-notifier": "^7.3.1",
"yaml": "^2.6.1",
Expand Down
2 changes: 1 addition & 1 deletion src/cli.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import chalk from "chalk";
import yargs from "yargs";

import databaseCommand from "./commands/database/database.mjs";
import keyCommand from "./commands/key.mjs";
import keyCommand from "./commands/key/key.mjs";
import loginCommand from "./commands/login.mjs";
import queryCommand from "./commands/query.mjs";
import schemaCommand from "./commands/schema/schema.mjs";
Expand Down
66 changes: 0 additions & 66 deletions src/commands/key.mjs

This file was deleted.

77 changes: 77 additions & 0 deletions src/commands/key/create.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import { DateTime, Settings } from "luxon";

import { container } from "../../cli.mjs";
import { FaunaAccountClient } from "../../lib/fauna-account-client.mjs";
import { formatObject } from "../../lib/misc.mjs";

Settings.defaultZone = "utc";

async function createKey(argv) {
if (argv.secret) {
return createKeyWithSecret(argv);
}
return createKeyWithAccountApi(argv);
}

async function createKeyWithSecret(/*argv*/) {
const logger = container.resolve("logger");
logger.stderr("TODO");
}

async function createKeyWithAccountApi(argv) {
const accountClient = new FaunaAccountClient();
const { database, keyRole, ttl, name } = argv;
const databaseKey = await accountClient.createKey({
path: database,
role: keyRole,
ttl,
name,
});
const { path: db, ...rest } = databaseKey;
container
.resolve("logger")
.stdout(formatObject({ ...rest, database: db }, argv));
}

function buildCreateCommand(yargs) {
return yargs
.options({
name: {
type: "string",
required: false,
description: "The name of the key",
},
ttl: {
type: "string",
required: false,
description:
"The time-to-live for the key. Provide as an ISO 8601 date time string.",
},
keyRole: {
type: "string",
required: true,
description: "The role to assign to the key; e.g. admin",
},
})
.check((argv) => {
if (argv.ttl && !DateTime.fromISO(argv.ttl).isValid) {
throw new Error(
`Invalid ttl '${argv.ttl}'. Provide as an ISO 8601 date time string.`,
);
}
if (argv.database === undefined && argv.secret === undefined) {
throw new Error(
"You must provide at least one of: --database, --secret, --local.",
);
}
return true;
})
.help("help", "show help");
}

export default {
command: "create",
describe: "Create a key for a database",
builder: buildCreateCommand,
handler: createKey,
};
18 changes: 18 additions & 0 deletions src/commands/key/key.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
//@ts-check

import { yargsWithCommonQueryOptions } from "../../lib/command-helpers.mjs";
import createCommand from "./create.mjs";

function buildKeyCommand(yargs) {
return yargsWithCommonQueryOptions(yargs)
.command(createCommand)
.demandCommand()
.help("help", "show help");
}

export default {
command: "key <method>",
describe: "Create and manage database keys",
builder: buildKeyCommand,
handler: () => {}, // eslint-disable-line no-empty-function
};
1 change: 1 addition & 0 deletions src/lib/auth/databaseKeys.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ export class DatabaseKeys {
const newSecret = await accountClient.createKey({
path,
role,
name: "System generated shell key",
ttl: new Date(expiration).toISOString(),
});
this.keyStore.save(this.keyName, {
Expand Down
14 changes: 8 additions & 6 deletions src/lib/fauna-account-client.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
import { container } from "../cli.mjs";
import { InvalidCredsError } from "./misc.mjs";

// const KEY_TTL_DEFAULT_MS = 1000 * 60 * 60 * 24;

/**
* Class representing a client for interacting with the Fauna account API.
*/
Expand Down Expand Up @@ -198,20 +200,20 @@ export class FaunaAccountClient {
* @param {Object} params - The parameters for creating the key.
* @param {string} params.path - The path of the database, including region group
* @param {string} params.role - The builtin role for the key.
* @param {string} params.ttl - ISO String for the key's expiration time
* @param {string | undefined} params.ttl - ISO String for the key's expiration time, optional
* @param {string | undefined} params.name - The name for the key, optional
* @returns {Promise<Object>} - A promise that resolves when the key is created.
* @throws {Error} - Throws an error if there is an issue during key creation.
*/
async createKey({ path, role, ttl }) {
const TTL_DEFAULT_MS = 1000 * 60 * 60 * 24;
return await this.retryableAccountRequest({
async createKey({ path, role, ttl, name }) {
return this.retryableAccountRequest({
method: "POST",
path: "/databases/keys",
body: JSON.stringify({
role,
path: FaunaAccountClient.standardizeRegion(path),
ttl: ttl || new Date(Date.now() + TTL_DEFAULT_MS).toISOString(),
name: "System generated shell key",
ttl,
name,
}),
secret: this.accountKeys.key,
});
Expand Down
13 changes: 3 additions & 10 deletions src/lib/fauna.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import {

import { container } from "../cli.mjs";
import { ValidationError } from "./command-helpers.mjs";
import { formatFullErrorForShell, formatObjectForShell } from "./misc.mjs";
import { formatFullErrorForShell, formatObject } from "./misc.mjs";

/**
* Interprets a string as a FQL expression and returns a query.
Expand Down Expand Up @@ -159,18 +159,11 @@ export const formatError = (err, opts = {}) => {
* @returns {string} The formatted response
*/
export const formatQueryResponse = (res, opts = {}) => {
const { extra, json, color } = opts;
const { extra } = opts;

// If extra is set, return the full response object.
const data = extra ? res : res.data;

// If json is set, return the response as a JSON string.
if (json) {
return JSON.stringify(data);
}

// Otherwise, return the response as a pretty-printed JSON string.
return formatObjectForShell(data, { color });
return formatObject(data, opts);
};

/**
Expand Down
10 changes: 3 additions & 7 deletions src/lib/faunadb.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import { createContext, runInContext } from "node:vm";

import { container } from "../cli.mjs";
import { formatFullErrorForShell, formatObjectForShell } from "./misc.mjs";
import { formatFullErrorForShell, formatObject } from "./misc.mjs";

/**
* Creates a V4 Fauna client.
Expand Down Expand Up @@ -129,13 +129,9 @@ export const formatError = (err, opts = {}) => {
* @returns {string} The formatted response
*/
export const formatQueryResponse = (res, opts = {}) => {
const { extra, json, color } = opts;
const { extra } = opts;
const data = extra ? res : res.value;
if (json) {
return JSON.stringify(data);
}

return formatObjectForShell(data, { color });
return formatObject(data, opts);
};

/**
Expand Down
1 change: 1 addition & 0 deletions src/lib/middleware.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -100,4 +100,5 @@ export function applyLocalArg(argv) {
argv,
);
}
return argv;
}
17 changes: 17 additions & 0 deletions src/lib/misc.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,23 @@ export function isTTY() {
return process.stdout.isTTY;
}

/**
* Formats an object for display.
* @param {any} obj - The object to format
* @param {object} [opts] - Options
* @param {boolean} [opts.json] - Whether to return a JSON string with no pretty-printing
* @returns {string} The formatted object
*/
export function formatObject(obj, opts = {}) {
const { json } = opts;
// if json is set return a JSON string
if (json) {
return JSON.stringify(obj);
}
// Otherwise, return a pretty-printed JSON string
return formatObjectForShell(obj, opts);
}

/**
* Formats an object for display in the shell.
* @param {any} obj - The object to format
Expand Down
Loading

0 comments on commit a7227a3

Please sign in to comment.