Administration
Work with API
Import from Bitwarden
10min
You can import Bitwarden JSON files into Passwork via API. Run import.js script and follow the instructions.
TOTP codes must be valid, otherwise the script will terminate with an error
- Get root privileges and update the local package database:
Shell
1sudo -i
2apt-get update
- Install Node.js and npm:
Shell
1apt install nodejs npm -y
Node.js version must be 17 or higher
- Check the installed version:
Shell
1node -v
- Install modules for importing:
Shell
1npm install dotenv readline fs util passwork-js
- Create the import script — import.js
Source code of import.js:
Shell
1require("util").inspect.defaultOptions.depth = null;
2const env = require('dotenv').config().parsed;
3const readline = require('readline');
4const fs = require('fs');
5const Passwork = require('./node_modules/passwork-js/src/passwork-api');
6/** @type PassworkAPI */
7const passwork = new Passwork(env.HOST);
8
9function throwFatalError(message, error) {
10 console.error(message);
11 console.error(error);
12 process.exit(0);
13}
14
15(async () => {
16 try {
17 const [argFileName, argCollections, argPath] = process.argv.slice(2);
18 let jsonFileName;
19 let jsonData;
20 let collectionsToImport = [];
21 let importVault;
22
23 // Authorize
24 try {
25 await passwork.login(env.API_KEY, env.USER_MASTER_PASS);
26 } catch (e) {
27 throwFatalError('Failed to authorise', e);
28 }
29
30 const rl = readline.createInterface({
31 input: process.stdin,
32 output: process.stdout
33 });
34
35 // Read json from bitwarden
36 const answerFileName = await new Promise(resolve => {
37 rl.question('\nSpecify the file to export\n', resolve)
38 });
39 jsonFileName = answerFileName ? answerFileName : argFileName;
40 try {
41 jsonData = JSON.parse(fs.readFileSync(jsonFileName));
42 if (!jsonData || !jsonData.hasOwnProperty('items')) {
43 throw 'Invalid json file format';
44 }
45 } catch (e) {
46 throwFatalError('Failed to read json file', e);
47 }
48
49 // Specify collections to import
50 const answerCollections = await new Promise(resolve => {
51 rl.question('\nSpecify comma separated id or name of collections to be exported (optional)\n', resolve)
52 });
53 let collections = answerCollections ? answerCollections : argCollections;
54 if (collections) {
55 collections = collections.split(',').map(c => c.trim()).filter((c) => c);
56 } else {
57 collections = [];
58 }
59 if (jsonData.collections && jsonData.collections.length) {
60 if (collections.length === 0) {
61 collectionsToImport = jsonData.collections;
62 } else {
63 jsonData.collections.forEach(c => {
64 if (collections.includes(c.name) || collections.includes(c.id)) {
65 collectionsToImport.push(c);
66 }
67 });
68 }
69 } else {
70 collectionsToImport = [];
71 }
72 collectionsToImport = [...new Set(collectionsToImport)];
73
74 // Specify vault id for import
75 const answerPath = await new Promise(resolve => {
76 rl.question('\nSpecify the id of the vault to import (optional) \n', resolve)
77 });
78 let path = answerPath ? answerPath : argPath;
79 if (path) {
80 importVault = await passwork.getVault(path);
81 if (!importVault) {
82 throwFatalError('The vault specified for import was not found');
83 }
84 }
85
86 // Confirm import
87 let confirmMessage = '\nThe following collections will be exported:\n';
88 if (jsonData.collections) {
89 collectionsToImport.forEach(c => {
90 confirmMessage += `${c.name} (${c.id})\n`;
91 });
92 } else {
93 confirmMessage += 'Private vault\n';
94 }
95 if (importVault) {
96 confirmMessage += `\nExports will be made to "${importVault.name}"\n`;
97 }
98 confirmMessage += 'To be continued? y/n\n';
99
100 const answerConfirm = await new Promise(resolve => {
101 rl.question(confirmMessage, resolve)
102 });
103 if (answerConfirm.toLowerCase() === 'y') {
104 rl.close();
105 importPasswords().then(() => process.exit(0)).catch((e) => {
106 throwFatalError('error', e);
107 });
108 } else {
109 console.log('The operation has been cancelled');
110 process.exit(0);
111 }
112
113 async function importPasswords() {
114 const logFileName = 'import-' + new Date().getTime() + '.log';
115
116 function logMessage(message) {
117 let msg = new Date().toISOString() + ' ' + message + '\n';
118 fs.appendFileSync(logFileName, msg);
119 console.log(msg);
120 }
121
122 function preparePasswordFields(data, directories) {
123 const vaultsNames = getDirectoriesNames(directories);
124
125 if (data.type !== 1 && data.type !== 2) {
126 logMessage(`Object type ${data.type}, ${data.name}`
127 + ` from collections ${vaultsNames} has not been imported`);
128 return;
129 }
130 const fields = {
131 password: '',
132 name: data.name,
133 description: data.notes,
134 custom: [],
135 };
136 if (directories.length > 1) {
137 fields.description = fields.description ? (fields.description + '\n') : '';
138 fields.description += `A copy of the password can be found in: ${vaultsNames}`;
139 }
140 if (data.login) {
141 if (data.login.username) {
142 fields.login = data.login.username;
143 }
144 if (data.login.password) {
145 fields.password = data.login.password;
146 }
147 if (data.login.totp) {
148 fields.custom.push({
149 name: 'TOTP',
150 value: data.login.totp,
151 type: 'totp'
152 });
153 }
154 if (data.login.uris) {
155 fields.url = data.login.uris.length === 1
156 ? data.login.uris[0].uri : data.login.uris.reduce((a, b) => (a.uri || a) + ", " + b.uri, '')
157 }
158 }
159 if (data.fields) {
160 data.fields.forEach((field) => {
161 if (field.type === 0 || field.type === 2) {
162 fields.custom.push({
163 name: String(field.name),
164 value: String(field.value),
165 type: 'text'
166 });
167 } else if (field.type === 1) {
168 fields.custom.push({
169 name: String(field.name),
170 value: String(field.value),
171 type: 'password'
172 });
173 } else {
174 logMessage(`Field of type "link" of the object ${data.name}`
175 + ` from collections ${vaultsNames} has not been imported`);
176 }
177 });
178 }
179
180 return fields;
181 }
182
183 function getDirectories(passwordCollectionIds, collections) {
184 const directories = [];
185 for (const collectionId of passwordCollectionIds) {
186 if (collections.hasOwnProperty(collectionId)) {
187 directories.push(collections[collectionId]);
188 }
189 }
190 return directories;
191 }
192
193 function getDirectoriesNames(directories) {
194 return directories.length > 1
195 ? directories.reduce((a, b) => (a.name || a) + ", " + b.name)
196 : directories[0].name;
197 }
198
199 logMessage('Import from file ' + jsonFileName);
200
201 if (collectionsToImport.length) {
202 if (importVault) {
203 // Collections as folders
204 const folders = {};
205 for (let c = 0; c < collectionsToImport.length; c++) {
206 const item = collectionsToImport[c];
207 folders[item.id] = await passwork.addFolder(importVault.id, item.name);
208 logMessage(`A folder has been created ${folders[item.id].name} based on the collection ${item.id}`)
209 }
210 for (let p = 0; p < jsonData.items.length; p++) {
211 const passwordData = jsonData.items[p];
212 const foldersList = getDirectories(passwordData.collectionIds, folders);
213 if (foldersList.length === 0) {
214 continue;
215 }
216
217 logMessage(`Imports started ${passwordData.name}`);
218 let fields = preparePasswordFields(passwordData, foldersList);
219 if (!fields) {
220 continue;
221 }
222 fields.vaultId = importVault.id;
223 for (const folder of foldersList) {
224 fields.folderId = folder.id;
225 await passwork.addPassword(Object.assign({}, fields));
226 logMessage(`Importation completed ${passwordData.name}`);
227 }
228 }
229 } else {
230 // Collections as vaults
231 const vaults = [];
232 for (let c = 0; c < collectionsToImport.length; c++) {
233 const item = collectionsToImport[c];
234 const vaultId = await passwork.addVault(item.name);
235 vaults[item.id] = await passwork.getVault(vaultId);
236 logMessage(`The vault has been created ${vaults[item.id].name} based on the collection ${item.id}`)
237 }
238 for (let p = 0; p < jsonData.items.length; p++) {
239 const passwordData = jsonData.items[p];
240 const vaultsList = getDirectories(passwordData.collectionIds, vaults);
241 if (vaultsList.length === 0) {
242 continue;
243 }
244
245 logMessage(`Imports started ${passwordData.name}`);
246 let fields = preparePasswordFields(passwordData, vaultsList);
247 if (!fields) {
248 continue;
249 }
250 for (const vault of vaultsList) {
251 fields.vaultId = vault.id;
252 await passwork.addPassword(Object.assign({}, fields));
253 logMessage(`Importation completed ${passwordData.name}`);
254 }
255 }
256 }
257 logMessage(`Import completed`);
258 process.exit(0);
259 return;
260 }
261
262 if (collectionsToImport.length === 0 && jsonData.items[0].organizationId === null) {
263 // Private vault import
264 if (!importVault) {
265 const vaultId = await passwork.addVault('Private safe', true);
266 importVault = await passwork.getVault(vaultId);
267 logMessage(`Vault ${importVault.name} was created `);
268 }
269 const folders = {};
270 if (jsonData.folders) {
271 for (const folder of jsonData.folders) {
272 folders[folder.id] = await passwork.addFolder(importVault.id, folder.name);
273 }
274 }
275
276 for (let p = 0; p < jsonData.items.length; p++) {
277 const passwordData = jsonData.items[p];
278 logMessage(`Imports started ${passwordData.name}`);
279
280 let fields = preparePasswordFields(passwordData, [importVault]);
281 if (!fields) {
282 continue;
283 }
284 fields.vaultId = importVault.id;
285 if (passwordData.folderId) {
286 fields.folderId = folders[passwordData.folderId].id;
287 }
288 await passwork.addPassword(Object.assign({}, fields));
289 logMessage(`Import completed ${passwordData.name}`);
290 }
291 logMessage(`Import completed`);
292 process.exit(0);
293 return;
294 }
295
296 logMessage(`Import format could not be determined`);
297 process.exit(0);
298 }
299 } catch (e) {
300 throwFatalError('error', e);
301 }
302})();
- Create an .env file and specify the Passwork host, the user's API key and its master password:
Shell
1HOST='https://your_host/api/v4'
2API_KEY=
3USER_MASTER_PASS=
- Load the Bitwarden XML file and run the script. The script will ask for the name of the file:
Shell
1node import.js
- You can also pass these parameters as arguments to the script:
Shell
1node import.js bitwarden_export_org.json "Collection 1"
Updated 24 Dec 2024
Did this page help you?