From c017308aa197810458f7342f697fbb79ae55cc3d Mon Sep 17 00:00:00 2001 From: Joe Julian Date: Sat, 11 Apr 2026 00:52:01 -0700 Subject: [PATCH 01/19] Add browser extension gRPC bridge --- Makefile | 5 +- README.md | 6 + browser/extension/README.md | 11 + browser/extension/background.js | 195 +++++ browser/extension/content.js | 69 ++ browser/extension/manifest.chromium.json | 26 + browser/extension/manifest.firefox.json | 31 + browser/extension/options.html | 34 + browser/extension/options.js | 47 + browser/extension/popup.html | 29 + browser/extension/popup.js | 101 +++ browser/extension/style.css | 174 ++++ cmd/keepassgo-browser-bridge/main.go | 121 +++ docs/browser-extension.md | 76 ++ internal/api/server.go | 162 ++++ internal/api/server_test.go | 78 ++ internal/browserbridge/bridge.go | 321 +++++++ internal/browserbridge/bridge_test.go | 182 ++++ internal/browserbridge/client.go | 59 ++ .../archlinux/keepassgo-git/PKGBUILD.tmpl | 2 + proto/keepassgo/v1/keepassgo.pb.go | 827 ++++++++++++------ proto/keepassgo/v1/keepassgo.proto | 31 + proto/keepassgo/v1/keepassgo_grpc.pb.go | 130 ++- 23 files changed, 2437 insertions(+), 280 deletions(-) create mode 100644 browser/extension/README.md create mode 100644 browser/extension/background.js create mode 100644 browser/extension/content.js create mode 100644 browser/extension/manifest.chromium.json create mode 100644 browser/extension/manifest.firefox.json create mode 100644 browser/extension/options.html create mode 100644 browser/extension/options.js create mode 100644 browser/extension/popup.html create mode 100644 browser/extension/popup.js create mode 100644 browser/extension/style.css create mode 100644 cmd/keepassgo-browser-bridge/main.go create mode 100644 docs/browser-extension.md create mode 100644 internal/browserbridge/bridge.go create mode 100644 internal/browserbridge/bridge_test.go create mode 100644 internal/browserbridge/client.go diff --git a/Makefile b/Makefile index 0e8fed1..b16aa4f 100644 --- a/Makefile +++ b/Makefile @@ -25,7 +25,7 @@ ifneq ($(strip $(SIGNPASS)),) GOGIO_SIGN_FLAGS += -signpass $(SIGNPASS) endif -.PHONY: apk archlinux-pkgbuild +.PHONY: apk archlinux-pkgbuild browser-bridge apk: android/keepassgo-android.jar @test -x "$(JAVA_HOME)/bin/java" || { echo "JAVA_HOME must point to a working JDK install"; exit 1; } @test -d "$(ANDROID_SDK_ROOT)" || { echo "ANDROID_SDK_ROOT must point to an Android SDK install"; exit 1; } @@ -68,3 +68,6 @@ archlinux-pkgbuild: $(ARCH_PKG_TMPL) Makefile -e 's|@PKGVER@|$(ARCH_PKGVER)|g' \ -e 's|@REPO_DIR@|$(ARCH_REPO_DIR)|g' \ "$(ARCH_PKG_TMPL)" > "$(ARCH_PKGBUILD)" + +browser-bridge: + go build ./cmd/keepassgo-browser-bridge diff --git a/README.md b/README.md index 8118850..19fe7c0 100644 --- a/README.md +++ b/README.md @@ -63,6 +63,7 @@ makepkg -si The package installs: - `/usr/bin/keepassgo` +- `/usr/bin/keepassgo-browser-bridge` - a desktop entry at `/usr/share/applications/keepassgo.desktop` - application icons under the hicolor theme @@ -98,3 +99,8 @@ You will need the Android SDK and NDK installed and configured for real device o Desktop automation is resolved through the secure gRPC API rather than synthetic auto-type. See [`docs/desktop-automation.md`](./docs/desktop-automation.md). + +## Browser Extension + +Firefox and Chromium browser integration is available through the local gRPC API plus a native messaging bridge. +See [`docs/browser-extension.md`](./docs/browser-extension.md). diff --git a/browser/extension/README.md b/browser/extension/README.md new file mode 100644 index 0000000..fbd3f03 --- /dev/null +++ b/browser/extension/README.md @@ -0,0 +1,11 @@ +# KeePassGO Browser Extension + +Shared extension assets for Firefox and Chromium-based browsers live here. + +- `manifest.firefox.json` uses the fixed Firefox extension id `browser@keepassgo.invalid` +- `manifest.chromium.json` is the Chromium/Chrome manifest template +- `background.js` talks to the native messaging host `org.keepassgo.browser` +- `content.js` fills username and password fields on the current page +- `options.html` stores the local gRPC address and API token in browser extension storage + +The extension sends the API token to the native host on each request. The bridge does not store the token on disk. diff --git a/browser/extension/background.js b/browser/extension/background.js new file mode 100644 index 0000000..07e0a76 --- /dev/null +++ b/browser/extension/background.js @@ -0,0 +1,195 @@ +const ext = globalThis.browser ?? globalThis.chrome; +const nativeHost = "org.keepassgo.browser"; +const defaultSettings = { + grpcAddress: "127.0.0.1:47777", + bearerToken: "" +}; + +function storageGet(keys) { + return new Promise((resolve, reject) => { + ext.storage.local.get(keys, (value) => { + const error = ext.runtime.lastError; + if (error) { + reject(new Error(error.message)); + return; + } + resolve(value); + }); + }); +} + +function storageSet(value) { + return new Promise((resolve, reject) => { + ext.storage.local.set(value, () => { + const error = ext.runtime.lastError; + if (error) { + reject(new Error(error.message)); + return; + } + resolve(); + }); + }); +} + +function tabsQuery(query) { + return new Promise((resolve, reject) => { + ext.tabs.query(query, (tabs) => { + const error = ext.runtime.lastError; + if (error) { + reject(new Error(error.message)); + return; + } + resolve(tabs); + }); + }); +} + +function tabsSendMessage(tabId, message) { + return new Promise((resolve, reject) => { + ext.tabs.sendMessage(tabId, message, (response) => { + const error = ext.runtime.lastError; + if (error) { + reject(new Error(error.message)); + return; + } + resolve(response); + }); + }); +} + +function connectNative(message) { + return new Promise((resolve, reject) => { + ext.runtime.sendNativeMessage(nativeHost, message, (response) => { + const error = ext.runtime.lastError; + if (error) { + reject(new Error(error.message)); + return; + } + resolve(response); + }); + }); +} + +async function loadSettings() { + const stored = await storageGet(["grpcAddress", "bearerToken"]); + return { + grpcAddress: (stored.grpcAddress || defaultSettings.grpcAddress).trim(), + bearerToken: (stored.bearerToken || "").trim() + }; +} + +async function activePageContext() { + const [tab] = await tabsQuery({ active: true, currentWindow: true }); + return { + tabId: tab?.id ?? null, + url: typeof tab?.url === "string" ? tab.url : "" + }; +} + +async function statusForPage() { + const settings = await loadSettings(); + const page = await activePageContext(); + if (!settings.bearerToken) { + return { + success: false, + configured: false, + status: null, + pageUrl: page.url, + matches: [], + error: "Set an API token in extension settings." + }; + } + + const status = await connectNative({ + action: "status", + grpcAddress: settings.grpcAddress, + bearerToken: settings.bearerToken + }); + if (!status.success || status.status?.locked || !page.url.startsWith("http")) { + return { + success: status.success, + configured: true, + status: status.status ?? null, + pageUrl: page.url, + matches: [], + error: status.error ?? "" + }; + } + + const matches = await connectNative({ + action: "find-logins", + grpcAddress: settings.grpcAddress, + bearerToken: settings.bearerToken, + url: page.url + }); + return { + success: matches.success, + configured: true, + status: matches.status ?? status.status ?? null, + pageUrl: page.url, + matches: matches.matches ?? [], + error: matches.error ?? "" + }; +} + +async function fillLogin(entryId) { + const settings = await loadSettings(); + const page = await activePageContext(); + if (!settings.bearerToken) { + throw new Error("API token is not configured."); + } + if (page.tabId == null) { + throw new Error("No active tab is available."); + } + + const response = await connectNative({ + action: "get-login", + grpcAddress: settings.grpcAddress, + bearerToken: settings.bearerToken, + entryId, + url: page.url + }); + if (!response.success || !response.credential) { + throw new Error(response.error || "KeePassGO did not return a credential."); + } + + const fillResponse = await tabsSendMessage(page.tabId, { + type: "keepassgo-fill-credential", + credential: response.credential + }); + if (!fillResponse?.ok) { + throw new Error(fillResponse?.error || "The current page could not be filled."); + } + return { + credential: response.credential, + pageUrl: page.url + }; +} + +ext.runtime.onMessage.addListener((message, _sender, sendResponse) => { + (async () => { + switch (message?.type) { + case "keepassgo-popup-state": + sendResponse(await statusForPage()); + return; + case "keepassgo-fill-entry": + sendResponse({ success: true, ...(await fillLogin(message.entryId)) }); + return; + case "keepassgo-load-settings": + sendResponse({ success: true, settings: await loadSettings() }); + return; + case "keepassgo-save-settings": + await storageSet({ + grpcAddress: String(message.settings?.grpcAddress || defaultSettings.grpcAddress).trim(), + bearerToken: String(message.settings?.bearerToken || "").trim() + }); + sendResponse({ success: true }); + return; + default: + sendResponse({ success: false, error: `Unsupported message ${message?.type || ""}`.trim() }); + } + })().catch((error) => { + sendResponse({ success: false, error: error instanceof Error ? error.message : String(error) }); + }); + return true; +}); diff --git a/browser/extension/content.js b/browser/extension/content.js new file mode 100644 index 0000000..3680808 --- /dev/null +++ b/browser/extension/content.js @@ -0,0 +1,69 @@ +function isVisibleInput(input) { + if (!(input instanceof HTMLInputElement)) { + return false; + } + if (input.disabled || input.readOnly) { + return false; + } + const style = window.getComputedStyle(input); + if (style.display === "none" || style.visibility === "hidden") { + return false; + } + return input.offsetParent !== null || style.position === "fixed"; +} + +function dispatchFillEvents(input) { + input.dispatchEvent(new Event("input", { bubbles: true })); + input.dispatchEvent(new Event("change", { bubbles: true })); +} + +function findPasswordInput() { + return Array.from(document.querySelectorAll('input[type="password"]')).find(isVisibleInput) || null; +} + +function findUsernameInput(passwordInput) { + const form = passwordInput?.form || null; + const scope = form || document; + const candidates = Array.from(scope.querySelectorAll('input[type="text"], input[type="email"], input:not([type])')) + .filter(isVisibleInput); + if (passwordInput) { + const sameForm = candidates.find((input) => input.form === passwordInput.form); + if (sameForm) { + return sameForm; + } + } + return candidates[0] || null; +} + +function fillCredential(credential) { + const passwordInput = findPasswordInput(); + const usernameInput = findUsernameInput(passwordInput); + + if (usernameInput && credential.username) { + usernameInput.focus(); + usernameInput.value = credential.username; + dispatchFillEvents(usernameInput); + } + if (passwordInput && credential.password) { + passwordInput.focus(); + passwordInput.value = credential.password; + dispatchFillEvents(passwordInput); + } + + if (!usernameInput && !passwordInput) { + return { ok: false, error: "No fillable username or password fields were found." }; + } + return { ok: true }; +} + +(globalThis.browser ?? globalThis.chrome).runtime.onMessage.addListener((message, _sender, sendResponse) => { + if (message?.type !== "keepassgo-fill-credential") { + return false; + } + try { + sendResponse(fillCredential(message.credential || {})); + } catch (error) { + sendResponse({ ok: false, error: error instanceof Error ? error.message : String(error) }); + } + return false; +}); diff --git a/browser/extension/manifest.chromium.json b/browser/extension/manifest.chromium.json new file mode 100644 index 0000000..77b3c68 --- /dev/null +++ b/browser/extension/manifest.chromium.json @@ -0,0 +1,26 @@ +{ + "manifest_version": 3, + "name": "KeePassGO Browser", + "version": "0.1.0", + "description": "Fill credentials from KeePassGO over the local gRPC API.", + "permissions": ["activeTab", "nativeMessaging", "storage", "tabs"], + "host_permissions": ["http://*/*", "https://*/*"], + "background": { + "service_worker": "background.js" + }, + "action": { + "default_title": "KeePassGO Browser", + "default_popup": "popup.html" + }, + "options_ui": { + "page": "options.html", + "open_in_tab": true + }, + "content_scripts": [ + { + "matches": ["http://*/*", "https://*/*"], + "js": ["content.js"], + "run_at": "document_idle" + } + ] +} diff --git a/browser/extension/manifest.firefox.json b/browser/extension/manifest.firefox.json new file mode 100644 index 0000000..ea23659 --- /dev/null +++ b/browser/extension/manifest.firefox.json @@ -0,0 +1,31 @@ +{ + "manifest_version": 3, + "name": "KeePassGO Browser", + "version": "0.1.0", + "description": "Fill credentials from KeePassGO over the local gRPC API.", + "permissions": ["activeTab", "nativeMessaging", "storage", "tabs"], + "host_permissions": ["http://*/*", "https://*/*"], + "background": { + "service_worker": "background.js" + }, + "action": { + "default_title": "KeePassGO Browser", + "default_popup": "popup.html" + }, + "options_ui": { + "page": "options.html", + "open_in_tab": true + }, + "content_scripts": [ + { + "matches": ["http://*/*", "https://*/*"], + "js": ["content.js"], + "run_at": "document_idle" + } + ], + "browser_specific_settings": { + "gecko": { + "id": "browser@keepassgo.invalid" + } + } +} diff --git a/browser/extension/options.html b/browser/extension/options.html new file mode 100644 index 0000000..a1f1246 --- /dev/null +++ b/browser/extension/options.html @@ -0,0 +1,34 @@ + + + + + + KeePassGO Browser Settings + + + +
+
+
+

Browser Settings

+

Configure how the extension reaches KeePassGO.

+
+
+
+ + +
+ +
+

+
+
+ + + diff --git a/browser/extension/options.js b/browser/extension/options.js new file mode 100644 index 0000000..75eeee3 --- /dev/null +++ b/browser/extension/options.js @@ -0,0 +1,47 @@ +const extOptions = globalThis.browser ?? globalThis.chrome; + +function runtimeSend(message) { + return new Promise((resolve, reject) => { + extOptions.runtime.sendMessage(message, (response) => { + const error = extOptions.runtime.lastError; + if (error) { + reject(new Error(error.message)); + return; + } + resolve(response); + }); + }); +} + +async function loadSettings() { + const response = await runtimeSend({ type: "keepassgo-load-settings" }); + if (!response?.success) { + throw new Error(response?.error || "Could not load settings."); + } + document.getElementById("grpc-address").value = response.settings.grpcAddress || "127.0.0.1:47777"; + document.getElementById("bearer-token").value = response.settings.bearerToken || ""; +} + +async function saveSettings(event) { + event.preventDefault(); + const status = document.getElementById("settings-status"); + status.textContent = "Saving…"; + try { + const response = await runtimeSend({ + type: "keepassgo-save-settings", + settings: { + grpcAddress: document.getElementById("grpc-address").value, + bearerToken: document.getElementById("bearer-token").value + } + }); + if (!response?.success) { + throw new Error(response?.error || "Could not save settings."); + } + status.textContent = "Saved."; + } catch (error) { + status.textContent = error instanceof Error ? error.message : String(error); + } +} + +document.getElementById("settings-form").addEventListener("submit", saveSettings); +void loadSettings(); diff --git a/browser/extension/popup.html b/browser/extension/popup.html new file mode 100644 index 0000000..b5a6c2c --- /dev/null +++ b/browser/extension/popup.html @@ -0,0 +1,29 @@ + + + + + + KeePassGO Browser + + + +
+
+
+

KeePassGO

+

Checking current page

+
+ Settings +
+
+ Loading +

Checking KeePassGO.

+
+
+

Matches

+
+
+
+ + + diff --git a/browser/extension/popup.js b/browser/extension/popup.js new file mode 100644 index 0000000..b6bd51e --- /dev/null +++ b/browser/extension/popup.js @@ -0,0 +1,101 @@ +const extPopup = globalThis.browser ?? globalThis.chrome; + +function runtimeSend(message) { + return new Promise((resolve, reject) => { + extPopup.runtime.sendMessage(message, (response) => { + const error = extPopup.runtime.lastError; + if (error) { + reject(new Error(error.message)); + return; + } + resolve(response); + }); + }); +} + +function hostFromURL(rawURL) { + try { + return new URL(rawURL).host || rawURL; + } catch (_error) { + return rawURL || "Current page"; + } +} + +function setStatus(title, message, tone) { + const card = document.getElementById("status-card"); + card.dataset.tone = tone || "neutral"; + document.getElementById("status-title").textContent = title; + document.getElementById("status-message").textContent = message; +} + +function renderMatches(state) { + const root = document.getElementById("matches"); + root.textContent = ""; + if (!Array.isArray(state.matches) || state.matches.length === 0) { + const empty = document.createElement("p"); + empty.className = "subtle"; + empty.textContent = "No matching entries for this page."; + root.appendChild(empty); + return; + } + + for (const match of state.matches) { + const row = document.createElement("button"); + row.type = "button"; + row.className = "match-row"; + row.innerHTML = ` + + ${match.title} + ${match.username || "No username"} + + ${match.quality || ""} + `; + row.addEventListener("click", async () => { + row.disabled = true; + try { + const result = await runtimeSend({ type: "keepassgo-fill-entry", entryId: match.id }); + if (!result?.success) { + throw new Error(result?.error || "Fill failed."); + } + setStatus("Filled", `${match.title} was sent to the current page.`, "ready"); + } catch (error) { + setStatus("Fill failed", error instanceof Error ? error.message : String(error), "error"); + } finally { + row.disabled = false; + } + }); + root.appendChild(row); + } +} + +async function main() { + try { + const state = await runtimeSend({ type: "keepassgo-popup-state" }); + document.getElementById("page-host").textContent = hostFromURL(state.pageUrl || ""); + + if (!state.configured) { + setStatus("Configure access", state.error || "Set the API token in extension settings.", "warning"); + renderMatches({ matches: [] }); + return; + } + if (!state.success) { + setStatus("KeePassGO unavailable", state.error || "The native host could not reach KeePassGO.", "error"); + renderMatches({ matches: [] }); + return; + } + if (state.status?.locked) { + setStatus("Vault locked", "Unlock KeePassGO, then open the popup again.", "warning"); + renderMatches({ matches: [] }); + return; + } + + const count = Array.isArray(state.matches) ? state.matches.length : 0; + setStatus("Ready", count === 0 ? "KeePassGO is connected." : `${count} matching entr${count === 1 ? "y" : "ies"} found.`, "ready"); + renderMatches(state); + } catch (error) { + setStatus("Error", error instanceof Error ? error.message : String(error), "error"); + renderMatches({ matches: [] }); + } +} + +void main(); diff --git a/browser/extension/style.css b/browser/extension/style.css new file mode 100644 index 0000000..3fc7536 --- /dev/null +++ b/browser/extension/style.css @@ -0,0 +1,174 @@ +:root { + color-scheme: light; + --ink: #214f44; + --ink-soft: #4d6d66; + --surface: #fffdfa; + --surface-2: #f2f7f3; + --line: #d7e3dc; + --accent: #255f4a; + --accent-soft: #dfeee6; + --warn: #9f5f0e; + --error: #9f2f2f; +} + +* { + box-sizing: border-box; +} + +body { + margin: 0; + font: 14px/1.4 "Noto Sans", "Liberation Sans", sans-serif; + color: var(--ink); + background: + radial-gradient(circle at top right, #ecf5ef, transparent 38%), + linear-gradient(180deg, #f8fbf8, #eef4f0); +} + +body.popup { + min-width: 360px; +} + +.surface { + padding: 16px; +} + +.topbar { + display: flex; + align-items: start; + justify-content: space-between; + gap: 12px; + margin-bottom: 16px; +} + +h1, +h2, +p { + margin: 0; +} + +h1 { + font-size: 22px; + line-height: 1.1; +} + +h2 { + font-size: 14px; + text-transform: uppercase; + letter-spacing: 0.08em; + margin-bottom: 8px; + color: var(--ink-soft); +} + +.subtle { + color: var(--ink-soft); +} + +.status-card { + padding: 12px 14px; + border-radius: 12px; + border: 1px solid var(--line); + background: var(--surface); + margin-bottom: 16px; +} + +.status-card[data-tone="ready"] { + border-color: #c5dccf; + background: var(--accent-soft); +} + +.status-card[data-tone="warning"] { + border-color: #e4d0ae; + background: #fbf4e7; +} + +.status-card[data-tone="error"] { + border-color: #e4bcbc; + background: #fcf1f1; +} + +.match-list { + display: flex; + flex-direction: column; + gap: 8px; +} + +.match-row, +button, +.link-button { + appearance: none; + border: 0; + border-radius: 12px; + background: var(--surface); + color: var(--ink); + text-decoration: none; +} + +.match-row { + display: flex; + width: 100%; + align-items: center; + justify-content: space-between; + gap: 12px; + padding: 12px 14px; + border: 1px solid var(--line); + cursor: pointer; +} + +.match-row:hover, +button:hover, +.link-button:hover { + background: var(--surface-2); +} + +.match-main { + display: flex; + flex-direction: column; + align-items: start; + text-align: left; +} + +.quality { + font-size: 12px; + color: var(--ink-soft); + text-transform: uppercase; +} + +.settings { + max-width: 720px; + margin: 0 auto; + min-height: 100vh; +} + +.settings-form { + display: grid; + gap: 16px; +} + +label { + display: grid; + gap: 8px; +} + +input, +textarea { + width: 100%; + padding: 10px 12px; + border: 1px solid var(--line); + border-radius: 10px; + background: #fff; + color: var(--ink); + font: inherit; +} + +button, +.link-button { + padding: 10px 14px; + background: var(--accent); + color: #fff; + cursor: pointer; +} + +.actions { + display: flex; + justify-content: end; +} diff --git a/cmd/keepassgo-browser-bridge/main.go b/cmd/keepassgo-browser-bridge/main.go new file mode 100644 index 0000000..0fd8e8d --- /dev/null +++ b/cmd/keepassgo-browser-bridge/main.go @@ -0,0 +1,121 @@ +package main + +import ( + "context" + "encoding/json" + "flag" + "fmt" + "os" + "os/exec" + "path/filepath" + "strings" + + "git.julianfamily.org/keepassgo/internal/browserbridge" +) + +func main() { + if len(os.Args) > 1 { + switch strings.TrimSpace(os.Args[1]) { + case "install-native-host": + if err := runInstallNativeHost(os.Args[2:]); err != nil { + fail(err) + } + return + case "status": + if err := runStatus(os.Args[2:]); err != nil { + fail(err) + } + return + } + } + if err := runNativeMessage(); err != nil { + _ = browserbridge.WriteResponse(os.Stdout, browserbridge.Response{Success: false, Error: err.Error()}) + os.Exit(1) + } +} + +func runInstallNativeHost(args []string) error { + fs := flag.NewFlagSet("install-native-host", flag.ContinueOnError) + browserName := fs.String("browser", string(browserbridge.BrowserFirefox), "target browser: firefox, chrome, chromium") + binaryPath := fs.String("binary", "", "path to keepassgo-browser-bridge binary") + extensionID := fs.String("extension-id", "", "browser extension id (required for chrome/chromium)") + outputPath := fs.String("output", "", "native host manifest output path") + if err := fs.Parse(args); err != nil { + return err + } + path := strings.TrimSpace(*binaryPath) + if path == "" { + resolved, err := defaultBinaryPath() + if err != nil { + return err + } + path = resolved + } + installed, err := browserbridge.InstallManifest(browserbridge.Browser(strings.TrimSpace(*browserName)), path, strings.TrimSpace(*extensionID), strings.TrimSpace(*outputPath)) + if err != nil { + return err + } + fmt.Fprintln(os.Stdout, installed) + return nil +} + +func runStatus(args []string) error { + fs := flag.NewFlagSet("status", flag.ContinueOnError) + grpcAddr := fs.String("grpc-addr", browserbridge.DefaultGRPCAddress, "KeePassGO local gRPC address") + token := fs.String("token", "", "KeePassGO API bearer token") + if err := fs.Parse(args); err != nil { + return err + } + req := browserbridge.Request{ + Action: "status", + GRPCAddress: strings.TrimSpace(*grpcAddr), + BearerToken: strings.TrimSpace(*token), + } + connCfg, err := req.Connection() + if err != nil { + return err + } + conn, client, ctx, err := browserbridge.Dial(context.Background(), connCfg) + if err != nil { + return err + } + defer func() { _ = conn.Close() }() + resp := browserbridge.HandleRequest(ctx, req, client) + enc := json.NewEncoder(os.Stdout) + enc.SetIndent("", " ") + return enc.Encode(resp) +} + +func runNativeMessage() error { + req, err := browserbridge.ReadRequest(os.Stdin) + if err != nil { + return err + } + connCfg, err := req.Connection() + if err != nil { + return browserbridge.WriteResponse(os.Stdout, browserbridge.Response{Success: false, Error: err.Error()}) + } + conn, client, ctx, err := browserbridge.Dial(context.Background(), connCfg) + if err != nil { + return browserbridge.WriteResponse(os.Stdout, browserbridge.Response{Success: false, Error: err.Error()}) + } + defer func() { _ = conn.Close() }() + return browserbridge.WriteResponse(os.Stdout, browserbridge.HandleRequest(ctx, req, client)) +} + +func defaultBinaryPath() (string, error) { + self, err := os.Executable() + if err == nil && strings.TrimSpace(self) != "" { + return self, nil + } + self, err = exec.LookPath("keepassgo-browser-bridge") + if err == nil { + return self, nil + } + return filepath.Abs("./keepassgo-browser-bridge") +} + +func fail(err error) { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) +} diff --git a/docs/browser-extension.md b/docs/browser-extension.md new file mode 100644 index 0000000..6fc9ae2 --- /dev/null +++ b/docs/browser-extension.md @@ -0,0 +1,76 @@ +# Browser Extension + +KeePassGO browser integration uses: + +- the existing local gRPC API in KeePassGO +- API tokens for authorization +- a tiny native messaging host for browser-to-gRPC transport adaptation + +The browser extension does **not** talk to vault files directly. + +## Security Model + +- KeePassGO remains the source of truth for authentication, authorization, approvals, and audit events. +- The browser extension stores the gRPC address and API token in browser extension storage. +- The native messaging host receives the token on each request from the extension. +- The native messaging host uses the token only to attach `authorization: Bearer ...` metadata to the local gRPC request. +- The native messaging host does not persist the token to disk. + +The native messaging host is therefore part of the trusted client for that browser profile. Scope the API token accordingly. + +## RPCs Used + +The browser integration uses: + +- `GetSessionStatus` +- `FindBrowserLogins` +- `GetBrowserCredential` + +The browser feature intentionally stays on the same secure gRPC surface used by other trusted automation. + +## Native Host + +Build the bridge: + +```bash +go build ./cmd/keepassgo-browser-bridge +``` + +Install a Firefox native messaging manifest: + +```bash +./keepassgo-browser-bridge install-native-host --browser firefox --binary /absolute/path/to/keepassgo-browser-bridge +``` + +Install a Chromium native messaging manifest: + +```bash +./keepassgo-browser-bridge install-native-host --browser chromium --binary /absolute/path/to/keepassgo-browser-bridge --extension-id +``` + +Chrome and Chromium require the actual extension id in the native host manifest. + +## Extension Setup + +Firefox: + +1. Load `browser/extension/manifest.firefox.json` as a temporary add-on or package it as an extension. +2. Open the extension settings page. +3. Set the KeePassGO gRPC address, usually `127.0.0.1:47777`. +4. Paste an API token scoped for browser login lookup and credential copy. + +Chromium / Chrome: + +1. Load `browser/extension/` with `manifest.chromium.json`. +2. Note the extension id the browser assigns. +3. Install the native host manifest with that extension id. +4. Configure the gRPC address and API token in the extension settings page. + +## Required Token Scope + +At minimum, the browser token should have policy rules allowing: + +- `list_entries` for the groups you want the browser to search +- `copy_username` for entries the browser may fill +- `copy_password` for entries the browser may fill +- `copy_url` for entries the browser may confirm against page URL diff --git a/internal/api/server.go b/internal/api/server.go index 8fdddfa..b52ea78 100644 --- a/internal/api/server.go +++ b/internal/api/server.go @@ -3,7 +3,9 @@ package api import ( "context" "errors" + "fmt" "maps" + "net/url" "os" "slices" "strings" @@ -225,6 +227,133 @@ func (s *Server) UnlockVault(ctx context.Context, req *keepassgov1.UnlockVaultRe return &keepassgov1.UnlockVaultResponse{}, nil } +func (s *Server) FindBrowserLogins(ctx context.Context, req *keepassgov1.FindBrowserLoginsRequest) (*keepassgov1.FindBrowserLoginsResponse, error) { + model, locked := s.snapshotModel() + if locked { + return nil, status.Error(codes.FailedPrecondition, "vault is locked") + } + token, err := s.authorizeVaultRequest(ctx, apitokens.OperationListEntries) + if err != nil { + return nil, err + } + pageHost, err := normalizedBrowserHost(req.GetPageUrl()) + if err != nil { + return nil, status.Error(codes.InvalidArgument, err.Error()) + } + + type rankedMatch struct { + match *keepassgov1.BrowserLoginMatch + score int + } + var matches []rankedMatch + for _, entry := range visibleModel(model).Entries { + quality, score := classifyBrowserEntryMatch(pageHost, entry.URL) + if score == 0 { + continue + } + matches = append(matches, rankedMatch{ + match: &keepassgov1.BrowserLoginMatch{ + Id: entry.ID, + Title: entry.Title, + Username: entry.Username, + Url: entry.URL, + Path: append([]string(nil), entry.Path...), + Quality: quality, + }, + score: score, + }) + } + slices.SortFunc(matches, func(a, b rankedMatch) int { + switch { + case a.score != b.score: + return b.score - a.score + case a.match.GetTitle() != b.match.GetTitle(): + return strings.Compare(a.match.GetTitle(), b.match.GetTitle()) + case a.match.GetUsername() != b.match.GetUsername(): + return strings.Compare(a.match.GetUsername(), b.match.GetUsername()) + default: + return strings.Compare(a.match.GetId(), b.match.GetId()) + } + }) + out := make([]*keepassgov1.BrowserLoginMatch, 0, len(matches)) + for _, match := range matches { + out = append(out, match.match) + } + switch len(out) { + case 1: + s.audit.Record(apiaudit.Event{ + Type: apiaudit.EventAutofillFound, + TokenID: token.ID, + TokenName: token.Name, + ClientName: token.ClientName, + Operation: apitokens.OperationListEntries, + Message: "browser login match found for " + pageHost, + }) + case 2, 3, 4, 5: + s.audit.Record(apiaudit.Event{ + Type: apiaudit.EventAutofillAmbiguous, + TokenID: token.ID, + TokenName: token.Name, + ClientName: token.ClientName, + Operation: apitokens.OperationListEntries, + Message: "browser login search returned multiple matches for " + pageHost, + }) + } + return &keepassgov1.FindBrowserLoginsResponse{Matches: out}, nil +} + +func (s *Server) GetBrowserCredential(ctx context.Context, req *keepassgov1.GetBrowserCredentialRequest) (*keepassgov1.GetBrowserCredentialResponse, error) { + model, locked := s.snapshotModel() + if locked { + return nil, status.Error(codes.FailedPrecondition, "vault is locked") + } + token, err := s.authenticateRequest(ctx) + if err != nil { + return nil, err + } + entry, err := findEntryByID(model, req.GetId()) + if err != nil { + return nil, status.Error(codes.NotFound, err.Error()) + } + if pageURL := strings.TrimSpace(req.GetPageUrl()); pageURL != "" { + pageHost, err := normalizedBrowserHost(pageURL) + if err != nil { + return nil, status.Error(codes.InvalidArgument, err.Error()) + } + if _, score := classifyBrowserEntryMatch(pageHost, entry.URL); score == 0 { + return nil, status.Error(codes.InvalidArgument, "entry url does not match requested page") + } + } + if strings.TrimSpace(entry.Username) != "" { + if _, err := s.authorizeResourceRequest(ctx, token, apitokens.OperationCopyUsername, apitokens.Resource{Kind: apitokens.ResourceEntry, EntryID: entry.ID, Path: entry.Path}); err != nil { + return nil, err + } + } + if _, err := s.authorizeResourceRequest(ctx, token, apitokens.OperationCopyPassword, apitokens.Resource{Kind: apitokens.ResourceEntry, EntryID: entry.ID, Path: entry.Path}); err != nil { + return nil, err + } + if strings.TrimSpace(entry.URL) != "" { + if _, err := s.authorizeResourceRequest(ctx, token, apitokens.OperationCopyURL, apitokens.Resource{Kind: apitokens.ResourceEntry, EntryID: entry.ID, Path: entry.Path}); err != nil { + return nil, err + } + } + s.audit.Record(apiaudit.Event{ + Type: apiaudit.EventAutofillFound, + TokenID: token.ID, + TokenName: token.Name, + ClientName: token.ClientName, + Operation: apitokens.OperationCopyPassword, + Resource: apitokens.Resource{Kind: apitokens.ResourceEntry, EntryID: entry.ID, Path: entry.Path}, + Message: "browser credential returned for " + entry.ID, + }) + return &keepassgov1.GetBrowserCredentialResponse{ + Id: entry.ID, + Username: entry.Username, + Password: entry.Password, + Url: entry.URL, + }, nil +} + func mapLifecycleError(operation string, err error) error { switch { case errors.Is(err, os.ErrNotExist): @@ -787,6 +916,39 @@ func findMutableEntryByID(model *vault.Model, id string) (vault.Entry, int, erro return vault.Entry{}, -1, vault.ErrEntryNotFound } +func normalizedBrowserHost(raw string) (string, error) { + parsed, err := url.Parse(strings.TrimSpace(raw)) + if err != nil { + return "", fmt.Errorf("parse page url: %w", err) + } + host := strings.ToLower(parsed.Hostname()) + if host == "" { + return "", fmt.Errorf("page url must include a hostname") + } + return host, nil +} + +func classifyBrowserEntryMatch(pageHost, rawEntryURL string) (string, int) { + parsed, err := url.Parse(strings.TrimSpace(rawEntryURL)) + if err != nil { + return "", 0 + } + entryHost := strings.ToLower(parsed.Hostname()) + if entryHost == "" { + return "", 0 + } + switch { + case pageHost == entryHost: + return "exact-host", 3 + case strings.HasSuffix(pageHost, "."+entryHost): + return "subdomain", 2 + case strings.HasSuffix(entryHost, "."+pageHost): + return "parent-domain", 1 + default: + return "", 0 + } +} + func visibleModel(model vault.Model) vault.Model { out := model out.Entries = nil diff --git a/internal/api/server_test.go b/internal/api/server_test.go index 80c6112..d02be90 100644 --- a/internal/api/server_test.go +++ b/internal/api/server_test.go @@ -159,6 +159,84 @@ func TestVaultServiceRejectsUnauthorizedPasswordGeneration(t *testing.T) { } } +func TestVaultServiceFindsBrowserLoginsForAuthorizedClients(t *testing.T) { + t.Parallel() + + client, _, cleanup := newTestClient(t) + defer cleanup() + + ctx := tokenContext(defaultTestTokenSecret) + resp, err := client.FindBrowserLogins(ctx, &keepassgov1.FindBrowserLoginsRequest{ + PageUrl: "https://vault.crew.example.invalid/login", + }) + if err != nil { + t.Fatalf("FindBrowserLogins() error = %v", err) + } + if len(resp.Matches) != 1 { + t.Fatalf("len(FindBrowserLogins().Matches) = %d, want 1", len(resp.Matches)) + } + if resp.Matches[0].Id != "vault-console" { + t.Fatalf("FindBrowserLogins().Matches[0].Id = %q, want vault-console", resp.Matches[0].Id) + } + if resp.Matches[0].Quality != "exact-host" { + t.Fatalf("FindBrowserLogins().Matches[0].Quality = %q, want exact-host", resp.Matches[0].Quality) + } +} + +func TestVaultServiceGetsBrowserCredentialForAuthorizedClients(t *testing.T) { + t.Parallel() + + client, _, cleanup := newTestClient(t) + defer cleanup() + + ctx := tokenContext(defaultTestTokenSecret) + resp, err := client.GetBrowserCredential(ctx, &keepassgov1.GetBrowserCredentialRequest{ + Id: "vault-console", + PageUrl: "https://vault.crew.example.invalid/login", + }) + if err != nil { + t.Fatalf("GetBrowserCredential() error = %v", err) + } + if resp.Id != "vault-console" { + t.Fatalf("GetBrowserCredential().Id = %q, want vault-console", resp.Id) + } + if resp.Password != "token-1" { + t.Fatalf("GetBrowserCredential().Password = %q, want token-1", resp.Password) + } +} + +func TestVaultServiceRejectsUnauthorizedBrowserCredentialAccess(t *testing.T) { + t.Parallel() + + client, _, cleanup := newTestClientForModel(t, vault.Model{ + Entries: []vault.Entry{ + { + ID: "vault-console", + Title: "Vault Console", + Username: "dannyocean", + Password: "token-1", + URL: "https://vault.crew.example.invalid", + Path: []string{"Root", "Internet"}, + }, + testAPITokenEntry(t, + apitokens.PolicyRule{Effect: apitokens.EffectAllow, Operation: apitokens.OperationListEntries, Resource: apitokens.Resource{Kind: apitokens.ResourceGroup, Path: []string{"Root"}}}, + apitokens.PolicyRule{Effect: apitokens.EffectAllow, Operation: apitokens.OperationCopyUsername, Resource: apitokens.Resource{Kind: apitokens.ResourceEntry, EntryID: "vault-console", Path: []string{"Root", "Internet"}}}, + apitokens.PolicyRule{Effect: apitokens.EffectAllow, Operation: apitokens.OperationCopyURL, Resource: apitokens.Resource{Kind: apitokens.ResourceEntry, EntryID: "vault-console", Path: []string{"Root", "Internet"}}}, + apitokens.PolicyRule{Effect: apitokens.EffectDeny, Operation: apitokens.OperationCopyPassword, Resource: apitokens.Resource{Kind: apitokens.ResourceEntry, EntryID: "vault-console", Path: []string{"Root", "Internet"}}}, + ), + }, + }) + defer cleanup() + + _, err := client.GetBrowserCredential(tokenContext(defaultTestTokenSecret), &keepassgov1.GetBrowserCredentialRequest{ + Id: "vault-console", + PageUrl: "https://vault.crew.example.invalid/login", + }) + if status.Code(err) != codes.PermissionDenied { + t.Fatalf("GetBrowserCredential() code = %v, want %v", status.Code(err), codes.PermissionDenied) + } +} + func TestVaultServicePromptsAndResumesWhenApproved(t *testing.T) { t.Parallel() diff --git a/internal/browserbridge/bridge.go b/internal/browserbridge/bridge.go new file mode 100644 index 0000000..1e9f05f --- /dev/null +++ b/internal/browserbridge/bridge.go @@ -0,0 +1,321 @@ +package browserbridge + +import ( + "context" + "encoding/binary" + "encoding/json" + "fmt" + "io" + "os" + "path/filepath" + "strings" + + keepassgov1 "git.julianfamily.org/keepassgo/proto/keepassgo/v1" +) + +const ( + NativeHostName = "org.keepassgo.browser" + DefaultGRPCAddress = "127.0.0.1:47777" + defaultFirefoxID = "browser@keepassgo.invalid" + maxNativeMessageSize = 1024 * 1024 +) + +type Request struct { + Action string `json:"action"` + GRPCAddress string `json:"grpcAddress,omitempty"` + BearerToken string `json:"bearerToken,omitempty"` + URL string `json:"url,omitempty"` + EntryID string `json:"entryId,omitempty"` +} + +type Response struct { + Success bool `json:"success"` + Error string `json:"error,omitempty"` + Status *Status `json:"status,omitempty"` + Matches []Match `json:"matches,omitempty"` + Credential *Credential `json:"credential,omitempty"` + Version string `json:"version,omitempty"` +} + +type Status struct { + Connected bool `json:"connected"` + Locked bool `json:"locked"` + Dirty bool `json:"dirty,omitempty"` + EntryCount uint32 `json:"entryCount,omitempty"` + GRPCAddress string `json:"grpcAddress,omitempty"` +} + +type Match struct { + ID string `json:"id"` + Title string `json:"title"` + Username string `json:"username,omitempty"` + URL string `json:"url,omitempty"` + Path []string `json:"path,omitempty"` + Quality string `json:"quality,omitempty"` +} + +type Credential struct { + ID string `json:"id"` + Username string `json:"username,omitempty"` + Password string `json:"password,omitempty"` + URL string `json:"url,omitempty"` +} + +type Connection struct { + GRPCAddress string + BearerToken string +} + +type Client interface { + Status(context.Context) (*keepassgov1.GetSessionStatusResponse, error) + FindBrowserLogins(context.Context, string) ([]*keepassgov1.BrowserLoginMatch, error) + GetBrowserCredential(context.Context, string, string) (*keepassgov1.GetBrowserCredentialResponse, error) +} + +type Browser string + +const ( + BrowserFirefox Browser = "firefox" + BrowserChrome Browser = "chrome" + BrowserChromium Browser = "chromium" +) + +type NativeHostManifest struct { + Name string `json:"name"` + Description string `json:"description"` + Path string `json:"path"` + Type string `json:"type"` + AllowedExtensions []string `json:"allowed_extensions,omitempty"` + AllowedOrigins []string `json:"allowed_origins,omitempty"` +} + +func DefaultFirefoxExtensionID() string { + return defaultFirefoxID +} + +func ReadRequest(r io.Reader) (Request, error) { + var sizeBuf [4]byte + if _, err := io.ReadFull(r, sizeBuf[:]); err != nil { + return Request{}, err + } + size := binary.LittleEndian.Uint32(sizeBuf[:]) + if size == 0 || size > maxNativeMessageSize { + return Request{}, fmt.Errorf("invalid native message size %d", size) + } + body := make([]byte, size) + if _, err := io.ReadFull(r, body); err != nil { + return Request{}, err + } + var req Request + if err := json.Unmarshal(body, &req); err != nil { + return Request{}, fmt.Errorf("decode native request: %w", err) + } + return req, nil +} + +func WriteResponse(w io.Writer, resp Response) error { + data, err := json.Marshal(resp) + if err != nil { + return fmt.Errorf("encode native response: %w", err) + } + if len(data) > maxNativeMessageSize { + return fmt.Errorf("native response too large: %d", len(data)) + } + var sizeBuf [4]byte + binary.LittleEndian.PutUint32(sizeBuf[:], uint32(len(data))) + if _, err := w.Write(sizeBuf[:]); err != nil { + return err + } + _, err = w.Write(data) + return err +} + +func (r Request) Connection() (Connection, error) { + conn := Connection{ + GRPCAddress: strings.TrimSpace(r.GRPCAddress), + BearerToken: strings.TrimSpace(r.BearerToken), + } + if conn.GRPCAddress == "" { + conn.GRPCAddress = DefaultGRPCAddress + } + if conn.BearerToken == "" { + return Connection{}, fmt.Errorf("browser bridge bearer token is required") + } + return conn, nil +} + +func HandleRequest(ctx context.Context, req Request, client Client) Response { + conn, err := req.Connection() + if err != nil { + return Response{Success: false, Error: err.Error()} + } + action := strings.TrimSpace(req.Action) + switch action { + case "status": + status, err := statusResponse(ctx, client, conn.GRPCAddress) + if err != nil { + return Response{Success: false, Error: err.Error(), Status: disconnectedStatus(conn.GRPCAddress)} + } + return Response{Success: true, Status: status, Version: "1"} + case "find-logins": + status, err := statusResponse(ctx, client, conn.GRPCAddress) + if err != nil { + return Response{Success: false, Error: err.Error(), Status: disconnectedStatus(conn.GRPCAddress)} + } + if status.Locked { + return Response{Success: true, Status: status, Matches: nil, Version: "1"} + } + matches, err := findMatches(ctx, client, req.URL) + if err != nil { + return Response{Success: false, Error: err.Error(), Status: status} + } + return Response{Success: true, Status: status, Matches: matches, Version: "1"} + case "get-login": + status, err := statusResponse(ctx, client, conn.GRPCAddress) + if err != nil { + return Response{Success: false, Error: err.Error(), Status: disconnectedStatus(conn.GRPCAddress)} + } + if status.Locked { + return Response{Success: false, Error: "vault is locked", Status: status} + } + credential, err := loadCredential(ctx, client, req.EntryID, req.URL) + if err != nil { + return Response{Success: false, Error: err.Error(), Status: status} + } + return Response{Success: true, Status: status, Credential: credential, Version: "1"} + default: + return Response{Success: false, Error: fmt.Sprintf("unsupported action %q", action)} + } +} + +func disconnectedStatus(addr string) *Status { + return &Status{Connected: false, GRPCAddress: strings.TrimSpace(addr)} +} + +func statusResponse(ctx context.Context, client Client, addr string) (*Status, error) { + resp, err := client.Status(ctx) + if err != nil { + return nil, err + } + return &Status{ + Connected: true, + Locked: resp.GetLocked(), + Dirty: resp.GetDirty(), + EntryCount: resp.GetEntryCount(), + GRPCAddress: strings.TrimSpace(addr), + }, nil +} + +func findMatches(ctx context.Context, client Client, rawURL string) ([]Match, error) { + resp, err := client.FindBrowserLogins(ctx, strings.TrimSpace(rawURL)) + if err != nil { + return nil, err + } + out := make([]Match, 0, len(resp)) + for _, match := range resp { + out = append(out, Match{ + ID: match.GetId(), + Title: match.GetTitle(), + Username: match.GetUsername(), + URL: match.GetUrl(), + Path: append([]string(nil), match.GetPath()...), + Quality: match.GetQuality(), + }) + } + return out, nil +} + +func loadCredential(ctx context.Context, client Client, entryID, rawURL string) (*Credential, error) { + id := strings.TrimSpace(entryID) + if id == "" { + return nil, fmt.Errorf("entry id is required") + } + resp, err := client.GetBrowserCredential(ctx, id, strings.TrimSpace(rawURL)) + if err != nil { + return nil, err + } + return &Credential{ + ID: resp.GetId(), + Username: resp.GetUsername(), + Password: resp.GetPassword(), + URL: resp.GetUrl(), + }, nil +} + +func Manifest(browser Browser, binaryPath, extensionID string) (NativeHostManifest, error) { + path := strings.TrimSpace(binaryPath) + if path == "" { + return NativeHostManifest{}, fmt.Errorf("native host binary path is required") + } + switch browser { + case BrowserFirefox: + id := strings.TrimSpace(extensionID) + if id == "" { + id = defaultFirefoxID + } + return NativeHostManifest{ + Name: NativeHostName, + Description: "KeePassGO browser bridge", + Path: path, + Type: "stdio", + AllowedExtensions: []string{id}, + }, nil + case BrowserChrome, BrowserChromium: + id := strings.TrimSpace(extensionID) + if id == "" { + return NativeHostManifest{}, fmt.Errorf("%s extension id is required", browser) + } + return NativeHostManifest{ + Name: NativeHostName, + Description: "KeePassGO browser bridge", + Path: path, + Type: "stdio", + AllowedOrigins: []string{"chrome-extension://" + id + "/"}, + }, nil + default: + return NativeHostManifest{}, fmt.Errorf("unsupported browser %q", browser) + } +} + +func DefaultManifestPath(browser Browser) (string, error) { + home, err := os.UserHomeDir() + if err != nil { + return "", err + } + switch browser { + case BrowserFirefox: + return filepath.Join(home, ".mozilla", "native-messaging-hosts", NativeHostName+".json"), nil + case BrowserChrome: + return filepath.Join(home, ".config", "google-chrome", "NativeMessagingHosts", NativeHostName+".json"), nil + case BrowserChromium: + return filepath.Join(home, ".config", "chromium", "NativeMessagingHosts", NativeHostName+".json"), nil + default: + return "", fmt.Errorf("unsupported browser %q", browser) + } +} + +func InstallManifest(browser Browser, binaryPath, extensionID, outputPath string) (string, error) { + manifest, err := Manifest(browser, binaryPath, extensionID) + if err != nil { + return "", err + } + path := strings.TrimSpace(outputPath) + if path == "" { + path, err = DefaultManifestPath(browser) + if err != nil { + return "", err + } + } + if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil { + return "", fmt.Errorf("create native host manifest dir: %w", err) + } + data, err := json.MarshalIndent(manifest, "", " ") + if err != nil { + return "", fmt.Errorf("encode native host manifest: %w", err) + } + data = append(data, '\n') + if err := os.WriteFile(path, data, 0o644); err != nil { + return "", fmt.Errorf("write native host manifest: %w", err) + } + return path, nil +} diff --git a/internal/browserbridge/bridge_test.go b/internal/browserbridge/bridge_test.go new file mode 100644 index 0000000..f9b360c --- /dev/null +++ b/internal/browserbridge/bridge_test.go @@ -0,0 +1,182 @@ +package browserbridge + +import ( + "bytes" + "context" + "encoding/binary" + "encoding/json" + "os" + "path/filepath" + "testing" + + keepassgov1 "git.julianfamily.org/keepassgo/proto/keepassgo/v1" +) + +func TestReadRequestAndWriteResponse(t *testing.T) { + t.Parallel() + + var input bytes.Buffer + body, err := json.Marshal(Request{ + Action: "find-logins", + GRPCAddress: "127.0.0.1:47777", + BearerToken: "secret", + URL: "https://example.invalid/login", + }) + if err != nil { + t.Fatalf("Marshal() error = %v", err) + } + if err := binary.Write(&input, binary.LittleEndian, uint32(len(body))); err != nil { + t.Fatalf("binary.Write() error = %v", err) + } + if _, err := input.Write(body); err != nil { + t.Fatalf("Write() error = %v", err) + } + + req, err := ReadRequest(&input) + if err != nil { + t.Fatalf("ReadRequest() error = %v", err) + } + if req.Action != "find-logins" || req.BearerToken != "secret" { + t.Fatalf("ReadRequest() = %#v, want action and token preserved", req) + } + + var output bytes.Buffer + if err := WriteResponse(&output, Response{Success: true, Version: "1"}); err != nil { + t.Fatalf("WriteResponse() error = %v", err) + } + var size uint32 + if err := binary.Read(&output, binary.LittleEndian, &size); err != nil { + t.Fatalf("binary.Read() error = %v", err) + } + payload := make([]byte, size) + if _, err := output.Read(payload); err != nil { + t.Fatalf("Read() payload error = %v", err) + } + var resp Response + if err := json.Unmarshal(payload, &resp); err != nil { + t.Fatalf("Unmarshal() error = %v", err) + } + if !resp.Success || resp.Version != "1" { + t.Fatalf("response = %#v, want success version 1", resp) + } +} + +func TestHandleRequestFindLogins(t *testing.T) { + t.Parallel() + + client := fakeClient{ + status: &keepassgov1.GetSessionStatusResponse{Locked: false, EntryCount: 2}, + matches: []*keepassgov1.BrowserLoginMatch{ + {Id: "vault-console", Title: "Vault Console", Username: "dannyocean", Url: "https://vault.example.invalid", Quality: "exact-host"}, + }, + } + resp := HandleRequest(context.Background(), Request{ + Action: "find-logins", + BearerToken: "secret", + URL: "https://vault.example.invalid/login", + }, client) + if !resp.Success { + t.Fatalf("HandleRequest() success = false, error = %q", resp.Error) + } + if len(resp.Matches) != 1 || resp.Matches[0].ID != "vault-console" { + t.Fatalf("HandleRequest().Matches = %#v, want vault-console", resp.Matches) + } +} + +func TestHandleRequestGetLogin(t *testing.T) { + t.Parallel() + + client := fakeClient{ + status: &keepassgov1.GetSessionStatusResponse{Locked: false, EntryCount: 1}, + credential: &keepassgov1.GetBrowserCredentialResponse{ + Id: "vault-console", + Username: "dannyocean", + Password: "token-1", + Url: "https://vault.example.invalid", + }, + } + resp := HandleRequest(context.Background(), Request{ + Action: "get-login", + BearerToken: "secret", + EntryID: "vault-console", + URL: "https://vault.example.invalid/login", + }, client) + if !resp.Success { + t.Fatalf("HandleRequest() success = false, error = %q", resp.Error) + } + if resp.Credential == nil || resp.Credential.ID != "vault-console" { + t.Fatalf("HandleRequest().Credential = %#v, want vault-console", resp.Credential) + } +} + +func TestHandleRequestRequiresBearerToken(t *testing.T) { + t.Parallel() + + resp := HandleRequest(context.Background(), Request{Action: "status"}, fakeClient{}) + if resp.Success { + t.Fatal("HandleRequest().Success = true, want false without token") + } +} + +func TestInstallManifest(t *testing.T) { + t.Parallel() + + tmp := t.TempDir() + binaryPath := filepath.Join(tmp, "keepassgo-browser-bridge") + if err := os.WriteFile(binaryPath, []byte("#!/bin/sh\n"), 0o755); err != nil { + t.Fatalf("WriteFile(binary) error = %v", err) + } + + path, err := InstallManifest(BrowserFirefox, binaryPath, "", filepath.Join(tmp, "firefox-host.json")) + if err != nil { + t.Fatalf("InstallManifest() error = %v", err) + } + data, err := os.ReadFile(path) + if err != nil { + t.Fatalf("ReadFile() error = %v", err) + } + var manifest NativeHostManifest + if err := json.Unmarshal(data, &manifest); err != nil { + t.Fatalf("Unmarshal() error = %v", err) + } + if manifest.Path != binaryPath { + t.Fatalf("manifest.Path = %q, want %q", manifest.Path, binaryPath) + } + if len(manifest.AllowedExtensions) != 1 || manifest.AllowedExtensions[0] != DefaultFirefoxExtensionID() { + t.Fatalf("manifest.AllowedExtensions = %#v, want default firefox extension id", manifest.AllowedExtensions) + } +} + +type fakeClient struct { + status *keepassgov1.GetSessionStatusResponse + matches []*keepassgov1.BrowserLoginMatch + credential *keepassgov1.GetBrowserCredentialResponse + err error +} + +func (f fakeClient) Status(context.Context) (*keepassgov1.GetSessionStatusResponse, error) { + if f.err != nil { + return nil, f.err + } + if f.status == nil { + return &keepassgov1.GetSessionStatusResponse{}, nil + } + return f.status, nil +} + +func (f fakeClient) FindBrowserLogins(context.Context, string) ([]*keepassgov1.BrowserLoginMatch, error) { + if f.err != nil { + return nil, f.err + } + return f.matches, nil +} + +func (f fakeClient) GetBrowserCredential(context.Context, string, string) (*keepassgov1.GetBrowserCredentialResponse, error) { + if f.err != nil { + return nil, f.err + } + if f.credential == nil { + return &keepassgov1.GetBrowserCredentialResponse{}, nil + } + return f.credential, nil +} diff --git a/internal/browserbridge/client.go b/internal/browserbridge/client.go new file mode 100644 index 0000000..6a5f37f --- /dev/null +++ b/internal/browserbridge/client.go @@ -0,0 +1,59 @@ +package browserbridge + +import ( + "context" + "fmt" + "net" + "strings" + + keepassgov1 "git.julianfamily.org/keepassgo/proto/keepassgo/v1" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials/insecure" + "google.golang.org/grpc/metadata" +) + +type GRPCClient struct { + client keepassgov1.VaultServiceClient +} + +func Dial(ctx context.Context, conn Connection) (*grpc.ClientConn, *GRPCClient, context.Context, error) { + if strings.TrimSpace(conn.GRPCAddress) == "" { + conn.GRPCAddress = DefaultGRPCAddress + } + if strings.TrimSpace(conn.BearerToken) == "" { + return nil, nil, nil, fmt.Errorf("browser bridge bearer token is required") + } + address := strings.TrimSpace(conn.GRPCAddress) + grpcConn, err := grpc.NewClient("passthrough:///"+address, + grpc.WithTransportCredentials(insecure.NewCredentials()), + grpc.WithContextDialer(func(context.Context, string) (net.Conn, error) { + return net.Dial("tcp", address) + }), + ) + if err != nil { + return nil, nil, nil, fmt.Errorf("dial gRPC host %s: %w", address, err) + } + ctx = metadata.AppendToOutgoingContext(ctx, "authorization", "Bearer "+strings.TrimSpace(conn.BearerToken)) + return grpcConn, &GRPCClient{client: keepassgov1.NewVaultServiceClient(grpcConn)}, ctx, nil +} + +func (c *GRPCClient) Status(ctx context.Context) (*keepassgov1.GetSessionStatusResponse, error) { + return c.client.GetSessionStatus(ctx, &keepassgov1.GetSessionStatusRequest{}) +} + +func (c *GRPCClient) FindBrowserLogins(ctx context.Context, pageURL string) ([]*keepassgov1.BrowserLoginMatch, error) { + resp, err := c.client.FindBrowserLogins(ctx, &keepassgov1.FindBrowserLoginsRequest{ + PageUrl: strings.TrimSpace(pageURL), + }) + if err != nil { + return nil, err + } + return resp.GetMatches(), nil +} + +func (c *GRPCClient) GetBrowserCredential(ctx context.Context, entryID, pageURL string) (*keepassgov1.GetBrowserCredentialResponse, error) { + return c.client.GetBrowserCredential(ctx, &keepassgov1.GetBrowserCredentialRequest{ + Id: strings.TrimSpace(entryID), + PageUrl: strings.TrimSpace(pageURL), + }) +} diff --git a/packaging/archlinux/keepassgo-git/PKGBUILD.tmpl b/packaging/archlinux/keepassgo-git/PKGBUILD.tmpl index fc6e40b..9bd7a05 100644 --- a/packaging/archlinux/keepassgo-git/PKGBUILD.tmpl +++ b/packaging/archlinux/keepassgo-git/PKGBUILD.tmpl @@ -42,12 +42,14 @@ build() { local app_version app_version="$(git describe --tags --always --dirty)" go build -ldflags "-X git.julianfamily.org/keepassgo/internal/appui.appVersion=${app_version}" -o keepassgo ./cmd/keepassgo + go build -ldflags "-X git.julianfamily.org/keepassgo/internal/appui.appVersion=${app_version}" -o keepassgo-browser-bridge ./cmd/keepassgo-browser-bridge } package() { cd "$(_repo_dir)" install -Dm755 keepassgo "${pkgdir}/usr/bin/keepassgo" + install -Dm755 keepassgo-browser-bridge "${pkgdir}/usr/bin/keepassgo-browser-bridge" install -Dm644 internal/assets/keepassgo-icon.png \ "${pkgdir}/usr/share/icons/hicolor/512x512/apps/keepassgo.png" install -Dm644 internal/assets/keepassgo-icon.svg \ diff --git a/proto/keepassgo/v1/keepassgo.pb.go b/proto/keepassgo/v1/keepassgo.pb.go index 92e07f2..c798612 100644 --- a/proto/keepassgo/v1/keepassgo.pb.go +++ b/proto/keepassgo/v1/keepassgo.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.36.11 -// protoc v6.33.1 +// protoc v7.34.1 // source: proto/keepassgo/v1/keepassgo.proto package keepassgov1 @@ -565,6 +565,298 @@ func (*UnlockVaultResponse) Descriptor() ([]byte, []int) { return file_proto_keepassgo_v1_keepassgo_proto_rawDescGZIP(), []int{11} } +type FindBrowserLoginsRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + PageUrl string `protobuf:"bytes,1,opt,name=page_url,json=pageUrl,proto3" json:"page_url,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *FindBrowserLoginsRequest) Reset() { + *x = FindBrowserLoginsRequest{} + mi := &file_proto_keepassgo_v1_keepassgo_proto_msgTypes[12] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *FindBrowserLoginsRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*FindBrowserLoginsRequest) ProtoMessage() {} + +func (x *FindBrowserLoginsRequest) ProtoReflect() protoreflect.Message { + mi := &file_proto_keepassgo_v1_keepassgo_proto_msgTypes[12] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use FindBrowserLoginsRequest.ProtoReflect.Descriptor instead. +func (*FindBrowserLoginsRequest) Descriptor() ([]byte, []int) { + return file_proto_keepassgo_v1_keepassgo_proto_rawDescGZIP(), []int{12} +} + +func (x *FindBrowserLoginsRequest) GetPageUrl() string { + if x != nil { + return x.PageUrl + } + return "" +} + +type BrowserLoginMatch struct { + state protoimpl.MessageState `protogen:"open.v1"` + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + Title string `protobuf:"bytes,2,opt,name=title,proto3" json:"title,omitempty"` + Username string `protobuf:"bytes,3,opt,name=username,proto3" json:"username,omitempty"` + Url string `protobuf:"bytes,4,opt,name=url,proto3" json:"url,omitempty"` + Path []string `protobuf:"bytes,5,rep,name=path,proto3" json:"path,omitempty"` + Quality string `protobuf:"bytes,6,opt,name=quality,proto3" json:"quality,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *BrowserLoginMatch) Reset() { + *x = BrowserLoginMatch{} + mi := &file_proto_keepassgo_v1_keepassgo_proto_msgTypes[13] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *BrowserLoginMatch) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*BrowserLoginMatch) ProtoMessage() {} + +func (x *BrowserLoginMatch) ProtoReflect() protoreflect.Message { + mi := &file_proto_keepassgo_v1_keepassgo_proto_msgTypes[13] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use BrowserLoginMatch.ProtoReflect.Descriptor instead. +func (*BrowserLoginMatch) Descriptor() ([]byte, []int) { + return file_proto_keepassgo_v1_keepassgo_proto_rawDescGZIP(), []int{13} +} + +func (x *BrowserLoginMatch) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +func (x *BrowserLoginMatch) GetTitle() string { + if x != nil { + return x.Title + } + return "" +} + +func (x *BrowserLoginMatch) GetUsername() string { + if x != nil { + return x.Username + } + return "" +} + +func (x *BrowserLoginMatch) GetUrl() string { + if x != nil { + return x.Url + } + return "" +} + +func (x *BrowserLoginMatch) GetPath() []string { + if x != nil { + return x.Path + } + return nil +} + +func (x *BrowserLoginMatch) GetQuality() string { + if x != nil { + return x.Quality + } + return "" +} + +type FindBrowserLoginsResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Matches []*BrowserLoginMatch `protobuf:"bytes,1,rep,name=matches,proto3" json:"matches,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *FindBrowserLoginsResponse) Reset() { + *x = FindBrowserLoginsResponse{} + mi := &file_proto_keepassgo_v1_keepassgo_proto_msgTypes[14] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *FindBrowserLoginsResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*FindBrowserLoginsResponse) ProtoMessage() {} + +func (x *FindBrowserLoginsResponse) ProtoReflect() protoreflect.Message { + mi := &file_proto_keepassgo_v1_keepassgo_proto_msgTypes[14] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use FindBrowserLoginsResponse.ProtoReflect.Descriptor instead. +func (*FindBrowserLoginsResponse) Descriptor() ([]byte, []int) { + return file_proto_keepassgo_v1_keepassgo_proto_rawDescGZIP(), []int{14} +} + +func (x *FindBrowserLoginsResponse) GetMatches() []*BrowserLoginMatch { + if x != nil { + return x.Matches + } + return nil +} + +type GetBrowserCredentialRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + PageUrl string `protobuf:"bytes,2,opt,name=page_url,json=pageUrl,proto3" json:"page_url,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetBrowserCredentialRequest) Reset() { + *x = GetBrowserCredentialRequest{} + mi := &file_proto_keepassgo_v1_keepassgo_proto_msgTypes[15] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetBrowserCredentialRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetBrowserCredentialRequest) ProtoMessage() {} + +func (x *GetBrowserCredentialRequest) ProtoReflect() protoreflect.Message { + mi := &file_proto_keepassgo_v1_keepassgo_proto_msgTypes[15] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetBrowserCredentialRequest.ProtoReflect.Descriptor instead. +func (*GetBrowserCredentialRequest) Descriptor() ([]byte, []int) { + return file_proto_keepassgo_v1_keepassgo_proto_rawDescGZIP(), []int{15} +} + +func (x *GetBrowserCredentialRequest) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +func (x *GetBrowserCredentialRequest) GetPageUrl() string { + if x != nil { + return x.PageUrl + } + return "" +} + +type GetBrowserCredentialResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + Username string `protobuf:"bytes,2,opt,name=username,proto3" json:"username,omitempty"` + Password string `protobuf:"bytes,3,opt,name=password,proto3" json:"password,omitempty"` + Url string `protobuf:"bytes,4,opt,name=url,proto3" json:"url,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetBrowserCredentialResponse) Reset() { + *x = GetBrowserCredentialResponse{} + mi := &file_proto_keepassgo_v1_keepassgo_proto_msgTypes[16] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetBrowserCredentialResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetBrowserCredentialResponse) ProtoMessage() {} + +func (x *GetBrowserCredentialResponse) ProtoReflect() protoreflect.Message { + mi := &file_proto_keepassgo_v1_keepassgo_proto_msgTypes[16] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetBrowserCredentialResponse.ProtoReflect.Descriptor instead. +func (*GetBrowserCredentialResponse) Descriptor() ([]byte, []int) { + return file_proto_keepassgo_v1_keepassgo_proto_rawDescGZIP(), []int{16} +} + +func (x *GetBrowserCredentialResponse) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +func (x *GetBrowserCredentialResponse) GetUsername() string { + if x != nil { + return x.Username + } + return "" +} + +func (x *GetBrowserCredentialResponse) GetPassword() string { + if x != nil { + return x.Password + } + return "" +} + +func (x *GetBrowserCredentialResponse) GetUrl() string { + if x != nil { + return x.Url + } + return "" +} + type ListEntriesRequest struct { state protoimpl.MessageState `protogen:"open.v1"` Path []string `protobuf:"bytes,1,rep,name=path,proto3" json:"path,omitempty"` @@ -575,7 +867,7 @@ type ListEntriesRequest struct { func (x *ListEntriesRequest) Reset() { *x = ListEntriesRequest{} - mi := &file_proto_keepassgo_v1_keepassgo_proto_msgTypes[12] + mi := &file_proto_keepassgo_v1_keepassgo_proto_msgTypes[17] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -587,7 +879,7 @@ func (x *ListEntriesRequest) String() string { func (*ListEntriesRequest) ProtoMessage() {} func (x *ListEntriesRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_keepassgo_v1_keepassgo_proto_msgTypes[12] + mi := &file_proto_keepassgo_v1_keepassgo_proto_msgTypes[17] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -600,7 +892,7 @@ func (x *ListEntriesRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListEntriesRequest.ProtoReflect.Descriptor instead. func (*ListEntriesRequest) Descriptor() ([]byte, []int) { - return file_proto_keepassgo_v1_keepassgo_proto_rawDescGZIP(), []int{12} + return file_proto_keepassgo_v1_keepassgo_proto_rawDescGZIP(), []int{17} } func (x *ListEntriesRequest) GetPath() []string { @@ -634,7 +926,7 @@ type Entry struct { func (x *Entry) Reset() { *x = Entry{} - mi := &file_proto_keepassgo_v1_keepassgo_proto_msgTypes[13] + mi := &file_proto_keepassgo_v1_keepassgo_proto_msgTypes[18] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -646,7 +938,7 @@ func (x *Entry) String() string { func (*Entry) ProtoMessage() {} func (x *Entry) ProtoReflect() protoreflect.Message { - mi := &file_proto_keepassgo_v1_keepassgo_proto_msgTypes[13] + mi := &file_proto_keepassgo_v1_keepassgo_proto_msgTypes[18] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -659,7 +951,7 @@ func (x *Entry) ProtoReflect() protoreflect.Message { // Deprecated: Use Entry.ProtoReflect.Descriptor instead. func (*Entry) Descriptor() ([]byte, []int) { - return file_proto_keepassgo_v1_keepassgo_proto_rawDescGZIP(), []int{13} + return file_proto_keepassgo_v1_keepassgo_proto_rawDescGZIP(), []int{18} } func (x *Entry) GetId() string { @@ -734,7 +1026,7 @@ type ListEntriesResponse struct { func (x *ListEntriesResponse) Reset() { *x = ListEntriesResponse{} - mi := &file_proto_keepassgo_v1_keepassgo_proto_msgTypes[14] + mi := &file_proto_keepassgo_v1_keepassgo_proto_msgTypes[19] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -746,7 +1038,7 @@ func (x *ListEntriesResponse) String() string { func (*ListEntriesResponse) ProtoMessage() {} func (x *ListEntriesResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_keepassgo_v1_keepassgo_proto_msgTypes[14] + mi := &file_proto_keepassgo_v1_keepassgo_proto_msgTypes[19] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -759,7 +1051,7 @@ func (x *ListEntriesResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListEntriesResponse.ProtoReflect.Descriptor instead. func (*ListEntriesResponse) Descriptor() ([]byte, []int) { - return file_proto_keepassgo_v1_keepassgo_proto_rawDescGZIP(), []int{14} + return file_proto_keepassgo_v1_keepassgo_proto_rawDescGZIP(), []int{19} } func (x *ListEntriesResponse) GetEntries() []*Entry { @@ -778,7 +1070,7 @@ type ListGroupsRequest struct { func (x *ListGroupsRequest) Reset() { *x = ListGroupsRequest{} - mi := &file_proto_keepassgo_v1_keepassgo_proto_msgTypes[15] + mi := &file_proto_keepassgo_v1_keepassgo_proto_msgTypes[20] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -790,7 +1082,7 @@ func (x *ListGroupsRequest) String() string { func (*ListGroupsRequest) ProtoMessage() {} func (x *ListGroupsRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_keepassgo_v1_keepassgo_proto_msgTypes[15] + mi := &file_proto_keepassgo_v1_keepassgo_proto_msgTypes[20] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -803,7 +1095,7 @@ func (x *ListGroupsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListGroupsRequest.ProtoReflect.Descriptor instead. func (*ListGroupsRequest) Descriptor() ([]byte, []int) { - return file_proto_keepassgo_v1_keepassgo_proto_rawDescGZIP(), []int{15} + return file_proto_keepassgo_v1_keepassgo_proto_rawDescGZIP(), []int{20} } func (x *ListGroupsRequest) GetPath() []string { @@ -822,7 +1114,7 @@ type ListGroupsResponse struct { func (x *ListGroupsResponse) Reset() { *x = ListGroupsResponse{} - mi := &file_proto_keepassgo_v1_keepassgo_proto_msgTypes[16] + mi := &file_proto_keepassgo_v1_keepassgo_proto_msgTypes[21] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -834,7 +1126,7 @@ func (x *ListGroupsResponse) String() string { func (*ListGroupsResponse) ProtoMessage() {} func (x *ListGroupsResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_keepassgo_v1_keepassgo_proto_msgTypes[16] + mi := &file_proto_keepassgo_v1_keepassgo_proto_msgTypes[21] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -847,7 +1139,7 @@ func (x *ListGroupsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListGroupsResponse.ProtoReflect.Descriptor instead. func (*ListGroupsResponse) Descriptor() ([]byte, []int) { - return file_proto_keepassgo_v1_keepassgo_proto_rawDescGZIP(), []int{16} + return file_proto_keepassgo_v1_keepassgo_proto_rawDescGZIP(), []int{21} } func (x *ListGroupsResponse) GetNames() []string { @@ -867,7 +1159,7 @@ type CreateGroupRequest struct { func (x *CreateGroupRequest) Reset() { *x = CreateGroupRequest{} - mi := &file_proto_keepassgo_v1_keepassgo_proto_msgTypes[17] + mi := &file_proto_keepassgo_v1_keepassgo_proto_msgTypes[22] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -879,7 +1171,7 @@ func (x *CreateGroupRequest) String() string { func (*CreateGroupRequest) ProtoMessage() {} func (x *CreateGroupRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_keepassgo_v1_keepassgo_proto_msgTypes[17] + mi := &file_proto_keepassgo_v1_keepassgo_proto_msgTypes[22] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -892,7 +1184,7 @@ func (x *CreateGroupRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use CreateGroupRequest.ProtoReflect.Descriptor instead. func (*CreateGroupRequest) Descriptor() ([]byte, []int) { - return file_proto_keepassgo_v1_keepassgo_proto_rawDescGZIP(), []int{17} + return file_proto_keepassgo_v1_keepassgo_proto_rawDescGZIP(), []int{22} } func (x *CreateGroupRequest) GetParentPath() []string { @@ -917,7 +1209,7 @@ type CreateGroupResponse struct { func (x *CreateGroupResponse) Reset() { *x = CreateGroupResponse{} - mi := &file_proto_keepassgo_v1_keepassgo_proto_msgTypes[18] + mi := &file_proto_keepassgo_v1_keepassgo_proto_msgTypes[23] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -929,7 +1221,7 @@ func (x *CreateGroupResponse) String() string { func (*CreateGroupResponse) ProtoMessage() {} func (x *CreateGroupResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_keepassgo_v1_keepassgo_proto_msgTypes[18] + mi := &file_proto_keepassgo_v1_keepassgo_proto_msgTypes[23] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -942,7 +1234,7 @@ func (x *CreateGroupResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use CreateGroupResponse.ProtoReflect.Descriptor instead. func (*CreateGroupResponse) Descriptor() ([]byte, []int) { - return file_proto_keepassgo_v1_keepassgo_proto_rawDescGZIP(), []int{18} + return file_proto_keepassgo_v1_keepassgo_proto_rawDescGZIP(), []int{23} } type RenameGroupRequest struct { @@ -955,7 +1247,7 @@ type RenameGroupRequest struct { func (x *RenameGroupRequest) Reset() { *x = RenameGroupRequest{} - mi := &file_proto_keepassgo_v1_keepassgo_proto_msgTypes[19] + mi := &file_proto_keepassgo_v1_keepassgo_proto_msgTypes[24] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -967,7 +1259,7 @@ func (x *RenameGroupRequest) String() string { func (*RenameGroupRequest) ProtoMessage() {} func (x *RenameGroupRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_keepassgo_v1_keepassgo_proto_msgTypes[19] + mi := &file_proto_keepassgo_v1_keepassgo_proto_msgTypes[24] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -980,7 +1272,7 @@ func (x *RenameGroupRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use RenameGroupRequest.ProtoReflect.Descriptor instead. func (*RenameGroupRequest) Descriptor() ([]byte, []int) { - return file_proto_keepassgo_v1_keepassgo_proto_rawDescGZIP(), []int{19} + return file_proto_keepassgo_v1_keepassgo_proto_rawDescGZIP(), []int{24} } func (x *RenameGroupRequest) GetPath() []string { @@ -1005,7 +1297,7 @@ type RenameGroupResponse struct { func (x *RenameGroupResponse) Reset() { *x = RenameGroupResponse{} - mi := &file_proto_keepassgo_v1_keepassgo_proto_msgTypes[20] + mi := &file_proto_keepassgo_v1_keepassgo_proto_msgTypes[25] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1017,7 +1309,7 @@ func (x *RenameGroupResponse) String() string { func (*RenameGroupResponse) ProtoMessage() {} func (x *RenameGroupResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_keepassgo_v1_keepassgo_proto_msgTypes[20] + mi := &file_proto_keepassgo_v1_keepassgo_proto_msgTypes[25] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1030,7 +1322,7 @@ func (x *RenameGroupResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use RenameGroupResponse.ProtoReflect.Descriptor instead. func (*RenameGroupResponse) Descriptor() ([]byte, []int) { - return file_proto_keepassgo_v1_keepassgo_proto_rawDescGZIP(), []int{20} + return file_proto_keepassgo_v1_keepassgo_proto_rawDescGZIP(), []int{25} } type DeleteGroupRequest struct { @@ -1042,7 +1334,7 @@ type DeleteGroupRequest struct { func (x *DeleteGroupRequest) Reset() { *x = DeleteGroupRequest{} - mi := &file_proto_keepassgo_v1_keepassgo_proto_msgTypes[21] + mi := &file_proto_keepassgo_v1_keepassgo_proto_msgTypes[26] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1054,7 +1346,7 @@ func (x *DeleteGroupRequest) String() string { func (*DeleteGroupRequest) ProtoMessage() {} func (x *DeleteGroupRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_keepassgo_v1_keepassgo_proto_msgTypes[21] + mi := &file_proto_keepassgo_v1_keepassgo_proto_msgTypes[26] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1067,7 +1359,7 @@ func (x *DeleteGroupRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use DeleteGroupRequest.ProtoReflect.Descriptor instead. func (*DeleteGroupRequest) Descriptor() ([]byte, []int) { - return file_proto_keepassgo_v1_keepassgo_proto_rawDescGZIP(), []int{21} + return file_proto_keepassgo_v1_keepassgo_proto_rawDescGZIP(), []int{26} } func (x *DeleteGroupRequest) GetPath() []string { @@ -1085,7 +1377,7 @@ type DeleteGroupResponse struct { func (x *DeleteGroupResponse) Reset() { *x = DeleteGroupResponse{} - mi := &file_proto_keepassgo_v1_keepassgo_proto_msgTypes[22] + mi := &file_proto_keepassgo_v1_keepassgo_proto_msgTypes[27] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1097,7 +1389,7 @@ func (x *DeleteGroupResponse) String() string { func (*DeleteGroupResponse) ProtoMessage() {} func (x *DeleteGroupResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_keepassgo_v1_keepassgo_proto_msgTypes[22] + mi := &file_proto_keepassgo_v1_keepassgo_proto_msgTypes[27] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1110,7 +1402,7 @@ func (x *DeleteGroupResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use DeleteGroupResponse.ProtoReflect.Descriptor instead. func (*DeleteGroupResponse) Descriptor() ([]byte, []int) { - return file_proto_keepassgo_v1_keepassgo_proto_rawDescGZIP(), []int{22} + return file_proto_keepassgo_v1_keepassgo_proto_rawDescGZIP(), []int{27} } type UpsertEntryRequest struct { @@ -1122,7 +1414,7 @@ type UpsertEntryRequest struct { func (x *UpsertEntryRequest) Reset() { *x = UpsertEntryRequest{} - mi := &file_proto_keepassgo_v1_keepassgo_proto_msgTypes[23] + mi := &file_proto_keepassgo_v1_keepassgo_proto_msgTypes[28] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1134,7 +1426,7 @@ func (x *UpsertEntryRequest) String() string { func (*UpsertEntryRequest) ProtoMessage() {} func (x *UpsertEntryRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_keepassgo_v1_keepassgo_proto_msgTypes[23] + mi := &file_proto_keepassgo_v1_keepassgo_proto_msgTypes[28] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1147,7 +1439,7 @@ func (x *UpsertEntryRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use UpsertEntryRequest.ProtoReflect.Descriptor instead. func (*UpsertEntryRequest) Descriptor() ([]byte, []int) { - return file_proto_keepassgo_v1_keepassgo_proto_rawDescGZIP(), []int{23} + return file_proto_keepassgo_v1_keepassgo_proto_rawDescGZIP(), []int{28} } func (x *UpsertEntryRequest) GetEntry() *Entry { @@ -1166,7 +1458,7 @@ type UpsertEntryResponse struct { func (x *UpsertEntryResponse) Reset() { *x = UpsertEntryResponse{} - mi := &file_proto_keepassgo_v1_keepassgo_proto_msgTypes[24] + mi := &file_proto_keepassgo_v1_keepassgo_proto_msgTypes[29] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1178,7 +1470,7 @@ func (x *UpsertEntryResponse) String() string { func (*UpsertEntryResponse) ProtoMessage() {} func (x *UpsertEntryResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_keepassgo_v1_keepassgo_proto_msgTypes[24] + mi := &file_proto_keepassgo_v1_keepassgo_proto_msgTypes[29] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1191,7 +1483,7 @@ func (x *UpsertEntryResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use UpsertEntryResponse.ProtoReflect.Descriptor instead. func (*UpsertEntryResponse) Descriptor() ([]byte, []int) { - return file_proto_keepassgo_v1_keepassgo_proto_rawDescGZIP(), []int{24} + return file_proto_keepassgo_v1_keepassgo_proto_rawDescGZIP(), []int{29} } func (x *UpsertEntryResponse) GetEntry() *Entry { @@ -1210,7 +1502,7 @@ type DeleteEntryRequest struct { func (x *DeleteEntryRequest) Reset() { *x = DeleteEntryRequest{} - mi := &file_proto_keepassgo_v1_keepassgo_proto_msgTypes[25] + mi := &file_proto_keepassgo_v1_keepassgo_proto_msgTypes[30] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1222,7 +1514,7 @@ func (x *DeleteEntryRequest) String() string { func (*DeleteEntryRequest) ProtoMessage() {} func (x *DeleteEntryRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_keepassgo_v1_keepassgo_proto_msgTypes[25] + mi := &file_proto_keepassgo_v1_keepassgo_proto_msgTypes[30] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1235,7 +1527,7 @@ func (x *DeleteEntryRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use DeleteEntryRequest.ProtoReflect.Descriptor instead. func (*DeleteEntryRequest) Descriptor() ([]byte, []int) { - return file_proto_keepassgo_v1_keepassgo_proto_rawDescGZIP(), []int{25} + return file_proto_keepassgo_v1_keepassgo_proto_rawDescGZIP(), []int{30} } func (x *DeleteEntryRequest) GetId() string { @@ -1253,7 +1545,7 @@ type DeleteEntryResponse struct { func (x *DeleteEntryResponse) Reset() { *x = DeleteEntryResponse{} - mi := &file_proto_keepassgo_v1_keepassgo_proto_msgTypes[26] + mi := &file_proto_keepassgo_v1_keepassgo_proto_msgTypes[31] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1265,7 +1557,7 @@ func (x *DeleteEntryResponse) String() string { func (*DeleteEntryResponse) ProtoMessage() {} func (x *DeleteEntryResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_keepassgo_v1_keepassgo_proto_msgTypes[26] + mi := &file_proto_keepassgo_v1_keepassgo_proto_msgTypes[31] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1278,7 +1570,7 @@ func (x *DeleteEntryResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use DeleteEntryResponse.ProtoReflect.Descriptor instead. func (*DeleteEntryResponse) Descriptor() ([]byte, []int) { - return file_proto_keepassgo_v1_keepassgo_proto_rawDescGZIP(), []int{26} + return file_proto_keepassgo_v1_keepassgo_proto_rawDescGZIP(), []int{31} } type RestoreEntryRequest struct { @@ -1290,7 +1582,7 @@ type RestoreEntryRequest struct { func (x *RestoreEntryRequest) Reset() { *x = RestoreEntryRequest{} - mi := &file_proto_keepassgo_v1_keepassgo_proto_msgTypes[27] + mi := &file_proto_keepassgo_v1_keepassgo_proto_msgTypes[32] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1302,7 +1594,7 @@ func (x *RestoreEntryRequest) String() string { func (*RestoreEntryRequest) ProtoMessage() {} func (x *RestoreEntryRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_keepassgo_v1_keepassgo_proto_msgTypes[27] + mi := &file_proto_keepassgo_v1_keepassgo_proto_msgTypes[32] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1315,7 +1607,7 @@ func (x *RestoreEntryRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use RestoreEntryRequest.ProtoReflect.Descriptor instead. func (*RestoreEntryRequest) Descriptor() ([]byte, []int) { - return file_proto_keepassgo_v1_keepassgo_proto_rawDescGZIP(), []int{27} + return file_proto_keepassgo_v1_keepassgo_proto_rawDescGZIP(), []int{32} } func (x *RestoreEntryRequest) GetId() string { @@ -1334,7 +1626,7 @@ type RestoreEntryResponse struct { func (x *RestoreEntryResponse) Reset() { *x = RestoreEntryResponse{} - mi := &file_proto_keepassgo_v1_keepassgo_proto_msgTypes[28] + mi := &file_proto_keepassgo_v1_keepassgo_proto_msgTypes[33] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1346,7 +1638,7 @@ func (x *RestoreEntryResponse) String() string { func (*RestoreEntryResponse) ProtoMessage() {} func (x *RestoreEntryResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_keepassgo_v1_keepassgo_proto_msgTypes[28] + mi := &file_proto_keepassgo_v1_keepassgo_proto_msgTypes[33] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1359,7 +1651,7 @@ func (x *RestoreEntryResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use RestoreEntryResponse.ProtoReflect.Descriptor instead. func (*RestoreEntryResponse) Descriptor() ([]byte, []int) { - return file_proto_keepassgo_v1_keepassgo_proto_rawDescGZIP(), []int{28} + return file_proto_keepassgo_v1_keepassgo_proto_rawDescGZIP(), []int{33} } func (x *RestoreEntryResponse) GetEntry() *Entry { @@ -1378,7 +1670,7 @@ type ListEntryHistoryRequest struct { func (x *ListEntryHistoryRequest) Reset() { *x = ListEntryHistoryRequest{} - mi := &file_proto_keepassgo_v1_keepassgo_proto_msgTypes[29] + mi := &file_proto_keepassgo_v1_keepassgo_proto_msgTypes[34] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1390,7 +1682,7 @@ func (x *ListEntryHistoryRequest) String() string { func (*ListEntryHistoryRequest) ProtoMessage() {} func (x *ListEntryHistoryRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_keepassgo_v1_keepassgo_proto_msgTypes[29] + mi := &file_proto_keepassgo_v1_keepassgo_proto_msgTypes[34] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1403,7 +1695,7 @@ func (x *ListEntryHistoryRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListEntryHistoryRequest.ProtoReflect.Descriptor instead. func (*ListEntryHistoryRequest) Descriptor() ([]byte, []int) { - return file_proto_keepassgo_v1_keepassgo_proto_rawDescGZIP(), []int{29} + return file_proto_keepassgo_v1_keepassgo_proto_rawDescGZIP(), []int{34} } func (x *ListEntryHistoryRequest) GetId() string { @@ -1422,7 +1714,7 @@ type ListEntryHistoryResponse struct { func (x *ListEntryHistoryResponse) Reset() { *x = ListEntryHistoryResponse{} - mi := &file_proto_keepassgo_v1_keepassgo_proto_msgTypes[30] + mi := &file_proto_keepassgo_v1_keepassgo_proto_msgTypes[35] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1434,7 +1726,7 @@ func (x *ListEntryHistoryResponse) String() string { func (*ListEntryHistoryResponse) ProtoMessage() {} func (x *ListEntryHistoryResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_keepassgo_v1_keepassgo_proto_msgTypes[30] + mi := &file_proto_keepassgo_v1_keepassgo_proto_msgTypes[35] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1447,7 +1739,7 @@ func (x *ListEntryHistoryResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListEntryHistoryResponse.ProtoReflect.Descriptor instead. func (*ListEntryHistoryResponse) Descriptor() ([]byte, []int) { - return file_proto_keepassgo_v1_keepassgo_proto_rawDescGZIP(), []int{30} + return file_proto_keepassgo_v1_keepassgo_proto_rawDescGZIP(), []int{35} } func (x *ListEntryHistoryResponse) GetEntries() []*Entry { @@ -1467,7 +1759,7 @@ type RestoreEntryHistoryRequest struct { func (x *RestoreEntryHistoryRequest) Reset() { *x = RestoreEntryHistoryRequest{} - mi := &file_proto_keepassgo_v1_keepassgo_proto_msgTypes[31] + mi := &file_proto_keepassgo_v1_keepassgo_proto_msgTypes[36] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1479,7 +1771,7 @@ func (x *RestoreEntryHistoryRequest) String() string { func (*RestoreEntryHistoryRequest) ProtoMessage() {} func (x *RestoreEntryHistoryRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_keepassgo_v1_keepassgo_proto_msgTypes[31] + mi := &file_proto_keepassgo_v1_keepassgo_proto_msgTypes[36] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1492,7 +1784,7 @@ func (x *RestoreEntryHistoryRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use RestoreEntryHistoryRequest.ProtoReflect.Descriptor instead. func (*RestoreEntryHistoryRequest) Descriptor() ([]byte, []int) { - return file_proto_keepassgo_v1_keepassgo_proto_rawDescGZIP(), []int{31} + return file_proto_keepassgo_v1_keepassgo_proto_rawDescGZIP(), []int{36} } func (x *RestoreEntryHistoryRequest) GetId() string { @@ -1518,7 +1810,7 @@ type RestoreEntryHistoryResponse struct { func (x *RestoreEntryHistoryResponse) Reset() { *x = RestoreEntryHistoryResponse{} - mi := &file_proto_keepassgo_v1_keepassgo_proto_msgTypes[32] + mi := &file_proto_keepassgo_v1_keepassgo_proto_msgTypes[37] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1530,7 +1822,7 @@ func (x *RestoreEntryHistoryResponse) String() string { func (*RestoreEntryHistoryResponse) ProtoMessage() {} func (x *RestoreEntryHistoryResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_keepassgo_v1_keepassgo_proto_msgTypes[32] + mi := &file_proto_keepassgo_v1_keepassgo_proto_msgTypes[37] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1543,7 +1835,7 @@ func (x *RestoreEntryHistoryResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use RestoreEntryHistoryResponse.ProtoReflect.Descriptor instead. func (*RestoreEntryHistoryResponse) Descriptor() ([]byte, []int) { - return file_proto_keepassgo_v1_keepassgo_proto_rawDescGZIP(), []int{32} + return file_proto_keepassgo_v1_keepassgo_proto_rawDescGZIP(), []int{37} } func (x *RestoreEntryHistoryResponse) GetEntry() *Entry { @@ -1561,7 +1853,7 @@ type ListTemplatesRequest struct { func (x *ListTemplatesRequest) Reset() { *x = ListTemplatesRequest{} - mi := &file_proto_keepassgo_v1_keepassgo_proto_msgTypes[33] + mi := &file_proto_keepassgo_v1_keepassgo_proto_msgTypes[38] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1573,7 +1865,7 @@ func (x *ListTemplatesRequest) String() string { func (*ListTemplatesRequest) ProtoMessage() {} func (x *ListTemplatesRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_keepassgo_v1_keepassgo_proto_msgTypes[33] + mi := &file_proto_keepassgo_v1_keepassgo_proto_msgTypes[38] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1586,7 +1878,7 @@ func (x *ListTemplatesRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListTemplatesRequest.ProtoReflect.Descriptor instead. func (*ListTemplatesRequest) Descriptor() ([]byte, []int) { - return file_proto_keepassgo_v1_keepassgo_proto_rawDescGZIP(), []int{33} + return file_proto_keepassgo_v1_keepassgo_proto_rawDescGZIP(), []int{38} } type ListTemplatesResponse struct { @@ -1598,7 +1890,7 @@ type ListTemplatesResponse struct { func (x *ListTemplatesResponse) Reset() { *x = ListTemplatesResponse{} - mi := &file_proto_keepassgo_v1_keepassgo_proto_msgTypes[34] + mi := &file_proto_keepassgo_v1_keepassgo_proto_msgTypes[39] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1610,7 +1902,7 @@ func (x *ListTemplatesResponse) String() string { func (*ListTemplatesResponse) ProtoMessage() {} func (x *ListTemplatesResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_keepassgo_v1_keepassgo_proto_msgTypes[34] + mi := &file_proto_keepassgo_v1_keepassgo_proto_msgTypes[39] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1623,7 +1915,7 @@ func (x *ListTemplatesResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListTemplatesResponse.ProtoReflect.Descriptor instead. func (*ListTemplatesResponse) Descriptor() ([]byte, []int) { - return file_proto_keepassgo_v1_keepassgo_proto_rawDescGZIP(), []int{34} + return file_proto_keepassgo_v1_keepassgo_proto_rawDescGZIP(), []int{39} } func (x *ListTemplatesResponse) GetTemplates() []*Entry { @@ -1642,7 +1934,7 @@ type UpsertTemplateRequest struct { func (x *UpsertTemplateRequest) Reset() { *x = UpsertTemplateRequest{} - mi := &file_proto_keepassgo_v1_keepassgo_proto_msgTypes[35] + mi := &file_proto_keepassgo_v1_keepassgo_proto_msgTypes[40] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1654,7 +1946,7 @@ func (x *UpsertTemplateRequest) String() string { func (*UpsertTemplateRequest) ProtoMessage() {} func (x *UpsertTemplateRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_keepassgo_v1_keepassgo_proto_msgTypes[35] + mi := &file_proto_keepassgo_v1_keepassgo_proto_msgTypes[40] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1667,7 +1959,7 @@ func (x *UpsertTemplateRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use UpsertTemplateRequest.ProtoReflect.Descriptor instead. func (*UpsertTemplateRequest) Descriptor() ([]byte, []int) { - return file_proto_keepassgo_v1_keepassgo_proto_rawDescGZIP(), []int{35} + return file_proto_keepassgo_v1_keepassgo_proto_rawDescGZIP(), []int{40} } func (x *UpsertTemplateRequest) GetTemplate() *Entry { @@ -1686,7 +1978,7 @@ type UpsertTemplateResponse struct { func (x *UpsertTemplateResponse) Reset() { *x = UpsertTemplateResponse{} - mi := &file_proto_keepassgo_v1_keepassgo_proto_msgTypes[36] + mi := &file_proto_keepassgo_v1_keepassgo_proto_msgTypes[41] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1698,7 +1990,7 @@ func (x *UpsertTemplateResponse) String() string { func (*UpsertTemplateResponse) ProtoMessage() {} func (x *UpsertTemplateResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_keepassgo_v1_keepassgo_proto_msgTypes[36] + mi := &file_proto_keepassgo_v1_keepassgo_proto_msgTypes[41] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1711,7 +2003,7 @@ func (x *UpsertTemplateResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use UpsertTemplateResponse.ProtoReflect.Descriptor instead. func (*UpsertTemplateResponse) Descriptor() ([]byte, []int) { - return file_proto_keepassgo_v1_keepassgo_proto_rawDescGZIP(), []int{36} + return file_proto_keepassgo_v1_keepassgo_proto_rawDescGZIP(), []int{41} } func (x *UpsertTemplateResponse) GetTemplate() *Entry { @@ -1730,7 +2022,7 @@ type DeleteTemplateRequest struct { func (x *DeleteTemplateRequest) Reset() { *x = DeleteTemplateRequest{} - mi := &file_proto_keepassgo_v1_keepassgo_proto_msgTypes[37] + mi := &file_proto_keepassgo_v1_keepassgo_proto_msgTypes[42] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1742,7 +2034,7 @@ func (x *DeleteTemplateRequest) String() string { func (*DeleteTemplateRequest) ProtoMessage() {} func (x *DeleteTemplateRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_keepassgo_v1_keepassgo_proto_msgTypes[37] + mi := &file_proto_keepassgo_v1_keepassgo_proto_msgTypes[42] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1755,7 +2047,7 @@ func (x *DeleteTemplateRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use DeleteTemplateRequest.ProtoReflect.Descriptor instead. func (*DeleteTemplateRequest) Descriptor() ([]byte, []int) { - return file_proto_keepassgo_v1_keepassgo_proto_rawDescGZIP(), []int{37} + return file_proto_keepassgo_v1_keepassgo_proto_rawDescGZIP(), []int{42} } func (x *DeleteTemplateRequest) GetId() string { @@ -1773,7 +2065,7 @@ type DeleteTemplateResponse struct { func (x *DeleteTemplateResponse) Reset() { *x = DeleteTemplateResponse{} - mi := &file_proto_keepassgo_v1_keepassgo_proto_msgTypes[38] + mi := &file_proto_keepassgo_v1_keepassgo_proto_msgTypes[43] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1785,7 +2077,7 @@ func (x *DeleteTemplateResponse) String() string { func (*DeleteTemplateResponse) ProtoMessage() {} func (x *DeleteTemplateResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_keepassgo_v1_keepassgo_proto_msgTypes[38] + mi := &file_proto_keepassgo_v1_keepassgo_proto_msgTypes[43] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1798,7 +2090,7 @@ func (x *DeleteTemplateResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use DeleteTemplateResponse.ProtoReflect.Descriptor instead. func (*DeleteTemplateResponse) Descriptor() ([]byte, []int) { - return file_proto_keepassgo_v1_keepassgo_proto_rawDescGZIP(), []int{38} + return file_proto_keepassgo_v1_keepassgo_proto_rawDescGZIP(), []int{43} } type InstantiateTemplateRequest struct { @@ -1811,7 +2103,7 @@ type InstantiateTemplateRequest struct { func (x *InstantiateTemplateRequest) Reset() { *x = InstantiateTemplateRequest{} - mi := &file_proto_keepassgo_v1_keepassgo_proto_msgTypes[39] + mi := &file_proto_keepassgo_v1_keepassgo_proto_msgTypes[44] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1823,7 +2115,7 @@ func (x *InstantiateTemplateRequest) String() string { func (*InstantiateTemplateRequest) ProtoMessage() {} func (x *InstantiateTemplateRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_keepassgo_v1_keepassgo_proto_msgTypes[39] + mi := &file_proto_keepassgo_v1_keepassgo_proto_msgTypes[44] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1836,7 +2128,7 @@ func (x *InstantiateTemplateRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use InstantiateTemplateRequest.ProtoReflect.Descriptor instead. func (*InstantiateTemplateRequest) Descriptor() ([]byte, []int) { - return file_proto_keepassgo_v1_keepassgo_proto_rawDescGZIP(), []int{39} + return file_proto_keepassgo_v1_keepassgo_proto_rawDescGZIP(), []int{44} } func (x *InstantiateTemplateRequest) GetTemplateId() string { @@ -1862,7 +2154,7 @@ type InstantiateTemplateResponse struct { func (x *InstantiateTemplateResponse) Reset() { *x = InstantiateTemplateResponse{} - mi := &file_proto_keepassgo_v1_keepassgo_proto_msgTypes[40] + mi := &file_proto_keepassgo_v1_keepassgo_proto_msgTypes[45] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1874,7 +2166,7 @@ func (x *InstantiateTemplateResponse) String() string { func (*InstantiateTemplateResponse) ProtoMessage() {} func (x *InstantiateTemplateResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_keepassgo_v1_keepassgo_proto_msgTypes[40] + mi := &file_proto_keepassgo_v1_keepassgo_proto_msgTypes[45] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1887,7 +2179,7 @@ func (x *InstantiateTemplateResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use InstantiateTemplateResponse.ProtoReflect.Descriptor instead. func (*InstantiateTemplateResponse) Descriptor() ([]byte, []int) { - return file_proto_keepassgo_v1_keepassgo_proto_rawDescGZIP(), []int{40} + return file_proto_keepassgo_v1_keepassgo_proto_rawDescGZIP(), []int{45} } func (x *InstantiateTemplateResponse) GetEntry() *Entry { @@ -1906,7 +2198,7 @@ type ListAttachmentsRequest struct { func (x *ListAttachmentsRequest) Reset() { *x = ListAttachmentsRequest{} - mi := &file_proto_keepassgo_v1_keepassgo_proto_msgTypes[41] + mi := &file_proto_keepassgo_v1_keepassgo_proto_msgTypes[46] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1918,7 +2210,7 @@ func (x *ListAttachmentsRequest) String() string { func (*ListAttachmentsRequest) ProtoMessage() {} func (x *ListAttachmentsRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_keepassgo_v1_keepassgo_proto_msgTypes[41] + mi := &file_proto_keepassgo_v1_keepassgo_proto_msgTypes[46] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1931,7 +2223,7 @@ func (x *ListAttachmentsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListAttachmentsRequest.ProtoReflect.Descriptor instead. func (*ListAttachmentsRequest) Descriptor() ([]byte, []int) { - return file_proto_keepassgo_v1_keepassgo_proto_rawDescGZIP(), []int{41} + return file_proto_keepassgo_v1_keepassgo_proto_rawDescGZIP(), []int{46} } func (x *ListAttachmentsRequest) GetEntryId() string { @@ -1950,7 +2242,7 @@ type ListAttachmentsResponse struct { func (x *ListAttachmentsResponse) Reset() { *x = ListAttachmentsResponse{} - mi := &file_proto_keepassgo_v1_keepassgo_proto_msgTypes[42] + mi := &file_proto_keepassgo_v1_keepassgo_proto_msgTypes[47] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1962,7 +2254,7 @@ func (x *ListAttachmentsResponse) String() string { func (*ListAttachmentsResponse) ProtoMessage() {} func (x *ListAttachmentsResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_keepassgo_v1_keepassgo_proto_msgTypes[42] + mi := &file_proto_keepassgo_v1_keepassgo_proto_msgTypes[47] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1975,7 +2267,7 @@ func (x *ListAttachmentsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListAttachmentsResponse.ProtoReflect.Descriptor instead. func (*ListAttachmentsResponse) Descriptor() ([]byte, []int) { - return file_proto_keepassgo_v1_keepassgo_proto_rawDescGZIP(), []int{42} + return file_proto_keepassgo_v1_keepassgo_proto_rawDescGZIP(), []int{47} } func (x *ListAttachmentsResponse) GetNames() []string { @@ -1996,7 +2288,7 @@ type UploadAttachmentRequest struct { func (x *UploadAttachmentRequest) Reset() { *x = UploadAttachmentRequest{} - mi := &file_proto_keepassgo_v1_keepassgo_proto_msgTypes[43] + mi := &file_proto_keepassgo_v1_keepassgo_proto_msgTypes[48] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2008,7 +2300,7 @@ func (x *UploadAttachmentRequest) String() string { func (*UploadAttachmentRequest) ProtoMessage() {} func (x *UploadAttachmentRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_keepassgo_v1_keepassgo_proto_msgTypes[43] + mi := &file_proto_keepassgo_v1_keepassgo_proto_msgTypes[48] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2021,7 +2313,7 @@ func (x *UploadAttachmentRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use UploadAttachmentRequest.ProtoReflect.Descriptor instead. func (*UploadAttachmentRequest) Descriptor() ([]byte, []int) { - return file_proto_keepassgo_v1_keepassgo_proto_rawDescGZIP(), []int{43} + return file_proto_keepassgo_v1_keepassgo_proto_rawDescGZIP(), []int{48} } func (x *UploadAttachmentRequest) GetEntryId() string { @@ -2053,7 +2345,7 @@ type UploadAttachmentResponse struct { func (x *UploadAttachmentResponse) Reset() { *x = UploadAttachmentResponse{} - mi := &file_proto_keepassgo_v1_keepassgo_proto_msgTypes[44] + mi := &file_proto_keepassgo_v1_keepassgo_proto_msgTypes[49] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2065,7 +2357,7 @@ func (x *UploadAttachmentResponse) String() string { func (*UploadAttachmentResponse) ProtoMessage() {} func (x *UploadAttachmentResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_keepassgo_v1_keepassgo_proto_msgTypes[44] + mi := &file_proto_keepassgo_v1_keepassgo_proto_msgTypes[49] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2078,7 +2370,7 @@ func (x *UploadAttachmentResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use UploadAttachmentResponse.ProtoReflect.Descriptor instead. func (*UploadAttachmentResponse) Descriptor() ([]byte, []int) { - return file_proto_keepassgo_v1_keepassgo_proto_rawDescGZIP(), []int{44} + return file_proto_keepassgo_v1_keepassgo_proto_rawDescGZIP(), []int{49} } type DownloadAttachmentRequest struct { @@ -2091,7 +2383,7 @@ type DownloadAttachmentRequest struct { func (x *DownloadAttachmentRequest) Reset() { *x = DownloadAttachmentRequest{} - mi := &file_proto_keepassgo_v1_keepassgo_proto_msgTypes[45] + mi := &file_proto_keepassgo_v1_keepassgo_proto_msgTypes[50] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2103,7 +2395,7 @@ func (x *DownloadAttachmentRequest) String() string { func (*DownloadAttachmentRequest) ProtoMessage() {} func (x *DownloadAttachmentRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_keepassgo_v1_keepassgo_proto_msgTypes[45] + mi := &file_proto_keepassgo_v1_keepassgo_proto_msgTypes[50] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2116,7 +2408,7 @@ func (x *DownloadAttachmentRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use DownloadAttachmentRequest.ProtoReflect.Descriptor instead. func (*DownloadAttachmentRequest) Descriptor() ([]byte, []int) { - return file_proto_keepassgo_v1_keepassgo_proto_rawDescGZIP(), []int{45} + return file_proto_keepassgo_v1_keepassgo_proto_rawDescGZIP(), []int{50} } func (x *DownloadAttachmentRequest) GetEntryId() string { @@ -2142,7 +2434,7 @@ type DownloadAttachmentResponse struct { func (x *DownloadAttachmentResponse) Reset() { *x = DownloadAttachmentResponse{} - mi := &file_proto_keepassgo_v1_keepassgo_proto_msgTypes[46] + mi := &file_proto_keepassgo_v1_keepassgo_proto_msgTypes[51] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2154,7 +2446,7 @@ func (x *DownloadAttachmentResponse) String() string { func (*DownloadAttachmentResponse) ProtoMessage() {} func (x *DownloadAttachmentResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_keepassgo_v1_keepassgo_proto_msgTypes[46] + mi := &file_proto_keepassgo_v1_keepassgo_proto_msgTypes[51] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2167,7 +2459,7 @@ func (x *DownloadAttachmentResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use DownloadAttachmentResponse.ProtoReflect.Descriptor instead. func (*DownloadAttachmentResponse) Descriptor() ([]byte, []int) { - return file_proto_keepassgo_v1_keepassgo_proto_rawDescGZIP(), []int{46} + return file_proto_keepassgo_v1_keepassgo_proto_rawDescGZIP(), []int{51} } func (x *DownloadAttachmentResponse) GetContent() []byte { @@ -2187,7 +2479,7 @@ type DeleteAttachmentRequest struct { func (x *DeleteAttachmentRequest) Reset() { *x = DeleteAttachmentRequest{} - mi := &file_proto_keepassgo_v1_keepassgo_proto_msgTypes[47] + mi := &file_proto_keepassgo_v1_keepassgo_proto_msgTypes[52] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2199,7 +2491,7 @@ func (x *DeleteAttachmentRequest) String() string { func (*DeleteAttachmentRequest) ProtoMessage() {} func (x *DeleteAttachmentRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_keepassgo_v1_keepassgo_proto_msgTypes[47] + mi := &file_proto_keepassgo_v1_keepassgo_proto_msgTypes[52] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2212,7 +2504,7 @@ func (x *DeleteAttachmentRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use DeleteAttachmentRequest.ProtoReflect.Descriptor instead. func (*DeleteAttachmentRequest) Descriptor() ([]byte, []int) { - return file_proto_keepassgo_v1_keepassgo_proto_rawDescGZIP(), []int{47} + return file_proto_keepassgo_v1_keepassgo_proto_rawDescGZIP(), []int{52} } func (x *DeleteAttachmentRequest) GetEntryId() string { @@ -2237,7 +2529,7 @@ type DeleteAttachmentResponse struct { func (x *DeleteAttachmentResponse) Reset() { *x = DeleteAttachmentResponse{} - mi := &file_proto_keepassgo_v1_keepassgo_proto_msgTypes[48] + mi := &file_proto_keepassgo_v1_keepassgo_proto_msgTypes[53] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2249,7 +2541,7 @@ func (x *DeleteAttachmentResponse) String() string { func (*DeleteAttachmentResponse) ProtoMessage() {} func (x *DeleteAttachmentResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_keepassgo_v1_keepassgo_proto_msgTypes[48] + mi := &file_proto_keepassgo_v1_keepassgo_proto_msgTypes[53] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2262,7 +2554,7 @@ func (x *DeleteAttachmentResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use DeleteAttachmentResponse.ProtoReflect.Descriptor instead. func (*DeleteAttachmentResponse) Descriptor() ([]byte, []int) { - return file_proto_keepassgo_v1_keepassgo_proto_rawDescGZIP(), []int{48} + return file_proto_keepassgo_v1_keepassgo_proto_rawDescGZIP(), []int{53} } type CopyEntryFieldRequest struct { @@ -2275,7 +2567,7 @@ type CopyEntryFieldRequest struct { func (x *CopyEntryFieldRequest) Reset() { *x = CopyEntryFieldRequest{} - mi := &file_proto_keepassgo_v1_keepassgo_proto_msgTypes[49] + mi := &file_proto_keepassgo_v1_keepassgo_proto_msgTypes[54] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2287,7 +2579,7 @@ func (x *CopyEntryFieldRequest) String() string { func (*CopyEntryFieldRequest) ProtoMessage() {} func (x *CopyEntryFieldRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_keepassgo_v1_keepassgo_proto_msgTypes[49] + mi := &file_proto_keepassgo_v1_keepassgo_proto_msgTypes[54] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2300,7 +2592,7 @@ func (x *CopyEntryFieldRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use CopyEntryFieldRequest.ProtoReflect.Descriptor instead. func (*CopyEntryFieldRequest) Descriptor() ([]byte, []int) { - return file_proto_keepassgo_v1_keepassgo_proto_rawDescGZIP(), []int{49} + return file_proto_keepassgo_v1_keepassgo_proto_rawDescGZIP(), []int{54} } func (x *CopyEntryFieldRequest) GetId() string { @@ -2325,7 +2617,7 @@ type CopyEntryFieldResponse struct { func (x *CopyEntryFieldResponse) Reset() { *x = CopyEntryFieldResponse{} - mi := &file_proto_keepassgo_v1_keepassgo_proto_msgTypes[50] + mi := &file_proto_keepassgo_v1_keepassgo_proto_msgTypes[55] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2337,7 +2629,7 @@ func (x *CopyEntryFieldResponse) String() string { func (*CopyEntryFieldResponse) ProtoMessage() {} func (x *CopyEntryFieldResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_keepassgo_v1_keepassgo_proto_msgTypes[50] + mi := &file_proto_keepassgo_v1_keepassgo_proto_msgTypes[55] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2350,7 +2642,7 @@ func (x *CopyEntryFieldResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use CopyEntryFieldResponse.ProtoReflect.Descriptor instead. func (*CopyEntryFieldResponse) Descriptor() ([]byte, []int) { - return file_proto_keepassgo_v1_keepassgo_proto_rawDescGZIP(), []int{50} + return file_proto_keepassgo_v1_keepassgo_proto_rawDescGZIP(), []int{55} } type GeneratePasswordRequest struct { @@ -2362,7 +2654,7 @@ type GeneratePasswordRequest struct { func (x *GeneratePasswordRequest) Reset() { *x = GeneratePasswordRequest{} - mi := &file_proto_keepassgo_v1_keepassgo_proto_msgTypes[51] + mi := &file_proto_keepassgo_v1_keepassgo_proto_msgTypes[56] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2374,7 +2666,7 @@ func (x *GeneratePasswordRequest) String() string { func (*GeneratePasswordRequest) ProtoMessage() {} func (x *GeneratePasswordRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_keepassgo_v1_keepassgo_proto_msgTypes[51] + mi := &file_proto_keepassgo_v1_keepassgo_proto_msgTypes[56] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2387,7 +2679,7 @@ func (x *GeneratePasswordRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GeneratePasswordRequest.ProtoReflect.Descriptor instead. func (*GeneratePasswordRequest) Descriptor() ([]byte, []int) { - return file_proto_keepassgo_v1_keepassgo_proto_rawDescGZIP(), []int{51} + return file_proto_keepassgo_v1_keepassgo_proto_rawDescGZIP(), []int{56} } func (x *GeneratePasswordRequest) GetProfile() string { @@ -2406,7 +2698,7 @@ type GeneratePasswordResponse struct { func (x *GeneratePasswordResponse) Reset() { *x = GeneratePasswordResponse{} - mi := &file_proto_keepassgo_v1_keepassgo_proto_msgTypes[52] + mi := &file_proto_keepassgo_v1_keepassgo_proto_msgTypes[57] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2418,7 +2710,7 @@ func (x *GeneratePasswordResponse) String() string { func (*GeneratePasswordResponse) ProtoMessage() {} func (x *GeneratePasswordResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_keepassgo_v1_keepassgo_proto_msgTypes[52] + mi := &file_proto_keepassgo_v1_keepassgo_proto_msgTypes[57] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2431,7 +2723,7 @@ func (x *GeneratePasswordResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GeneratePasswordResponse.ProtoReflect.Descriptor instead. func (*GeneratePasswordResponse) Descriptor() ([]byte, []int) { - return file_proto_keepassgo_v1_keepassgo_proto_rawDescGZIP(), []int{52} + return file_proto_keepassgo_v1_keepassgo_proto_rawDescGZIP(), []int{57} } func (x *GeneratePasswordResponse) GetPassword() string { @@ -2472,7 +2764,26 @@ const file_proto_keepassgo_v1_keepassgo_proto_rawDesc = "" + "\x12UnlockVaultRequest\x12\x1a\n" + "\bpassword\x18\x01 \x01(\tR\bpassword\x12\"\n" + "\rkey_file_data\x18\x02 \x01(\fR\vkeyFileData\"\x15\n" + - "\x13UnlockVaultResponse\">\n" + + "\x13UnlockVaultResponse\"5\n" + + "\x18FindBrowserLoginsRequest\x12\x19\n" + + "\bpage_url\x18\x01 \x01(\tR\apageUrl\"\x95\x01\n" + + "\x11BrowserLoginMatch\x12\x0e\n" + + "\x02id\x18\x01 \x01(\tR\x02id\x12\x14\n" + + "\x05title\x18\x02 \x01(\tR\x05title\x12\x1a\n" + + "\busername\x18\x03 \x01(\tR\busername\x12\x10\n" + + "\x03url\x18\x04 \x01(\tR\x03url\x12\x12\n" + + "\x04path\x18\x05 \x03(\tR\x04path\x12\x18\n" + + "\aquality\x18\x06 \x01(\tR\aquality\"V\n" + + "\x19FindBrowserLoginsResponse\x129\n" + + "\amatches\x18\x01 \x03(\v2\x1f.keepassgo.v1.BrowserLoginMatchR\amatches\"H\n" + + "\x1bGetBrowserCredentialRequest\x12\x0e\n" + + "\x02id\x18\x01 \x01(\tR\x02id\x12\x19\n" + + "\bpage_url\x18\x02 \x01(\tR\apageUrl\"x\n" + + "\x1cGetBrowserCredentialResponse\x12\x0e\n" + + "\x02id\x18\x01 \x01(\tR\x02id\x12\x1a\n" + + "\busername\x18\x02 \x01(\tR\busername\x12\x1a\n" + + "\bpassword\x18\x03 \x01(\tR\bpassword\x12\x10\n" + + "\x03url\x18\x04 \x01(\tR\x03url\">\n" + "\x12ListEntriesRequest\x12\x12\n" + "\x04path\x18\x01 \x03(\tR\x04path\x12\x14\n" + "\x05query\x18\x02 \x01(\tR\x05query\"\xa9\x02\n" + @@ -2568,14 +2879,16 @@ const file_proto_keepassgo_v1_keepassgo_proto_rawDesc = "" + "\x17GeneratePasswordRequest\x12\x18\n" + "\aprofile\x18\x01 \x01(\tR\aprofile\"6\n" + "\x18GeneratePasswordResponse\x12\x1a\n" + - "\bpassword\x18\x01 \x01(\tR\bpassword2\xcd\x12\n" + + "\bpassword\x18\x01 \x01(\tR\bpassword2\xa2\x14\n" + "\fVaultService\x12a\n" + "\x10GetSessionStatus\x12%.keepassgo.v1.GetSessionStatusRequest\x1a&.keepassgo.v1.GetSessionStatusResponse\x12L\n" + "\tOpenVault\x12\x1e.keepassgo.v1.OpenVaultRequest\x1a\x1f.keepassgo.v1.OpenVaultResponse\x12^\n" + "\x0fOpenRemoteVault\x12$.keepassgo.v1.OpenRemoteVaultRequest\x1a%.keepassgo.v1.OpenRemoteVaultResponse\x12L\n" + "\tSaveVault\x12\x1e.keepassgo.v1.SaveVaultRequest\x1a\x1f.keepassgo.v1.SaveVaultResponse\x12L\n" + "\tLockVault\x12\x1e.keepassgo.v1.LockVaultRequest\x1a\x1f.keepassgo.v1.LockVaultResponse\x12R\n" + - "\vUnlockVault\x12 .keepassgo.v1.UnlockVaultRequest\x1a!.keepassgo.v1.UnlockVaultResponse\x12R\n" + + "\vUnlockVault\x12 .keepassgo.v1.UnlockVaultRequest\x1a!.keepassgo.v1.UnlockVaultResponse\x12d\n" + + "\x11FindBrowserLogins\x12&.keepassgo.v1.FindBrowserLoginsRequest\x1a'.keepassgo.v1.FindBrowserLoginsResponse\x12m\n" + + "\x14GetBrowserCredential\x12).keepassgo.v1.GetBrowserCredentialRequest\x1a*.keepassgo.v1.GetBrowserCredentialResponse\x12R\n" + "\vListEntries\x12 .keepassgo.v1.ListEntriesRequest\x1a!.keepassgo.v1.ListEntriesResponse\x12O\n" + "\n" + "ListGroups\x12\x1f.keepassgo.v1.ListGroupsRequest\x1a .keepassgo.v1.ListGroupsResponse\x12R\n" + @@ -2610,133 +2923,143 @@ func file_proto_keepassgo_v1_keepassgo_proto_rawDescGZIP() []byte { return file_proto_keepassgo_v1_keepassgo_proto_rawDescData } -var file_proto_keepassgo_v1_keepassgo_proto_msgTypes = make([]protoimpl.MessageInfo, 54) +var file_proto_keepassgo_v1_keepassgo_proto_msgTypes = make([]protoimpl.MessageInfo, 59) var file_proto_keepassgo_v1_keepassgo_proto_goTypes = []any{ - (*GetSessionStatusRequest)(nil), // 0: keepassgo.v1.GetSessionStatusRequest - (*GetSessionStatusResponse)(nil), // 1: keepassgo.v1.GetSessionStatusResponse - (*OpenVaultRequest)(nil), // 2: keepassgo.v1.OpenVaultRequest - (*OpenVaultResponse)(nil), // 3: keepassgo.v1.OpenVaultResponse - (*OpenRemoteVaultRequest)(nil), // 4: keepassgo.v1.OpenRemoteVaultRequest - (*OpenRemoteVaultResponse)(nil), // 5: keepassgo.v1.OpenRemoteVaultResponse - (*SaveVaultRequest)(nil), // 6: keepassgo.v1.SaveVaultRequest - (*SaveVaultResponse)(nil), // 7: keepassgo.v1.SaveVaultResponse - (*LockVaultRequest)(nil), // 8: keepassgo.v1.LockVaultRequest - (*LockVaultResponse)(nil), // 9: keepassgo.v1.LockVaultResponse - (*UnlockVaultRequest)(nil), // 10: keepassgo.v1.UnlockVaultRequest - (*UnlockVaultResponse)(nil), // 11: keepassgo.v1.UnlockVaultResponse - (*ListEntriesRequest)(nil), // 12: keepassgo.v1.ListEntriesRequest - (*Entry)(nil), // 13: keepassgo.v1.Entry - (*ListEntriesResponse)(nil), // 14: keepassgo.v1.ListEntriesResponse - (*ListGroupsRequest)(nil), // 15: keepassgo.v1.ListGroupsRequest - (*ListGroupsResponse)(nil), // 16: keepassgo.v1.ListGroupsResponse - (*CreateGroupRequest)(nil), // 17: keepassgo.v1.CreateGroupRequest - (*CreateGroupResponse)(nil), // 18: keepassgo.v1.CreateGroupResponse - (*RenameGroupRequest)(nil), // 19: keepassgo.v1.RenameGroupRequest - (*RenameGroupResponse)(nil), // 20: keepassgo.v1.RenameGroupResponse - (*DeleteGroupRequest)(nil), // 21: keepassgo.v1.DeleteGroupRequest - (*DeleteGroupResponse)(nil), // 22: keepassgo.v1.DeleteGroupResponse - (*UpsertEntryRequest)(nil), // 23: keepassgo.v1.UpsertEntryRequest - (*UpsertEntryResponse)(nil), // 24: keepassgo.v1.UpsertEntryResponse - (*DeleteEntryRequest)(nil), // 25: keepassgo.v1.DeleteEntryRequest - (*DeleteEntryResponse)(nil), // 26: keepassgo.v1.DeleteEntryResponse - (*RestoreEntryRequest)(nil), // 27: keepassgo.v1.RestoreEntryRequest - (*RestoreEntryResponse)(nil), // 28: keepassgo.v1.RestoreEntryResponse - (*ListEntryHistoryRequest)(nil), // 29: keepassgo.v1.ListEntryHistoryRequest - (*ListEntryHistoryResponse)(nil), // 30: keepassgo.v1.ListEntryHistoryResponse - (*RestoreEntryHistoryRequest)(nil), // 31: keepassgo.v1.RestoreEntryHistoryRequest - (*RestoreEntryHistoryResponse)(nil), // 32: keepassgo.v1.RestoreEntryHistoryResponse - (*ListTemplatesRequest)(nil), // 33: keepassgo.v1.ListTemplatesRequest - (*ListTemplatesResponse)(nil), // 34: keepassgo.v1.ListTemplatesResponse - (*UpsertTemplateRequest)(nil), // 35: keepassgo.v1.UpsertTemplateRequest - (*UpsertTemplateResponse)(nil), // 36: keepassgo.v1.UpsertTemplateResponse - (*DeleteTemplateRequest)(nil), // 37: keepassgo.v1.DeleteTemplateRequest - (*DeleteTemplateResponse)(nil), // 38: keepassgo.v1.DeleteTemplateResponse - (*InstantiateTemplateRequest)(nil), // 39: keepassgo.v1.InstantiateTemplateRequest - (*InstantiateTemplateResponse)(nil), // 40: keepassgo.v1.InstantiateTemplateResponse - (*ListAttachmentsRequest)(nil), // 41: keepassgo.v1.ListAttachmentsRequest - (*ListAttachmentsResponse)(nil), // 42: keepassgo.v1.ListAttachmentsResponse - (*UploadAttachmentRequest)(nil), // 43: keepassgo.v1.UploadAttachmentRequest - (*UploadAttachmentResponse)(nil), // 44: keepassgo.v1.UploadAttachmentResponse - (*DownloadAttachmentRequest)(nil), // 45: keepassgo.v1.DownloadAttachmentRequest - (*DownloadAttachmentResponse)(nil), // 46: keepassgo.v1.DownloadAttachmentResponse - (*DeleteAttachmentRequest)(nil), // 47: keepassgo.v1.DeleteAttachmentRequest - (*DeleteAttachmentResponse)(nil), // 48: keepassgo.v1.DeleteAttachmentResponse - (*CopyEntryFieldRequest)(nil), // 49: keepassgo.v1.CopyEntryFieldRequest - (*CopyEntryFieldResponse)(nil), // 50: keepassgo.v1.CopyEntryFieldResponse - (*GeneratePasswordRequest)(nil), // 51: keepassgo.v1.GeneratePasswordRequest - (*GeneratePasswordResponse)(nil), // 52: keepassgo.v1.GeneratePasswordResponse - nil, // 53: keepassgo.v1.Entry.FieldsEntry + (*GetSessionStatusRequest)(nil), // 0: keepassgo.v1.GetSessionStatusRequest + (*GetSessionStatusResponse)(nil), // 1: keepassgo.v1.GetSessionStatusResponse + (*OpenVaultRequest)(nil), // 2: keepassgo.v1.OpenVaultRequest + (*OpenVaultResponse)(nil), // 3: keepassgo.v1.OpenVaultResponse + (*OpenRemoteVaultRequest)(nil), // 4: keepassgo.v1.OpenRemoteVaultRequest + (*OpenRemoteVaultResponse)(nil), // 5: keepassgo.v1.OpenRemoteVaultResponse + (*SaveVaultRequest)(nil), // 6: keepassgo.v1.SaveVaultRequest + (*SaveVaultResponse)(nil), // 7: keepassgo.v1.SaveVaultResponse + (*LockVaultRequest)(nil), // 8: keepassgo.v1.LockVaultRequest + (*LockVaultResponse)(nil), // 9: keepassgo.v1.LockVaultResponse + (*UnlockVaultRequest)(nil), // 10: keepassgo.v1.UnlockVaultRequest + (*UnlockVaultResponse)(nil), // 11: keepassgo.v1.UnlockVaultResponse + (*FindBrowserLoginsRequest)(nil), // 12: keepassgo.v1.FindBrowserLoginsRequest + (*BrowserLoginMatch)(nil), // 13: keepassgo.v1.BrowserLoginMatch + (*FindBrowserLoginsResponse)(nil), // 14: keepassgo.v1.FindBrowserLoginsResponse + (*GetBrowserCredentialRequest)(nil), // 15: keepassgo.v1.GetBrowserCredentialRequest + (*GetBrowserCredentialResponse)(nil), // 16: keepassgo.v1.GetBrowserCredentialResponse + (*ListEntriesRequest)(nil), // 17: keepassgo.v1.ListEntriesRequest + (*Entry)(nil), // 18: keepassgo.v1.Entry + (*ListEntriesResponse)(nil), // 19: keepassgo.v1.ListEntriesResponse + (*ListGroupsRequest)(nil), // 20: keepassgo.v1.ListGroupsRequest + (*ListGroupsResponse)(nil), // 21: keepassgo.v1.ListGroupsResponse + (*CreateGroupRequest)(nil), // 22: keepassgo.v1.CreateGroupRequest + (*CreateGroupResponse)(nil), // 23: keepassgo.v1.CreateGroupResponse + (*RenameGroupRequest)(nil), // 24: keepassgo.v1.RenameGroupRequest + (*RenameGroupResponse)(nil), // 25: keepassgo.v1.RenameGroupResponse + (*DeleteGroupRequest)(nil), // 26: keepassgo.v1.DeleteGroupRequest + (*DeleteGroupResponse)(nil), // 27: keepassgo.v1.DeleteGroupResponse + (*UpsertEntryRequest)(nil), // 28: keepassgo.v1.UpsertEntryRequest + (*UpsertEntryResponse)(nil), // 29: keepassgo.v1.UpsertEntryResponse + (*DeleteEntryRequest)(nil), // 30: keepassgo.v1.DeleteEntryRequest + (*DeleteEntryResponse)(nil), // 31: keepassgo.v1.DeleteEntryResponse + (*RestoreEntryRequest)(nil), // 32: keepassgo.v1.RestoreEntryRequest + (*RestoreEntryResponse)(nil), // 33: keepassgo.v1.RestoreEntryResponse + (*ListEntryHistoryRequest)(nil), // 34: keepassgo.v1.ListEntryHistoryRequest + (*ListEntryHistoryResponse)(nil), // 35: keepassgo.v1.ListEntryHistoryResponse + (*RestoreEntryHistoryRequest)(nil), // 36: keepassgo.v1.RestoreEntryHistoryRequest + (*RestoreEntryHistoryResponse)(nil), // 37: keepassgo.v1.RestoreEntryHistoryResponse + (*ListTemplatesRequest)(nil), // 38: keepassgo.v1.ListTemplatesRequest + (*ListTemplatesResponse)(nil), // 39: keepassgo.v1.ListTemplatesResponse + (*UpsertTemplateRequest)(nil), // 40: keepassgo.v1.UpsertTemplateRequest + (*UpsertTemplateResponse)(nil), // 41: keepassgo.v1.UpsertTemplateResponse + (*DeleteTemplateRequest)(nil), // 42: keepassgo.v1.DeleteTemplateRequest + (*DeleteTemplateResponse)(nil), // 43: keepassgo.v1.DeleteTemplateResponse + (*InstantiateTemplateRequest)(nil), // 44: keepassgo.v1.InstantiateTemplateRequest + (*InstantiateTemplateResponse)(nil), // 45: keepassgo.v1.InstantiateTemplateResponse + (*ListAttachmentsRequest)(nil), // 46: keepassgo.v1.ListAttachmentsRequest + (*ListAttachmentsResponse)(nil), // 47: keepassgo.v1.ListAttachmentsResponse + (*UploadAttachmentRequest)(nil), // 48: keepassgo.v1.UploadAttachmentRequest + (*UploadAttachmentResponse)(nil), // 49: keepassgo.v1.UploadAttachmentResponse + (*DownloadAttachmentRequest)(nil), // 50: keepassgo.v1.DownloadAttachmentRequest + (*DownloadAttachmentResponse)(nil), // 51: keepassgo.v1.DownloadAttachmentResponse + (*DeleteAttachmentRequest)(nil), // 52: keepassgo.v1.DeleteAttachmentRequest + (*DeleteAttachmentResponse)(nil), // 53: keepassgo.v1.DeleteAttachmentResponse + (*CopyEntryFieldRequest)(nil), // 54: keepassgo.v1.CopyEntryFieldRequest + (*CopyEntryFieldResponse)(nil), // 55: keepassgo.v1.CopyEntryFieldResponse + (*GeneratePasswordRequest)(nil), // 56: keepassgo.v1.GeneratePasswordRequest + (*GeneratePasswordResponse)(nil), // 57: keepassgo.v1.GeneratePasswordResponse + nil, // 58: keepassgo.v1.Entry.FieldsEntry } var file_proto_keepassgo_v1_keepassgo_proto_depIdxs = []int32{ - 53, // 0: keepassgo.v1.Entry.fields:type_name -> keepassgo.v1.Entry.FieldsEntry - 13, // 1: keepassgo.v1.ListEntriesResponse.entries:type_name -> keepassgo.v1.Entry - 13, // 2: keepassgo.v1.UpsertEntryRequest.entry:type_name -> keepassgo.v1.Entry - 13, // 3: keepassgo.v1.UpsertEntryResponse.entry:type_name -> keepassgo.v1.Entry - 13, // 4: keepassgo.v1.RestoreEntryResponse.entry:type_name -> keepassgo.v1.Entry - 13, // 5: keepassgo.v1.ListEntryHistoryResponse.entries:type_name -> keepassgo.v1.Entry - 13, // 6: keepassgo.v1.RestoreEntryHistoryResponse.entry:type_name -> keepassgo.v1.Entry - 13, // 7: keepassgo.v1.ListTemplatesResponse.templates:type_name -> keepassgo.v1.Entry - 13, // 8: keepassgo.v1.UpsertTemplateRequest.template:type_name -> keepassgo.v1.Entry - 13, // 9: keepassgo.v1.UpsertTemplateResponse.template:type_name -> keepassgo.v1.Entry - 13, // 10: keepassgo.v1.InstantiateTemplateRequest.overrides:type_name -> keepassgo.v1.Entry - 13, // 11: keepassgo.v1.InstantiateTemplateResponse.entry:type_name -> keepassgo.v1.Entry - 0, // 12: keepassgo.v1.VaultService.GetSessionStatus:input_type -> keepassgo.v1.GetSessionStatusRequest - 2, // 13: keepassgo.v1.VaultService.OpenVault:input_type -> keepassgo.v1.OpenVaultRequest - 4, // 14: keepassgo.v1.VaultService.OpenRemoteVault:input_type -> keepassgo.v1.OpenRemoteVaultRequest - 6, // 15: keepassgo.v1.VaultService.SaveVault:input_type -> keepassgo.v1.SaveVaultRequest - 8, // 16: keepassgo.v1.VaultService.LockVault:input_type -> keepassgo.v1.LockVaultRequest - 10, // 17: keepassgo.v1.VaultService.UnlockVault:input_type -> keepassgo.v1.UnlockVaultRequest - 12, // 18: keepassgo.v1.VaultService.ListEntries:input_type -> keepassgo.v1.ListEntriesRequest - 15, // 19: keepassgo.v1.VaultService.ListGroups:input_type -> keepassgo.v1.ListGroupsRequest - 17, // 20: keepassgo.v1.VaultService.CreateGroup:input_type -> keepassgo.v1.CreateGroupRequest - 19, // 21: keepassgo.v1.VaultService.RenameGroup:input_type -> keepassgo.v1.RenameGroupRequest - 21, // 22: keepassgo.v1.VaultService.DeleteGroup:input_type -> keepassgo.v1.DeleteGroupRequest - 23, // 23: keepassgo.v1.VaultService.UpsertEntry:input_type -> keepassgo.v1.UpsertEntryRequest - 25, // 24: keepassgo.v1.VaultService.DeleteEntry:input_type -> keepassgo.v1.DeleteEntryRequest - 27, // 25: keepassgo.v1.VaultService.RestoreEntry:input_type -> keepassgo.v1.RestoreEntryRequest - 29, // 26: keepassgo.v1.VaultService.ListEntryHistory:input_type -> keepassgo.v1.ListEntryHistoryRequest - 31, // 27: keepassgo.v1.VaultService.RestoreEntryHistory:input_type -> keepassgo.v1.RestoreEntryHistoryRequest - 33, // 28: keepassgo.v1.VaultService.ListTemplates:input_type -> keepassgo.v1.ListTemplatesRequest - 35, // 29: keepassgo.v1.VaultService.UpsertTemplate:input_type -> keepassgo.v1.UpsertTemplateRequest - 37, // 30: keepassgo.v1.VaultService.DeleteTemplate:input_type -> keepassgo.v1.DeleteTemplateRequest - 39, // 31: keepassgo.v1.VaultService.InstantiateTemplate:input_type -> keepassgo.v1.InstantiateTemplateRequest - 41, // 32: keepassgo.v1.VaultService.ListAttachments:input_type -> keepassgo.v1.ListAttachmentsRequest - 43, // 33: keepassgo.v1.VaultService.UploadAttachment:input_type -> keepassgo.v1.UploadAttachmentRequest - 45, // 34: keepassgo.v1.VaultService.DownloadAttachment:input_type -> keepassgo.v1.DownloadAttachmentRequest - 47, // 35: keepassgo.v1.VaultService.DeleteAttachment:input_type -> keepassgo.v1.DeleteAttachmentRequest - 49, // 36: keepassgo.v1.VaultService.CopyEntryField:input_type -> keepassgo.v1.CopyEntryFieldRequest - 51, // 37: keepassgo.v1.VaultService.GeneratePassword:input_type -> keepassgo.v1.GeneratePasswordRequest - 1, // 38: keepassgo.v1.VaultService.GetSessionStatus:output_type -> keepassgo.v1.GetSessionStatusResponse - 3, // 39: keepassgo.v1.VaultService.OpenVault:output_type -> keepassgo.v1.OpenVaultResponse - 5, // 40: keepassgo.v1.VaultService.OpenRemoteVault:output_type -> keepassgo.v1.OpenRemoteVaultResponse - 7, // 41: keepassgo.v1.VaultService.SaveVault:output_type -> keepassgo.v1.SaveVaultResponse - 9, // 42: keepassgo.v1.VaultService.LockVault:output_type -> keepassgo.v1.LockVaultResponse - 11, // 43: keepassgo.v1.VaultService.UnlockVault:output_type -> keepassgo.v1.UnlockVaultResponse - 14, // 44: keepassgo.v1.VaultService.ListEntries:output_type -> keepassgo.v1.ListEntriesResponse - 16, // 45: keepassgo.v1.VaultService.ListGroups:output_type -> keepassgo.v1.ListGroupsResponse - 18, // 46: keepassgo.v1.VaultService.CreateGroup:output_type -> keepassgo.v1.CreateGroupResponse - 20, // 47: keepassgo.v1.VaultService.RenameGroup:output_type -> keepassgo.v1.RenameGroupResponse - 22, // 48: keepassgo.v1.VaultService.DeleteGroup:output_type -> keepassgo.v1.DeleteGroupResponse - 24, // 49: keepassgo.v1.VaultService.UpsertEntry:output_type -> keepassgo.v1.UpsertEntryResponse - 26, // 50: keepassgo.v1.VaultService.DeleteEntry:output_type -> keepassgo.v1.DeleteEntryResponse - 28, // 51: keepassgo.v1.VaultService.RestoreEntry:output_type -> keepassgo.v1.RestoreEntryResponse - 30, // 52: keepassgo.v1.VaultService.ListEntryHistory:output_type -> keepassgo.v1.ListEntryHistoryResponse - 32, // 53: keepassgo.v1.VaultService.RestoreEntryHistory:output_type -> keepassgo.v1.RestoreEntryHistoryResponse - 34, // 54: keepassgo.v1.VaultService.ListTemplates:output_type -> keepassgo.v1.ListTemplatesResponse - 36, // 55: keepassgo.v1.VaultService.UpsertTemplate:output_type -> keepassgo.v1.UpsertTemplateResponse - 38, // 56: keepassgo.v1.VaultService.DeleteTemplate:output_type -> keepassgo.v1.DeleteTemplateResponse - 40, // 57: keepassgo.v1.VaultService.InstantiateTemplate:output_type -> keepassgo.v1.InstantiateTemplateResponse - 42, // 58: keepassgo.v1.VaultService.ListAttachments:output_type -> keepassgo.v1.ListAttachmentsResponse - 44, // 59: keepassgo.v1.VaultService.UploadAttachment:output_type -> keepassgo.v1.UploadAttachmentResponse - 46, // 60: keepassgo.v1.VaultService.DownloadAttachment:output_type -> keepassgo.v1.DownloadAttachmentResponse - 48, // 61: keepassgo.v1.VaultService.DeleteAttachment:output_type -> keepassgo.v1.DeleteAttachmentResponse - 50, // 62: keepassgo.v1.VaultService.CopyEntryField:output_type -> keepassgo.v1.CopyEntryFieldResponse - 52, // 63: keepassgo.v1.VaultService.GeneratePassword:output_type -> keepassgo.v1.GeneratePasswordResponse - 38, // [38:64] is the sub-list for method output_type - 12, // [12:38] is the sub-list for method input_type - 12, // [12:12] is the sub-list for extension type_name - 12, // [12:12] is the sub-list for extension extendee - 0, // [0:12] is the sub-list for field type_name + 13, // 0: keepassgo.v1.FindBrowserLoginsResponse.matches:type_name -> keepassgo.v1.BrowserLoginMatch + 58, // 1: keepassgo.v1.Entry.fields:type_name -> keepassgo.v1.Entry.FieldsEntry + 18, // 2: keepassgo.v1.ListEntriesResponse.entries:type_name -> keepassgo.v1.Entry + 18, // 3: keepassgo.v1.UpsertEntryRequest.entry:type_name -> keepassgo.v1.Entry + 18, // 4: keepassgo.v1.UpsertEntryResponse.entry:type_name -> keepassgo.v1.Entry + 18, // 5: keepassgo.v1.RestoreEntryResponse.entry:type_name -> keepassgo.v1.Entry + 18, // 6: keepassgo.v1.ListEntryHistoryResponse.entries:type_name -> keepassgo.v1.Entry + 18, // 7: keepassgo.v1.RestoreEntryHistoryResponse.entry:type_name -> keepassgo.v1.Entry + 18, // 8: keepassgo.v1.ListTemplatesResponse.templates:type_name -> keepassgo.v1.Entry + 18, // 9: keepassgo.v1.UpsertTemplateRequest.template:type_name -> keepassgo.v1.Entry + 18, // 10: keepassgo.v1.UpsertTemplateResponse.template:type_name -> keepassgo.v1.Entry + 18, // 11: keepassgo.v1.InstantiateTemplateRequest.overrides:type_name -> keepassgo.v1.Entry + 18, // 12: keepassgo.v1.InstantiateTemplateResponse.entry:type_name -> keepassgo.v1.Entry + 0, // 13: keepassgo.v1.VaultService.GetSessionStatus:input_type -> keepassgo.v1.GetSessionStatusRequest + 2, // 14: keepassgo.v1.VaultService.OpenVault:input_type -> keepassgo.v1.OpenVaultRequest + 4, // 15: keepassgo.v1.VaultService.OpenRemoteVault:input_type -> keepassgo.v1.OpenRemoteVaultRequest + 6, // 16: keepassgo.v1.VaultService.SaveVault:input_type -> keepassgo.v1.SaveVaultRequest + 8, // 17: keepassgo.v1.VaultService.LockVault:input_type -> keepassgo.v1.LockVaultRequest + 10, // 18: keepassgo.v1.VaultService.UnlockVault:input_type -> keepassgo.v1.UnlockVaultRequest + 12, // 19: keepassgo.v1.VaultService.FindBrowserLogins:input_type -> keepassgo.v1.FindBrowserLoginsRequest + 15, // 20: keepassgo.v1.VaultService.GetBrowserCredential:input_type -> keepassgo.v1.GetBrowserCredentialRequest + 17, // 21: keepassgo.v1.VaultService.ListEntries:input_type -> keepassgo.v1.ListEntriesRequest + 20, // 22: keepassgo.v1.VaultService.ListGroups:input_type -> keepassgo.v1.ListGroupsRequest + 22, // 23: keepassgo.v1.VaultService.CreateGroup:input_type -> keepassgo.v1.CreateGroupRequest + 24, // 24: keepassgo.v1.VaultService.RenameGroup:input_type -> keepassgo.v1.RenameGroupRequest + 26, // 25: keepassgo.v1.VaultService.DeleteGroup:input_type -> keepassgo.v1.DeleteGroupRequest + 28, // 26: keepassgo.v1.VaultService.UpsertEntry:input_type -> keepassgo.v1.UpsertEntryRequest + 30, // 27: keepassgo.v1.VaultService.DeleteEntry:input_type -> keepassgo.v1.DeleteEntryRequest + 32, // 28: keepassgo.v1.VaultService.RestoreEntry:input_type -> keepassgo.v1.RestoreEntryRequest + 34, // 29: keepassgo.v1.VaultService.ListEntryHistory:input_type -> keepassgo.v1.ListEntryHistoryRequest + 36, // 30: keepassgo.v1.VaultService.RestoreEntryHistory:input_type -> keepassgo.v1.RestoreEntryHistoryRequest + 38, // 31: keepassgo.v1.VaultService.ListTemplates:input_type -> keepassgo.v1.ListTemplatesRequest + 40, // 32: keepassgo.v1.VaultService.UpsertTemplate:input_type -> keepassgo.v1.UpsertTemplateRequest + 42, // 33: keepassgo.v1.VaultService.DeleteTemplate:input_type -> keepassgo.v1.DeleteTemplateRequest + 44, // 34: keepassgo.v1.VaultService.InstantiateTemplate:input_type -> keepassgo.v1.InstantiateTemplateRequest + 46, // 35: keepassgo.v1.VaultService.ListAttachments:input_type -> keepassgo.v1.ListAttachmentsRequest + 48, // 36: keepassgo.v1.VaultService.UploadAttachment:input_type -> keepassgo.v1.UploadAttachmentRequest + 50, // 37: keepassgo.v1.VaultService.DownloadAttachment:input_type -> keepassgo.v1.DownloadAttachmentRequest + 52, // 38: keepassgo.v1.VaultService.DeleteAttachment:input_type -> keepassgo.v1.DeleteAttachmentRequest + 54, // 39: keepassgo.v1.VaultService.CopyEntryField:input_type -> keepassgo.v1.CopyEntryFieldRequest + 56, // 40: keepassgo.v1.VaultService.GeneratePassword:input_type -> keepassgo.v1.GeneratePasswordRequest + 1, // 41: keepassgo.v1.VaultService.GetSessionStatus:output_type -> keepassgo.v1.GetSessionStatusResponse + 3, // 42: keepassgo.v1.VaultService.OpenVault:output_type -> keepassgo.v1.OpenVaultResponse + 5, // 43: keepassgo.v1.VaultService.OpenRemoteVault:output_type -> keepassgo.v1.OpenRemoteVaultResponse + 7, // 44: keepassgo.v1.VaultService.SaveVault:output_type -> keepassgo.v1.SaveVaultResponse + 9, // 45: keepassgo.v1.VaultService.LockVault:output_type -> keepassgo.v1.LockVaultResponse + 11, // 46: keepassgo.v1.VaultService.UnlockVault:output_type -> keepassgo.v1.UnlockVaultResponse + 14, // 47: keepassgo.v1.VaultService.FindBrowserLogins:output_type -> keepassgo.v1.FindBrowserLoginsResponse + 16, // 48: keepassgo.v1.VaultService.GetBrowserCredential:output_type -> keepassgo.v1.GetBrowserCredentialResponse + 19, // 49: keepassgo.v1.VaultService.ListEntries:output_type -> keepassgo.v1.ListEntriesResponse + 21, // 50: keepassgo.v1.VaultService.ListGroups:output_type -> keepassgo.v1.ListGroupsResponse + 23, // 51: keepassgo.v1.VaultService.CreateGroup:output_type -> keepassgo.v1.CreateGroupResponse + 25, // 52: keepassgo.v1.VaultService.RenameGroup:output_type -> keepassgo.v1.RenameGroupResponse + 27, // 53: keepassgo.v1.VaultService.DeleteGroup:output_type -> keepassgo.v1.DeleteGroupResponse + 29, // 54: keepassgo.v1.VaultService.UpsertEntry:output_type -> keepassgo.v1.UpsertEntryResponse + 31, // 55: keepassgo.v1.VaultService.DeleteEntry:output_type -> keepassgo.v1.DeleteEntryResponse + 33, // 56: keepassgo.v1.VaultService.RestoreEntry:output_type -> keepassgo.v1.RestoreEntryResponse + 35, // 57: keepassgo.v1.VaultService.ListEntryHistory:output_type -> keepassgo.v1.ListEntryHistoryResponse + 37, // 58: keepassgo.v1.VaultService.RestoreEntryHistory:output_type -> keepassgo.v1.RestoreEntryHistoryResponse + 39, // 59: keepassgo.v1.VaultService.ListTemplates:output_type -> keepassgo.v1.ListTemplatesResponse + 41, // 60: keepassgo.v1.VaultService.UpsertTemplate:output_type -> keepassgo.v1.UpsertTemplateResponse + 43, // 61: keepassgo.v1.VaultService.DeleteTemplate:output_type -> keepassgo.v1.DeleteTemplateResponse + 45, // 62: keepassgo.v1.VaultService.InstantiateTemplate:output_type -> keepassgo.v1.InstantiateTemplateResponse + 47, // 63: keepassgo.v1.VaultService.ListAttachments:output_type -> keepassgo.v1.ListAttachmentsResponse + 49, // 64: keepassgo.v1.VaultService.UploadAttachment:output_type -> keepassgo.v1.UploadAttachmentResponse + 51, // 65: keepassgo.v1.VaultService.DownloadAttachment:output_type -> keepassgo.v1.DownloadAttachmentResponse + 53, // 66: keepassgo.v1.VaultService.DeleteAttachment:output_type -> keepassgo.v1.DeleteAttachmentResponse + 55, // 67: keepassgo.v1.VaultService.CopyEntryField:output_type -> keepassgo.v1.CopyEntryFieldResponse + 57, // 68: keepassgo.v1.VaultService.GeneratePassword:output_type -> keepassgo.v1.GeneratePasswordResponse + 41, // [41:69] is the sub-list for method output_type + 13, // [13:41] is the sub-list for method input_type + 13, // [13:13] is the sub-list for extension type_name + 13, // [13:13] is the sub-list for extension extendee + 0, // [0:13] is the sub-list for field type_name } func init() { file_proto_keepassgo_v1_keepassgo_proto_init() } @@ -2750,7 +3073,7 @@ func file_proto_keepassgo_v1_keepassgo_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_proto_keepassgo_v1_keepassgo_proto_rawDesc), len(file_proto_keepassgo_v1_keepassgo_proto_rawDesc)), NumEnums: 0, - NumMessages: 54, + NumMessages: 59, NumExtensions: 0, NumServices: 1, }, diff --git a/proto/keepassgo/v1/keepassgo.proto b/proto/keepassgo/v1/keepassgo.proto index ef57ec8..5b3208f 100644 --- a/proto/keepassgo/v1/keepassgo.proto +++ b/proto/keepassgo/v1/keepassgo.proto @@ -11,6 +11,8 @@ service VaultService { rpc SaveVault(SaveVaultRequest) returns (SaveVaultResponse); rpc LockVault(LockVaultRequest) returns (LockVaultResponse); rpc UnlockVault(UnlockVaultRequest) returns (UnlockVaultResponse); + rpc FindBrowserLogins(FindBrowserLoginsRequest) returns (FindBrowserLoginsResponse); + rpc GetBrowserCredential(GetBrowserCredentialRequest) returns (GetBrowserCredentialResponse); rpc ListEntries(ListEntriesRequest) returns (ListEntriesResponse); rpc ListGroups(ListGroupsRequest) returns (ListGroupsResponse); rpc CreateGroup(CreateGroupRequest) returns (CreateGroupResponse); @@ -75,6 +77,35 @@ message UnlockVaultRequest { message UnlockVaultResponse {} +message FindBrowserLoginsRequest { + string page_url = 1; +} + +message BrowserLoginMatch { + string id = 1; + string title = 2; + string username = 3; + string url = 4; + repeated string path = 5; + string quality = 6; +} + +message FindBrowserLoginsResponse { + repeated BrowserLoginMatch matches = 1; +} + +message GetBrowserCredentialRequest { + string id = 1; + string page_url = 2; +} + +message GetBrowserCredentialResponse { + string id = 1; + string username = 2; + string password = 3; + string url = 4; +} + message ListEntriesRequest { repeated string path = 1; string query = 2; diff --git a/proto/keepassgo/v1/keepassgo_grpc.pb.go b/proto/keepassgo/v1/keepassgo_grpc.pb.go index 2420a58..0be1429 100644 --- a/proto/keepassgo/v1/keepassgo_grpc.pb.go +++ b/proto/keepassgo/v1/keepassgo_grpc.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go-grpc. DO NOT EDIT. // versions: // - protoc-gen-go-grpc v1.5.1 -// - protoc v6.33.1 +// - protoc v7.34.1 // source: proto/keepassgo/v1/keepassgo.proto package keepassgov1 @@ -19,32 +19,34 @@ import ( const _ = grpc.SupportPackageIsVersion9 const ( - VaultService_GetSessionStatus_FullMethodName = "/keepassgo.v1.VaultService/GetSessionStatus" - VaultService_OpenVault_FullMethodName = "/keepassgo.v1.VaultService/OpenVault" - VaultService_OpenRemoteVault_FullMethodName = "/keepassgo.v1.VaultService/OpenRemoteVault" - VaultService_SaveVault_FullMethodName = "/keepassgo.v1.VaultService/SaveVault" - VaultService_LockVault_FullMethodName = "/keepassgo.v1.VaultService/LockVault" - VaultService_UnlockVault_FullMethodName = "/keepassgo.v1.VaultService/UnlockVault" - VaultService_ListEntries_FullMethodName = "/keepassgo.v1.VaultService/ListEntries" - VaultService_ListGroups_FullMethodName = "/keepassgo.v1.VaultService/ListGroups" - VaultService_CreateGroup_FullMethodName = "/keepassgo.v1.VaultService/CreateGroup" - VaultService_RenameGroup_FullMethodName = "/keepassgo.v1.VaultService/RenameGroup" - VaultService_DeleteGroup_FullMethodName = "/keepassgo.v1.VaultService/DeleteGroup" - VaultService_UpsertEntry_FullMethodName = "/keepassgo.v1.VaultService/UpsertEntry" - VaultService_DeleteEntry_FullMethodName = "/keepassgo.v1.VaultService/DeleteEntry" - VaultService_RestoreEntry_FullMethodName = "/keepassgo.v1.VaultService/RestoreEntry" - VaultService_ListEntryHistory_FullMethodName = "/keepassgo.v1.VaultService/ListEntryHistory" - VaultService_RestoreEntryHistory_FullMethodName = "/keepassgo.v1.VaultService/RestoreEntryHistory" - VaultService_ListTemplates_FullMethodName = "/keepassgo.v1.VaultService/ListTemplates" - VaultService_UpsertTemplate_FullMethodName = "/keepassgo.v1.VaultService/UpsertTemplate" - VaultService_DeleteTemplate_FullMethodName = "/keepassgo.v1.VaultService/DeleteTemplate" - VaultService_InstantiateTemplate_FullMethodName = "/keepassgo.v1.VaultService/InstantiateTemplate" - VaultService_ListAttachments_FullMethodName = "/keepassgo.v1.VaultService/ListAttachments" - VaultService_UploadAttachment_FullMethodName = "/keepassgo.v1.VaultService/UploadAttachment" - VaultService_DownloadAttachment_FullMethodName = "/keepassgo.v1.VaultService/DownloadAttachment" - VaultService_DeleteAttachment_FullMethodName = "/keepassgo.v1.VaultService/DeleteAttachment" - VaultService_CopyEntryField_FullMethodName = "/keepassgo.v1.VaultService/CopyEntryField" - VaultService_GeneratePassword_FullMethodName = "/keepassgo.v1.VaultService/GeneratePassword" + VaultService_GetSessionStatus_FullMethodName = "/keepassgo.v1.VaultService/GetSessionStatus" + VaultService_OpenVault_FullMethodName = "/keepassgo.v1.VaultService/OpenVault" + VaultService_OpenRemoteVault_FullMethodName = "/keepassgo.v1.VaultService/OpenRemoteVault" + VaultService_SaveVault_FullMethodName = "/keepassgo.v1.VaultService/SaveVault" + VaultService_LockVault_FullMethodName = "/keepassgo.v1.VaultService/LockVault" + VaultService_UnlockVault_FullMethodName = "/keepassgo.v1.VaultService/UnlockVault" + VaultService_FindBrowserLogins_FullMethodName = "/keepassgo.v1.VaultService/FindBrowserLogins" + VaultService_GetBrowserCredential_FullMethodName = "/keepassgo.v1.VaultService/GetBrowserCredential" + VaultService_ListEntries_FullMethodName = "/keepassgo.v1.VaultService/ListEntries" + VaultService_ListGroups_FullMethodName = "/keepassgo.v1.VaultService/ListGroups" + VaultService_CreateGroup_FullMethodName = "/keepassgo.v1.VaultService/CreateGroup" + VaultService_RenameGroup_FullMethodName = "/keepassgo.v1.VaultService/RenameGroup" + VaultService_DeleteGroup_FullMethodName = "/keepassgo.v1.VaultService/DeleteGroup" + VaultService_UpsertEntry_FullMethodName = "/keepassgo.v1.VaultService/UpsertEntry" + VaultService_DeleteEntry_FullMethodName = "/keepassgo.v1.VaultService/DeleteEntry" + VaultService_RestoreEntry_FullMethodName = "/keepassgo.v1.VaultService/RestoreEntry" + VaultService_ListEntryHistory_FullMethodName = "/keepassgo.v1.VaultService/ListEntryHistory" + VaultService_RestoreEntryHistory_FullMethodName = "/keepassgo.v1.VaultService/RestoreEntryHistory" + VaultService_ListTemplates_FullMethodName = "/keepassgo.v1.VaultService/ListTemplates" + VaultService_UpsertTemplate_FullMethodName = "/keepassgo.v1.VaultService/UpsertTemplate" + VaultService_DeleteTemplate_FullMethodName = "/keepassgo.v1.VaultService/DeleteTemplate" + VaultService_InstantiateTemplate_FullMethodName = "/keepassgo.v1.VaultService/InstantiateTemplate" + VaultService_ListAttachments_FullMethodName = "/keepassgo.v1.VaultService/ListAttachments" + VaultService_UploadAttachment_FullMethodName = "/keepassgo.v1.VaultService/UploadAttachment" + VaultService_DownloadAttachment_FullMethodName = "/keepassgo.v1.VaultService/DownloadAttachment" + VaultService_DeleteAttachment_FullMethodName = "/keepassgo.v1.VaultService/DeleteAttachment" + VaultService_CopyEntryField_FullMethodName = "/keepassgo.v1.VaultService/CopyEntryField" + VaultService_GeneratePassword_FullMethodName = "/keepassgo.v1.VaultService/GeneratePassword" ) // VaultServiceClient is the client API for VaultService service. @@ -57,6 +59,8 @@ type VaultServiceClient interface { SaveVault(ctx context.Context, in *SaveVaultRequest, opts ...grpc.CallOption) (*SaveVaultResponse, error) LockVault(ctx context.Context, in *LockVaultRequest, opts ...grpc.CallOption) (*LockVaultResponse, error) UnlockVault(ctx context.Context, in *UnlockVaultRequest, opts ...grpc.CallOption) (*UnlockVaultResponse, error) + FindBrowserLogins(ctx context.Context, in *FindBrowserLoginsRequest, opts ...grpc.CallOption) (*FindBrowserLoginsResponse, error) + GetBrowserCredential(ctx context.Context, in *GetBrowserCredentialRequest, opts ...grpc.CallOption) (*GetBrowserCredentialResponse, error) ListEntries(ctx context.Context, in *ListEntriesRequest, opts ...grpc.CallOption) (*ListEntriesResponse, error) ListGroups(ctx context.Context, in *ListGroupsRequest, opts ...grpc.CallOption) (*ListGroupsResponse, error) CreateGroup(ctx context.Context, in *CreateGroupRequest, opts ...grpc.CallOption) (*CreateGroupResponse, error) @@ -147,6 +151,26 @@ func (c *vaultServiceClient) UnlockVault(ctx context.Context, in *UnlockVaultReq return out, nil } +func (c *vaultServiceClient) FindBrowserLogins(ctx context.Context, in *FindBrowserLoginsRequest, opts ...grpc.CallOption) (*FindBrowserLoginsResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(FindBrowserLoginsResponse) + err := c.cc.Invoke(ctx, VaultService_FindBrowserLogins_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *vaultServiceClient) GetBrowserCredential(ctx context.Context, in *GetBrowserCredentialRequest, opts ...grpc.CallOption) (*GetBrowserCredentialResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(GetBrowserCredentialResponse) + err := c.cc.Invoke(ctx, VaultService_GetBrowserCredential_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + func (c *vaultServiceClient) ListEntries(ctx context.Context, in *ListEntriesRequest, opts ...grpc.CallOption) (*ListEntriesResponse, error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(ListEntriesResponse) @@ -357,6 +381,8 @@ type VaultServiceServer interface { SaveVault(context.Context, *SaveVaultRequest) (*SaveVaultResponse, error) LockVault(context.Context, *LockVaultRequest) (*LockVaultResponse, error) UnlockVault(context.Context, *UnlockVaultRequest) (*UnlockVaultResponse, error) + FindBrowserLogins(context.Context, *FindBrowserLoginsRequest) (*FindBrowserLoginsResponse, error) + GetBrowserCredential(context.Context, *GetBrowserCredentialRequest) (*GetBrowserCredentialResponse, error) ListEntries(context.Context, *ListEntriesRequest) (*ListEntriesResponse, error) ListGroups(context.Context, *ListGroupsRequest) (*ListGroupsResponse, error) CreateGroup(context.Context, *CreateGroupRequest) (*CreateGroupResponse, error) @@ -405,6 +431,12 @@ func (UnimplementedVaultServiceServer) LockVault(context.Context, *LockVaultRequ func (UnimplementedVaultServiceServer) UnlockVault(context.Context, *UnlockVaultRequest) (*UnlockVaultResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method UnlockVault not implemented") } +func (UnimplementedVaultServiceServer) FindBrowserLogins(context.Context, *FindBrowserLoginsRequest) (*FindBrowserLoginsResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method FindBrowserLogins not implemented") +} +func (UnimplementedVaultServiceServer) GetBrowserCredential(context.Context, *GetBrowserCredentialRequest) (*GetBrowserCredentialResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetBrowserCredential not implemented") +} func (UnimplementedVaultServiceServer) ListEntries(context.Context, *ListEntriesRequest) (*ListEntriesResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method ListEntries not implemented") } @@ -594,6 +626,42 @@ func _VaultService_UnlockVault_Handler(srv interface{}, ctx context.Context, dec return interceptor(ctx, in, info, handler) } +func _VaultService_FindBrowserLogins_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(FindBrowserLoginsRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(VaultServiceServer).FindBrowserLogins(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: VaultService_FindBrowserLogins_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(VaultServiceServer).FindBrowserLogins(ctx, req.(*FindBrowserLoginsRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _VaultService_GetBrowserCredential_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetBrowserCredentialRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(VaultServiceServer).GetBrowserCredential(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: VaultService_GetBrowserCredential_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(VaultServiceServer).GetBrowserCredential(ctx, req.(*GetBrowserCredentialRequest)) + } + return interceptor(ctx, in, info, handler) +} + func _VaultService_ListEntries_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(ListEntriesRequest) if err := dec(in); err != nil { @@ -985,6 +1053,14 @@ var VaultService_ServiceDesc = grpc.ServiceDesc{ MethodName: "UnlockVault", Handler: _VaultService_UnlockVault_Handler, }, + { + MethodName: "FindBrowserLogins", + Handler: _VaultService_FindBrowserLogins_Handler, + }, + { + MethodName: "GetBrowserCredential", + Handler: _VaultService_GetBrowserCredential_Handler, + }, { MethodName: "ListEntries", Handler: _VaultService_ListEntries_Handler, From 2ef571c241d6544a6ccf6dcb017f932c875bd1b3 Mon Sep 17 00:00:00 2001 From: Joe Julian Date: Sat, 11 Apr 2026 08:26:37 -0700 Subject: [PATCH 02/19] Use runtime-dir Unix sockets for local gRPC --- README.md | 3 ++ browser/extension/README.md | 4 +- browser/extension/background.js | 4 +- browser/extension/manifest.firefox.json | 2 +- browser/extension/options.html | 2 +- browser/extension/options.js | 2 +- cmd/keepassgo-browser-bridge/main.go | 26 +++++++++- docs/browser-extension.md | 26 ++++++++-- internal/api/host.go | 53 ++++++++++++++++++-- internal/api/host_test.go | 43 +++++++++++++++- internal/appui/runtime.go | 6 +-- internal/browserbridge/bridge.go | 37 ++++++++++++-- internal/browserbridge/bridge_test.go | 34 +++++++++++++ internal/browserbridge/client.go | 19 +++++-- internal/grpcaddr/addr.go | 66 +++++++++++++++++++++++++ internal/grpcaddr/addr_test.go | 48 ++++++++++++++++++ 16 files changed, 346 insertions(+), 29 deletions(-) create mode 100644 internal/grpcaddr/addr.go create mode 100644 internal/grpcaddr/addr_test.go diff --git a/README.md b/README.md index 19fe7c0..a84c875 100644 --- a/README.md +++ b/README.md @@ -100,6 +100,9 @@ You will need the Android SDK and NDK installed and configured for real device o Desktop automation is resolved through the secure gRPC API rather than synthetic auto-type. See [`docs/desktop-automation.md`](./docs/desktop-automation.md). +On desktop, KeePassGO now listens on a Unix socket by default under the user runtime directory. +Set `KEEPASSGO_GRPC_ADDR` or `-grpc-addr` to override it, for example `tcp://127.0.0.1:47777`. + ## Browser Extension Firefox and Chromium browser integration is available through the local gRPC API plus a native messaging bridge. diff --git a/browser/extension/README.md b/browser/extension/README.md index fbd3f03..0da7df8 100644 --- a/browser/extension/README.md +++ b/browser/extension/README.md @@ -2,9 +2,9 @@ Shared extension assets for Firefox and Chromium-based browsers live here. -- `manifest.firefox.json` uses the fixed Firefox extension id `browser@keepassgo.invalid` +- `manifest.firefox.json` uses the fixed Firefox extension id `browser@keepassgo.com` - `manifest.chromium.json` is the Chromium/Chrome manifest template -- `background.js` talks to the native messaging host `org.keepassgo.browser` +- `background.js` talks to the native messaging host `com.keepassgo.browser` - `content.js` fills username and password fields on the current page - `options.html` stores the local gRPC address and API token in browser extension storage diff --git a/browser/extension/background.js b/browser/extension/background.js index 07e0a76..d77862f 100644 --- a/browser/extension/background.js +++ b/browser/extension/background.js @@ -1,7 +1,7 @@ const ext = globalThis.browser ?? globalThis.chrome; -const nativeHost = "org.keepassgo.browser"; +const nativeHost = "com.keepassgo.browser"; const defaultSettings = { - grpcAddress: "127.0.0.1:47777", + grpcAddress: "", bearerToken: "" }; diff --git a/browser/extension/manifest.firefox.json b/browser/extension/manifest.firefox.json index ea23659..2632fb5 100644 --- a/browser/extension/manifest.firefox.json +++ b/browser/extension/manifest.firefox.json @@ -25,7 +25,7 @@ ], "browser_specific_settings": { "gecko": { - "id": "browser@keepassgo.invalid" + "id": "browser@keepassgo.com" } } } diff --git a/browser/extension/options.html b/browser/extension/options.html index a1f1246..d1b0b6e 100644 --- a/browser/extension/options.html +++ b/browser/extension/options.html @@ -17,7 +17,7 @@