Administration
Import from Bitwarden
6min
Import Bitwarden json file to Passwork via API. Run script import.js and follow the instructions .
💡 Please note TOTP codes must be valid, otherwise the script will terminate with error
- Installing nodejs, npm (example for Debian)
Shell
su
cd ~
yum makecache
2. Installing passwork-js package
Shell
sudo apt install nodejs
sudo apt install npm
3.Create import.js
Source code of import.js
Shell
require("util").inspect.defaultOptions.depth = null;
const env = require('dotenv').config().parsed;
const readline = require('readline');
const fs = require('fs');
const Passwork = require('./node_modules/passwork-js/src/passwork-api');
/** @type PassworkAPI */
const passwork = new Passwork(env.HOST);
function throwFatalError(message, error) {
console.error(message);
console.error(error);
process.exit(0);
}
(async () => {
try {
const [argFileName, argCollections, argPath] = process.argv.slice(2);
let jsonFileName;
let jsonData;
let collectionsToImport = [];
let importVault;
// Authorize
try {
await passwork.login(env.API_KEY, env.USER_MASTER_PASS);
} catch (e) {
throwFatalError('Не удалось авторизоваться', e);
}
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
// Read json from bitwarden
const answerFileName = await new Promise(resolve => {
rl.question('\nУкажите файл для экспорта\n', resolve)
});
jsonFileName = answerFileName ? answerFileName : argFileName;
try {
jsonData = JSON.parse(fs.readFileSync(jsonFileName));
if (!jsonData || !jsonData.hasOwnProperty('items')) {
throw 'Неверный формат json файл';
}
} catch (e) {
throwFatalError('Не удалось прочитать json файл', e);
}
// Specify collections to import
const answerCollections = await new Promise(resolve => {
rl.question('\nУкажите через запятую id или название коллекций для экспорта (необязательно)\n', resolve)
});
let collections = answerCollections ? answerCollections : argCollections;
if (collections) {
collections = collections.split(',').map(c => c.trim()).filter((c) => c);
} else {
collections = [];
}
if (jsonData.collections && jsonData.collections.length) {
if (collections.length === 0) {
collectionsToImport = jsonData.collections;
} else {
jsonData.collections.forEach(c => {
if (collections.includes(c.name) || collections.includes(c.id)) {
collectionsToImport.push(c);
}
});
}
} else {
collectionsToImport = [];
}
collectionsToImport = [...new Set(collectionsToImport)];
// Specify vault id for import
const answerPath = await new Promise(resolve => {
rl.question('\nУкажите id сейфа для импорта (необязательно) \n', resolve)
});
let path = answerPath ? answerPath : argPath;
if (path) {
importVault = await passwork.getVault(path);
if (!importVault) {
throwFatalError('Указанный для импорта сейф не найден');
}
}
// Confirm import
let confirmMessage = '\nБудут экспортированы следующие коллекции:\n';
if (jsonData.collections) {
collectionsToImport.forEach(c => {
confirmMessage += `${c.name} (${c.id})\n`;
});
} else {
confirmMessage += 'Личный сейф\n';
}
if (importVault) {
confirmMessage += `\nЭкспорт будет произведен в "${importVault.name}"\n`;
}
confirmMessage += 'Продолжить? y/n\n';
const answerConfirm = await new Promise(resolve => {
rl.question(confirmMessage, resolve)
});
if (answerConfirm.toLowerCase() === 'y') {
rl.close();
importPasswords().then(() => process.exit(0)).catch((e) => {
throwFatalError('error', e);
});
} else {
console.log('Операция отменена');
process.exit(0);
}
async function importPasswords() {
const logFileName = 'import-' + new Date().getTime() + '.log';
function logMessage(message) {
let msg = new Date().toISOString() + ' ' + message + '\n';
fs.appendFileSync(logFileName, msg);
console.log(msg);
}
function preparePasswordFields(data, directories) {
const vaultsNames = getDirectoriesNames(directories);
if (data.type !== 1 && data.type !== 2) {
logMessage(`Объект типа ${data.type}, ${data.name}`
+ ` из коллекций ${vaultsNames} не был импортирован`);
return;
}
const fields = {
password: '',
name: data.name,
description: data.notes,
custom: [],
};
if (directories.length > 1) {
fields.description = fields.description ? (fields.description + '\n') : '';
fields.description += `Копия пароля находится в: ${vaultsNames}`;
}
if (data.login) {
if (data.login.username) {
fields.login = data.login.username;
}
if (data.login.password) {
fields.password = data.login.password;
}
if (data.login.totp) {
fields.custom.push({
name: 'TOTP',
value: data.login.totp,
type: 'totp'
});
}
if (data.login.uris) {
fields.url = data.login.uris.length === 1
? data.login.uris[0].uri : data.login.uris.reduce((a, b) => (a.uri || a) + ", " + b.uri, '')
}
}
if (data.fields) {
data.fields.forEach((field) => {
if (field.type === 0 || field.type === 2) {
fields.custom.push({
name: String(field.name),
value: String(field.value),
type: 'text'
});
} else if (field.type === 1) {
fields.custom.push({
name: String(field.name),
value: String(field.value),
type: 'password'
});
} else {
logMessage(`Поле типа "link" объекта ${data.name}`
+ ` из коллекций ${vaultsNames} не было импортирован`);
}
});
}
return fields;
}
function getDirectories(passwordCollectionIds, collections) {
const directories = [];
for (const collectionId of passwordCollectionIds) {
if (collections.hasOwnProperty(collectionId)) {
directories.push(collections[collectionId]);
}
}
return directories;
}
function getDirectoriesNames(directories) {
return directories.length > 1
? directories.reduce((a, b) => (a.name || a) + ", " + b.name)
: directories[0].name;
}
logMessage('Импорт из файла ' + jsonFileName);
if (collectionsToImport.length) {
if (importVault) {
// Collections as folders
const folders = {};
for (let c = 0; c < collectionsToImport.length; c++) {
const item = collectionsToImport[c];
folders[item.id] = await passwork.addFolder(importVault.id, item.name);
logMessage(`Создана папка ${folders[item.id].name} на основе коллекции ${item.id}`)
}
for (let p = 0; p < jsonData.items.length; p++) {
const passwordData = jsonData.items[p];
const foldersList = getDirectories(passwordData.collectionIds, folders);
if (foldersList.length === 0) {
continue;
}
logMessage(`Начат импорт ${passwordData.name}`);
let fields = preparePasswordFields(passwordData, foldersList);
if (!fields) {
continue;
}
fields.vaultId = importVault.id;
for (const folder of foldersList) {
fields.folderId = folder.id;
await passwork.addPassword(Object.assign({}, fields));
logMessage(`Завершен импорт ${passwordData.name}`);
}
}
} else {
// Collections as vaults
const vaults = [];
for (let c = 0; c < collectionsToImport.length; c++) {
const item = collectionsToImport[c];
const vaultId = await passwork.addVault(item.name);
vaults[item.id] = await passwork.getVault(vaultId);
logMessage(`Создан сейф ${vaults[item.id].name} на основе коллекции ${item.id}`)
}
for (let p = 0; p < jsonData.items.length; p++) {
const passwordData = jsonData.items[p];
const vaultsList = getDirectories(passwordData.collectionIds, vaults);
if (vaultsList.length === 0) {
continue;
}
logMessage(`Начат импорт ${passwordData.name}`);
let fields = preparePasswordFields(passwordData, vaultsList);
if (!fields) {
continue;
}
for (const vault of vaultsList) {
fields.vaultId = vault.id;
await passwork.addPassword(Object.assign({}, fields));
logMessage(`Завершен импорт ${passwordData.name}`);
}
}
}
logMessage(`Импорт завершен`);
process.exit(0);
return;
}
if (collectionsToImport.length === 0 && jsonData.items[0].organizationId === null) {
// Private vault import
if (!importVault) {
const vaultId = await passwork.addVault('Личный сейф', true);
importVault = await passwork.getVault(vaultId);
logMessage(`Сейф ${importVault.name} был создан `);
}
const folders = {};
if (jsonData.folders) {
for (const folder of jsonData.folders) {
folders[folder.id] = await passwork.addFolder(importVault.id, folder.name);
}
}
for (let p = 0; p < jsonData.items.length; p++) {
const passwordData = jsonData.items[p];
logMessage(`Начат импорт ${passwordData.name}`);
let fields = preparePasswordFields(passwordData, [importVault]);
if (!fields) {
continue;
}
fields.vaultId = importVault.id;
if (passwordData.folderId) {
fields.folderId = folders[passwordData.folderId].id;
}
await passwork.addPassword(Object.assign({}, fields));
logMessage(`Импорт завершен ${passwordData.name}`);
}
logMessage(`Импорт завершен`);
process.exit(0);
return;
}
logMessage(`Не удалось определить формат импорта`);
process.exit(0);
}
} catch (e) {
throwFatalError('error', e);
}
})();
4.Create file.env and indicate next parameters
Shell
HOST='https://your-passwork-here/api/v4'
API_KEY=
USER_MASTER_PASS=
5.Load the Bitwarden json import file and run the script. The script requests a link to the file
Shell
node import.js
6.Also you can pass these parameters as arguments to the script
Shell
node import.js bitwarden_export_org.json "Collection 1"
Updated 10 Apr 2024
Did this page help you?