Skip to content

Instantly share code, notes, and snippets.

@chaorace
Last active July 7, 2024 01:13
Show Gist options
  • Save chaorace/98417a693ca73c51a8688ac09e61d2b0 to your computer and use it in GitHub Desktop.
Save chaorace/98417a693ca73c51a8688ac09e61d2b0 to your computer and use it in GitHub Desktop.
Nyxt extension nx-passt: a pass interface with filename-based username support. See also: https://github.com/atlas-engineer/nyxt/issues/3293
(define-nyxt-user-system-and-load "nyxt-user/nx-passt"
:depends-on (:nx-passt) :components ("passt.lisp"))
(defmethod initialize-instance :after
((interface nx-passt:password-store-interface) &key &allow-other-keys)
;; Leave this as the default t if you only want to get username from the filename as a last resort
(setf (nx-passt:use-encrypted-username interface) nil))
(define-configuration nyxt/mode/password:password-mode
((nyxt/mode/password:password-interface
(make-instance 'nx-passt:password-store-interface))))
(define-configuration buffer
((default-modes
(append (list 'nyxt/mode/password:password-mode) %slot-value%))))
(asdf:defsystem #:nx-passt
:description "Introduces pass provider with filename-based username support"
:author "Christopher Crockett"
:license "MIT"
:version "1.0.0"
:serial t
:depends-on (#:nyxt)
:components ((:file "package")
(:file "nx-passt")))
(in-package #:nx-passt)
(define-class password-store-interface (password:password-store-interface)
((use-encrypted-username t :documentation
"If nil, Nyxt skips decrypting credentials to look for *username-keys* and instead immediately uses the fallback filename-based strategy."))
(:export-class-name-p t)
(:export-accessor-names-p t))
(push 'password-store-interface password:*interfaces*)
(defun username-from-name (password-name)
"Compute 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/me@example.net), username is taken as-is from the filename (me@example.net)
- If the credential exists in the root of the password store (e.g.: me@example.net@example.com), username will be the first half of the filename (me@example.net)
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-key (password-interface password-name)
"Decrypt the `password-name' credential and extract username from the first key matching one of `*username-keys*'"
(let* ((content (password::execute password-interface (list "show" password-name)
:output '(:string :stripped t)))
(entries (password::parse-multiline content))
(username-entry (when entries
(some (lambda (key)
(find key entries :test #'string-equal :key #'first))
password::*username-keys*))))
(when username-entry (second username-entry))))
(defmethod password:clip-username ((password-interface password-store-interface) &key password-name service)
"Save the username of the `password-name' credential to clipboard.
If `use-encrypted-username' is t, the credential will be decrypted to look for a key matching one of `*username-keys'.
If a matching key is found, the corresponding multiline entry will be saved to clipboard.
If no matching key is found or `*use-encrypted-username' is nil, username detection falls back to deriving a username based upon `password-name'.
Case is ignored.
The resulting username is also returned."
(declare (ignore service))
(when password-name
(let ((username
(or (when (use-encrypted-username password-interface)
(username-from-key password-interface password-name))
(username-from-name password-name))))
(when username
(trivial-clipboard:text username)
username))))
(nyxt:define-package #:nx-passt
(:use #:nyxt/mode/password))
@chaorace
Copy link
Author

chaorace commented Jul 7, 2024

For those unfamiliar: Extension files go in .local/share/nyxt/extensions/nx-passt. Config files go in .config/nyxt

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment