bitwarden.el 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181
  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. ;; if trying to unlock, check if logged in
  92. (when (string-match "^You are not logged in" string)
  93. (message "Bitwarden: cannot unlock: not logged in"))
  94. ;; read the 2fa code
  95. (when (string-match "^? Two-step login code:" string)
  96. (process-send-string
  97. proc (concat (read-passwd "Bitwarden two-step login code: ") "\n")))
  98. ;; check for bad code
  99. (when (string-match "^Login failed" string)
  100. (message "Bitwarden: incorrect two-step code"))
  101. ;; check for already logged in
  102. (when (string-match "^You are already logged in" string)
  103. (string-match "You are already logged in as \\(.*\\)\\." string)
  104. (message "Bitwarden: already logged in as %s" (match-string 1 string)))
  105. ;; success! now save the BW_SESSION into the environment so spawned processes
  106. ;; inherit it
  107. (when (string-match "^\\(You are logged in\\|Your vault is now unlocked\\)"
  108. string)
  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--raw-unlock (cmd)
  115. "Raw CMD to either unlock a vault or login.
  116. The only difference between unlock and login is just the name of
  117. the command and whether to pass the user."
  118. (when (get-process "bitwarden")
  119. (delete-process "bitwarden"))
  120. (let ((process (start-process-shell-command
  121. "bitwarden"
  122. nil ; don't use a buffer
  123. (concat bitwarden-bw-executable " " cmd))))
  124. (set-process-filter process #'bitwarden--login-proc-filter)))
  125. (defun bitwarden-unlock ()
  126. "Unlock bitwarden vault.
  127. It is not sufficient to check the env variable for BW_SESSION
  128. since that could be set yet could be expired or incorrect."
  129. (interactive "M")
  130. (bitwarden--raw-unlock "unlock"))
  131. (defun bitwarden-login ()
  132. "Prompts user for password if not logged in."
  133. (interactive "M")
  134. (bitwarden--raw-unlock (concat "login " bitwarden-user)))
  135. (defun bitwarden-lock ()
  136. "Lock the bw vault. Does not ask for confirmation."
  137. (interactive)
  138. (when (bitwarden-unlocked-p)
  139. (setenv "BW_SESSION" nil)))
  140. ;;;###autoload
  141. (defun bitwarden-logout ()
  142. "Log out bw. Does not ask for confirmation."
  143. (interactive)
  144. (when (bitwarden-logged-in-p)
  145. (bitwarden-runcmd "logout")
  146. (bitwarden-lock)))
  147. (provide 'bitwarden)
  148. ;;; bitwarden.el ends here