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 7, 2024
1 parent e08b998 commit 4fd53ff
Showing 1 changed file with 46 additions and 13 deletions.
59 changes: 46 additions & 13 deletions libraries/password-manager/password-pass.lisp
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,14 @@
(password-directory (or (uiop:getenv "PASSWORD_STORE_DIR")
(format nil "~a/.password-store" (uiop:getenv "HOME")))
:type string
:reader password-directory))
:reader password-directory)
(scan-for-username-entries t
:type boolean
:documentation "If t, Nyxt uses a two-step process to determine a credential's username:
1. Open the credential file and scan for an entry with a key matching `*username-keys*'.
2. If no username key could be found, fall back to determining username based on the credential's filename.
If nil, Nyxt skips the first step and instead always uses the fallback strategy. This is useful if you don't use key-based usernames in your password store as it skips unnecessarily decrypting the credential file (if originally encrypted)."))
(:export-class-name-p t)
(:export-accessor-names-p t))

Expand Down Expand Up @@ -69,22 +76,48 @@ 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'.")

(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 the credential exists in a subdirectory of the password store (e.g.: example.com/[email protected]),
username is taken as-is from the filename ([email protected])
- If the credential exists in the root of the password store (e.g.: [email protected]@example.com),
username will be the first half of the filename ([email protected])
This is meant to handle the naming patterns described in Emacs documentation
https://www.gnu.org/software/emacs/manual/html_node/auth/The-Unix-password-store.html
"
(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 a username from the first entry within the `password-name' 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 the multiline entry that's prefixed with on of the `*username-keys*' to clipboard.
"Save the 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 4fd53ff

Please sign in to comment.