bitwarden.el 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162
  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. (defvar bitwarden--2fa nil
  38. "Bitwarden private, global variable.
  39. This variable determines if we already asked for master password
  40. and the two-step verification was incorrect. Reported
  41. https://github.com/dani-garcia/bitwarden_rs/issues/215")
  42. (defun bitwarden-logged-in-p ()
  43. "Check if `bitwarden-user' is logged in.
  44. Returns nil if not logged in."
  45. (let* ((json-object-type 'hash-table)
  46. (json-key-type 'string)
  47. (json (json-read-file bitwarden-data-file)))
  48. (gethash "__PROTECTED__key" json)))
  49. (defun bitwarden-unlocked-p ()
  50. "Check if we have already set the 'BW_SESSION' environment variable."
  51. (and (bitwarden-logged-in-p) (getenv "BW_SESSION")))
  52. (defun bitwarden--raw-runcmd (cmd &rest args)
  53. "Run bw command CMD with ARGS.
  54. Returns a list with the first element being the exit code and the
  55. second element being the output."
  56. (with-temp-buffer
  57. (list (apply 'call-process
  58. bitwarden-bw-executable
  59. nil (current-buffer) nil
  60. (cons cmd args))
  61. (replace-regexp-in-string "\n$" ""
  62. (buffer-string)))))
  63. (defun bitwarden-runcmd (cmd &rest args)
  64. "Run bw command CMD with ARGS.
  65. This is a wrapper for `bitwarden--raw-runcmd' that also checks
  66. for common errors."
  67. (if (bitwarden-logged-in-p)
  68. (if (bitwarden-unlocked-p)
  69. (let* ((ret (apply #'bitwarden--raw-runcmd cmd args))
  70. (exit-code (nth 0 ret))
  71. (output (nth 1 ret)))
  72. (if (eq exit-code 0)
  73. output
  74. (cond ((string-match "^More than one result was found." output)
  75. (message "Bitwarden: more than one result found"))
  76. (t
  77. (message "Bitwarden: unknown error: %s" output)))))
  78. (message "Bitwarden: vault is locked"))
  79. (message "Bitwarden: you are not logged in")))
  80. (defun bitwarden--login-proc-filter (proc string)
  81. "Interacts with PROC by sending line-by-line STRING."
  82. ;; read username if not defined
  83. (when (string-match "^? Email address:" string)
  84. (let ((user (read-string "Bitwarden email:")))
  85. ;; if we are here then the user forgot to fill in this field so let's do
  86. ;; that now
  87. (setq bitwarden-user user)
  88. (process-send-string proc (concat bitwarden-user "\n"))))
  89. ;; read master password
  90. (when (string-match "^? Master password:" string)
  91. (process-send-string
  92. proc (concat (read-passwd "Bitwarden master password:") "\n")))
  93. ;; this is an incorrect return from the server (bitwarden_rs), reported here:
  94. ;; https://github.com/dani-garcia/bitwarden_rs/issues/215
  95. (when (string-match "^The model state is invalid" string)
  96. (if bitwarden--2fa
  97. (message "Bitwarden: incorrect two-step code")
  98. (message "Bitwarden: incorrect master password"))
  99. (setq bitwarden--2fa nil))
  100. ;; read the 2fa code
  101. (when (string-match "^? Two-step login code:" string)
  102. (setq bitwarden--2fa t)
  103. (process-send-string
  104. proc (concat (read-passwd "Bitwarden two-step login code:") "\n")))
  105. ;; success! now save the BW_SESSION into the environment so spawned processes
  106. ;; inherit it
  107. (when (string-match "^You are logged in!" string)
  108. (setq bitwarden--2fa nil)
  109. ;; set the session env variable so spawned processes inherit
  110. (string-match "export BW_SESSION=\"\\(.*\\)\"" string)
  111. (setenv "BW_SESSION" (match-string 1 string))
  112. (message
  113. (concat "Bitwarden: successfully logged in as " bitwarden-user))))
  114. (defun bitwarden-login ()
  115. "Prompts user for password if not logged in."
  116. (interactive "M")
  117. (when (bitwarden-logged-in-p)
  118. (error "Bitwarden: already logged in"))
  119. (when (get-process "bitwarden")
  120. (delete-process "bitwarden"))
  121. (setq bitwarden--2fa nil)
  122. (let ((process (start-process-shell-command
  123. "bitwarden"
  124. nil ; don't use a buffer
  125. (concat bitwarden-bw-executable " login " bitwarden-user))))
  126. (set-process-filter process #'bitwarden--login-proc-filter)))
  127. ;;;###autoload
  128. (defun bitwarden-logout ()
  129. "Log out bw. Does not ask for confirmation."
  130. (interactive)
  131. (when (bitwarden-logged-in-p)
  132. (setenv "BW_SESSION" nil)
  133. (bitwarden-runcmd "logout")))
  134. (provide 'bitwarden)
  135. ;;; bitwarden.el ends here