summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFlorian Bruhin <me@the-compiler.org>2022-06-13 11:26:43 +0200
committerFlorian Bruhin <me@the-compiler.org>2022-06-13 11:26:43 +0200
commit870fccb36625bfdf2f29a60bdf33d9a98d5b3214 (patch)
treebfc16786193cfaa54c92c9f81618fbe9e57fc73f
parent609a862e019835e934590e82ed66347569781a4b (diff)
parent6c1d6dd2c55299f348dc512fb1943b0afdf7d7cc (diff)
downloadqutebrowser-870fccb36625bfdf2f29a60bdf33d9a98d5b3214.tar.gz
qutebrowser-870fccb36625bfdf2f29a60bdf33d9a98d5b3214.zip
Merge remote-tracking branch 'origin/pr/7196'
-rwxr-xr-xmisc/userscripts/qute-keepassxc94
1 files changed, 89 insertions, 5 deletions
diff --git a/misc/userscripts/qute-keepassxc b/misc/userscripts/qute-keepassxc
index a128c2c3e..61a6c7bce 100755
--- a/misc/userscripts/qute-keepassxc
+++ b/misc/userscripts/qute-keepassxc
@@ -43,6 +43,8 @@ config.bind('<Alt-Shift-u>', 'spawn --userscript qute-keepassxc --key ABC1234',
config.bind('pw', 'spawn --userscript qute-keepassxc --key ABC1234', mode='normal')
```
+To manage multiple accounts you also need [rofi](https://github.com/davatorium/rofi) installed.
+
# Usage
@@ -65,6 +67,26 @@ Therefore you need to have a public-key-pair readily set up.
GPG might then ask for your private-key password whenever you query the database for login credentials.
+# TOTP
+
+This script recently received experimental TOTP support.
+To use it, you need to have working TOTP authentication within KeepassXC.
+Then call `qute-keepassxc` with the `--totp` flags.
+
+For example, I have the following line in my `config.py`:
+
+```python
+config.bind('pt', 'spawn --userscript qute-keepassxc --key ABC1234 --totp', mode='normal')
+```
+
+For now this script will simply insert the TOTP-token into the currently selected
+input field, since I have not yet found a reliable way to identify the correct field
+within all existing login forms.
+Thus you need to manually select the TOTP input field, press escape to leave input
+mode and then enter `pt` to fill in the token (or configure another key-binding for
+insert mode if you prefer that).
+
+
[1]: https://keepassxc.org/
[2]: https://qutebrowser.org/
[3]: https://gnupg.org/
@@ -88,6 +110,8 @@ import nacl.public
def parse_args():
parser = argparse.ArgumentParser(description="Full passwords from KeepassXC")
parser.add_argument('url', nargs='?', default=os.environ.get('QUTE_URL'))
+ parser.add_argument('--totp', action='store_true',
+ help="Fill in current TOTP field instead of username/password")
parser.add_argument('--socket', '-s', default='/run/user/{}/org.keepassxc.KeePassXC.BrowserServer'.format(os.getuid()),
help='Path to KeepassXC browser socket')
parser.add_argument('--key', '-k', default='alice@example.com',
@@ -160,7 +184,7 @@ class KeepassXC:
action = 'test-associate',
id = self.id,
key = base64.b64encode(self.id_key.public_key.encode()).decode('utf-8')
- ))
+ ), triggerUnlock = 'true')
return self.recv_msg()['success'] == 'true'
def associate(self):
@@ -180,6 +204,16 @@ class KeepassXC:
))
return self.recv_msg()['entries']
+ def get_totp(self, uuid):
+ self.send_msg(dict(
+ action = 'get-totp',
+ uuid = uuid
+ ))
+ response = self.recv_msg()
+ if response['success'] != 'true' or not response['totp']:
+ return None
+ return response['totp']
+
def send_raw_msg(self, msg):
self.sock.send( json.dumps(msg).encode('utf-8') )
@@ -274,6 +308,30 @@ def connect_to_keepassxc(args):
return kp
+def select_account(creds):
+ try:
+ if len(creds) == 1:
+ return creds[0]
+ idx = subprocess.check_output(
+ ['rofi', '-dmenu', '-format', 'i', '-matching', 'fuzzy',
+ '-p', 'Search',
+ '-mesg', '<b>qute-keepassxc</b>: select an account, please!'],
+ input=b"\n".join(c['login'].encode('utf-8') for c in creds)
+ )
+ idx = int(idx)
+ if idx < 0:
+ return None
+ return creds[idx]
+ except subprocess.CalledProcessError:
+ return None
+ except FileNotFoundError:
+ error("rofi not found. Please install rofi to select from multiple credentials")
+ return creds[0]
+ except Exception as e:
+ error(f"Error while picking account: {e}")
+ return None
+
+
def make_js_code(username, password):
return ' '.join("""
function isVisible(elem) {
@@ -335,6 +393,21 @@ def make_js_code(username, password):
""".splitlines()) % (json.dumps(username), json.dumps(password))
+def make_js_totp_code(totp):
+ return ' '.join("""
+ (function () {
+ var input = document.activeElement;
+ if (!input || input.tagName !== "INPUT") {
+ alert("No TOTP input field selected");
+ return;
+ }
+ input.value = %s;
+ input.dispatchEvent(new Event('input', { 'bubbles': true }));
+ input.dispatchEvent(new Event('change', { 'bubbles': true }));
+ })();
+ """.splitlines()) % (json.dumps(totp),)
+
+
def main():
if 'QUTE_FIFO' not in os.environ:
print(f"No QUTE_FIFO found - {sys.argv[0]} must be run as a qutebrowser userscript")
@@ -351,10 +424,21 @@ def main():
if not creds:
error('No credentials found')
return
- # TODO: handle multiple matches
- name, pw = creds[0]['login'], creds[0]['password']
- if name and pw:
- qute('jseval -q ' + make_js_code(name, pw))
+ cred = select_account(creds)
+ if not cred:
+ error('No credentials selected')
+ return
+ if args.totp:
+ uuid = cred['uuid']
+ totp = kp.get_totp(uuid)
+ if not totp:
+ error('No TOTP key found')
+ return
+ qute('jseval -q ' + make_js_totp_code(totp))
+ else:
+ name, pw = cred['login'], cred['password']
+ if name and pw:
+ qute('jseval -q ' + make_js_code(name, pw))
except Exception as e:
error(str(e))