bitwarden.el 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153
  1. ;;; bitwarden.el --- Bitwarden command wrapper -*- lexical-binding: t -*-
  2. ;; Copyright (C) 2018 Sean Farley
  3. ;; Author: Sean Farley
  4. ;; URL: https://github.com/seanfarley/emacs-bitwarden
  5. ;; Version: 0.1.0
  6. ;; Created: 2018-09-04
  7. ;; Package-Requires: ((emacs "24.4"))
  8. ;; Keywords: extensions processes bw bitwarden
  9. ;;; License
  10. ;; This program is free software: you can redistribute it and/or modify
  11. ;; it under the terms of the GNU General Public License as published by
  12. ;; the Free Software Foundation, either version 3 of the License, or
  13. ;; (at your option) any later version.
  14. ;; This program is distributed in the hope that it will be useful,
  15. ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
  16. ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  17. ;; GNU General Public License for more details.
  18. ;; You should have received a copy of the GNU General Public License
  19. ;; along with this program. If not, see <http://www.gnu.org/licenses/>.
  20. ;;; Commentary:
  21. ;; This package wraps the bitwarden command-line program.
  22. ;;; Code:
  23. (require 'json)
  24. (defcustom bitwarden-bw-executable (executable-find "bw")
  25. "The bw cli executable used by Bitwarden."
  26. :group 'bitwarden
  27. :type 'string)
  28. (defcustom bitwarden-data-file
  29. (expand-file-name "~/Library/Application Support/Bitwarden CLI/data.json")
  30. "The bw cli executable used by Bitwarden."
  31. :group 'bitwarden
  32. :type 'string)
  33. (defcustom bitwarden-user nil
  34. "Bitwarden user e-mail."
  35. :group 'bitwarden
  36. :type 'string)
  37. (defun bitwarden-logged-in-p ()
  38. "Check if `bitwarden-user' is logged in.
  39. Returns nil if not logged in."
  40. (let* ((json-object-type 'hash-table)
  41. (json-key-type 'string)
  42. (json (json-read-file bitwarden-data-file)))
  43. (gethash "__PROTECTED__key" json)))
  44. (defun bitwarden-unlocked-p ()
  45. "Check if we have already set the 'BW_SESSION' environment variable."
  46. (and (bitwarden-logged-in-p) (getenv "BW_SESSION")))
  47. (defun bitwarden--raw-runcmd (cmd &rest args)
  48. "Run bw command CMD with ARGS.
  49. Returns a list with the first element being the exit code and the
  50. second element being the output."
  51. (with-temp-buffer
  52. (list (apply 'call-process
  53. bitwarden-bw-executable
  54. nil (current-buffer) nil
  55. (cons cmd args))
  56. (replace-regexp-in-string "\n$" ""
  57. (buffer-string)))))
  58. (defun bitwarden-runcmd (cmd &rest args)
  59. "Run bw command CMD with ARGS.
  60. This is a wrapper for `bitwarden--raw-runcmd' that also checks
  61. for common errors."
  62. (if (bitwarden-logged-in-p)
  63. (if (bitwarden-unlocked-p)
  64. (let* ((ret (apply #'bitwarden--raw-runcmd cmd args))
  65. (exit-code (nth 0 ret))
  66. (output (nth 1 ret)))
  67. (if (eq exit-code 0)
  68. output
  69. (cond ((string-match "^More than one result was found." output)
  70. (message "Bitwarden: more than one result found"))
  71. (t
  72. (message "Bitwarden: unknown error: %s" output)))))
  73. (message "Bitwarden: vault is locked"))
  74. (message "Bitwarden: you are not logged in")))
  75. (defun bitwarden--login-proc-filter (proc string)
  76. "Interacts with PROC by sending line-by-line STRING."
  77. ;; read username if not defined
  78. (when (string-match "^? Email address:" string)
  79. (let ((user (read-string "Bitwarden email:")))
  80. ;; if we are here then the user forgot to fill in this field so let's do
  81. ;; that now
  82. (setq bitwarden-user user)
  83. (process-send-string proc (concat bitwarden-user "\n"))))
  84. ;; read master password
  85. (when (string-match "^? Master password:" string)
  86. (process-send-string
  87. proc (concat (read-passwd "Bitwarden master password:") "\n")))
  88. ;; check for bad password
  89. (when (string-match "^Username or password is incorrect" string)
  90. (message "Bitwarden: incorrect master password"))
  91. ;; read the 2fa code
  92. (when (string-match "^? Two-step login code:" string)
  93. (process-send-string
  94. proc (concat (read-passwd "Bitwarden two-step login code:") "\n")))
  95. ;; check for bad code
  96. (when (string-match "^Login failed" string)
  97. (message "Bitwarden: incorrect two-step code"))
  98. ;; success! now save the BW_SESSION into the environment so spawned processes
  99. ;; inherit it
  100. (when (string-match "^You are logged in!" string)
  101. ;; set the session env variable so spawned processes inherit
  102. (string-match "export BW_SESSION=\"\\(.*\\)\"" string)
  103. (setenv "BW_SESSION" (match-string 1 string))
  104. (message
  105. (concat "Bitwarden: successfully logged in as " bitwarden-user))))
  106. (defun bitwarden-login ()
  107. "Prompts user for password if not logged in."
  108. (interactive "M")
  109. (when (bitwarden-logged-in-p)
  110. (error "Bitwarden: already logged in"))
  111. (when (get-process "bitwarden")
  112. (delete-process "bitwarden"))
  113. (let ((process (start-process-shell-command
  114. "bitwarden"
  115. nil ; don't use a buffer
  116. (concat bitwarden-bw-executable " login " bitwarden-user))))
  117. (set-process-filter process #'bitwarden--login-proc-filter)))
  118. ;;;###autoload
  119. (defun bitwarden-logout ()
  120. "Log out bw. Does not ask for confirmation."
  121. (interactive)
  122. (when (bitwarden-logged-in-p)
  123. (bitwarden-runcmd "logout")
  124. (setenv "BW_SESSION" nil)))
  125. (provide 'bitwarden)
  126. ;;; bitwarden.el ends here