Skip to content

Commit

Permalink
don't create dupe keys
Browse files Browse the repository at this point in the history
  • Loading branch information
mwilde345 committed Nov 22, 2024
1 parent 15096a3 commit 7be933c
Show file tree
Hide file tree
Showing 5 changed files with 63 additions and 137 deletions.
2 changes: 1 addition & 1 deletion src/cli.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ import keyCommand from "./commands/key.mjs";
import loginCommand from "./commands/login.mjs";
import schemaCommand from "./commands/schema/schema.mjs";
import shellCommand from "./commands/shell.mjs";
import { checkForUpdates, fixPaths, logArgv } from "./lib/middleware.mjs";
import { cleanupSecretsFile } from "./lib/auth/authNZ.mjs";
import { checkForUpdates, fixPaths, logArgv } from "./lib/middleware.mjs";

/** @typedef {import('awilix').AwilixContainer<import('./config/setup-container.mjs').modifiedInjectables>} cliContainer */

Expand Down
90 changes: 0 additions & 90 deletions src/commands/database.mjs

This file was deleted.

3 changes: 2 additions & 1 deletion src/lib/auth/authNZ.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,14 @@ export async function refreshDBKey({ profile, database, role }) {
const secretCreds = container.resolve("secretCreds");
const AccountClient = new (container.resolve("AccountClient"))(profile);
const newSecret = await AccountClient.createKey({ path: database, role });
const accountKey = getAccountKey(profile).accountKey;
secretCreds.save({
creds: {
path: database,
role,
secret: newSecret.secret,
},
key: getAccountKey(profile).accountKey,
key: accountKey,
});
return newSecret;
}
Expand Down
101 changes: 56 additions & 45 deletions src/lib/command-helpers.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,21 @@ function buildHeaders() {
return headers;
}

/**
* This function will return a v4 or v10 client based on the version provided in the argv.
* The client will be configured with a secret provided via:
* - command line flag
* - environment variable
* - stored in the credentials file
* If provided via command line flag or env var, 401s from the client will show an error to the user.
* If the secret is stored in the credentials file, the client will attempt to refresh the secret
* and retry the query.
* @param {*} argv
* @returns
*/
export async function getSimpleClient(argv) {
const logger = container.resolve("logger");
const { profile, database: path, role, secret } = argv;
const { profile, secret } = argv;
const accountKey = getAccountKey(profile).accountKey;
if (secret) {
logger.debug("Using Database secret from command line flag");
Expand Down Expand Up @@ -48,21 +60,53 @@ export async function getSimpleClient(argv) {
logger.debug(
"No secret provided, checking for stored secret in credentials file",
);
const existingSecret = getDBKey({ accountKey, path, role })?.secret;
if (existingSecret) {
logger.debug("Found stored secret in credentials file");
client = await clientFromStoredSecret({
argv,
storedSecret: existingSecret,
});
} else {
logger.debug("No stored secret found, minting new secret");
client = await clientFromNewSecret({ argv });
}
client = await clientFromStoredSecret({
argv,
accountKey,
});
}
return client;
}

/**
* Build a client where the secret isn't provided as a class argument, but is instead
* fetched from the credentials file during client.query
* @param {*} param0
* @returns
*/
async function clientFromStoredSecret({ argv, accountKey }) {
const logger = container.resolve("logger");
const { database: path, role } = argv;
let client = await buildClient({
...argv,
// client.query will handle getting/refreshing the secret
secret: undefined,
});
const originalQuery = client.query.bind(client);
client.query = async function (...args) {
// Get latest stored secret for the requested path
const existingSecret = getDBKey({ accountKey, path, role })?.secret;
const newArgs = [args[0], { ...args[1], secret: existingSecret }];
return originalQuery(...newArgs).then(async (result) => {
if (result.status === 401) {
logger.debug("stored secret is invalid, refreshing");
// Refresh the secret, store it, and use it to try again
const newSecret = await refreshDBKey(argv);
const newArgs = [args[0], { ...args[1], secret: newSecret.secret }];
const result = await originalQuery(...newArgs);
return result;
}
return result;
});
};
return client;
}

/**
* Build a client based on the command line options provided
* @param {*} argv
* @returns
*/
async function buildClient(argv) {
let client;
if (argv.version === "4") {
Expand Down Expand Up @@ -92,39 +136,6 @@ async function buildClient(argv) {
return client;
}

async function clientFromStoredSecret({ argv, storedSecret }) {
const logger = container.resolve("logger");
let client = await buildClient({
...argv,
secret: storedSecret,
});
const originalQuery = client.query.bind(client);
client.query = async function (...args) {
return originalQuery(...args).then(async (result) => {
if (result.status === 401) {
logger.debug("stored secret is invalid, refreshing");
// TODO: this refreshes the db key and stores in local storage, but the client instance
// is not updated with the new secret.
const newSecret = await refreshDBKey(argv);
const newArgs = [args[0], { ...args[1], secret: newSecret.secret }];
const result = await originalQuery(...newArgs);
return result;
}
return result;
});
};
return client;
}

async function clientFromNewSecret({ argv }) {
const newSecret = await refreshDBKey(argv);
const client = await buildClient({
...argv,
secret: newSecret.secret,
});
return client;
}

// export async function ensureDbScopeClient({ scope, version, argv }) {
// const path = scope.split("/");

Expand Down
4 changes: 4 additions & 0 deletions src/lib/fauna-account-client.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -171,13 +171,17 @@ export class FaunaAccountClient {
* @throws {Error} - Throws an error if there is an issue during key creation.
*/
async createKey({ path, role = "admin" }) {
// TODO: improve key lifecycle management, incl tracking expiration in filesystem
const TTL_DEFAULT_MS = 1000 * 60 * 15; // 15 minutes
// TODO: specify a ttl
return await this.makeAccountRequest({
method: "POST",
path: "/databases/keys",
body: JSON.stringify({
path,
role,
name: "System generated shell key",
ttl: new Date(Date.now() + TTL_DEFAULT_MS).toISOString(),
}),
secret: this.accountKey,
});
Expand Down

0 comments on commit 7be933c

Please sign in to comment.