diff --git a/src/io/sixtant/secrets.clj b/src/io/sixtant/secrets.clj index 5aacd7e..bfaf677 100644 --- a/src/io/sixtant/secrets.clj +++ b/src/io/sixtant/secrets.clj @@ -168,6 +168,17 @@ ;;; Main API: read & update secrets +(def ^:dynamic *password* "See `with-password`." nil) + + +(defmacro with-password + "Any calls inside `body` which would otherwise prompt for a password instead + use the given `password`." + [password & body] + `(binding [*password* ~password] + ~@body)) + + (def ^:dynamic *secrets* "See `with-secrets`." nil) @@ -175,7 +186,7 @@ "Prefer `with-secrets`." [] (if (.isFile (io/file *path*)) - (let [p (read-password "Password: ")] + (let [p (or *password* (read-password "Password: "))] {:data (decrypt-from-disk {:password p :path *path*}) :password p}) {:data {} diff --git a/test/io/sixtant/secrets_test.clj b/test/io/sixtant/secrets_test.clj index 73daaed..aeb7bf8 100644 --- a/test/io/sixtant/secrets_test.clj +++ b/test/io/sixtant/secrets_test.clj @@ -1,10 +1,37 @@ (ns io.sixtant.secrets-test (:require [clojure.test :refer :all] + [clojure.java.io :as io] [io.sixtant.secrets :refer :all]) (:import (clojure.lang ExceptionInfo) (java.io File))) +(def ^:dynamic *temp-files* []) + + +(defn temp + "Return the path to a temporary file with the given `prefix` and `suffix`." + [prefix suffix] + (let [p (.getCanonicalPath (File/createTempFile prefix suffix))] + (try + (set! *temp-files* (conj *temp-files* p)) + (catch IllegalStateException _ + (throw (ex-info "`temp` called outside of `with-temp-files`" {})))) + p)) + + +(defmacro with-temp-files + "Ensure the deletion of any temp files created via `temp`." + [& body] + `(binding [*temp-files* []] + (try + (do ~@body) + (finally + (run! + (fn [path#] (.delete (io/file path#))) + *temp-files*))))) + + (deftest encrypt-decrypt-test (let [data "secret data" pass "pass"] @@ -15,10 +42,26 @@ (deftest encrypt-to-disk-test - (let [temp (.getCanonicalPath (File/createTempFile "encrypted" ".edn")) - conf {:password "pass" :path temp} - data {:bitso {:production {:key "foo" :secret "bar"}}}] - (testing "Encryption & persistence of Clojure data structures" - (encrypt-to-disk data conf) - (is (not= (read-string (slurp temp)) data) "Data encrypted on disk") - (is (= (decrypt-from-disk conf) data) "Can be decrypted")))) + (with-temp-files + (let [temp (temp "encrypted" ".edn") + conf {:password "pass" :path temp} + data {:bitso {:production {:key "foo" :secret "bar"}}}] + (testing "Encryption & persistence of Clojure data structures" + (encrypt-to-disk data conf) + (is (not= (read-string (slurp temp)) data) "Data encrypted on disk") + (is (= (decrypt-from-disk conf) data) "Can be decrypted"))))) + + +(deftest high-level-api-test + (with-temp-files + (let [temp (temp "encrypted" ".edn") + data {:bitso {:prod {:key "foo" :secret "bar"}}}] + + ; Write data to a temporary secrets fil + (with-path temp + (write-secrets {:data data :password "pass"})) + + (with-password "pass" + (with-path temp + (with-secrets + (is (= (secrets :bitso :prod :key) "foo"))))))))