Skip to content
API Documentation
BastionGuard™
PasswordManagerPage.cpp
Go to the documentation of this file.
1/*
2 * BastionGuard™
3 * Copyright (C) 2025–2026 Calogero Scarnà
4 *
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, version 3.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <https://www.gnu.org/licenses/>.
16 *
17 * BastionGuard™ is a trademark of Calogero Scarnà.
18 * The BastionGuard™ name and branding are not licensed under the GPL.
19 */
20
22#include "PasswordDialogs.hpp"
24#include "PasswordVaultView.hpp"
25
26#include <glibmm/i18n.h>
27#include <algorithm>
28#include <cctype>
29
30// ─── Costruttore ─────────────────────────────────────────────────────────────
31
33: Gtk::Box(Gtk::Orientation::VERTICAL, 6),
34 securityBanner_(Gtk::Orientation::HORIZONTAL, 8),
35 topBar_(Gtk::Orientation::HORIZONTAL, 8),
36 unlockButton_(_("🔓 Sblocca vault")),
37 lockButton_(_("🔒 Blocca")),
38 stateBox_(Gtk::Orientation::HORIZONTAL, 0),
39 actionBar_(Gtk::Orientation::HORIZONTAL, 8),
40 addButton_(_("+ Nuova")),
41 importButton_(_("⬆ Importa")),
42 vaultViewButton_(_("🗄 Vault")),
43 refreshButton_(_("↺ Aggiorna")),
44 deleteAllButton_(_("🗑 Elimina tutto")),
45 entriesBox_(Gtk::Orientation::VERTICAL, 6)
46{
47 auto cssProvider = Gtk::CssProvider::create();
48 Gtk::StyleContext::add_provider_for_display(
49 Gdk::Display::get_default(),
50 cssProvider,
51 GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
52
53 buildUi();
55
56 unlockButton_.signal_clicked().connect(sigc::mem_fun(*this, &PasswordManagerPage::onUnlockClicked));
57 lockButton_.signal_clicked().connect(sigc::mem_fun(*this, &PasswordManagerPage::onLockClicked));
58 addButton_.signal_clicked().connect(sigc::mem_fun(*this, &PasswordManagerPage::onAddClicked));
59 importButton_.signal_clicked().connect(sigc::mem_fun(*this, &PasswordManagerPage::onImportClicked));
60 vaultViewButton_.signal_clicked().connect(sigc::mem_fun(*this, &PasswordManagerPage::onVaultViewClicked));
61 refreshButton_.signal_clicked().connect(sigc::mem_fun(*this, &PasswordManagerPage::onRefreshClicked));
62 deleteAllButton_.signal_clicked().connect(sigc::mem_fun(*this, &PasswordManagerPage::onDeleteAllClicked));
63 searchEntry_.signal_search_changed().connect(sigc::mem_fun(*this, &PasswordManagerPage::onSearchChanged));
64
65 reload();
66}
67
69 if (alive_) alive_->store(false, std::memory_order_release);
70}
71
72
74 buffer_ = Gtk::TextBuffer::create();
75 outputView_.set_buffer(buffer_);
76 outputView_.set_editable(false);
77 outputView_.set_monospace(true);
78
79 set_margin_top(8);
80 set_margin_bottom(8);
81 set_margin_start(8);
82 set_margin_end(8);
83
84 securityBanner_.get_style_context()->add_class("security-banner");
85 securityBanner_.get_style_context()->add_class("alert-info");
86 securityIcon_.set_text("🔐");
88
89 securityLabel_.set_markup(_(
90 "<b>Protezione avanzata AES-256-GCM</b> — "
91 "Le password sono cifrate con AES-256-GCM e la chiave è derivata dalla master password "
92 "tramite PBKDF2-SHA256 (100.000 iterazioni). Il file cifrato include un tag di "
93 "autenticazione: qualsiasi manipolazione esterna renderà il vault permanentemente illeggibile."
94 ));
95 securityLabel_.set_wrap(true);
96 securityLabel_.set_xalign(0.0f);
97 securityLabel_.set_hexpand(true);
99
100 auto* dangerBanner = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::HORIZONTAL, 8);
101 dangerBanner->get_style_context()->add_class("security-banner");
102 dangerBanner->get_style_context()->add_class("alert-danger");
103
104 auto* dangerIcon = Gtk::make_managed<Gtk::Label>();
105 dangerIcon->set_text("⚠️");
106 dangerBanner->append(*dangerIcon);
107
108 auto* dangerLabel = Gtk::make_managed<Gtk::Label>();
109 dangerLabel->set_markup(_(
110 "<b>Attenzione:</b> — L'esportazione del vault salva il file CSV con password in chiaro. "
111 "Salvare il file in un luogo sicuro e non accessibile."
112 ));
113 dangerLabel->set_wrap(true);
114 dangerLabel->set_xalign(0.0f);
115 dangerLabel->set_hexpand(true);
116 dangerBanner->append(*dangerLabel);
117
118 topBar_.set_margin_bottom(4);
119 topBar_.append(unlockButton_);
120 topBar_.append(lockButton_);
121
122 auto* spacer1 = Gtk::make_managed<Gtk::Box>();
123 spacer1->set_hexpand(true);
124 topBar_.append(*spacer1);
125 stateBox_.set_margin_start(4);
126 stateLabel_.set_text(_("🔒 Vault bloccato"));
127 stateBox_.append(stateLabel_);
128 stateBox_.get_style_context()->add_class("alert-success");
129 topBar_.append(stateBox_);
130 actionBar_.set_margin_bottom(4);
131
132 addButton_.get_style_context()->add_class("suggested-action");
133 actionBar_.append(addButton_);
134
135 {
136 importButton_.get_style_context()->add_class("suggested-action");
137 auto p = Gtk::CssProvider::create();
138 p->load_from_data(
139 "button.suggested-action#importBtn {"
140 " background:linear-gradient(to bottom,#33d17a,#26a269);"
141 " color:white;font-weight:bold;"
142 " padding:6px 16px;border-radius:6px;"
143 " border:1px solid #1e8449;"
144 "}");
145 importButton_.set_name("importBtn");
146 importButton_.get_style_context()->add_provider(p, GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
147 }
149 {
150 vaultViewButton_.get_style_context()->add_class("suggested-action");
151 auto p = Gtk::CssProvider::create();
152 p->load_from_data(
153 "button.suggested-action#vaultBtn {"
154 " background:linear-gradient(to bottom,#3584e4,#1c71d8);"
155 " color:white;font-weight:bold;"
156 " padding:6px 16px;border-radius:6px;"
157 " border:1px solid #1558b0;"
158 "}");
159 vaultViewButton_.set_name("vaultBtn");
160 vaultViewButton_.get_style_context()->add_provider(p, GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
161 }
164
165 auto* spacer2 = Gtk::make_managed<Gtk::Box>();
166 spacer2->set_hexpand(true);
167 actionBar_.append(*spacer2);
168
169 deleteAllButton_.get_style_context()->add_class("destructive-action");
171
172 searchEntry_.set_placeholder_text(_("Cerca credenziali..."));
173 searchEntry_.set_size_request(200, -1);
174 actionBar_.append(searchEntry_);
175
176 auto* sep = Gtk::make_managed<Gtk::Separator>(Gtk::Orientation::HORIZONTAL);
177
178 listScrolled_.set_child(entriesBox_);
179 listScrolled_.set_expand(true);
180
181 logScrolled_.set_child(outputView_);
182 logScrolled_.set_min_content_height(100);
183 logScrolled_.set_expand(false);
184
185 append(securityBanner_);
186 append(*dangerBanner);
187 append(topBar_);
188 append(actionBar_);
189 append(*sep);
190 append(listScrolled_);
191 append(logScrolled_);
192}
193
194
195
196
197
198void PasswordManagerPage::setVaultState(bool unlocked, const std::string& msg) {
199 auto ctx = stateBox_.get_style_context();
200 ctx->remove_class("alert-success");
201 ctx->remove_class("alert-danger");
202
203 if (unlocked) {
204 ctx->add_class("alert-danger");
205 stateLabel_.set_text(msg.empty() ? _("🔓 Vault sbloccato") : msg);
206 } else {
207 ctx->add_class("alert-success");
208 stateLabel_.set_text(msg.empty() ? _("🔒 Vault bloccato") : msg);
209 }
210}
211
212
217
218void PasswordManagerPage::refreshEntries(const std::string& filter) {
219 while (auto* child = entriesBox_.get_first_child())
220 entriesBox_.remove(*child);
221
222 std::string needle = filter;
223 std::transform(needle.begin(), needle.end(), needle.begin(), ::tolower);
224
225 for (const auto& entry : entriesCache_) {
226 std::string hay = entry.name + " " + entry.username + " " + entry.url + " " + entry.notes;
227 std::transform(hay.begin(), hay.end(), hay.begin(), ::tolower);
228 if (!needle.empty() && hay.find(needle) == std::string::npos) continue;
229 createEntryRow(entry);
230 }
231}
232
234 auto* row = Gtk::make_managed<PasswordSearchRow>(entry);
235 row->signal_reveal().connect([this](const PasswordEntry& e) { onRevealEntry(e); });
236 row->signal_edit().connect([this](const PasswordEntry& e) { onEditEntry(e); });
237 row->signal_delete().connect([this](const PasswordEntry& e) { onDeleteEntry(e); });
238 entriesBox_.append(*row);
239}
240
241
243
244 auto* parent = getParentWindow();
245 if (!parent) { logSafe(_("Impossibile trovare la finestra padre.\n")); return; }
246
247 if (vault_.isFirstRun()) {
248 auto* dialog = Gtk::make_managed<Gtk::Dialog>(_("Crea master password"), *parent, true);
249 dialog->get_content_area()->set_margin(12);
250 dialog->get_content_area()->set_spacing(8);
251
252 auto* infoLabel = Gtk::make_managed<Gtk::Label>(
253 _("Prima configurazione: scegli una master password.\n"
254 "Minimo 8 caratteri. Non può essere recuperata se dimenticata."));
255 infoLabel->set_wrap(true);
256 infoLabel->set_xalign(0.0f);
257 infoLabel->get_style_context()->add_class("dim-label");
258
259 auto* entry1 = Gtk::make_managed<Gtk::Entry>();
260 entry1->set_placeholder_text(_("Nuova master password"));
261 entry1->set_visibility(false);
262 entry1->set_input_purpose(Gtk::InputPurpose::PASSWORD);
263
264 auto* entry2 = Gtk::make_managed<Gtk::Entry>();
265 entry2->set_placeholder_text(_("Conferma master password"));
266 entry2->set_visibility(false);
267 entry2->set_input_purpose(Gtk::InputPurpose::PASSWORD);
268 entry2->set_activates_default(true);
269
270 auto* strengthLabel = Gtk::make_managed<Gtk::Label>("");
271 strengthLabel->set_xalign(0.0f);
272
273 dialog->get_content_area()->append(*infoLabel);
274 dialog->get_content_area()->append(*entry1);
275 dialog->get_content_area()->append(*entry2);
276 dialog->get_content_area()->append(*strengthLabel);
277 dialog->add_button(_("Annulla"), Gtk::ResponseType::CANCEL);
278 dialog->add_button(_("Crea vault"), Gtk::ResponseType::OK);
279 dialog->set_default_response(Gtk::ResponseType::OK);
280
281 entry1->signal_changed().connect([entry1, strengthLabel]() {
282 const auto pwd = entry1->get_text();
283 const size_t len = pwd.size();
284 bool hasUpper = false, hasLower = false, hasDigit = false, hasSymbol = false;
285 for (char c : pwd) {
286 if (std::isupper(c)) hasUpper = true;
287 else if (std::islower(c)) hasLower = true;
288 else if (std::isdigit(c)) hasDigit = true;
289 else hasSymbol = true;
290 }
291 const int score = (len >= 8) + (len >= 12) + hasUpper + hasLower + hasDigit + hasSymbol;
292 std::string markup;
293 if (len == 0) markup = "";
294 else if (score <= 2) markup = "<span foreground='red'>⚠ Debole</span>";
295 else if (score <= 4) markup = "<span foreground='orange'>◑ Media</span>";
296 else markup = "<span foreground='green'>✔ Forte</span>";
297 strengthLabel->set_markup(markup);
298 });
299
300 dialog->signal_response().connect([this, dialog, entry1, entry2](int response) {
301 if (response == Gtk::ResponseType::OK) {
302 const auto pwd1 = entry1->get_text();
303 const auto pwd2 = entry2->get_text();
304 if (pwd1 != pwd2) {
305 logSafe(_("Le password non coincidono.\n"));
306 } else if (pwd1.size() < 8) {
307 logSafe(_("La master password deve essere di almeno 8 caratteri.\n"));
308 } else if (vault_.createMasterPassword(pwd1)) {
309 setVaultState(true, _("🔓 Vault creato e sbloccato"));
310 logSafe(_("Vault creato. Master password impostata.\n"));
311 } else {
312 logSafe(_("Errore durante la creazione del vault.\n"));
313 }
314 }
315 dialog->hide();
316 });
317 dialog->present();
318
319 } else {
320 auto* dialog = Gtk::make_managed<Gtk::Dialog>(_("Sblocca vault"), *parent, true);
321 auto* entry = Gtk::make_managed<Gtk::Entry>();
322 entry->set_placeholder_text(_("Master password"));
323 entry->set_visibility(false);
324 entry->set_input_purpose(Gtk::InputPurpose::PASSWORD);
325 entry->set_activates_default(true);
326 dialog->get_content_area()->set_margin(12);
327 dialog->get_content_area()->append(*entry);
328 dialog->add_button(_("Annulla"), Gtk::ResponseType::CANCEL);
329 dialog->add_button(_("Sblocca"), Gtk::ResponseType::OK);
330 dialog->set_default_response(Gtk::ResponseType::OK);
331
332 dialog->signal_response().connect([this, dialog, entry](int response) {
333 if (response == Gtk::ResponseType::OK) {
334 if (vault_.unlock(entry->get_text())) {
335 setVaultState(true, _("🔓 Vault sbloccato — caricamento cache…"));
336 logSafe(_("Vault sbloccato. Precaricamento password in background…\n"));
337 Glib::signal_timeout().connect_once([this]() {
338 if (vault_.isUnlocked())
339 setVaultState(true);
340 }, 3000);
341 } else {
342 logSafe(_("Password errata.\n"));
343 }
344 }
345 dialog->hide();
346 });
347 dialog->present();
348 }
349}
350
351
353
354 vault_.lock();
355 setVaultState(false);
356 logSafe(_("Vault bloccato.\n"));
357}
358
359
361
362 if (!vault_.isUnlocked()) { logSafe(_("Sblocca il vault prima.\n")); return; }
363 auto* parent = getParentWindow();
364 if (!parent) return;
365
366 auto* dialog = new PasswordDialog(*parent, _("Nuova credenziale"));
367 dialog->signal_response().connect([this, dialog](int response) {
368 if (response == Gtk::ResponseType::OK) {
369 PasswordEntry entry = dialog->getEntry();
370 if (entry.name.empty()) {
371 logSafe(_("Nome credenziale obbligatorio.\n"));
372 } else {
373 entry.id = vault_.generateId();
374 entry.created_at = vault_.nowIso();
375 entry.updated_at = entry.created_at;
376 if (vault_.addEntry(entry, dialog->getSecret())) {
377 logSafe(std::string(_("Credenziale aggiunta: ")) + entry.name + "\n");
378 reload();
379 } else {
380 logSafe(_("Errore durante il salvataggio.\n"));
381 }
382 }
383 }
384 dialog->hide();
385 delete dialog;
386 });
387 dialog->present();
388}
389
390
392
393 if (!vault_.isUnlocked()) { logSafe(_("Sblocca il vault prima di importare.\n")); return; }
394 auto* parent = getParentWindow();
395 if (!parent) return;
396
397 auto* dialog = new PasswordImportDialog(*parent);
398 dialog->signal_import_confirmed().connect(
399 [this, dialog](std::vector<PasswordImportDialog::BulkItem> items) {
400 dialog->hide();
401 if (items.empty()) { delete dialog; return; }
402
403 const std::string now = vault_.nowIso();
404 for (auto& [entry, pwd] : items) {
405 entry.id = vault_.generateId();
406 entry.created_at = now;
407 entry.updated_at = now;
408 if (entry.name.empty() && !entry.url.empty()) {
409 auto url = entry.url;
410 auto p = url.find("://");
411 if (p != std::string::npos) url = url.substr(p + 3);
412 auto sl = url.find('/');
413 if (sl != std::string::npos) url = url.substr(0, sl);
414 entry.name = url;
415 }
416 }
417
418 vault_.addEntriesBulk(items, [this, dialog](int ok, int fail) {
419 logSafe(std::string(_("Import completato: ")) + std::to_string(ok) +
420 std::string(_(" importate")) +
421 (fail > 0 ? (", " + std::to_string(fail) + std::string(_(" fallite"))) : "") +
422 ".\n");
423 reload();
424 delete dialog;
425 });
426 });
427 dialog->present();
428}
429
430
432
433 if (!vault_.isUnlocked()) { logSafe(_("Sblocca il vault prima.\n")); return; }
434 auto* parent = getParentWindow();
435 if (!parent) return;
436 auto* view = new PasswordVaultView(*parent, vault_);
437 view->present();
438}
439
440
442
443 if (!vault_.isUnlocked()) { logSafe(_("Sblocca il vault prima.\n")); return; }
444 auto* parent = getParentWindow();
445 if (!parent) return;
446
447 const auto count = entriesCache_.size();
448 if (count == 0) { logSafe(_("Nessuna credenziale da eliminare.\n")); return; }
449
450 auto* confirm1 = new Gtk::MessageDialog(
451 *parent, _("Eliminare tutte le credenziali?"),
452 false, Gtk::MessageType::WARNING, Gtk::ButtonsType::OK_CANCEL, true);
453 confirm1->set_secondary_text(
454 std::string(_("Questa operazione eliminerà ")) + std::to_string(count) +
455 std::string(_(" credenziali e non può essere annullata.")));
456
457 confirm1->signal_response().connect([this, confirm1, parent](int r1) {
458 confirm1->hide();
459 if (r1 != Gtk::ResponseType::OK) { delete confirm1; return; }
460
461 auto* confirm2 = new Gtk::MessageDialog(
462 *parent, _("Sei sicuro? L'operazione è irreversibile."),
463 false, Gtk::MessageType::ERROR, Gtk::ButtonsType::NONE, true);
464 confirm2->set_secondary_text(_("Il file cifrato verrà eliminato definitivamente."));
465 confirm2->add_button(_("Annulla"), Gtk::ResponseType::CANCEL);
466 confirm2->add_button(_("Sì, elimina tutto"), Gtk::ResponseType::OK);
467 auto* ok_btn = confirm2->get_widget_for_response(Gtk::ResponseType::OK);
468 if (ok_btn) ok_btn->get_style_context()->add_class("destructive-action");
469
470 confirm2->signal_response().connect([this, confirm2](int r2) {
471 confirm2->hide();
472 if (r2 == Gtk::ResponseType::OK) {
473 logSafe(_("Eliminazione in corso…\n"));
474 vault_.deleteAllEntries([this](bool ok) {
475 logSafe(ok ? _("✔ Tutte le credenziali eliminate.\n")
476 : _("Errore durante l'eliminazione.\n"));
477 reload();
478 });
479 }
480 delete confirm2;
481 });
482 confirm2->present();
483 delete confirm1;
484 });
485 confirm1->present();
486}
487
488
490
491 reload();
492 logSafe(_("Elenco aggiornato.\n"));
493}
494
499
501
502 if (!vault_.isUnlocked()) { logSafe(_("Vault bloccato.\n")); return; }
503 auto* parent = getParentWindow();
504 if (!parent) return;
505
506 auto alive = alive_;
507 vault_.getSecretAsync(entry.id, [this, alive, entry](std::string secret) {
508 if (!alive || !alive->load(std::memory_order_acquire)) return;
509 auto* p = getParentWindow();
510 if (!p) return;
511 auto* reveal = new Gtk::MessageDialog(
512 *p, _("Password per: ") + entry.name,
513 false, Gtk::MessageType::INFO, Gtk::ButtonsType::CLOSE, true);
514 reveal->set_secondary_text(secret, false);
515 reveal->signal_response().connect([reveal](int) { reveal->hide(); delete reveal; });
516 reveal->present();
517 });
518}
519
520
522
523 if (!vault_.isUnlocked()) { logSafe(_("Vault bloccato.\n")); return; }
524 auto* parent = getParentWindow();
525 if (!parent) return;
526
527 auto alive = alive_;
528 vault_.getSecretAsync(entry.id, [this, alive, entry](std::string secret) {
529 if (!alive || !alive->load(std::memory_order_acquire)) return;
530 auto* p = getParentWindow();
531 if (!p) return;
532 auto* dialog = new PasswordDialog(
533 *p, _("Modifica credenziale"), entry, secret);
534 dialog->signal_response().connect([this, dialog, entry](int response) {
535 if (response == Gtk::ResponseType::OK) {
536 PasswordEntry updated = dialog->getEntry();
537 updated.id = entry.id;
538 updated.created_at = entry.created_at;
539 updated.updated_at = vault_.nowIso();
540 if (vault_.updateEntry(updated, dialog->getSecret())) {
541 logSafe(std::string(_("Credenziale aggiornata: ")) + updated.name + "\n");
542 reload();
543 } else {
544 logSafe(_("Errore durante l'aggiornamento.\n"));
545 }
546 }
547 dialog->hide();
548 delete dialog;
549 });
550 dialog->present();
551 });
552}
553
554
556
557 if (!vault_.isUnlocked()) { logSafe(_("Vault bloccato.\n")); return; }
558 auto* parent = getParentWindow();
559 if (!parent) return;
560
561 auto* confirm = new Gtk::MessageDialog(
562 *parent, _("Eliminare la credenziale selezionata?"),
563 false, Gtk::MessageType::QUESTION, Gtk::ButtonsType::OK_CANCEL, true);
564 confirm->set_secondary_text(entry.name);
565 confirm->signal_response().connect([this, confirm, entry](int response) {
566 if (response == Gtk::ResponseType::OK) {
567 if (vault_.deleteEntry(entry.id)) {
568 logSafe(std::string(_("Credenziale eliminata: ")) + entry.name + "\n");
569 reload();
570 } else {
571 logSafe(_("Errore durante l'eliminazione.\n"));
572 }
573 }
574 confirm->hide();
575 delete confirm;
576 });
577 confirm->present();
578}
579
580
582 auto* root = get_root();
583 return dynamic_cast<Gtk::Window*>(root);
584}
585
586void PasswordManagerPage::logSafe(const std::string& msg) {
587 auto alive = alive_;
588 Glib::signal_idle().connect_once([this, alive, msg]() {
589 BastionGuard::if_alive(alive, [this, &msg]() {
590 buffer_->insert(buffer_->end(), msg);
591 });
592 });
593}
#define _(s)
PasswordDialog(Gtk::Window &parent, const Glib::ustring &title)
PasswordImportDialog(Gtk::Window &parent)
Gtk::Window * getParentWindow()
Gtk::ScrolledWindow logScrolled_
Gtk::ScrolledWindow listScrolled_
void onRevealEntry(const PasswordEntry &entry)
void setVaultState(bool unlocked, const std::string &msg="")
void createEntryRow(const PasswordEntry &entry)
Gtk::SearchEntry searchEntry_
BastionGuard::AliveFlag alive_
void refreshEntries(const std::string &filter="")
std::vector< PasswordEntry > entriesCache_
Glib::RefPtr< Gtk::TextBuffer > buffer_
void onDeleteEntry(const PasswordEntry &entry)
void logSafe(const std::string &msg)
void onEditEntry(const PasswordEntry &entry)
PasswordVaultView(Gtk::Window &parent, PasswordVault &vault)
bool createMasterPassword(const std::string &masterPassword)
std::string generateId() const
bool isUnlocked() const
bool addEntry(const PasswordEntry &entry, const std::string &secret)
bool isFirstRun() const
std::string nowIso() const
void getSecretAsync(const std::string &id, std::function< void(std::string)> callback) const
bool unlock(const std::string &masterPassword)
void deleteAllEntries(std::function< void(bool)> onDone)
std::vector< PasswordEntry > loadIndex() const
bool deleteEntry(const std::string &id)
void addEntriesBulk(const std::vector< std::pair< PasswordEntry, std::string > > &items, std::function< void(int ok, int fail)> onDone)
void if_alive(const AliveFlag &alive, Fn &&fn)
std::string created_at
std::string id
std::string updated_at
std::string name