diff --git a/modules/tools/password-store/autoload.el b/modules/tools/password-store/autoload.el index cbed035e8..b16244bc3 100644 --- a/modules/tools/password-store/autoload.el +++ b/modules/tools/password-store/autoload.el @@ -1,77 +1,142 @@ ;;; tools/password-store/autoload.el -*- lexical-binding: t; -*- -;;;###autoload -(defun +pass/open () - (interactive) - (cond ((featurep! :completion ivy) - (+pass/ivy)) - ((featurep! :completion helm) - (helm-pass)) - (t - (pass)))) - -;;;###autoload -(defalias '+pass--get-entry - #'auth-source-pass-parse-entry) - -;;;###autoload -(defun +pass-get-field (entry fields) - (if-let* ((data (if (listp entry) entry (+pass--get-entry entry)))) - (cl-loop for key in (doom-enlist fields) - when (assoc key data) - return (cdr it)) - (error "Couldn't find entry: %s" entry))) - -;;;###autoload -(defun +pass-get-user (entry) - (+pass-get-field entry +pass-user-fields)) - -;;;###autoload -(defun +pass-get-secret (entry) - (+pass-get-field entry 'secret)) - -(defun +pass-ivy-action--open-url (entry) +(defun +pass--open-url (entry) (if-let* ((url (+pass-get-field entry +pass-url-fields))) (and (or (string-match-p "https?://" url) (error "Field for %s doesn't look like an url" item)) (browse-url url)) (error "url not found."))) -(defun +pass-ivy-action--get-field (item) - (let* ((data (+pass--get-entry item)) - (field (if data (completing-read "Field: " (mapcar #'car data) nil t)))) - (if data - (progn - (password-store-clear) - (message "Copied %s's %s field to clipboard. Will clear in %s seconds" - item field (password-store-timeout)) - (kill-new (+pass-get-field data field)) - (setq password-store-timeout-timer - (run-at-time (password-store-timeout) nil 'password-store-clear))) - (error "Couldn't find entry: %s" item)))) +(defun +pass--copy (entry field text) + (password-store-clear) + (message "Copied %s's %s field to clipboard. Will clear in %s seconds" + entry field (password-store-timeout)) + (kill-new text) + (setq password-store-timeout-timer + (run-at-time (password-store-timeout) nil 'password-store-clear))) -(defun +pass-ivy-action--copy-username (entry) +(defun +pass--copy-username (entry) (if-let* ((user (+pass-get-field entry +pass-user-fields))) (progn (password-store-clear) (message "Copied username to the kill ring.") (kill-new user)) (error "Username not found."))) -(after! ivy - (ivy-add-actions - '+pass/ivy - '(("o" password-store-copy "copy password") - ("e" password-store-edit "edit entry") - ("u" +pass-ivy-action--copy-username "copy username") - ("b" +pass-ivy-action--open-url "open url in browser") - ("f" +pass-ivy-action--get-field "get field")))) +(defun +pass--completing-read-field (entry) + (let* ((data (+pass-get-entry entry)) + (field (if data (completing-read "Field: " (mapcar #'car data) nil t)))) + (+pass-get-field data field))) + + +;; +;; API +;; + +;;;###autoload (autoload 'auth-source-pass-parse-entry "auth-source-pass" nil nil t) +;;;###autoload +(defalias '+pass-get-entry #'auth-source-pass-parse-entry) ;;;###autoload -(defun +pass/ivy (&optional browse-url) +(defun +pass-get-field (entry fields &optional noerror) + "Fetches the value of a field. FIELDS can be a list of string field names or a +single one. If a list, the first field found will be returned. Will error out +otherwise, unless NOERROR is non-nill." + (if-let* ((data (if (listp entry) entry (+pass-get-entry entry)))) + (cl-loop for key in (doom-enlist fields) + when (assoc key data) + return (cdr it)) + (unless noerror + (error "Couldn't find entry: %s" entry)))) + +;;;###autoload +(defun +pass-get-user (entry) + "Fetches the user field from ENTRY. Each of `+pass-user-fields' are tried in +search of your username. May prompt for your gpg passphrase." + (+pass-get-field entry +pass-user-fields)) + +;;;###autoload +(defun +pass-get-secret (entry) + "Fetches your secret from ENTRY. May prompt for your gpg passphrase." + (+pass-get-field entry 'secret)) + + +;; +;; Commands +;; + +;;;###autoload (autoload 'password-store-list "pass" nil nil t) +;;;###autoload (autoload 'password-store--completing-read "password-store" nil nil t) + +;;;###autoload +(defun +pass/edit-entry (entry) + "Interactively search for and open a pass entry for editing." + (interactive + (list (password-store--completing-read))) + (find-file (concat (expand-file-name entry (password-store-dir)) ".gpg"))) + +;;;###autoload +(defun +pass/copy-field (entry) + "Interactively search for an entry and copy a particular field to your +clipboard." + (interactive + (list (password-store--completing-read))) + (let* ((data (+pass-get-entry entry)) + (field (if data (completing-read "Field: " (mapcar #'car data) nil t)))) + (+pass--copy entry field (+pass-get-field data field)))) + +;;;###autoload +(defun +pass/copy-secret (entry) + "Interactively search for an entry and copy its secret/password to your +clipboard." + (interactive + (list (password-store--completing-read))) + (+pass--copy entry 'secret (+pass-get-secret entry))) + +;;;###autoload +(defun +pass/copy-user (entry) + "Interactively search for an entry and copy the login to your clipboard. The +fields in `+pass-user-fields' is used to find the login field." + (interactive + (list (password-store--completing-read))) + (+pass--copy-username entry)) + +;;;###autoload +(defun +pass/browse-url (entry) + "Interactively search for an entry and open its url in your browser. The +fields in `+pass-url-fields' is used to find the url field." + (interactive + (list (password-store--completing-read))) + (+pass--open-url entry)) + + +;; +;; Ivy interface +;; + +(defun +pass/ivy (arg) + "TODO" (interactive "P") (ivy-read "Pass: " (password-store-list) - :action (if browse-url + :action (if arg #'password-store-url #'password-store-copy) :caller '+pass/ivy)) +(after! ivy + (ivy-add-actions + '+pass/ivy + '(("o" password-store-copy "copy password") + ("e" +pass/edit-entry "edit entry") + ("u" +pass/copy-login "copy username") + ("b" +pass/copy-url "open url in browser") + ("f" +pass/copy-field "get field")))) + + +;; +;; TODO Helm interface +;; + +;; (defun +pass/helm () +;; (interactive) +;; ) + diff --git a/modules/tools/password-store/config.el b/modules/tools/password-store/config.el index 4fac1bb32..922361fdd 100644 --- a/modules/tools/password-store/config.el +++ b/modules/tools/password-store/config.el @@ -14,11 +14,19 @@ ;; `password-store' (setq password-store-password-length 12) +;; Fix hard-coded password-store location; respect PASSWORD_STORE_DIR envvar +(defun +password-store*read-entry (entry) + "Return a string with the file content of ENTRY." + (with-temp-buffer + (insert-file-contents + (expand-file-name (format "%s.gpg" entry) (password-store-dir))) + (buffer-substring-no-properties (point-min) (point-max)))) +(advice-add #'auth-source-pass--read-entry :override #'+password-store*read-entry) + ;; `pass' -(def-package! pass - :defer t - :config +(after! pass + (set! :env "PASSWORD_STORE_DIR") (set! :evil-state 'pass-mode 'emacs) (set! :popup "^\\*Password-Store" '((side . left) (size . 0.25)) diff --git a/modules/tools/password-store/packages.el b/modules/tools/password-store/packages.el index dbc5f2afd..10744af9d 100644 --- a/modules/tools/password-store/packages.el +++ b/modules/tools/password-store/packages.el @@ -3,9 +3,13 @@ (package! pass) (package! password-store) +(package! password-store-otp) -(when (and EMACS26+ (featurep! +auth)) +;; `auto-source-pass' is built into Emacs 26+ +(unless EMACS26+ (package! auth-source-pass)) +(when (featurep! :completion ivy) + (package! ivy-pass)) (when (featurep! :completion helm) (package! helm-pass))