Skip to content

Instantly share code, notes, and snippets.

@jeremy-jameson
Created March 6, 2025 17:08
Show Gist options
  • Save jeremy-jameson/0b52b97ebbd809047235a70021085e2e to your computer and use it in GitHub Desktop.
Save jeremy-jameson/0b52b97ebbd809047235a70021085e2e to your computer and use it in GitHub Desktop.
JavaScript used to migrate Bitwarden work passwords to personal vault
const fs = require("fs");
const path = require("path");
const personalPasswordsFilePath = path.join(__dirname, "personal.json");
const workPasswordsFilePath = path.join(__dirname, "technology-toolbox.json");
const outputFilePath = path.join(__dirname, "tmp.json");
// Reference: https://stackoverflow.com/a/979325
const sort_by = (field, reverse, primer) => {
const key = primer
? function (x) {
return primer(x[field]);
}
: function (x) {
return x[field];
};
reverse = !reverse ? 1 : -1;
return function (a, b) {
return (a = key(a)), (b = key(b)), reverse * ((a > b) - (b > a));
};
};
const map_collection_to_folder = (workPassword) => {
const collectionMappings = [
{
id: "0aea0f40-4c8b-4f26-890a-aa4c00bf074c",
name: "Contoso",
folderId: "7b866a9b-0e60-4849-a574-b2900116104d",
},
{
id: "7dbee7e7-6588-401c-92e2-aa4c00bf074c",
name: "Default Collection",
folderId: "699d6ade-4433-442f-8758-b09e00eeea98",
},
{
id: "6260efe7-f288-46fd-9658-aa4c00bf074c",
name: "Development",
folderId: "dc1ca518-f5b6-4417-bb2c-b2900150e93d",
},
{
id: "7ae71c2f-2f3e-47cf-9ec3-aa4c00bf074c",
name: "Quality Assurance",
folderId: "4ec08460-c077-438b-8c20-b29001512ef8",
},
{
id: "1a317298-b293-4cea-9eda-aa4c00bf074c",
name: "Production - Azure",
folderId: "8591f26a-14f3-4134-97ff-b29800fb481f",
},
{
id: "a0f348c1-edf5-415d-a26e-aa4c00bf074c",
name: "Quality Assurance - Azure",
},
{
id: "abdbf275-8e85-469a-addc-aa4c00bf074c",
name: "Production - Infrastructure",
},
{
id: "d71aa3ea-c330-4264-b3b0-aa4c00bf074c",
name: "Development - Azure",
},
{
id: "cb1c8cf3-9d07-46e7-b48b-aa4c00bf074c",
name: "Production",
folderId: "1941b7e0-de93-4b4d-9d0f-b29001513ee2",
},
{
id: "7e3d43e1-97d2-4901-b8a8-aa4c00bf074c",
name: "Fabrikam",
folderId: "55e2ac19-dc0f-4059-bdc8-b24e00c2a344",
},
{
id: "8c18ea99-3bf1-4bd0-bbe0-aa4c00bf074c",
name: "Production - Extranet",
folderId: "c0345369-90c3-4ded-9062-b2900151b526",
},
];
workPassword.folderId = collectionMappings.find(
(mapping) => mapping.id == workPassword.collectionIds[0]
).folderId;
workPassword.collectionIds = null;
};
const merge_passwords = (
personalPasswords,
workPasswords,
predicate = (passwordItem1, passwordItem2) =>
passwordItem1.name === passwordItem2.name
) => {
// copy to avoid side effects
const mergedPasswords = {
encrypted: false,
folders: [...personalPasswords.folders],
items: [...personalPasswords.items],
};
// add all items from workPasswords to mergedPasswords if they're not already present
workPasswords.items.forEach((workPassword) => {
let password = mergedPasswords.items.find(
(password) => password.name === workPassword.name
);
if (password == null) {
workPassword.organizationId = null;
map_collection_to_folder(workPassword);
mergedPasswords.items.push(workPassword);
}
});
// overwrite existing work passwords (e.g. to check for password changes)
workPasswords.items.forEach((workPassword) => {
let password = mergedPasswords.items.find(
(password) => password.name === workPassword.name
&& password.login.username === workPassword.login.username
);
if (password != null) {
workPassword.organizationId = null;
workPassword.folderId = password.folderId;
workPassword.collectionIds = null;
const index = mergedPasswords.items.indexOf(password);
mergedPasswords.items.splice(index, 1);
mergedPasswords.items.push(workPassword);
}
});
return mergedPasswords;
};
fs.readFile(workPasswordsFilePath, "utf8", (err, data) => {
if (err) {
console.error("Error reading the file:", err);
return;
}
const workPasswords = JSON.parse(data);
fs.readFile(personalPasswordsFilePath, "utf8", (err, data) => {
if (err) {
console.error("Error reading the file:", err);
return;
}
const personalPasswords = JSON.parse(data);
const mergedPasswords = merge_passwords(personalPasswords, workPasswords);
mergedPasswords.items = mergedPasswords.items.sort(sort_by("name", false));
fs.writeFileSync(outputFilePath, JSON.stringify(mergedPasswords));
});
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment