Skip to content

Commit

Permalink
feat: changed parsers to allow using vault name without secret path
Browse files Browse the repository at this point in the history
chore: vault commands are now using vaultNameParser

chore: writing fastcheck tests for parsers

chore: jestified outputFormatter tests
[ci skip]
  • Loading branch information
aryanjassal committed Oct 24, 2024
1 parent 3377c18 commit fc7bb0b
Show file tree
Hide file tree
Showing 15 changed files with 399 additions and 306 deletions.
3 changes: 2 additions & 1 deletion src/secrets/CommandCat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ class CommandGet extends CommandPolykey {
for (const [vaultName, secretPath] of secretPaths) {
await writer.write({
nameOrId: vaultName,
secretName: secretPath,
secretName: secretPath ?? '/',
metadata: first
? { ...auth, options: { continueOnError: true } }
: undefined,
Expand All @@ -98,6 +98,7 @@ class CommandGet extends CommandPolykey {
if (chunk.error) process.stderr.write(chunk.error);
else process.stdout.write(chunk.secretContent);
}
process.stderr.write("\n");
}, meta);
} finally {
if (pkClient! != null) await pkClient.stop();
Expand Down
4 changes: 2 additions & 2 deletions src/secrets/CommandEdit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ class CommandEdit extends CommandPolykey {
this.argument(
'<secretPath>',
'Path to the secret to be edited, specified as <vaultName>:<directoryPath>',
binParsers.parseSecretPathValue,
binParsers.parseSecretPath,
);
this.addOption(binOptions.nodeId);
this.addOption(binOptions.clientHost);
Expand Down Expand Up @@ -68,7 +68,7 @@ class CommandEdit extends CommandPolykey {
const writer = res.writable.getWriter();
await writer.write({
nameOrId: secretPath[0],
secretName: secretPath[1],
secretName: secretPath[1] ?? '/',
metadata: auth,
});
await writer.close();
Expand Down
2 changes: 1 addition & 1 deletion src/secrets/CommandList.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ class CommandList extends CommandPolykey {
this.argument(
'<directoryPath>',
'Directory to list files from, specified as <vaultName>[:<path>]',
binParsers.parseSecretPathOptional,
binParsers.parseSecretPath,
);
this.addOption(binOptions.nodeId);
this.addOption(binOptions.clientHost);
Expand Down
6 changes: 3 additions & 3 deletions src/secrets/CommandMkdir.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ class CommandMkdir extends CommandPolykey {
this.addOption(binOptions.nodeId);
this.addOption(binOptions.clientHost);
this.addOption(binOptions.clientPort);
this.addOption(binOptions.recursive);
this.addOption(binOptions.parents);
this.action(async (secretPaths, options) => {
secretPaths = secretPaths.map((path: string) =>
binParsers.parseSecretPath(path),
Expand Down Expand Up @@ -67,9 +67,9 @@ class CommandMkdir extends CommandPolykey {
for (const [vault, path] of secretPaths) {
await writer.write({
nameOrId: vault,
dirName: path,
dirName: path ?? '/',
metadata: first
? { ...auth, options: { recursive: options.recursive } }
? { ...auth, options: { recursive: options.parents } }
: undefined,
});
first = false;
Expand Down
2 changes: 1 addition & 1 deletion src/secrets/CommandRemove.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ class CommandRemove extends CommandPolykey {
this.addOption(binOptions.recursive);
this.action(async (secretPaths, options) => {
secretPaths = secretPaths.map((path: string) =>
binParsers.parseSecretPathValue(path),
binParsers.parseSecretPath(path),
);
const { default: PolykeyClient } = await import(
'polykey/dist/PolykeyClient'
Expand Down
2 changes: 1 addition & 1 deletion src/secrets/CommandStat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ class CommandStat extends CommandPolykey {
this.argument(
'<secretPath>',
'Path to where the secret, specified as <vaultName>:<directoryPath>',
binParsers.parseSecretPathValue,
binParsers.parseSecretPath,
);
this.addOption(binOptions.nodeId);
this.addOption(binOptions.clientHost);
Expand Down
4 changes: 2 additions & 2 deletions src/secrets/CommandWrite.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ class CommandWrite extends CommandPolykey {
this.argument(
'<secretPath>',
'Path to the secret, specified as <vaultName>:<directoryPath>',
binParsers.parseSecretPathValue,
binParsers.parseSecretPath,
);
this.addOption(binOptions.nodeId);
this.addOption(binOptions.clientHost);
Expand Down Expand Up @@ -77,7 +77,7 @@ class CommandWrite extends CommandPolykey {
await pkClient.rpcClient.methods.vaultsSecretsWriteFile({
metadata: auth,
nameOrId: secretPath[0],
secretName: secretPath[1],
secretName: secretPath[1] ?? '/',
secretContent: stdin,
}),
meta,
Expand Down
8 changes: 7 additions & 1 deletion src/utils/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -310,7 +310,12 @@ const order = new commander.Option(

const recursive = new commander.Option(
'--recursive',
'If enabled, specified directories will be removed along with their contents',
'If enabled, specified operation will be applied recursively to the directory and its contents',
).default(false);

const parents = new commander.Option(
'--parents',
'If enabled, create all parent directories as well. If the directories exist, do nothing.',
).default(false);

export {
Expand Down Expand Up @@ -355,4 +360,5 @@ export {
limit,
order,
recursive,
parents,
};
85 changes: 28 additions & 57 deletions src/utils/parsers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,32 +67,6 @@ function parseCoreCount(v: string): number | undefined {
}
}

function parseSecretPathOptional(
secretPath: string,
): [string, string?, string?] {
// E.g. If 'vault1:a/b/c', ['vault1', 'a/b/c'] is returned
// If 'vault1', ['vault1, undefined] is returned
// splits out everything after an `=` separator
const lastEqualIndex = secretPath.lastIndexOf('=');
const splitSecretPath =
lastEqualIndex === -1
? secretPath
: secretPath.substring(0, lastEqualIndex);
const value =
lastEqualIndex === -1
? undefined
: secretPath.substring(lastEqualIndex + 1);
if (!vaultNameSecretPathRegex.test(splitSecretPath)) {
throw new commander.InvalidArgumentError(
`${secretPath} is not of the format <vaultName>[:<directoryPath>][=<value>]`,
);
}
const [, vaultName, directoryPath] = splitSecretPath.match(
vaultNameSecretPathRegex,
)!;
return [vaultName, directoryPath, value];
}

function parseVaultName(vaultName: string): string {
if (!vaultNameRegex.test(vaultName)) {
throw new commander.InvalidArgumentError(
Expand All @@ -102,29 +76,12 @@ function parseVaultName(vaultName: string): string {
return vaultName;
}

function parseSecretPath(secretPath: string): [string, string, string?] {
// E.g. If 'vault1:a/b/c', ['vault1', 'a/b/c'] is returned
// If 'vault1', an error is thrown
const [vaultName, secretName, value] = parseSecretPathOptional(secretPath);
if (secretName === undefined) {
throw new commander.InvalidArgumentError(
`${secretPath} is not of the format <vaultName>:<directoryPath>[=<value>]`,
);
}
return [vaultName, secretName, value];
}

function parseSecretPathValue(secretPath: string): [string, string, string?] {
const [vaultName, directoryPath, value] = parseSecretPath(secretPath);
if (value != null && !secretPathValueRegex.test(value)) {
throw new commander.InvalidArgumentError(
`${value} is not a valid value name`,
);
}
return [vaultName, directoryPath, value];
}

function parseSecretPathEnv(secretPath: string): [string, string?, string?] {
// E.g. If 'vault1:a/b/c', ['vault1', 'a/b/c'] is returned
// If 'vault1', ['vault1, undefined] is returned
// If 'vault1:', an error is thrown
// If 'a/b/c', an error is thrown
// Splits out everything after an `=` separator
function parseSecretPath(secretPath: string): [string, string?, string?] {
// The colon character `:` is prohibited in vaultName, so it's first occurence
// means that this is the delimiter between vaultName and secretPath.
const colonIndex = secretPath.indexOf(':');
Expand All @@ -141,20 +98,35 @@ function parseSecretPathEnv(secretPath: string): [string, string?, string?] {
equalIndex === -1
? secretPathPart
: secretPathPart.substring(0, equalIndex);
const valueData =
const value =
equalIndex === -1 ? undefined : secretPathPart.substring(equalIndex + 1);
if (splitSecretPath != null && !secretPathRegex.test(splitSecretPath)) {
throw new commander.InvalidArgumentError(
`${secretPath} is not of the format <vaultName>[:<secretPath>][=<value>]`,
);
}
const parsedVaultName = parseVaultName(vaultNamePart);
const parsedSecretPath = splitSecretPath.match(secretPathRegex)?.[0] ?? '/';
const [vaultName, directoryPath, value] = [
parsedVaultName,
parsedSecretPath,
valueData,
];
const parsedSecretPath = splitSecretPath.match(secretPathRegex)?.[0];
return [parsedVaultName, parsedSecretPath, value];
}

function parseSecretPathValue(secretPath: string): [string, string, string?] {
const [vaultName, directoryPath, value] = parseSecretPath(secretPath);
if (value != null && !secretPathValueRegex.test(value)) {
throw new commander.InvalidArgumentError(
`${value} is not a valid value name`,
);
}
if (directoryPath == null) {
throw new commander.InvalidArgumentError(
`${secretPath} is not of the format <vaultName>:<directoryPath>[=<value>]`,
);
}
return [vaultName, directoryPath, value];
}

function parseSecretPathEnv(secretPath: string): [string, string?, string?] {
const [vaultName, directoryPath, value] = parseSecretPath(secretPath);
if (value != null && !environmentVariableRegex.test(value)) {
throw new commander.InvalidArgumentError(
`${value} is not a valid environment variable name`,
Expand Down Expand Up @@ -263,7 +235,6 @@ export {
validateParserToArgParser,
validateParserToArgListParser,
parseCoreCount,
parseSecretPathOptional,
parseVaultName,
parseSecretPath,
parseSecretPathValue,
Expand Down
18 changes: 17 additions & 1 deletion tests/secrets/cat.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,22 @@ describe('commandCatSecret', () => {
expect(result.exitCode).toBe(0);
expect(result.stdout).toBe(secretContent);
});
test('should fail when reading root without secret path', async () => {
const vaultName = 'Vault3' as VaultName;
const vaultId = await polykeyAgent.vaultManager.createVault(vaultName);
const secretName = 'secret-name';
const secretContent = 'this is the contents of the secret';
await polykeyAgent.vaultManager.withVaults([vaultId], async (vault) => {
await vaultOps.addSecret(vault, secretName, secretContent);
});
const command = ['secrets', 'cat', '-np', dataDir, vaultName];
const result = await testUtils.pkStdio(command, {
env: { PK_PASSWORD: password },
cwd: dataDir,
});
expect(result.exitCode).not.toBe(0);
expect(result.stderr).toBeDefined();
});
test('should concatenate multiple secrets', async () => {
const vaultName = 'Vault3' as VaultName;
const vaultId = await polykeyAgent.vaultManager.createVault(vaultName);
Expand Down Expand Up @@ -169,7 +185,7 @@ describe('commandCatSecret', () => {
resolve(exitCode);
});
});
expect(exitCode).toStrictEqual(0);
expect(exitCode).toBe(0);
expect(stdout).toBe(stdinData);
});
});
Loading

0 comments on commit fc7bb0b

Please sign in to comment.