Skip to content
This repository was archived by the owner on Jun 26, 2025. It is now read-only.
This repository was archived by the owner on Jun 26, 2025. It is now read-only.

Sample vault reading code in README.md does not work as suggested in NodeJS. #326

Description

@thaining

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 => {

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions