Перейти к основному содержимому
Это документация для Пассворка версии 6.0, которая больше не поддерживается.

Актуальная документация находится на странице последней версии 7.0.
Версия: 6.0

Импорт из Bitwarden

Passwork поддерживает импорт паролей из файла Bitwarden с помощью API. Запустите скрипт import.js и следуйте инструкциям.

осторожно

TOTP-коды должны быть верные, иначе скрипт завершится с ошибкой

Поиск пароля в расширении

Настройка и запуск (DEB-архитектура)

  • Получить права root и обновить локальную базу данных пакетов:
sudo -i 
apt-get update
  • Установить Node.js и npm:
apt install nodejs npm -y
осторожно

Для успешного импорта версия Node.js должна быть выше 16

  • Проверка установленной версии:
node -v
  • Установка модулей для импорта:
npm install dotenv readline fs util passwork-js
  • Создать файл импорта import.js

Исходный код import.js

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);
}
})();
  • Создать файл .env и указать хост Пассворка, API-ключ пользователя и его мастер-пароль:
HOST='https://your_host/api/v4'
API_KEY=
USER_MASTER_PASS=
  • Загрузить XML-файл Bitwarden и запустить скрипт. Скрипт запросит название файла:
node import.js
  • Также эти параметры можно передать аргументами:
node import.js bitwarden_export_org.json "Collection 1"