diff --git a/CONTRIBUTING.org b/CONTRIBUTING.org index b9c4e9c..8674810 100644 --- a/CONTRIBUTING.org +++ b/CONTRIBUTING.org @@ -4,8 +4,8 @@ First of all, thank you for being interested in contributing! You rock! In this document, you will find some guidelines for contributing to -~TEMPLATE-NAME.el~. These are more guidelines than rules, so don’t try -too hard to follow them. +~bitwarden.el~. These are more guidelines than rules, so don’t try too +hard to follow them. * Table of Contents :TOC_5_gh: - [[#how-can-i-contribute][How Can I Contribute?]] diff --git a/README.org b/README.org index 9857c8b..987a9fa 100644 --- a/README.org +++ b/README.org @@ -1,34 +1,42 @@ -#+title: TEMPLATE-NAME +#+title: bitwarden.el #+author: Lucien Cartier-Tilet #+email: lucien@phundrak.com * Introduction -Describe briefly what the package is, what it does, and who it is for. +Bitwarden.el is a Bitwarden porcelain for Emacs. It aims to be a +complete text-based interface for the [[https://github.com/bitwarden/cli][Bitwarden CLI]]. + +Most of its public function are transient functions from the +transient library to provide the user an easy-to-use interface with +most of its options exposed. + * Table of Contents :TOC_2_gh: - [[#introduction][Introduction]] - [[#installation][Installation]] +- [[#notes][Notes]] + - [[#login][Login]] - [[#customizing][Customizing]] - [[#contributing][Contributing]] - [[#license][License]] * Installation -A couple of options are available for installing ~TEMPLATE-NAME~. +A couple of options are available for installing bitwarden.el. The first one is to clone the repository in your ~load-path~ and add the following to your ~.emacs~ or your ~init.el~: #+begin_src emacs-lisp - (require 'TEMPLATE-NAME) + (require 'bitwarden.el) #+end_src In my case, I prefer using ~use-package~ with ~straight~: #+begin_src emacs-lisp - (use-package TEMPLATE-NAME + (use-package bitwarden :ensure t :defer t - :straight (TEMPLATE-NAME :type git + :straight (bitwarden :type git :host nil - :repo "https://labs.phundrak.com/phundrak/TEMPLATE-NAME")) + :repo "https://labs.phundrak.com/phundrak/bitwarden.el")) #+end_src I personally also added ~:build t~ in the straight recipe to ensure @@ -42,14 +50,56 @@ a PR to add some more installation instructions! There is currently no plans of making this package available on MELPA or non-gnu elpa. +* Notes +** Login +Loging in with the ~--apikey~ option is not supported due to its +interactive nature. + +Bitwarden allows three different sources for your password: +- a plain password as an argument following the username +- an environment variable containing the password +- a file containing the password +Bitwarden.el allows a fourth option: the authinfo file on computer. To +use this option, simply add the following line in your ~.authinfo~ or +~.authinfo.gpg~ file: +#+begin_src text +machine bitwarden.example.com login yourusername password yourpassword +#+end_src + +Of course, you will have to replace ~bitwarden.example.com~ with the +actual server, ~yourusername~ with your actual username, and +~yourpassword~ with your actual password. If you do not set your +username or your password in bitwarden.el, the package will look for +them in your auth source file on login. Bitwarden.el retrieves the +server name from the command +#+begin_src bash +$ bw config server +#+end_src +and it strips the result from any ~http://~ or ~https://~ prefix. For +instance, if the command returns ~https://example.com/bitwarden~, +bitwarden.el will look for ~example.com/bitwarden~ in your authinfo +file. + * Customizing -Indicate the various customization options available, e.g. variables -and faces. For each one of them, give a brief description and their -default value. +Bitwarden.el has a couple of customizable variables you can find in +the ~bitwarden~ group when executing ~M-x customize-group~. Here is a +quick description of these variables: +- ~bitwarden-cli-executable~ :: Your Bitwarden CLI executable. Set this + variable if Emacs doesn’t find ~bw~ in your ~$PATH~ or if ~bw~ does not + refer to your Bitwarden CLI. + + Default value: ~bw~ +- ~bitwarden-default-cli-arguments~ :: A list of the default arguments + to pass to Bitwarden CLI. By default, only the package only passes + ~--nointeraction~ in order to inhibit any attempt from the CLI to + launch anything interactive --- it should be taken care of by the + package itself. + + Default value: ~'("--nointeraction")~ * Contributing See [[file:CONTRIBUTING.org]]. * License -~TEMPLATE-NAME.el~ is available under the GNU GPL-3.0 license. You can +~bitwarden.el~ is available under the GNU GPL-3.0 license. You can find the full text in [[file:LICENSE.md][LICENSE.md]]. diff --git a/bitwarden.el b/bitwarden.el new file mode 100644 index 0000000..e818ed4 --- /dev/null +++ b/bitwarden.el @@ -0,0 +1,305 @@ +;;; bitwarden.el --- Interact with Bitwarden from Emacs -*- lexical-binding: t -*- + +;; Author: Lucien Cartier-Tilet +;; Maintainer: Lucien Cartier-Tilet +;; Version: 0.1.0 +;; Package-Requires: ((emacs "27.1") (transient "0.3.7")) +;; Homepage: https://labs.phundrak.com/phundrak/bitwarden.el +;; Keywords: + + +;; This file is not part of GNU Emacs + +;; This program is free software: you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; This program is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with this program. If not, see . + + +;;; Commentary: + +;; # Introduction + +;; bitwarden.el is a Bitwarden porcelain for Emacs. It aims to be a +;; complete text-based interface for the Bitwarden CLI +;; (https://github.com/bitwarden/cli). +;; +;; Most of its public function are transient functions from the +;; transient library to provide the user an easy-to-use interface with +;; most of its options exposed. +;; +;; # Notes +;; ## Login +;; +;; Loging in with the --apikey option is not supported due to its +;; interactive nature. +;; +;; Bitwarden allows three different sources for your password: +;; - a plain password as an argument following the username +;; - an environment variable containing the password +;; - a file containing the password +;; Bitwarden.el allows a fourth option: the authinfo file on computer. +;; To use this option, simply add the following line in your .authinfo +;; or .authinfo.gpg file: +;; +;; machine bitwarden.example.com login yourusername password yourpassword +;; +;; Of course, you will have to replace `bitwarden.example.com` with +;; the actual server, `yourusername` with your actual username, and +;; `yourpassword` with your actual password. If you do not set your +;; username or your password in bitwarden.el, the package will look +;; for them in your auth source file on login. Bitwarden.el retrieves +;; the server name from the command +;; +;; bw config server +;; +;; and it strips the result from any http:// or https:// prefix. For +;; instance, if the command returns "https://example.com/bitwarden", +;; bitwarden.el will look for "example.com/bitwarden" in your authinfo +;; file. + +;;; Code: + +;;; Requires + +(require 'cl-lib) +(require 'eshell) +(require 'auth-source) +(require 'transient) + + +;;; Constants +(defconst bitwarden-version "0.1.0") + +(defconst bitwarden-login-methods '(("Authentificator" . "0") ("Email" . "1") ("YubiKey" . "3")) + "Bitwarden login methods.") + + +;;; Group and Custom Variables +(defgroup bitwarden () + "Interact with Bitwarden from Emacs." + :group 'applications + :prefix "bitwarden-" + :link '(url-link :tag "Gitea" "https://labs.phundrak.com/phundrak/bitwarden.el")) + +(defcustom bitwarden-cli-executable "bw" + "Path to the Bitwarden CLI executable." + :type 'string + :group 'bitwarden + :safe #'stringp + :version "0.1.0") + +(defcustom bitwarden-default-cli-arguments '("--nointeraction") + "Default arguments for the Bitwarden CLI." + :group 'bitwarden + :type '(repeat string) + :version "0.1.0") + +(defvar bitwarden--shell-status) + + +;;; Internal Functions + +(defun bitwarden--run-cli (&rest args) + "Run the Bitwarden cli with ARGS. +The default arguments specified by +`bitwarden-default-cli-arguments' immediately follow the cli’s +name defined in `bitwarden-cli-executable'. + +The output status of the command is stored in RESULT, which +should be a variable’s symbol." + (eshell-command-result + (mapconcat #'identity + (flatten-tree `(,bitwarden-cli-executable ,bitwarden-default-cli-arguments ,args)) + " ") + 'bitwarden--shell-status)) + +(defun bitwarden-hello () + "Dummy function." + (interactive) + (message "hello")) + +;;; Transient Infixes +(eval-when-compile + (defmacro bitwarden--define-infix (key name description type version + default &rest reader) + "Define infix and its corresponding variable at once. +The variable is named bitwarden-NAME is of type TYPE, has a +DESCRIPTION and a specified VERSION. +The KEY and READER are for the infix declaration. + +This macro is largely copied from Tecosaur’s screenshot.el" + (let ((var-name (concat "bitwarden-" name))) + `(progn + (defcustom ,(intern var-name) ,default + ,description + :type ,type + :group 'bitwarden + :version ,version) + (transient-define-infix ,(intern (concat "bitwarden--set-" name)) () + "Set Bitwarden options." + :class 'transient-lisp-variable + :variable ',(intern var-name) + :key ,key + :description ,description + :argument ,(concat "--" name) + :reader (lambda (&rest _) ,@reader))))) + + ;; Config + + (bitwarden--define-infix + "-s" "server" "On-premises hosted installation URL" + 'string "0.1.0" + (let ((output-str (bitwarden--run-cli "config" "server"))) + (if (eq 0 bitwarden--shell-status) + (string-trim output-str "https?://") + "")) + (read-string "Server URL: ")) + + ;; Login + + (bitwarden--define-infix + "-u" "username" "Email of the user." + 'string "0.1.0" + "" + (read-string "Email address: ")) + + (bitwarden--define-infix + "-p" "password" "Password of the user" + 'string "0.1.0" + "" + (read-string "Password: ")) + + (bitwarden--define-infix + "-s" "sso" "Log in with Single-Sign On" + 'boolean "0.1.0" nil + (not bitwarden-sso)) + + (bitwarden--define-infix + "-e" "passwordenv" "Environment variable storing your password" + 'string "0.1.0" + "" + (read-string "Environment variable: ")) + + (bitwarden--define-infix + "-f" "passwordfile" + "Path to a file containing your password as its first line" + 'string "0.1.0" "" + (read-file-name "Password file: ")) + + (bitwarden--define-infix + "-c" "code" + "Two-step login code" + 'string "0.1.0" "" + (read-string "Two-step code: ")) + + (bitwarden--define-infix + "-m" "method" + "Two-step login method" + 'string "0.1.0" "0" + (cdr (assoc (completing-read "Two-step login method: " + bitwarden-login-methods) + bitwarden-login-methods)))) + + +;;; Transient Actions +(eval-when-compile + (defmacro bitwarden--def-action (name description transient &rest body) + "Create a function called from TRANSIENT. +DESCRIPTION is the docstring of the function named +bitwarden--NAME, and it gets BODY as its body." + `(defun ,(intern (concat "bitwarden--action-" name)) (&optional _args) + ,(concat description " +This function is meant to be called by a transient.") + (interactive + (list (transient-args ,transient))) + ,@body)) + + ;; Config + + (bitwarden--def-action + "set-server" + "Set the Bitwarden server." + 'bitwarden--server + (message "%s" (format "%S" _args))) + + ;; Login + + (bitwarden--def-action + "login" + "Log in Bitwarden." + 'bitwarden--login + (if bitwarden-sso + (bitwarden--run-cli "login" "--raw" "--sso") + (bitwarden--run-cli "login" "--raw" + ;; bitwarden-username + (format "\"%s\"" + (if (string= "" bitwarden-username)) + (plist-get (car (auth-source-search + :max 1 + :host bitwarden-server)) + :user) + bitwarden-username) + (format "\"%s\"" + (cond + ((not (string= "" bitwarden-password)) + bitwarden-password) + ((! (string= "" bitwarden-passwordenv)) + bitwarden-passwordenv) + ((! (string= "" bitwarden-passwordfile)) + bitwarden-passwordfile) + (t (funcall + (plist-get + (car (auth-source-search :max 1 + :host bitwarden-server)) + :secret)))))))) + + (bitwarden--def-action + "logout" + "Log out of Bitwarden." + 'bitwarden--login + (shell-command (concat bitwarden-cli-executable " logout")))) + + +;;; Transient Prefixes +(transient-define-prefix bitwarden-config () + ["Options" + (bitwarden--set-server)] + ["Actions" + ("g" "Get current server" bitwarden-hello) + ("s" "Set current server" bitwarden-hello)]) + +(transient-define-prefix bitwarden-login () + ["Options" + (bitwarden--set-username) + (bitwarden--set-password) + (bitwarden--set-passwordenv) + (bitwarden--set-passwordfile) + (bitwarden--set-method) + (bitwarden--set-code)] + ["Actions" + ("l" "Login" bitwarden-login) + ("L" "Logout" bitwarden-logout)]) + +(transient-define-prefix bitwarden-transient () + ["Actions" + ("c" "Config" bitwarden--config) + ("l" "Login" bitwarden--login) + ("L" "Lock" bitwarden-hello) + ("s" "sync" bitwarden-hello)]) + +;; (defun bitwarden () +;; (interactive) +;; (call-interactively #'bitwarden-transient)) + +(provide 'bitwarden) + +;;; bitwarden.el ends here