-
-
Notifications
You must be signed in to change notification settings - Fork 423
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 #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
48 additions
and
16 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 |
---|---|---|
|
@@ -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)) | ||
|
||
|
@@ -69,22 +79,44 @@ 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 credential exists in a subdirectory (e.g.: x.example/[email protected]), | ||
username is taken as-is from the filename ([email protected]) | ||
- Otherwise, if no subdirectory is used (e.g.: [email protected]@y.example), | ||
username is taken from the first half of the filename ([email protected])" | ||
(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) | ||
|