Skip to content

Commit

Permalink
Merge pull request #805 from MatrixAI/feature-unix-cat
Browse files Browse the repository at this point in the history
Updating behaviour of `VaultsSecretsGet` to align with requirements of `secrets cat`
  • Loading branch information
aryanjassal authored Sep 24, 2024
2 parents c70c147 + 81510fd commit 57ce8d7
Show file tree
Hide file tree
Showing 6 changed files with 378 additions and 230 deletions.
4 changes: 2 additions & 2 deletions src/client/callers/vaultsSecretsGet.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import type { HandlerTypes } from '@matrixai/rpc';
import type VaultsSecretsGet from '../handlers/VaultsSecretsGet';
import { UnaryCaller } from '@matrixai/rpc';
import { DuplexCaller } from '@matrixai/rpc';

type CallerTypes = HandlerTypes<VaultsSecretsGet>;

const vaultsSecretsGet = new UnaryCaller<
const vaultsSecretsGet = new DuplexCaller<
CallerTypes['input'],
CallerTypes['output']
>();
Expand Down
4 changes: 2 additions & 2 deletions src/client/callers/vaultsSecretsRemove.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import type { HandlerTypes } from '@matrixai/rpc';
import type VaultsSecretsRemove from '../handlers/VaultsSecretsRemove';
import { UnaryCaller } from '@matrixai/rpc';
import { ClientCaller } from '@matrixai/rpc';

type CallerTypes = HandlerTypes<VaultsSecretsRemove>;

const vaultsSecretsRemove = new UnaryCaller<
const vaultsSecretsRemove = new ClientCaller<
CallerTypes['input'],
CallerTypes['output']
>();
Expand Down
76 changes: 50 additions & 26 deletions src/client/handlers/VaultsSecretsGet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,47 +2,71 @@ import type { DB } from '@matrixai/db';
import type {
ClientRPCRequestParams,
ClientRPCResponseResult,
ContentMessage,
ContentWithErrorMessage,
SecretIdentifierMessage,
} 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 VaultsSecretsGet extends UnaryHandler<
class VaultsSecretsGet extends DuplexHandler<
{
vaultManager: VaultManager;
db: DB;
},
ClientRPCRequestParams<SecretIdentifierMessage>,
ClientRPCResponseResult<ContentMessage>
ClientRPCResponseResult<ContentWithErrorMessage>
> {
public handle = async (
input: ClientRPCRequestParams<SecretIdentifierMessage>,
): Promise<ClientRPCResponseResult<ContentMessage>> => {
public handle = async function* (
input: AsyncIterable<ClientRPCRequestParams<SecretIdentifierMessage>>,
_cancel,
_meta,
ctx,
): AsyncGenerator<ClientRPCResponseResult<ContentWithErrorMessage>> {
if (ctx.signal.aborted) throw ctx.signal.reason;
const { vaultManager, db } = this.container;
return 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();
yield* db.withTransactionG(async function* (tran): AsyncGenerator<
ClientRPCResponseResult<ContentWithErrorMessage>
> {
if (ctx.signal.aborted) throw ctx.signal.reason;
// As we need to preserve the order of parameters, we need to loop over
// them individually, as grouping them would make them go out of order.
let metadata: any = undefined;
for await (const secretIdentiferMessage of input) {
if (ctx.signal.aborted) throw ctx.signal.reason;
if (metadata == null) metadata = secretIdentiferMessage.metadata ?? {};
const { nameOrId, secretName } = secretIdentiferMessage;
const vaultIdFromName = await vaultManager.getVaultId(nameOrId, tran);
const vaultId = vaultIdFromName ?? vaultsUtils.decodeVaultId(nameOrId);
if (vaultId == null) throw new vaultsErrors.ErrorVaultsVaultUndefined();
yield await vaultManager.withVaults(
[vaultId],
async (vault) => {
try {
const content = await vaultOps.getSecret(vault, secretName);
return { secretContent: content.toString('binary') };
} catch (e) {
if (metadata?.options?.continueOnError === true) {
if (e instanceof vaultsErrors.ErrorSecretsSecretUndefined) {
return {
secretContent: '',
error: `${e.name}: ${secretName}: No such secret or directory\n`,
};
} else if (e instanceof vaultsErrors.ErrorSecretsIsDirectory) {
return {
secretContent: '',
error: `${e.name}: ${secretName}: Is a directory\n`,
};
}
}
throw e;
}
},
tran,
);
}
const secretContent = await vaultManager.withVaults(
[vaultId],
async (vault) => {
return await vaultOps.getSecret(vault, input.secretName);
},
tran,
);
return {
secretContent: secretContent.toString('binary'),
};
});
};
}
Expand Down
26 changes: 18 additions & 8 deletions src/client/handlers/VaultsSecretsRemove.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,34 +3,44 @@ import type {
ClientRPCRequestParams,
ClientRPCResponseResult,
SuccessMessage,
SecretRemoveMessage,
SecretIdentifierMessage,
} from '../types';
import type VaultManager from '../../vaults/VaultManager';
import { UnaryHandler } from '@matrixai/rpc';
import { ClientHandler } from '@matrixai/rpc';
import * as vaultsUtils from '../../vaults/utils';
import * as vaultsErrors from '../../vaults/errors';
import * as vaultOps from '../../vaults/VaultOps';

class VaultsSecretsRemove extends UnaryHandler<
class VaultsSecretsRemove extends ClientHandler<
{
vaultManager: VaultManager;
db: DB;
},
ClientRPCRequestParams<SecretRemoveMessage>,
ClientRPCRequestParams<SecretIdentifierMessage>,
ClientRPCResponseResult<SuccessMessage>
> {
public handle = async (
input: ClientRPCRequestParams<SecretRemoveMessage>,
input: AsyncIterable<ClientRPCRequestParams<SecretIdentifierMessage>>,
): Promise<ClientRPCResponseResult<SuccessMessage>> => {
const { vaultManager, db } = this.container;
// Create a record of secrets to be removed, grouped by vault names
const vaultGroups: Record<string, string[]> = {};
input.secretNames.forEach(([vaultName, secretName]) => {
const vaultGroups: Record<string, Array<string>> = {};
const secretNames: Array<[string, string]> = [];
let metadata: any = undefined;
for await (const secretRemoveMessage of input) {
if (metadata == null) metadata = secretRemoveMessage.metadata ?? {};
secretNames.push([
secretRemoveMessage.nameOrId,
secretRemoveMessage.secretName,
]);
}
secretNames.forEach(([vaultName, secretName]) => {
if (vaultGroups[vaultName] == null) {
vaultGroups[vaultName] = [];
}
vaultGroups[vaultName].push(secretName);
});

await db.withTransactionF(async (tran) => {
for (const [vaultName, secretNames] of Object.entries(vaultGroups)) {
const vaultIdFromName = await vaultManager.getVaultId(vaultName, tran);
Expand All @@ -40,7 +50,7 @@ class VaultsSecretsRemove extends UnaryHandler<
[vaultId],
async (vault) => {
await vaultOps.deleteSecret(vault, secretNames, {
recursive: input.options?.recursive,
recursive: metadata?.options?.recursive,
});
},
tran,
Expand Down
13 changes: 5 additions & 8 deletions src/client/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -306,18 +306,15 @@ type SecretPathMessage = {

type SecretIdentifierMessage = VaultIdentifierMessage & SecretPathMessage;

type SecretRemoveMessage = {
secretNames: Array<Array<string>>;
options?: {
recursive?: boolean;
};
};

// Contains binary content as a binary string 'toString('binary')'
type ContentMessage = {
secretContent: string;
};

type ContentWithErrorMessage = ContentMessage & {
error?: string;
};

type SecretContentMessage = SecretIdentifierMessage & ContentMessage;

type SecretMkdirMessage = VaultIdentifierMessage & {
Expand Down Expand Up @@ -423,8 +420,8 @@ export type {
VaultsLatestVersionMessage,
SecretPathMessage,
SecretIdentifierMessage,
SecretRemoveMessage,
ContentMessage,
ContentWithErrorMessage,
SecretContentMessage,
SecretMkdirMessage,
SecretDirMessage,
Expand Down
Loading

0 comments on commit 57ce8d7

Please sign in to comment.