Add some helper functions to interact with pass

This commit is contained in:
Ray Miller 2025-01-26 18:36:38 +00:00
parent 96ee23d777
commit 0419c90c9b
Signed by: ray
GPG key ID: 043F786C4CD681B8

View file

@ -0,0 +1,65 @@
(define-module (ordo password-store)
#:use-module (ice-9 exceptions)
#:use-module (ice-9 format)
#:use-module (ice-9 popen)
#:use-module ((srfi srfi-1) #:select (last))
#:use-module ((srfi srfi-9) #:select (define-record-type))
#:use-module (ordo util read-lines)
#:use-module (ordo util shell-quote)
#:export (make-password-store
get-password
generate-password))
(define-exception-type &password-store-error &external-error
make-password-store-error
password-store-error?
(message password-store-error-message)
(cause password-store-error-cause))
(define-record-type <password-store>
(make-password-store dir)
password-store?
(dir password-store-dir))
(define (pass-command store . args)
(let ((base-cmd (if (password-store-dir store)
(format #f "env PASSWORD_STORE_DIR=~a pass" (string-shell-quote (password-store-dir store)))
"pass")))
(string-append base-cmd
" "
(string-join (map string-shell-quote args) " ")
" 2>&1")))
(define (get-password store path)
(let* ((command (pass-command store "show" path))
(port (open-input-pipe command))
(data (read-lines port))
(status (close-pipe port)))
(unless (zero? (status:exit-val status))
(raise-exception (make-password-store-error (format #f "Error getting password ~a" path) data)))
(car data)))
(define (password-exists? store path)
(and (false-if-exception (get-password store path)) #t))
(define* (generate-password store path #:key (overwrite? #f) (password-length 25))
;; WARNING: there is a race condition here between checking the password
;; exists and calling pass generate to create it. We have to pass the
;; -f option to generate in case we hit this race condition, when pass will prompt
;; for confirmation to overwrite an existing file. With the -f option, we will
;; go ahead and overwrite it, which seems the lesser of two evils.
(unless (or overwrite? (not (password-exists? store path)))
(raise-exception (make-password-store-error (format #f "Error generating password ~a" path)
"Password already exists")))
(let* ((command (pass-command store "generate" "-f" path (number->string password-length)))
(port (open-input-pipe command))
(data (read-lines port))
(status (close-pipe port)))
(unless (zero? (status:exit-val status))
(raise-exception (make-password-store-error (format #f "Error generating password for ~a" path) data)))
(let ((password (last data)))
;; Pass wraps the generated password in an escape sequence to change the
;; displayed colour: we strip this from the result.
(define prefix-len (string-length "\x1b[1m\x1b[93m"))
(define suffix-len (string-length "\x1b[0m"))
(substring password prefix-len (- (string-length password) suffix-len)))))