Sample vault reading code in README.md does not work as suggested in NodeJS.
thaining opened this issue · 1 comments
Code in the README.md to read Vault content does not work as suggested in NodeJS.
The README suggests that following code sample should work:
const { Credentials, FileDatasource, Vault, init } = require("buttercup");
init();
const datasourceCredentials = Credentials.fromDatasource({
path: "./user.bcup"
}, "masterPassword!");
const fileDatasource = new FileDatasource(datasourceCredentials);
fileDatasource
.load(datasourceCredentials)
.then(Vault.createFromHistory)
.then(vault => {
// ...
});
This hits two problems. The first problem is with the integrity of the datasourceCredentials
. Running the above snippet with node
and a valid buttercup file produces the following error during load()
:
/home/thaining/tmp/builder/creator/node_modules/buttercup/dist/datasources/TextDatasource.js:142
return Promise.reject(new Error("Provided credentials don't allow vault decryption"));
^
Error: Provided credentials don't allow vault decryption
at FileDatasource.load (/home/thaining/tmp/builder/creator/node_modules/buttercup/dist/datasources/TextDatasource.js:142:35)
at /home/thaining/tmp/builder/creator/node_modules/buttercup/dist/datasources/FileDatasource.js:103:30
This appears to be cause of imprecise use of copy-by-sharing in the TextDatasource
constructor:
constructor(credentials: Credentials) {
super();
this._credentials = credentials;
this._credentials.restrictPurposes([Credentials.PURPOSE_SECURE_EXPORT]);
this._content = "";
restrictPurposes()
modifies an object called by reference, not a private copy. The object only has a 'secure-export' purpose, which causes fileDatasource.load()
to fail.
An ugly workaround is to create another copy of the object.
import { Credentials, FileDatasource, Vault, init } from "buttercup";
init();
const datasourceCredentials = Credentials.fromDatasource({
path: "./user.bcup"
}, "masterPassword!");
const loadCredentials = Credentials.fromDatasource({
path: "./user.bcup"
}, "masterPassword!");
const fileDatasource = new FileDatasource(datasourceCredentials);
fileDatasource
.load(loadCredentials)
.then(Vault.createFromHistory)
.then(vault => {
// ...
});
This reveals a second error:
/home/thaining/tmp/builder/creator/node_modules/buttercup/dist/io/VaultFormatA.js:375
throw new Error(`Invalid command: ${command}`);
^
Error: Invalid command: [object Object]
at VaultFormatA._executeCommand (/home/thaining/tmp/builder/creator/node_modules/buttercup/dist/io/VaultFormatA.js:375:19)
at /home/thaining/tmp/builder/creator/node_modules/buttercup/dist/io/VaultFormatA.js:208:42
at Array.forEach (<anonymous>)
at VaultFormatA.execute (/home/thaining/tmp/builder/creator/node_modules/buttercup/dist/io/VaultFormatA.js:208:18)
at Function.createFromHistory (/home/thaining/tmp/builder/creator/node_modules/buttercup/dist/core/Vault.js:30:22)
at /home/thaining/tmp/builder/creator/reader.js:18:22
This occurs because of how TextDataSource.load()
eventually returns the Vault()
history:
const Format = detectFormat(this._content);
return Format.parseEncrypted(this._content, credentials).then((history: History) => ({
Format,
history
}));
It's returning an anonymous object, not a DatasourceLoadedData. The compiled javascript sees this as single returned object with history
and Format
members. The undefined
Format member passed back by load()
is tolerated by createFromHistory()
, but the object passed in is not a valid History. Execution fails as soon as that non-History object is used.
static createFromHistory(history: History, format: any = getDefaultFormat()): Vault {
const vault = new Vault(format);
vault.format.erase();
vault.format.execute(history);
vault.format
in my case resolves to VaultFormatA, and _executeCommand()
does not know what to do with it.
execute(commandOrCommands: string | Array<string>) {
if (this.readOnly) {
throw new Error("Format is in read-only mode");
}
const commands = Array.isArray(commandOrCommands) ? commandOrCommands : [commandOrCommands];
commands.forEach(command => this._executeCommand(command));
I'm not much of a typescript writer, but I tried compiling a typescript version of the snippet. The compiler recognized the same problem immediately:
reader.ts:12:11 - error TS2345: Argument of type '(history: History, format?: any) => Vault' is not assignable to parameter of type '(value: DatasourceLoadedData) => Vault | PromiseLike<Vault>'.
Types of parameters 'history' and 'value' are incompatible.
Type 'DatasourceLoadedData' is missing the following properties from type 'History': length, pop, push, concat, and 16 more.
12 .then(Vault.createFromHistory)
Doing this resolves the issue for NodeJS:
fileDatasource
.load(fooDatasourceCredentials)
.then(datasourceLoadedData => {
return Vault.createFromHistory(datasourceLoadedData.history,
datasourceLoadedData.Format);
})
.then(vault => {