Skip to content

Commit

Permalink
feat: VaultsSecretsMkdir now supports specifying multiple secret paths
Browse files Browse the repository at this point in the history
  • Loading branch information
aryanjassal committed Oct 1, 2024
1 parent ab66636 commit 9f7516b
Show file tree
Hide file tree
Showing 5 changed files with 187 additions and 90 deletions.
4 changes: 2 additions & 2 deletions src/client/callers/vaultsSecretsMkdir.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import type { HandlerTypes } from '@matrixai/rpc';
import type VaultsSecretsMkdir from '../handlers/VaultsSecretsMkdir';
import { UnaryCaller } from '@matrixai/rpc';
import { ClientCaller } from '@matrixai/rpc';

type CallerTypes = HandlerTypes<VaultsSecretsMkdir>;

const vaultsSecretsMkdir = new UnaryCaller<
const vaultsSecretsMkdir = new ClientCaller<
CallerTypes['input'],
CallerTypes['output']
>();
Expand Down
93 changes: 63 additions & 30 deletions src/client/handlers/VaultsSecretsMkdir.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,50 +2,83 @@ import type { DB } from '@matrixai/db';
import type {
ClientRPCRequestParams,
ClientRPCResponseResult,
SecretMkdirMessage,
SuccessMessage,
SecretDirMessage,
SuccessWithErrorMessage,
} from '../types';
import type VaultManager from '../../vaults/VaultManager';
import { UnaryHandler } from '@matrixai/rpc';
import { DuplexHandler } from '@matrixai/rpc';
import * as vaultsUtils from '../../vaults/utils';
import * as vaultsErrors from '../../vaults/errors';
import * as vaultOps from '../../vaults/VaultOps';

class VaultsSecretsMkdir extends UnaryHandler<
class VaultsSecretsMkdir extends DuplexHandler<
{
vaultManager: VaultManager;
db: DB;
},
ClientRPCRequestParams<SecretMkdirMessage>,
ClientRPCResponseResult<SuccessMessage>
ClientRPCRequestParams<SecretDirMessage>,
ClientRPCResponseResult<SuccessWithErrorMessage>
> {
public handle = async (
input: ClientRPCRequestParams<SecretMkdirMessage>,
): Promise<ClientRPCResponseResult<SuccessMessage>> => {
public handle = async function* (
input: AsyncIterable<ClientRPCRequestParams<SecretDirMessage>>,
): AsyncGenerator<ClientRPCResponseResult<SuccessWithErrorMessage>> {
const { vaultManager, db } = this.container;
await db.withTransactionF(async (tran) => {
const vaultIdFromName = await vaultManager.getVaultId(
input.nameOrId,
tran,
);
const vaultId =
vaultIdFromName ?? vaultsUtils.decodeVaultId(input.nameOrId);
if (vaultId == null) {
throw new vaultsErrors.ErrorVaultsVaultUndefined();
// Create a record of secrets to be removed, grouped by vault names
const vaultGroups: Record<string, Array<string>> = {};
const dirPaths: Array<[string, string]> = [];
let metadata: any = undefined;
for await (const secretDirMessage of input) {
if (metadata == null) metadata = secretDirMessage.metadata ?? {};
dirPaths.push([secretDirMessage.nameOrId, secretDirMessage.dirName]);
}
dirPaths.forEach(([vaultName, dirPath]) => {
if (vaultGroups[vaultName] == null) {
vaultGroups[vaultName] = [];
}
await vaultManager.withVaults(
[vaultId],
async (vault) => {
await vaultOps.mkdir(vault, input.dirName, {
recursive: input.recursive,
});
},
tran,
);
vaultGroups[vaultName].push(dirPath);
});
return {
success: true,
};
// Use the grouping to create directories for each vault in one commit
yield* db.withTransactionG(
async function* (tran): AsyncGenerator<SuccessWithErrorMessage> {
for (const [vaultName, dirPath] of Object.entries(vaultGroups)) {
const vaultIdFromName = await vaultManager.getVaultId(
vaultName,
tran,
);
const vaultId =
vaultIdFromName ?? vaultsUtils.decodeVaultId(vaultName);
if (vaultId == null)
throw new vaultsErrors.ErrorVaultsVaultUndefined();
yield await vaultManager.withVaults(
[vaultId],
async (vault) => {
const response = vaultOps.makeDirectories(vault, dirPath, {
recursive: metadata?.options?.recursive,
});
for await (const data of response) {
if (data.success) return { success: true };
if (
data.error instanceof vaultsErrors.ErrorSecretsSecretDefined
) {
return {
success: false,
error: `${data.error.name}: Cannot create directory ${dirPath}: Secret or directory exists`,
};
}
if (data.error instanceof vaultsErrors.ErrorVaultsRecursive) {
return {
success: false,
error: `${data.error.name}: Cannot create directory ${dirPath}: No such secret or directory`,
};
}
throw data.error;
}
},
tran,
);
}
},
);
};
}

Expand Down
11 changes: 5 additions & 6 deletions src/client/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -317,15 +317,14 @@ type ContentWithErrorMessage = ContentMessage & {

type SecretContentMessage = SecretIdentifierMessage & ContentMessage;

type SecretMkdirMessage = VaultIdentifierMessage & {
dirName: string;
recursive: boolean;
};

type SecretDirMessage = VaultIdentifierMessage & {
dirName: string;
};

type SuccessWithErrorMessage = SuccessMessage & {
error?: string;
}

type SecretRenameMessage = SecretIdentifierMessage & {
newSecretName: string;
};
Expand Down Expand Up @@ -423,8 +422,8 @@ export type {
ContentMessage,
ContentWithErrorMessage,
SecretContentMessage,
SecretMkdirMessage,
SecretDirMessage,
SuccessWithErrorMessage,
SecretRenameMessage,
SecretFilesMessage,
SecretStatMessage,
Expand Down
79 changes: 64 additions & 15 deletions src/vaults/VaultOps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@ type FileOptions = {
recursive?: boolean;
};

type SuccessMessage = {
success: boolean;
error?: Error;
};

async function addSecret(
vault: Vault,
secretName: string,
Expand Down Expand Up @@ -170,31 +175,74 @@ async function deleteSecret(
*/
async function mkdir(
vault: Vault,
dirPath: string,
dirPaths: Array<string>,
fileOptions?: FileOptions,
logger?: Logger,
): Promise<void> {
const recursive = fileOptions?.recursive ?? false;

await vault.writeF(async (efs) => {
try {
await efs.mkdir(dirPath, fileOptions);
} catch (e) {
if (e.code === 'ENOENT' && !recursive) {
throw new vaultsErrors.ErrorVaultsRecursive(
`Could not create directory '${dirPath}' without recursive option`,
{ cause: e },
);
for (const dirPath of dirPaths) {
try {
await efs.mkdir(dirPath, fileOptions);
} catch (e) {
if (e.code === 'ENOENT' && !recursive) {
throw new vaultsErrors.ErrorVaultsRecursive(
`Could not create directory '${dirPath}' without recursive option`,
{ cause: e },
);
}
if (e.code === 'EEXIST') {
throw new vaultsErrors.ErrorSecretsSecretDefined(
`${dirPath} already exists`,
{ cause: e },
);
}
throw e;
}
if (e.code === 'EEXIST') {
throw new vaultsErrors.ErrorSecretsSecretDefined(
`${dirPath} already exists`,
{ cause: e },
logger?.info(`Created secret directory at '${dirPath}'`);
}
});
}

/**
* Adds an empty directory to the root of the vault.
* i.e. mkdir("folder", { recursive = false }) creates the "<vaultDir>/folder" directory
*/
async function* makeDirectories(
vault: Vault,
dirPaths: Array<string>,
fileOptions?: FileOptions,
logger?: Logger,
): AsyncGenerator<SuccessMessage> {
const recursive = fileOptions?.recursive ?? false;
yield* vault.writeG(async function* (efs): AsyncGenerator<SuccessMessage> {
for (const dirPath of dirPaths) {
try {
await efs.mkdir(dirPath, fileOptions);
logger?.info(`Created secret directory at '${dirPath}'`);
yield { success: true };
} catch (e) {
logger?.error(
`Failed to create directory at '${dirPath}'. Reason: '${e.code}'`,
);
if (e.code === 'ENOENT' && !recursive) {
const error = new vaultsErrors.ErrorVaultsRecursive(
`Could not create directory '${dirPath}' without recursive option`,
{ cause: e },
);
yield { success: false, error: error };
}
if (e.code === 'EEXIST') {
const error = new vaultsErrors.ErrorSecretsSecretDefined(
`${dirPath} already exists`,
{ cause: e },
);
yield { success: false, error: error };
}
throw e;
}
throw e;
}
logger?.info(`Created secret directory at '${dirPath}'`);
});
}

Expand Down Expand Up @@ -286,6 +334,7 @@ export {
statSecret,
deleteSecret,
mkdir,
makeDirectories,
addSecretDirectory,
listSecrets,
writeSecret,
Expand Down
Loading

0 comments on commit 9f7516b

Please sign in to comment.