Skip to content

Commit

Permalink
mode/password: filename-based usernames for pass
Browse files Browse the repository at this point in the history
Fixes atlas-engineer#3293
The new filename-based username check is used as a fallback to the
current key-based username behavior.

Users who only want the filename-based behavior can disable scanning the
credential for username keys via the new scan-for-username-entries slot.
Disabling scanning for a key-based username is particularly useful in
use-cases where credential files are encrypted and require touch
verification to open.
  • Loading branch information
chaorace committed Jul 9, 2024
1 parent 102f4db commit 8ffda32
Showing 1 changed file with 51 additions and 17 deletions.
68 changes: 51 additions & 17 deletions libraries/password-manager/password-pass.lisp
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,20 @@
(define-class password-store-interface (password-interface)
((executable (pathname->string (sera:resolve-executable "pass")))
(sleep-timer (or (uiop:getenv "PASSWORD_STORE_CLIP_TIME") 45))
(password-directory (or (uiop:getenv "PASSWORD_STORE_DIR")
(format nil "~a/.password-store" (uiop:getenv "HOME")))
:type string
:reader password-directory))
(password-directory
(or (uiop:getenv "PASSWORD_STORE_DIR")
(format nil "~a/.password-store" (uiop:getenv "HOME")))
:type string
:reader password-directory)
(scan-for-username-entries
t
:type boolean
:documentation "When non-nil, a two-step process determines the username:
1. Scan credential content for an entry with a key matching `*username-keys*'.
2. If no match could be found, use the credential filename as a fallback.
When nil, Nyxt always immediately uses the fallback strategy.
If your store doesn't utilize username keys, this skips credential decryption."))
(:export-class-name-p t)
(:export-accessor-names-p t))

Expand Down Expand Up @@ -69,22 +79,46 @@ The first line (the password) is skipped."
(defvar *username-keys* '("login" "user" "username")
"A list of string keys used to find the `pass' username in `clip-username'.")

(defmethod clip-username ((password-interface password-store-interface) &key password-name service)
"Save the multiline entry that's prefixed with on of the `*username-keys*' to clipboard.
(defun username-from-name (password-name)
"Select a username using the path of the credential file.
The strategy for deriving the username is context-dependent:
- If credential exists in a subdirectory (e.g.: x.example/me@y.example),
username is taken as-is from the filename (me@y.example)
- Otherwise, if no subdirectory is used (e.g.: me@x.example@y.example),
username is taken from the first half of the filename (me@x.example)"
(multiple-value-bind (_ parent-dirs credential-name)
(uiop/pathname:split-unix-namestring-directory-components password-name)
(declare (ignore _))
(if parent-dirs
credential-name
(subseq credential-name 0 (position #\@ credential-name :from-end t)))))

(defun username-from-content (password-interface password-name)
"Select username from first entry in credential matching `*username-keys*'"
(let* ((content (execute password-interface (list "show" password-name)
:output '(:string :stripped t)))
(entries (parse-multiline content))
(username-entry (when entries
(some (lambda (key)
(find key entries :test #'string-equal :key #'first))
*username-keys*))))
(when username-entry (second username-entry))))

(defmethod clip-username ((password-interface password-store-interface)
&key password-name service)
"Save username of the `password-name' credential to clipboard.
See the `scan-for-usename-entries' slot for details.
Case is ignored.
The prefix is discarded from the result and returned."
The resulting username is also returned."
(declare (ignore service))
(when password-name
(let* ((content (execute password-interface (list "show" password-name)
:output '(:string :stripped t)))
(entries (parse-multiline content))
(username-entry (when entries
(some (lambda (key)
(find key entries :test #'string-equal :key #'first))
*username-keys*))))
(when username-entry
(trivial-clipboard:text (second username-entry))
(second username-entry)))))
(let ((username
(or (when (scan-for-username-entries password-interface)
(username-from-content password-interface password-name))
(username-from-name password-name))))
(when username
(trivial-clipboard:text username)
username))))

(defmethod save-password ((password-interface password-store-interface)
&key password-name username password service)
Expand Down

0 comments on commit 8ffda32

Please sign in to comment.