Skip to content

Commit

Permalink
chore: working on things
Browse files Browse the repository at this point in the history
[ci skip]
  • Loading branch information
aryanjassal committed Nov 26, 2024
1 parent 7f6a507 commit f7f3589
Show file tree
Hide file tree
Showing 4 changed files with 211 additions and 73 deletions.
6 changes: 6 additions & 0 deletions src/client/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@ class ErrorClientAuthDenied<T> extends ErrorClient<T> {
exitCode = sysexits.NOPERM;
}

class ErrorClientInvalidHeader<T> extends ErrorClient<T> {
static description = 'The header message does not match the expected type'
exitCode = sysexits.USAGE;
}

class ErrorClientService<T> extends ErrorClient<T> {}

class ErrorClientServiceRunning<T> extends ErrorClientService<T> {
Expand Down Expand Up @@ -45,6 +50,7 @@ export {
ErrorClientAuthMissing,
ErrorClientAuthFormat,
ErrorClientAuthDenied,
ErrorClientInvalidHeader,
ErrorClientService,
ErrorClientServiceRunning,
ErrorClientServiceNotRunning,
Expand Down
245 changes: 173 additions & 72 deletions src/client/handlers/VaultsSecretsRemove.ts
Original file line number Diff line number Diff line change
@@ -1,103 +1,204 @@
import type { DB } from '@matrixai/db';
import { ResourceAcquire, withG } from '@matrixai/resources';
import type {
ClientRPCRequestParams,
ClientRPCResponseResult,
SecretIdentifierMessage,
SecretsRemoveHeaderMessage,
SecretIdentifierMessageTagged,
SuccessOrErrorMessage,
} from '../types';
import type VaultManager from '../../vaults/VaultManager';
import { DuplexHandler } from '@matrixai/rpc';
import * as vaultsUtils from '../../vaults/utils';
import * as vaultsErrors from '../../vaults/errors';
import * as clientErrors from '../errors';
import { FileSystemWritable } from '../../vaults/types';
import * as utils from '../../utils';

class VaultsSecretsRemove extends DuplexHandler<
{
db: DB;
vaultManager: VaultManager;
},
ClientRPCRequestParams<SecretIdentifierMessage>,
ClientRPCRequestParams<
SecretsRemoveHeaderMessage | SecretIdentifierMessageTagged
>,
ClientRPCResponseResult<SuccessOrErrorMessage>
> {
public handle = async function* (
input: AsyncIterable<ClientRPCRequestParams<SecretIdentifierMessage>>,
input: AsyncIterable<
ClientRPCRequestParams<
SecretsRemoveHeaderMessage | SecretIdentifierMessageTagged
>
>,
): AsyncGenerator<ClientRPCResponseResult<SuccessOrErrorMessage>> {
const { db, vaultManager }: { db: DB; vaultManager: VaultManager } =
this.container;
// Create a record of secrets to be removed, grouped by vault names
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] = [];
const vaultAcquires: Array<ResourceAcquire<FileSystemWritable>> = [];
// Extracts the header message from the iterator
const headerMessage = await (async () => {
let header: SecretsRemoveHeaderMessage | undefined;
for await (const value of input) {
// TS cannot properly narrow down this type as it is too deeply wrapped.
// The as keyword is used to help it with type narrowing.
const message = value as
| SecretIdentifierMessageTagged
| SecretsRemoveHeaderMessage;
if (message.type === 'VaultNamesHeaderMesage') header = message;
break;
}
vaultGroups[vaultName].push(secretName);
});
// Now, all the paths will be removed for a vault within a single commit
yield* db.withTransactionG(
async function* (tran): AsyncGenerator<SuccessOrErrorMessage> {
for (const [vaultName, secretNames] of Object.entries(vaultGroups)) {
const vaultIdFromName = await vaultManager.getVaultId(
vaultName,
tran,
// The header message is mandatory
if (header == null) throw new clientErrors.ErrorClientInvalidHeader();
return header;
})();
// Create an array of write acquires
await db.withTransactionF(async (tran) => {
for (const vaultName of headerMessage!.vaultNames) {
const vaultIdFromName = await vaultManager.getVaultId(vaultName, tran);
const vaultId = vaultIdFromName ?? vaultsUtils.decodeVaultId(vaultName);
if (vaultId == null) {
throw new vaultsErrors.ErrorVaultsVaultUndefined(
`Vault ${vaultName} does not exist`,
);
const vaultId =
vaultIdFromName ?? vaultsUtils.decodeVaultId(vaultName);
if (vaultId == null) {
throw new vaultsErrors.ErrorVaultsVaultUndefined();
}
yield* vaultManager.withVaultsG(
[vaultId],
async function* (vault): AsyncGenerator<SuccessOrErrorMessage> {
yield* vault.writeG(
async function* (efs): AsyncGenerator<SuccessOrErrorMessage> {
for (const secretName of secretNames) {
try {
const stat = await efs.stat(secretName);
if (stat.isDirectory()) {
await efs.rmdir(secretName, {
recursive: metadata?.options?.recursive,
});
} else {
await efs.unlink(secretName);
}
yield {
type: 'success',
success: true,
};
} catch (e) {
if (
e.code === 'ENOENT' ||
e.code === 'ENOTEMPTY' ||
e.code === 'EINVAL'
) {
// INVAL can be triggered if removing the root of the
// vault is attempted.
yield {
type: 'error',
code: e.code,
reason: secretName,
};
} else {
throw e;
}
}
}
},
}
await vaultManager.withVaults([vaultId], async (vault) => {
vaultAcquires.push(vault.acquireWrite());
});
}
});
// Acquire all locks in parallel and perform all operations at once
yield* withG(
vaultAcquires,
async function* (efses): AsyncGenerator<SuccessOrErrorMessage> {
// Creating the vault name to efs map for easy access
const vaultMap = new Map<string, FileSystemWritable>();
for (let i = 0; i < efses.length; i++) {
vaultMap.set(headerMessage!.vaultNames[i], efses[i]);
}
for await (const value of input) {
// TS cannot properly narrow down this type as it is too deeply wrapped.
// The as keyword is used to help it with type narrowing.
const message = value as
| SecretIdentifierMessageTagged
| SecretsRemoveHeaderMessage;
// Ignoring any header messages
if (message.type === 'SecretIdentifierMessage') {
const efs = vaultMap.get(message.nameOrId);
if (efs == null) {
throw new vaultsErrors.ErrorVaultsVaultUndefined(
`Vault ${message.nameOrId} was not present in the header message`,
);
},
tran,
);
}
try {
const stat = await efs.stat(message.secretName);
if (stat.isDirectory()) {
await efs.rmdir(message.secretName, {
recursive: headerMessage.recursive,
});
} else {
await efs.unlink(message.secretName);
}
yield {
type: 'success',
success: true,
};
} catch (e) {
if (
e.code === 'ENOENT' ||
e.code === 'ENOTEMPTY' ||
e.code === 'EINVAL'
) {
// INVAL can be triggered if removing the root of the
// vault is attempted.
yield {
type: 'error',
code: e.code,
reason: message.secretName,
};
} else {
throw e;
}
}
}
}
},
);

// Create a record of secrets to be removed, grouped by vault names
// 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);
// });
// Now, all the paths will be removed for a vault within a single commit
// yield* db.withTransactionG(
// async function* (tran): AsyncGenerator<SuccessOrErrorMessage> {
// for (const [vaultName, secretNames] 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* vaultManager.withVaultsG(
// [vaultId],
// async function* (vault): AsyncGenerator<SuccessOrErrorMessage> {
// yield* vault.writeG(
// async function* (efs): AsyncGenerator<SuccessOrErrorMessage> {
// for (const secretName of secretNames) {
// try {
// const stat = await efs.stat(secretName);
// if (stat.isDirectory()) {
// await efs.rmdir(secretName, {
// recursive: metadata?.options?.recursive,
// });
// } else {
// await efs.unlink(secretName);
// }
// yield {
// type: 'success',
// success: true,
// };
// } catch (e) {
// if (
// e.code === 'ENOENT' ||
// e.code === 'ENOTEMPTY' ||
// e.code === 'EINVAL'
// ) {
// // INVAL can be triggered if removing the root of the
// // vault is attempted.
// yield {
// type: 'error',
// code: e.code,
// reason: secretName,
// };
// } else {
// throw e;
// }
// }
// }
// },
// );
// },
// tran,
// );
// }
// },
// );
};
}

Expand Down
16 changes: 16 additions & 0 deletions src/client/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -360,6 +360,19 @@ type SecretStatMessage = {
};
};

type SecretIdentifierMessageTagged = SecretIdentifierMessage & {
type: 'SecretIdentifierMessage';
}

type VaultNamesHeaderMesage = {
type: 'VaultNamesHeaderMesage';
vaultNames: Array<string>;
};

type SecretsRemoveHeaderMessage = VaultNamesHeaderMesage & {
recursive?: boolean;
};

// Type casting for tricky handlers

type OverrideRPClientType<T extends RPCClient<ClientManifest>> = Omit<
Expand Down Expand Up @@ -435,6 +448,9 @@ export type {
SecretRenameMessage,
SecretFilesMessage,
SecretStatMessage,
SecretIdentifierMessageTagged,
VaultNamesHeaderMesage,
SecretsRemoveHeaderMessage,
SignatureMessage,
OverrideRPClientType,
AuditMetricGetTypeOverride,
Expand Down
17 changes: 16 additions & 1 deletion tests/client/handlers/vaults.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,13 @@ import type { FileSystem } from '@/types';
import type { VaultId } from '@/ids';
import type NodeManager from '@/nodes/NodeManager';
import type {
ClientRPCRequestParams,
ContentSuccessMessage,
ErrorMessage,
LogEntryMessage,
SecretContentMessage,
SecretIdentifierMessage,
SecretsRemoveHeaderMessage,
VaultListMessage,
VaultPermissionMessage,
} from '@/client/types';
Expand Down Expand Up @@ -2371,7 +2374,19 @@ describe('vaultsSecretsRemove', () => {
// Write paths
const response = await rpcClient.methods.vaultsSecretsRemove();
const writer = response.writable.getWriter();
await writer.write({ nameOrId: 'invalid', secretName: 'invalid' });
let variable: ClientRPCRequestParams<SecretsRemoveHeaderMessage | SecretIdentifierMessage> = {type: 'VaultNamesHeaderMesage', vaultNames: ['invalid']};
console.log(variable);
// Header message
await writer.write({
type: 'VaultNamesHeaderMesage',
vaultNames: ['invalid'],
});
// Content messages
await writer.write({
type: 'SecretIdentifierMessage',
nameOrId: 'invalid',
secretName: 'invalid',
});
await writer.close();
// Read response
const consumeP = async () => {
Expand Down

0 comments on commit f7f3589

Please sign in to comment.