forked from atlas-engineer/nyxt
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
mode/password: filename-based usernames for pass
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
Showing
1 changed file
with
46 additions
and
13 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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)) | ||
|
||
|
@@ -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) | ||
|