-
Notifications
You must be signed in to change notification settings - Fork 6
Expand file tree
/
Copy pathCommandEdit.ts
More file actions
146 lines (142 loc) · 5.27 KB
/
Copy pathCommandEdit.ts
File metadata and controls
146 lines (142 loc) · 5.27 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
import type PolykeyClient from 'polykey/dist/PolykeyClient';
import fs from 'fs';
import path from 'path';
import CommandPolykey from '../CommandPolykey';
import * as errors from '../errors';
import * as binUtils from '../utils';
import * as binOptions from '../utils/options';
import * as binParsers from '../utils/parsers';
import * as binProcessors from '../utils/processors';
class CommandEdit extends CommandPolykey {
constructor(...args: ConstructorParameters<typeof CommandPolykey>) {
super(...args);
this.name('edit');
this.alias('ed');
this.description('Edit a Secret');
this.argument(
'<secretPath>',
'Path to the secret to be edited, specified as <vaultName>:<directoryPath>',
binParsers.parseSecretPath,
);
this.addOption(binOptions.nodeId);
this.addOption(binOptions.clientHost);
this.addOption(binOptions.clientPort);
this.action(async (secretPath, options) => {
const os = await import('os');
const { execSync } = await import('child_process');
const vaultsErrors = await import('polykey/dist/vaults/errors');
const { default: PolykeyClient } = await import(
'polykey/dist/PolykeyClient'
);
const clientOptions = await binProcessors.processClientOptions(
options.nodePath,
options.nodeId,
options.clientHost,
options.clientPort,
this.fs,
this.logger.getChild(binProcessors.processClientOptions.name),
);
const meta = await binProcessors.processAuthentication(
options.passwordFile,
this.fs,
);
let pkClient: PolykeyClient;
this.exitHandlers.handlers.push(async () => {
if (pkClient != null) await pkClient.stop();
});
const tmpDir = await fs.promises.mkdtemp(
path.join(os.tmpdir(), 'polykey-'),
);
try {
pkClient = await PolykeyClient.createPolykeyClient({
nodeId: clientOptions.nodeId,
host: clientOptions.clientHost,
port: clientOptions.clientPort,
options: {
nodePath: options.nodePath,
},
logger: this.logger.getChild(PolykeyClient.name),
});
const tmpFile = path.join(tmpDir, path.basename(secretPath[1]));
const secretExists = await binUtils.retryAuthentication(
async (auth) => {
let exists = true;
const res = await pkClient.rpcClient.methods.vaultsSecretsGet();
const writer = res.writable.getWriter();
await writer.write({
nameOrId: secretPath[0],
secretName: secretPath[1] ?? '/',
metadata: auth,
});
await writer.close();
try {
let rawSecretContent: string = '';
for await (const chunk of res.readable) {
rawSecretContent += chunk.secretContent;
}
const secretContent = Buffer.from(rawSecretContent, 'binary');
await this.fs.promises.writeFile(tmpFile, secretContent);
} catch (e) {
const [cause, _] = binUtils.remoteErrorCause(e);
if (cause instanceof vaultsErrors.ErrorSecretsSecretUndefined) {
exists = false;
} else {
throw e;
}
}
return exists;
},
meta,
);
// If the editor exited with a code other than zero, then execSync
// will throw an error. So, in the case of saving the file but the
// editor crashing, the program won't save the updated secret.
execSync(`${process.env.EDITOR} \"${tmpFile}\"`, { stdio: 'inherit' });
let content: string;
try {
const buffer = await this.fs.promises.readFile(tmpFile);
content = buffer.toString('binary');
} catch (e) {
if (e.code === 'ENOENT') {
// If the secret exists but the file doesn't, then something went
// wrong, and the file cannot be read anymore. This is bad.
if (secretExists) {
throw new errors.ErrorPolykeyCLIFileRead(e.message, {
data: {
errno: e.errno,
syscall: e.syscall,
code: e.code,
path: e.path,
},
cause: e,
});
// If the secret didn't exist before and we can't read the file,
// then the secret was never actually created or saved. The user
// doesn't want to make the secret anymore, so abort mision!
} else {
return;
}
}
throw e;
}
// We will reach here only when the user wants to write a new secret.
await binUtils.retryAuthentication(
async (auth) =>
await pkClient.rpcClient.methods.vaultsSecretsWriteFile({
metadata: auth,
nameOrId: secretPath[0],
secretName: secretPath[1],
secretContent: content,
}),
meta,
);
// Windows
// TODO: complete windows impl
} finally {
await this.fs.promises.rm(tmpDir, { recursive: true, force: true });
if (pkClient! != null) await pkClient.stop();
}
});
}
}
export default CommandEdit;