-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
a901228
commit adab612
Showing
4 changed files
with
249 additions
and
32 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,92 @@ | ||
import type PolykeyClient from 'polykey/dist/PolykeyClient'; | ||
import CommandPolykey from '../CommandPolykey'; | ||
import * as binProcessors from '../utils/processors'; | ||
import * as binParsers from '../utils/parsers'; | ||
import * as binUtils from '../utils'; | ||
import * as binOptions from '../utils/options'; | ||
|
||
class CommandStat extends CommandPolykey { | ||
constructor(...args: ConstructorParameters<typeof CommandPolykey>) { | ||
super(...args); | ||
this.name('write'); | ||
this.description('Write data into a secret from standard in'); | ||
this.argument( | ||
'<secretPath>', | ||
'Path to the secret, specified as <vaultName>:<directoryPath>', | ||
binParsers.parseSecretPathValue, | ||
); | ||
this.addOption(binOptions.nodeId); | ||
this.addOption(binOptions.clientHost); | ||
this.addOption(binOptions.clientPort); | ||
this.action(async (secretPath, options) => { | ||
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(); | ||
}); | ||
try { | ||
pkClient = await PolykeyClient.createPolykeyClient({ | ||
nodeId: clientOptions.nodeId, | ||
host: clientOptions.clientHost, | ||
port: clientOptions.clientPort, | ||
options: { | ||
nodePath: options.nodePath, | ||
}, | ||
logger: this.logger.getChild(PolykeyClient.name), | ||
}); | ||
|
||
let stdin: string = ''; | ||
await new Promise<void>((resolve, reject) => { | ||
const cleanup = () => { | ||
process.stdin.removeListener('data', dataHandler); | ||
process.stdin.removeListener('error', errorHandler); | ||
process.stdin.removeListener('end', endHandler); | ||
}; | ||
const dataHandler = (data: Buffer) => { | ||
stdin += data.toString(); | ||
}; | ||
const errorHandler = (err: Error) => { | ||
cleanup(); | ||
reject(err); | ||
}; | ||
const endHandler = () => { | ||
cleanup(); | ||
resolve(); | ||
}; | ||
process.stdin.on('data', dataHandler); | ||
process.stdin.once('error', errorHandler); | ||
process.stdin.once('end', endHandler); | ||
}); | ||
await binUtils.retryAuthentication( | ||
async (auth) => | ||
await pkClient.rpcClient.methods.vaultsSecretsWriteFile({ | ||
metadata: auth, | ||
nameOrId: secretPath[0], | ||
secretName: secretPath[1], | ||
secretContent: stdin, | ||
}), | ||
meta, | ||
); | ||
} finally { | ||
if (pkClient! != null) await pkClient.stop(); | ||
} | ||
}); | ||
} | ||
} | ||
|
||
export default CommandStat; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,136 @@ | ||
import type { VaultName } from 'polykey/dist/vaults/types'; | ||
import path from 'path'; | ||
import fs from 'fs'; | ||
import fc from 'fast-check'; | ||
import { test } from '@fast-check/jest'; | ||
import Logger, { LogLevel, StreamHandler } from '@matrixai/logger'; | ||
import PolykeyAgent from 'polykey/dist/PolykeyAgent'; | ||
import { vaultOps } from 'polykey/dist/vaults'; | ||
import * as keysUtils from 'polykey/dist/keys/utils'; | ||
import * as testUtils from '../utils'; | ||
|
||
describe('commandWriteFile', () => { | ||
const password = 'password'; | ||
const logger = new Logger('CLI Test', LogLevel.WARN, [new StreamHandler()]); | ||
const stdinArb = fc.string({ minLength: 0, maxLength: 100 }); | ||
const contentArb = fc.constantFrom('content', ''); | ||
let dataDir: string; | ||
let polykeyAgent: PolykeyAgent; | ||
let command: Array<string>; | ||
let vaultNumber: number = 0; | ||
|
||
// Helper function to generate unique vault names | ||
function genVaultName() { | ||
vaultNumber++; | ||
return `vault-${vaultNumber}` as VaultName; | ||
} | ||
|
||
beforeEach(async () => { | ||
dataDir = await fs.promises.mkdtemp( | ||
path.join(globalThis.tmpDir, 'polykey-test-'), | ||
); | ||
polykeyAgent = await PolykeyAgent.createPolykeyAgent({ | ||
password, | ||
options: { | ||
nodePath: dataDir, | ||
agentServiceHost: '127.0.0.1', | ||
clientServiceHost: '127.0.0.1', | ||
keys: { | ||
passwordOpsLimit: keysUtils.passwordOpsLimits.min, | ||
passwordMemLimit: keysUtils.passwordMemLimits.min, | ||
strictMemoryLock: false, | ||
}, | ||
}, | ||
logger: logger, | ||
}); | ||
}); | ||
afterEach(async () => { | ||
await polykeyAgent.stop(); | ||
await fs.promises.rm(dataDir, { | ||
force: true, | ||
recursive: true, | ||
}); | ||
}); | ||
|
||
test.prop([stdinArb, contentArb], { numRuns: 5 })( | ||
'should write secret', | ||
async (stdinData, secretContent) => { | ||
const vaultName = genVaultName(); | ||
const vaultId = await polykeyAgent.vaultManager.createVault(vaultName); | ||
const secretName = 'secret'; | ||
await polykeyAgent.vaultManager.withVaults([vaultId], async (vault) => { | ||
await vaultOps.addSecret(vault, secretName, secretContent); | ||
}); | ||
command = [ | ||
'secrets', | ||
'write', | ||
'-np', | ||
dataDir, | ||
`${vaultName}:${secretName}`, | ||
]; | ||
|
||
const childProcess = await testUtils.pkSpawn( | ||
command, | ||
{ | ||
env: { PK_PASSWORD: password }, | ||
cwd: dataDir, | ||
}, | ||
logger, | ||
); | ||
// The conditions of stdin being null will not be met in the test, so we | ||
// don't have to worry about the fields being null. | ||
childProcess.stdin!.write(stdinData); | ||
childProcess.stdin!.end(); | ||
const exitCode = await new Promise((resolve) => { | ||
childProcess.once('exit', (code) => { | ||
const exitCode = code ?? -255; | ||
childProcess.removeAllListeners('data'); | ||
resolve(exitCode); | ||
}); | ||
}); | ||
expect(exitCode).toStrictEqual(0); | ||
await polykeyAgent.vaultManager.withVaults([vaultId], async (vault) => { | ||
const contents = await vaultOps.getSecret(vault, secretName); | ||
expect(contents.toString()).toStrictEqual(stdinData); | ||
}); | ||
}, | ||
); | ||
test('should overwrite secret', async () => { | ||
const vaultName = 'vault' as VaultName; | ||
const vaultId = await polykeyAgent.vaultManager.createVault(vaultName); | ||
const secretName = 'secret'; | ||
const newContent = 'new contents'; | ||
command = [ | ||
'secrets', | ||
'write', | ||
'-np', | ||
dataDir, | ||
`${vaultName}:${secretName}`, | ||
]; | ||
|
||
const childProcess = await testUtils.pkSpawn( | ||
command, | ||
{ | ||
env: { PK_PASSWORD: password }, | ||
cwd: dataDir, | ||
}, | ||
logger, | ||
); | ||
// The conditions of stdin being null will not be met in the test, so we | ||
// don't have to worry about the fields being null. | ||
childProcess.stdin!.write(newContent); | ||
childProcess.stdin!.end(); | ||
const exitCode = await new Promise((resolve) => { | ||
childProcess.once('exit', (code) => { | ||
const exitCode = code ?? -255; | ||
childProcess.removeAllListeners('data'); | ||
resolve(exitCode); | ||
}); | ||
}); | ||
expect(exitCode).toStrictEqual(0); | ||
await polykeyAgent.vaultManager.withVaults([vaultId], async (vault) => { | ||
const contents = await vaultOps.getSecret(vault, secretName); | ||
expect(contents.toString()).toStrictEqual(newContent); | ||
}); | ||
}); | ||
}); |