Skip to content

Instantly share code, notes, and snippets.

@Yukaii
Created May 24, 2020
Embed
What would you like to do?
GNU Pass to Bitwarden migration

GNU Pass to Bitwarden migration

  1. Use pass2csv to export generic csv
  2. Add header folder,name,password,comments to first line of that csv file
  3. Use the script to convert file
  4. Import password with "Bitwarden (csv)" format

Remeber to delete your csv file safely once it's imported!

const fs = require('fs');
const path = require('path');
const csv = require('fast-csv');
fs.createReadStream(path.resolve(__dirname, 'pass.csv'))
.pipe(csv.parse({ headers: true }))
.pipe(csv.format({ headers: true }))
// .on('data', row => console.log(row))
.transform((row, next) => {
let login = ''
let url = ''
if (row.comments && row.comments.match(/login: (.+)/)) {
let match = row.comments.match(/login: (.+)/)
login = match[1]
}
if (!login) {
login = row.name
}
if (row.comments && row.comments.match(/url: (.+)/)) {
let match = row.comments.match(/url: (.+)/)
url = match[1]
}
return next(null, {
folder: row.folder,
favorite: '',
type: 'login',
name: row.name,
notes: row.comments,
fields: '',
login_uri: url,
login_username: login,
login_password: row.password,
login_totp: ''
})
})
.pipe(process.stdout)
.on('end', process.exit);
@d4mation
Copy link

d4mation commented Oct 14, 2021

This will save the output to a CSV file, ensure that no empty rows are created, and it will remove the Login and URL from the Notes field after putting them in their correct places.

Additionally, in my use-case with pass the file name was always the website URL so this accounts for that while falling back to a URL in the comments field.

const fs = require('fs');
const path = require('path');
const csv = require('fast-csv');

fs.createReadStream(path.resolve(__dirname, 'pass.csv'))
  .pipe(csv.parse({ headers: true }))
  .pipe(csv.format({ headers: true }))
// .on('data', row => console.log(row))
  .transform((row, next) => {

    if ( ! row.comments && ! row.name && ! row.password ) {
      return next();
    }

    let login = ''
    let url = ''

    if (row.comments && row.comments.match(/login:\s?(.+)/im)) {
      let match = row.comments.match(/login:\s?(.+)/im)
      if ( match ) {
        login = match[1]
        let replace = "login:\\s?" + login.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
        let re = new RegExp( replace, 'gim' );
        row.comments = row.comments.replace( re, '' );
        re.lastIndex = 0;
      }
    }

    if (row.comments && row.comments.match(/url:\s?(.+)/im)) {
      let match = row.comments.match(/url:\s?(.+)/im)
      if ( match ) {
        url = match[1]
        let replace = "url:\s?" + url.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
        let re = new RegExp( replace, 'gim' );
        re.lastIndex = 0;
        row.comments = row.comments.replace( re, '' );
      }
    }

    row.comments = row.comments.trim();

    return next(null, {
      folder: row.folder,
      favorite: '',
      type: 'login',
      name: row.name,
      notes: row.comments,
      fields: '',
      login_uri: ( row.name.indexOf( '.' ) > -1 ) ? row.name : url,
      login_username: login,
      login_password: row.password,
      login_totp: ''
    })
  })
  .pipe( fs.createWriteStream( __dirname + '/pass-converted.csv' ) )
 .on('end', process.exit);

@Msouza91
Copy link

Msouza91 commented Oct 30, 2021

I know you did this to yourself, but how do you run this code? I'm looking into this exact migration and have managed to export the csv with the first tool you mention in the gist, but I don't understand the need for and how to run your code. I'm sorry if this is a bad question, I don't know much about JS

Edit:
I figured it out, just call node and pass the file as argument node covert.js > bit-pass.csv with the pass.csv file in the same directory, might be a trivial detail, but it may save some time for someone that didn't know like me! :D. Oh and I changed the login_totp to a header that I created since I was using the otp extension for pass.
Thanks a lot for this!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment