mintBlue

Password manager

A command-line password store in about sixty lines, showing the write path, the list path and the read path of the SDK in one program.

A password store is a small program with a demanding requirement: whatever it publishes must be unreadable to everyone except the person who wrote it. That makes it a good first whole application, because it exercises the write path, the read path and the encryption decision in a program you can hold in your head.

Each entry is one transaction carrying one encrypted data output. Reading an entry means finding the transaction that holds it and letting the client decrypt it locally.

What it does

node manager.js add paypal mysecretpass
node manager.js get paypal
node manager.js list

The program

const { Mintblue } = require('@mintblue/sdk');

const [command, account, password] = process.argv.slice(2);

if (!['add', 'get', 'list'].includes(command)) {
  console.log('Usage:');
  console.log('  node manager.js add [account] [password]');
  console.log('  node manager.js get [account]');
  console.log('  node manager.js list');
  process.exit(1);
}

const projectId = process.env.MINTBLUE_PROJECT_ID;

async function entries(client) {
  const transactions = await client.listTransactions({
    project_id: projectId,
    order: 'desc',
  });

  const found = [];
  for (const summary of transactions) {
    const full = await client.getTransaction({
      txid: summary.txid,
      parse: true,
    });
    const record = full.outputs.find((output) => output.type === 'data');
    if (record && record.value && record.value.account) {
      found.push({ txid: summary.txid, ...record.value });
    }
  }
  return found;
}

async function main() {
  const client = await Mintblue.create({
    token: process.env.MINTBLUE_SDK_TOKEN,
  });

  if (command === 'add') {
    const { txid } = await client.createTransaction({
      project_id: projectId,
      outputs: [
        {
          type: 'data',
          value: { account, password },
          encrypt: true,
          sign: true,
        },
      ],
    });
    console.log(`stored ${account} in ${txid}`);
  }

  if (command === 'get') {
    const match = (await entries(client)).find((e) => e.account === account);
    console.log(match ? match.password : `no entry for ${account}`);
  }

  if (command === 'list') {
    for (const entry of await entries(client)) {
      console.log(`${entry.account} - ${entry.password}`);
    }
  }
}

main();

Run it with the token and the project id in the environment:

MINTBLUE_SDK_TOKEN=... MINTBLUE_PROJECT_ID=... node manager.js add paypal mysecretpass

The four decisions worth copying

encrypt: true on every entry. Without it the value is published in the clear. The encryption happens on your machine, before submission, with your own key, which is why get can decrypt without a secret being passed in. Encrypt and sign an output covers both flags.

sign: true alongside it. Encryption says who can read the entry. The signature says who wrote it, which is what you want the first time somebody asks whether an entry in the history is really yours.

Read the outputs by type, then by shape. entries filters for a data output and then checks that its value looks like an entry. A project can hold transactions this program did not write, and it will hold at least the outputs it did not submit itself, so neither the array position nor the presence of a data output is a guarantee of shape.

Newest first. order: 'desc' means an updated entry is found before the entry it replaces, so add doubles as update without any delete path. Nothing is removed; a later record simply wins.

What it is not

A password manager you should use. It fetches and decrypts every transaction in the project on every read, which is fine for a demonstration and wrong for anything with a real history behind it. It also has no master password of its own: whoever holds the SDK access token can read every entry, because that token carries the secret that unwraps the account's keys. Non-custodial keys sets out exactly what that token is.

Extend it

Two changes that teach the rest of the SDK without turning this into a project.

  • Add a tag to each entry and filter on it, which means changing the value you publish and the predicate in entries. See Write data.
  • Attach a file to an entry, for example a recovery-code sheet, by adding a file output next to the data output. See Combine outputs.

Next

On this page