Администрирование
Импорт из Bitwarden через API
5min
Импорт паролей из файла Bitwarden в Пассворк через API. Запустите скрипт import.js и следуйте инструкциям.
💡 Важно
TOTP коды должны быть валидные, иначе скрипт завершится с ошибкой

- Установка nodejs, npm (на примере Debian)
Shell
su cd ~ yum makecache
2. Установка пакета passwork-js
Shell
sudo apt install nodejs sudo apt install npm
3.Создать или загрузить файл импорта import.js
Исходный код 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.Создать файл .env и укажите хост вашего Пассворк, API ключ пользователя и его мастер пароль
Shell
HOST='https://your-passwork-here/api/v4' API_KEY= USER_MASTER_PASS=
5.Загрузить XML файл Bitwarden и запустить скрипт. Скрипт запросит ссылку на файл
Shell
node import.js
6.Либо эти параметры можно передать аргументами в скрипт.
Shell
node import.js bitwarden_export_org.json "Collection 1"
Обновлено 22 Nov 2023
Помогла ли вам эта страница?