branch: elpa/mastodon
commit 9a2bee2424c043fe688a17372b94bfe14816b5f6
Merge: 5308821c78 a1e525dacc
Author: marty hiatt <martianhia...@disroot.org>
Commit: marty hiatt <martianhia...@disroot.org>

    Merge branch 'token-encrypt' into develop
---
 README.org                    |  24 +++++-
 lisp/mastodon-auth.el         | 158 +++++++++++++++++++++++++++++--------
 lisp/mastodon-client.el       | 176 +++++++++++++++++++++++++++---------------
 lisp/mastodon.el              |  15 ++++
 mastodon-index.org            | 102 ++++++++++++------------
 test/mastodon-auth-tests.el   |  36 +++++++++
 test/mastodon-client-tests.el | 147 +++++++++++++++++++----------------
 7 files changed, 449 insertions(+), 209 deletions(-)

diff --git a/README.org b/README.org
index fe1759bfa9..6c54bacfaf 100644
--- a/README.org
+++ b/README.org
@@ -121,8 +121,28 @@ can activate one at a time by changing those two variables 
and restarting
 Emacs.
 
 If you were using mastodon.el before 2FA was implemented and the above
-steps do not work, delete the old file specified by
-=mastodon-client--token-file= and restart Emacs and follow the steps again.
+steps do not work, call =(mastodon-forget-all-logins)=, restart Emacs and
+follow the steps again.
+
+**** encrypted access tokens (from 1.2.0)
+
+By default, user access tokens are now stored in the user's auth source
+file (typically =~/.authinfo.gpg=, check the value of =auth-sources=). When
+you first update to 2.0.0, or if you encounter issues due to old
+credentials, call =(mastodon-forget-all-logins)= and then authenticate
+again. If you don't want this behaviour, set =mastodon-auth-use-auth-source=
+to nil. Entries will be stored encrypted in =mastodon-client--token-file= 
instead.
+
+And if for some reason you reauthenticate, you'll need to either remove the
+entry in your auth sources file, or manually update the token in it after
+doing so, as mastodon.el is unable to reliably update (or even remove)
+entires.
+
+Finally, if you find you're asked for your key passphrase too often while
+authenticating, consider setting =epa-file-encrypt-to= (for auth-source
+encryption) and =plstore-encrypt-to= (for plstore encryption) to your
+preferred key ID.
+
 
 *** Timelines
 
diff --git a/lisp/mastodon-auth.el b/lisp/mastodon-auth.el
index 6e90b5348f..d79eb60e6a 100644
--- a/lisp/mastodon-auth.el
+++ b/lisp/mastodon-auth.el
@@ -2,6 +2,7 @@
 
 ;; Copyright (C) 2017-2019 Johnson Denen
 ;; Copyright (C) 2021 Abhiseck Paira <abhiseckpa...@disroot.org>
+;; Copyright (C) 2025 Marty Hiatt <mouse...@disroot.org>
 ;; Author: Johnson Denen <johnson.de...@gmail.com>
 ;; Maintainer: Marty Hiatt <mouse...@disroot.org>
 ;; Homepage: https://codeberg.org/martianh/mastodon.el
@@ -32,6 +33,8 @@
 (require 'plstore)
 (require 'auth-source)
 (require 'json)
+(require 'url)
+
 (eval-when-compile (require 'subr-x)) ; for if-let*
 
 (autoload 'mastodon-client "mastodon-client")
@@ -44,17 +47,32 @@
 (autoload 'mastodon-http--get-json "mastodon-http")
 (autoload 'mastodon-http--post "mastodon-http")
 (autoload 'mastodon-return-credential-account "mastodon")
+(autoload 'mastodon-client--general-read "mastodon-client")
 
 (defvar mastodon-instance-url)
 (defvar mastodon-client-scopes)
 (defvar mastodon-client-redirect-uri)
 (defvar mastodon-active-user)
+(defvar mastodon-client--token-file)
 
 (defgroup mastodon-auth nil
   "Authenticate with Mastodon."
   :prefix "mastodon-auth-"
   :group 'mastodon)
 
+(defcustom mastodon-auth-use-auth-source t
+  "Whether to use auth sources for user credentials.
+If t, save and read user access token in the user's auth source
+file (see `auth-sources'). If nil, use `mastodon-client--token-file'
+instead.
+If you change the value of this variable, call
+`mastodon-forget-all-logins' and log in again.
+If for some reason you generate a new token, you'll have to update your
+auth souce file manually, or at least remove the entry and authenticate
+again, as auth-source.el only provides unreliable tools for updating
+entries."
+  :type 'boolean)
+
 (defvar mastodon-auth-source-file nil
   "This variable is obsolete.
 This variable currently serves no purpose and will be removed in
@@ -69,26 +87,25 @@ the future.")
 (defvar mastodon-auth--user-unaware
   "          ** MASTODON.EL - NOTICE **
 
-It appears that you are not aware of the recent developments in
-mastodon.el.  In short we now require that you also set the
-variable `mastodon-active-user' in your init file in addition to
-`mastodon-instance-url'.
+User variables not set: mastodon.el requires that you set both
+`mastodon-active-user' and `mastodon-instance-url' in your init file.
 
 Please see its documentation to understand what value it accepts
 by running M-x describe-variable on it or visiting our web page:
-https://codeberg.org/martianh/mastodon.el
-
-We apologize for the inconvenience.
+https://codeberg.org/martianh/mastodon.el.
 ")
 
 (defun mastodon-auth--get-browser-login-url ()
   "Return properly formed browser login url."
-  (mastodon-http--concat-params-to-url
-   (concat mastodon-instance-url "/oauth/authorize/")
-   `(("response_type" . "code")
-     ("redirect_uri" . ,mastodon-client-redirect-uri)
-     ("scope" . ,mastodon-client-scopes)
-     ("client_id" . ,(plist-get (mastodon-client) :client_id)))))
+  (let ((client-id (plist-get (mastodon-client) :client_id)))
+    (if (not client-id)
+        (error "Failed to set up client id")
+      (mastodon-http--concat-params-to-url
+       (concat mastodon-instance-url "/oauth/authorize/")
+       `(("response_type" . "code")
+         ("redirect_uri" . ,mastodon-client-redirect-uri)
+         ("scope" . ,mastodon-client-scopes)
+         ("client_id" . ,client-id))))))
 
 (defvar mastodon-auth--explanation
   (format
@@ -168,28 +185,72 @@ When ASK is absent return nil."
           (json-string (buffer-substring-no-properties (point) (point-max))))
       (json-read-from-string json-string))))
 
+(defun mastodon-auth--plstore-token-check (&optional auth-source)
+  "Signal an error if plstore contains unencrypted access-token.
+If AUTH-SOURCE, and if `mastodon-auth-use-auth-source' is non-nil,
+return non-nil if it contains any access token.
+Used to help users switch to the new encrypted auth token flow."
+  ;; FIXME: is it poss to move this plstore read to have one less read?
+  ;; e.g. inside of `mastodon-client--active-user'? the issue is that
+  ;; ideally we want to test "user-" entry, even if fetching "active-user"
+  ;; entry, so we would have to re-do the plstore read functions.
+  (when
+      (mastodon-auth--plstore-access-token-member auth-source)
+    (if auth-source
+        (user-error "Auth source storage of tokens is enabled,\
+ but there is also an access token in your plstore.\
+ If you're seeing this message after updating,\
+ call `mastodon-forget-all-logins', and try again.
+ If you don't want to use auth sources,\
+ also set `mastodon-auth-use-auth-source' to nil.\
+ If this message is in error, contact us on the mastodon.el repo")
+      (user-error "Unencrypted access token in your plstore.\
+ If you're seeing this message after updating,\
+ call `mastodon-forget-all-logins', and log in again.
+ If this message is in error, contact us on the mastodon.el repo"))))
+
+(defun mastodon-auth--plstore-access-token-member (&optional auth-source)
+  "Return non-nil if the user entry of the plstore contains :access_token.
+If AUTH-SOURCE, also check if it contains :secret-access_token."
+  (let* ((plstore (plstore-open (mastodon-client--token-file)))
+         (name (concat "user-" (mastodon-client--form-user-from-vars)))
+         ;; get alist like plstore.el does, so that keys will display with
+         ;; ":secret-" prefix if encrypted:
+         (alist (assoc name (plstore--get-merged-alist plstore))))
+    (if (and auth-source mastodon-auth-use-auth-source)
+        (or (member :access_token alist)
+            (member :secret-access_token alist))
+      (member :access_token alist))))
+
 (defun mastodon-auth--access-token ()
   "Return the access token to use with `mastodon-instance-url'.
 Generate/save token if none known yet."
-  (cond (mastodon-auth--token-alist
-         ;; user variables are known and initialised.
-         (alist-get mastodon-instance-url mastodon-auth--token-alist nil nil 
#'string=))
-        ((plist-get (mastodon-client--active-user) :access_token)
-         ;; user variables need to be read from plstore.
-         (push (cons mastodon-instance-url
-                     (plist-get (mastodon-client--active-user) :access_token))
-               mastodon-auth--token-alist)
-         (alist-get mastodon-instance-url mastodon-auth--token-alist nil nil 
#'string=))
-        ((null mastodon-active-user)
-         ;; user not aware of 2FA-related changes and has not set
-         ;; `mastodon-active-user'. Make user aware and error out.
-         (mastodon-auth--show-notice mastodon-auth--user-unaware
-                                     "*mastodon-notice*")
-         (error "Variables not set properly"))
-        (t
-         ;; user access-token needs to fetched from the server and
-         ;; stored and variables initialised.
-         (mastodon-auth--handle-token-response (mastodon-auth--get-token)))))
+  (cond
+   (mastodon-auth--token-alist
+    ;; user variables are known and initialised.
+    (alist-get mastodon-instance-url
+               mastodon-auth--token-alist nil nil #'string=))
+   ;; if auth source enabled, but we have an access token in plstore,
+   ;; error out and tell user to remove plstore and start over or disable
+   ;; auth source:
+   ((mastodon-auth--plstore-token-check))
+   ((plist-get (mastodon-client--active-user) :access_token)
+    ;; user variables need to be read from plstore active-user entry.
+    (push (cons mastodon-instance-url
+                (plist-get (mastodon-client--active-user) :access_token))
+          mastodon-auth--token-alist)
+    (alist-get mastodon-instance-url
+               mastodon-auth--token-alist nil nil #'string=))
+   ((null mastodon-active-user)
+    ;; user not aware of 2FA-related changes and has not set
+    ;; `mastodon-active-user'. Make user aware and error out.
+    (mastodon-auth--show-notice mastodon-auth--user-unaware
+                                "*mastodon-notice*")
+    (user-error "Variables not set properly"))
+   (t
+    ;; user access-token needs to fetched from the server and
+    ;; stored and variables initialised.
+    (mastodon-auth--handle-token-response (mastodon-auth--get-token)))))
 
 (defun mastodon-auth--handle-token-response (response)
   "Add token RESPONSE to `mastodon-auth--token-alist'.
@@ -206,6 +267,39 @@ Handle any errors from the server."
      (error "Mastodon-auth--access-token: %s: %s" class error))
     (_ (error "Unknown response from mastodon-auth--get-token!"))))
 
+(defun mastodon-auth-source-get (user host &optional token create)
+  "Fetch an auth source token, searching by USER and HOST.
+If CREATE, use TOKEN or prompt for it, and save it if there is no such entry.
+Return a list of user, password/secret, and the item's save-function."
+  (let* ((auth-source-creation-prompts
+          '((secret . "%u access token: ")))
+         (source
+          (car
+           (auth-source-search :host host :user user
+                               :require '(:user :secret)
+                               :secret (if token token nil)
+                               ;; "create" alone doesn't work here!:
+                               :create (if create t nil)))))
+    (when source
+      (let ((creds
+             `(,(plist-get source :user)
+               ,(auth-info-password source)
+               ,(plist-get source :save-function))))
+        (when create ;; call save function:
+          (when (functionp (nth 2 creds))
+            (funcall (nth 2 creds))))
+        creds))))
+
+(defun mastodon-auth-source-token (url handle &optional token create)
+  "Parse URL, search auth sources with it, user HANDLE and TOKEN.
+Calls `mastodon-auth-source-get', returns only the token.
+If CREATE, create an entry is none is found."
+  (let ((host (url-host
+               (url-generic-parse-url url)))
+        (username (car (split-string handle "@"))))
+    (nth 1
+         (mastodon-auth-source-get username host token create))))
+
 (defun mastodon-auth--get-account-name ()
   "Request user credentials and return an account name."
   (alist-get 'acct
diff --git a/lisp/mastodon-client.el b/lisp/mastodon-client.el
index c0db3d6172..1ad6d2bed7 100644
--- a/lisp/mastodon-client.el
+++ b/lisp/mastodon-client.el
@@ -2,6 +2,7 @@
 
 ;; Copyright (C) 2017-2019 Johnson Denen
 ;; Copyright (C) 2021 Abhiseck Paira <abhiseckpa...@disroot.org>
+;; Copyright (C) 2025 Marty Hiatt <mouse...@disroot.org>
 ;; Author: Johnson Denen <johnson.de...@gmail.com>
 ;; Maintainer: Marty Hiatt <mouse...@disroot.org>
 ;; Homepage: https://codeberg.org/martianh/mastodon.el
@@ -35,11 +36,14 @@
 
 (defvar mastodon-instance-url)
 (defvar mastodon-active-user)
+(defvar mastodon-auth-use-auth-source)
 
 (autoload 'mastodon-http--api "mastodon-http")
 (autoload 'mastodon-http--post "mastodon-http")
+(autoload 'mastodon-auth-source-token "mastodon-auth")
 
-(defcustom mastodon-client--token-file (concat user-emacs-directory 
"mastodon.plstore")
+(defcustom mastodon-client--token-file
+  (concat user-emacs-directory "mastodon.plstore")
   "File path where Mastodon access tokens are stored."
   :group 'mastodon
   :type 'file)
@@ -71,14 +75,23 @@
 
 (defun mastodon-client--fetch ()
   "Return JSON from `mastodon-client--register' call."
-  (with-current-buffer (mastodon-client--register)
-    (goto-char (point-min))
-    (re-search-forward "^$" nil 'move)
-    (let ((json-object-type 'plist)
-          (json-key-type 'keyword)
-          (json-array-type 'vector)
-          (json-string (buffer-substring-no-properties (point) (point-max))))
-      (json-read-from-string json-string))))
+  (let ((buf (mastodon-client--register)))
+    (if (not buf)
+        (user-error "Client registration failed.\
+ Is `mastodon-instance-url' correct?")
+      (with-current-buffer buf
+        (goto-char (point-min))
+        (re-search-forward "^$" nil 'move)
+        (let* ((json-object-type 'plist)
+               (json-key-type 'keyword)
+               (json-array-type 'vector)
+               (json-string
+                (buffer-substring-no-properties (point) (point-max)))
+               (parsed
+                (json-read-from-string json-string)))
+          (if (eq :error (car parsed))
+              (error "Error: %s" (cadr parsed))
+            parsed))))))
 
 (defun mastodon-client--token-file ()
   "Return `mastodon-client--token-file'."
@@ -86,19 +99,28 @@
 
 (defun mastodon-client--store ()
   "Store client_id and client_secret in `mastodon-client--token-file'.
-
-Make `mastodon-client--fetch' call to determine client values."
-  (let ((plstore (plstore-open (mastodon-client--token-file)))
-       (client (mastodon-client--fetch))
-       ;; alexgriffith reported seeing ellipses in the saved output
-       ;; which indicate some output truncating. Nothing in `plstore-save'
-       ;; seems to ensure this cannot happen so let's do that ourselves:
-       (print-length nil)
-       (print-level nil))
-    (plstore-put plstore (concat "mastodon-" mastodon-instance-url) client nil)
+Make `mastodon-client--fetch' call to determine client values.
+Return a plist of secret and non-secret key/val pairs."
+  (let* ((plstore (plstore-open (mastodon-client--token-file)))
+        (client (mastodon-client--fetch))
+         (secrets `( :client_id ,(plist-get client :client_id)
+                     :client_secret ,(plist-get client :client_secret)))
+         (sans-secrets
+          (dolist (x '(:client_id :client_secret) client)
+            (cl-remf client x)))
+        ;; alexgriffith reported seeing ellipses in the saved output
+        ;; which indicate some output truncating. Nothing in
+        ;; `plstore-save' seems to ensure this cannot happen so let's do
+        ;; that ourselves:
+        (print-length nil)
+        (print-level nil))
+    (plstore-put plstore
+                 (concat "mastodon-" mastodon-instance-url)
+                 sans-secrets secrets)
+    ;; FIXME: breaks tests: prompts for gpg passphrase
     (plstore-save plstore)
     (plstore-close plstore)
-    client))
+    (append secrets sans-secrets)))
 
 (defun mastodon-client--remove-key-from-plstore (plstore)
   "Remove KEY from PLSTORE."
@@ -109,7 +131,10 @@ Make `mastodon-client--fetch' call to determine client 
values."
 (defun mastodon-client--read ()
   "Retrieve client_id and client_secret from `mastodon-client--token-file'."
   (let* ((plstore (plstore-open (mastodon-client--token-file)))
-         (mastodon (plstore-get plstore (concat "mastodon-" 
mastodon-instance-url))))
+         (mastodon
+          (plstore-get plstore
+                       (concat "mastodon-" mastodon-instance-url))))
+    (plstore-close plstore)
     (mastodon-client--remove-key-from-plstore mastodon)))
 
 (defun mastodon-client--general-read (key)
@@ -117,38 +142,69 @@ Make `mastodon-client--fetch' call to determine client 
values."
 Return plist without the KEY."
   (let* ((plstore (plstore-open (mastodon-client--token-file)))
          (plstore-item (plstore-get plstore key)))
+    (plstore-close plstore)
     (mastodon-client--remove-key-from-plstore plstore-item)))
 
 (defun mastodon-client--make-user-details-plist ()
   "Make a plist with current user details.  Return it."
-  `(:username ,(mastodon-client--form-user-from-vars)
-              :instance ,mastodon-instance-url
-              :client_id ,(plist-get (mastodon-client) :client_id)
-              :client_secret ,(plist-get (mastodon-client) :client_secret)))
+  `( :username ,(mastodon-client--form-user-from-vars)
+     :instance ,mastodon-instance-url
+     :client_id ,(plist-get (mastodon-client) :client_id)
+     :client_secret ,(plist-get (mastodon-client) :client_secret)))
 
 (defun mastodon-client--store-access-token (token)
-  "Save TOKEN as :access_token in plstore of the current user.
-Return the plist after the operation."
+  "Save TOKEN as :access_token encrypted in the plstore.
+Return the plist after the operation.
+If `mastodon-auth-use-auth-source', encrypt it in auth source file."
   (let* ((user-details (mastodon-client--make-user-details-plist))
          (plstore (plstore-open (mastodon-client--token-file)))
-         (username (plist-get user-details :username))
-         (plstore-value (setq user-details
-                              (plist-put user-details :access_token token)))
+         (username (mastodon-client--form-user-from-vars))
+         (key (concat "user-" username))
+         (secrets `( :client_id ,(plist-get user-details :client_id)
+                     :client_secret ,(plist-get user-details :client_secret)))
+         (sans-secrets
+          (dolist (x '(:client_id :client_secret) user-details)
+            (cl-remf user-details x)))
          (print-length nil)
          (print-level nil))
-    (plstore-put plstore (concat "user-" username) plstore-value nil)
+    (if mastodon-auth-use-auth-source
+        ;; auth-source:
+        (progn
+          (mastodon-auth-source-token
+           mastodon-instance-url username token :create)
+          (plstore-put plstore key sans-secrets secrets))
+      ;; plstore encrypted:
+      (plstore-put plstore key sans-secrets
+                   (append secrets `(:access_token ,token))))
     (plstore-save plstore)
     (plstore-close plstore)
-    plstore-value))
+    (cdr (plstore-get plstore key))))
 
 (defun mastodon-client--make-user-active (user-details)
-  "USER-DETAILS is a plist consisting of user details."
-  (let ((plstore (plstore-open (mastodon-client--token-file)))
-        (print-length nil)
-        (print-level nil))
-    (plstore-put plstore "active-user" user-details nil)
+  "USER-DETAILS is a plist consisting of user details.
+Save it to plstore under key \"active-user\".
+If `mastodon-auth-use-auth-source' is non-nil, fetch the access token
+from the user's auth source file and add it to the active user entry.
+Return a plist of secret and non-secret key/val pairs."
+  (let* ((plstore (plstore-open (mastodon-client--token-file)))
+         (handle (plist-get user-details :username))
+         (token
+          (if mastodon-auth-use-auth-source
+              (mastodon-auth-source-token mastodon-instance-url handle)
+            (plist-get user-details :access_token)))
+         (secrets `( :access_token ,token
+                     :client_id ,(plist-get user-details :client_id)
+                     :client_secret ,(plist-get user-details :client_secret)))
+         (deets (copy-sequence user-details))
+         (sans-secrets
+          (dolist (x '(:client_id :client_secret :access_token) deets)
+            (cl-remf deets x)))
+         (print-length nil)
+         (print-level nil))
+    (plstore-put plstore "active-user" sans-secrets secrets)
     (plstore-save plstore)
-    (plstore-close plstore)))
+    (plstore-close plstore)
+    (append secrets sans-secrets)))
 
 (defun mastodon-client--form-user-from-vars ()
   "Create a username from user variable.  Return that username.
@@ -161,12 +217,12 @@ variables `mastodon-instance-url' and 
`mastodon-active-user'."
 (defun mastodon-client--make-current-user-active ()
   "Make the user specified by user variables active user.
 Return the details (plist)."
-  (let ((username (mastodon-client--form-user-from-vars))
-        user-plist)
-    (when (setq user-plist
-                (mastodon-client--general-read (concat "user-" username)))
-      (mastodon-client--make-user-active user-plist))
-    user-plist))
+  (let* ((username (mastodon-client--form-user-from-vars))
+         (user-plist (mastodon-client--general-read
+                      (concat "user-" username))))
+    (when user-plist
+      (mastodon-client--make-user-active user-plist)
+      user-plist)))
 
 (defun mastodon-client--current-user-active-p ()
   "Return user-details if the current user is active.
@@ -180,28 +236,26 @@ Otherwise return nil."
 (defun mastodon-client--active-user ()
   "Return the details of the currently active user.
 Details is a plist."
-  (let ((active-user-details mastodon-client--active-user-details-plist))
-    (unless active-user-details
-      (setq active-user-details
-            (or (mastodon-client--current-user-active-p)
-                (mastodon-client--make-current-user-active)))
+  (or mastodon-client--active-user-details-plist
       (setq mastodon-client--active-user-details-plist
-            active-user-details))
-    active-user-details))
+            (or (mastodon-client--current-user-active-p)
+                (mastodon-client--make-current-user-active)))))
 
 (defun mastodon-client ()
   "Return variable client secrets to use for `mastodon-instance-url'.
-Read plist from `mastodon-client--token-file' if variable is nil.
-Fetch and store plist if `mastodon-client--read' returns nil."
+If `mastodon-client--client-details-alist' is nil, read plist from
+`mastodon-client--token-file'.
+Fetch and store plist if `mastodon-client--read' returns nil.
+Return a plist."
   (let ((client-details
-         (cdr (assoc mastodon-instance-url 
mastodon-client--client-details-alist))))
-    (unless client-details
-      (setq client-details
-            (or (mastodon-client--read)
-                (mastodon-client--store)))
-      (push (cons mastodon-instance-url client-details)
-            mastodon-client--client-details-alist))
-    client-details))
+         (cdr (assoc mastodon-instance-url
+                     mastodon-client--client-details-alist))))
+    (or client-details
+        (let ((client-details (or (mastodon-client--read)
+                                  (mastodon-client--store))))
+          (push (cons mastodon-instance-url client-details)
+                mastodon-client--client-details-alist)
+          client-details))))
 
 (provide 'mastodon-client)
 ;;; mastodon-client.el ends here
diff --git a/lisp/mastodon.el b/lisp/mastodon.el
index c092f43850..daf6f28dba 100644
--- a/lisp/mastodon.el
+++ b/lisp/mastodon.el
@@ -105,6 +105,7 @@
 
 (defvar mastodon-tl--highlight-current-toot)
 (defvar mastodon-notifications--map)
+(defvar mastodon-client--token-file)
 
 (defvar mastodon-notifications-grouped-types
   '("reblog" "favourite") ;; TODO: implement follow!
@@ -195,6 +196,20 @@ and X others...\"."
   (interactive)
   (quit-window 'kill))
 
+;;;###autoload
+(defun mastodon-forget-all-logins ()
+  "Delete `mastodon-client--token-file'.
+Also nil `mastodon-auth--token-alist'."
+  (interactive)
+  (when (y-or-n-p "Remove all saved login data?")
+    (if (not (file-exists-p mastodon-client--token-file))
+        (message "No plstore file")
+      (delete-file mastodon-client--token-file)
+      (message "File %s deleted." mastodon-client--token-file))
+    ;; nil some vars too:
+    (setq mastodon-client--active-user-details-plist nil)
+    (setq mastodon-auth--token-alist nil)))
+
 (defvar mastodon-mode-map
   (let ((map (make-sparse-keymap)))
     ;; navigation inside a timeline
diff --git a/mastodon-index.org b/mastodon-index.org
index 7f0023ae5c..bcde1943f6 100644
--- a/mastodon-index.org
+++ b/mastodon-index.org
@@ -55,6 +55,7 @@
 | C-M-q            | mastodon-kill-all-buffers                         | Kill 
any and all open mastodon buffers, hopefully.                             |
 | Q                | mastodon-kill-window                              | Quit 
window and delete helper.                                                 |
 |                  | mastodon-mode                                     | Major 
mode for fediverse services using the Mastodon API.                      |
+|                  | mastodon-forget-all-logins                       | Delete 
`mastodon-client--token-file'.                                          |
 |                  | mastodon-notifications-clear-all                 | Clear 
all notifications.                                                       |
 | C-k              | mastodon-notifications-clear-current             | 
Dismiss the notification at point.                                             |
 |                  | mastodon-notifications-cycle-type                | Cycle 
the current notifications view.                                          |
@@ -135,6 +136,7 @@
 | !                | mastodon-tl-fold-post-toggle                     | Toggle 
the folding status of the toot at point.                                |
 |                  | mastodon-tl-follow-tag                           | Prompt 
for a tag (from post at point) and follow it.                           |
 | W                | mastodon-tl-follow-user                          | Query 
for USER-HANDLE from current status and follow that user.                |
+|                  | mastodon-tl-follow-user-by-handle                | Prompt 
for a USER-HANDLE and follow that user.                                 |
 |                  | mastodon-tl-follow-user-disable-boosts           | Prompt 
for a USER-HANDLE, and disable display of boosts in home timeline.      |
 |                  | mastodon-tl-follow-user-enable-boosts            | Prompt 
for a USER-HANDLE, and enable display of boosts in home timeline.       |
 | '                | mastodon-tl-followed-tags-timeline               | Open a 
timeline of multiple tags.                                              |
@@ -274,53 +276,55 @@
 #+end_src
 
 #+RESULTS:
-| Custom variable                                    | Description             
                                                      |
-|----------------------------------------------------+-------------------------------------------------------------------------------|
-| mastodon-active-user                               | Username of the active 
user.                                                  |
-| mastodon-client--token-file                        | File path where 
Mastodon access tokens are stored.                            |
-| mastodon-group-notifications                       | Whether to use grouped 
notifications.                                         |
-| mastodon-images-in-notifs                          | Whether to display 
attached images in notifications.                          |
-| mastodon-instance-url                              | Base URL for the 
fediverse instance you want to be active.                    |
-| mastodon-media--avatar-height                      | Height of the user 
avatar images (if shown).                                  |
-| mastodon-media--enable-image-caching               | Whether images should 
be cached.                                              |
-| mastodon-media--hide-sensitive-media               | Whether media marked as 
sensitive should be hidden.                           |
-| mastodon-media--preview-max-height                 | Max height of any media 
attachment preview to be shown in timelines.          |
-| mastodon-mode-hook                                 | Hook run when entering 
Mastodon mode.                                         |
-| mastodon-notifications-grouped-names-count         | The number of 
notification authors to display.                                |
-| mastodon-profile-mode-hook                         | Hook run after entering 
or leaving `mastodon-profile-mode'.                   |
-| mastodon-profile-note-in-foll-reqs                 | If non-nil, show a 
user's profile note in follow request notifications.       |
-| mastodon-profile-note-in-foll-reqs-max-length      | The max character 
length for user profile note in follow requests.            |
-| mastodon-profile-update-mode-hook                  | Hook run after entering 
or leaving `mastodon-profile-update-mode'.            |
-| mastodon-search-mode-hook                          | Hook run after entering 
or leaving `mastodon-search-mode'.                    |
-| mastodon-tl--display-caption-not-url-when-no-media | Display an image's 
caption rather than URL.                                   |
-| mastodon-tl--display-media-p                       | A boolean value stating 
whether to show media in timelines.                   |
-| mastodon-tl--enable-proportional-fonts             | Nonnil to enable using 
proportional fonts when rendering HTML.                |
-| mastodon-tl--enable-relative-timestamps            | Whether to show 
relative (to the current time) timestamps.                    |
-| mastodon-tl--expand-content-warnings               | Whether to expand 
content warnings by default.                                |
-| mastodon-tl--fold-toots-at-length                  | Length, in characters, 
to fold a toot at.                                     |
-| mastodon-tl--hide-replies                          | Whether to hide replies 
from the timelines.                                   |
-| mastodon-tl--highlight-current-toot                | Whether to highlight 
the toot at point. Uses `cursor-face' special property.  |
-| mastodon-tl--load-full-sized-images-in-emacs       | Whether to load 
full-sized images inside Emacs.                               |
-| mastodon-tl--no-fill-on-render                     | Non-nil to disable 
filling by shr.el while rendering toot body.               |
-| mastodon-tl--remote-local-domains                  | A list of domains to 
view the local timelines of.                             |
-| mastodon-tl--show-avatars                          | Whether to enable 
display of user avatars in timelines.                       |
-| mastodon-tl--show-stats                            | Whether to show toot 
stats (faves, boosts, replies counts).                   |
-| mastodon-tl--symbols                               | A set of symbols (and 
fallback strings) to be used in timeline.               |
+| Custom variable                                    | Description             
                                                     |
+|----------------------------------------------------+------------------------------------------------------------------------------|
+| mastodon-active-user                               | Username of the active 
user.                                                 |
+| mastodon-auth-encrypt-access-token                 | Whether to encrypt the 
user's authentication token in the plstore.           |
+| mastodon-auth-use-auth-source                      | Whether to use auth 
sources for user credentials.                            |
+| mastodon-client--token-file                        | File path where 
Mastodon access tokens are stored.                           |
+| mastodon-group-notifications                       | Whether to use grouped 
notifications.                                        |
+| mastodon-images-in-notifs                          | Whether to display 
attached images in notifications.                         |
+| mastodon-instance-url                              | Base URL for the 
fediverse instance you want to be active.                   |
+| mastodon-media--avatar-height                      | Height of the user 
avatar images (if shown).                                 |
+| mastodon-media--enable-image-caching               | Whether images should 
be cached.                                             |
+| mastodon-media--hide-sensitive-media               | Whether media marked as 
sensitive should be hidden.                          |
+| mastodon-media--preview-max-height                 | Max height of any media 
attachment preview to be shown in timelines.         |
+| mastodon-mode-hook                                 | Hook run when entering 
Mastodon mode.                                        |
+| mastodon-notifications-grouped-names-count         | The number of 
notification authors to display.                               |
+| mastodon-profile-mode-hook                         | Hook run after entering 
or leaving `mastodon-profile-mode'.                  |
+| mastodon-profile-note-in-foll-reqs                 | If non-nil, show a 
user's profile note in follow request notifications.      |
+| mastodon-profile-note-in-foll-reqs-max-length      | The max character 
length for user profile note in follow requests.           |
+| mastodon-profile-update-mode-hook                  | Hook run after entering 
or leaving `mastodon-profile-update-mode'.           |
+| mastodon-search-mode-hook                          | Hook run after entering 
or leaving `mastodon-search-mode'.                   |
+| mastodon-tl--display-caption-not-url-when-no-media | Display an image's 
caption rather than URL.                                  |
+| mastodon-tl--display-media-p                       | A boolean value stating 
whether to show media in timelines.                  |
+| mastodon-tl--enable-proportional-fonts             | Nonnil to enable using 
proportional fonts when rendering HTML.               |
+| mastodon-tl--enable-relative-timestamps            | Whether to show 
relative (to the current time) timestamps.                   |
+| mastodon-tl--expand-content-warnings               | Whether to expand 
content warnings by default.                               |
+| mastodon-tl--fold-toots-at-length                  | Length, in characters, 
to fold a toot at.                                    |
+| mastodon-tl--hide-replies                          | Whether to hide replies 
from the timelines.                                  |
+| mastodon-tl--highlight-current-toot                | Whether to highlight 
the toot at point. Uses `cursor-face' special property. |
+| mastodon-tl--load-full-sized-images-in-emacs       | Whether to load 
full-sized images inside Emacs.                              |
+| mastodon-tl--no-fill-on-render                     | Non-nil to disable 
filling by shr.el while rendering toot body.              |
+| mastodon-tl--remote-local-domains                  | A list of domains to 
view the local timelines of.                            |
+| mastodon-tl--show-avatars                          | Whether to enable 
display of user avatars in timelines.                      |
+| mastodon-tl--show-stats                            | Whether to show toot 
stats (faves, boosts, replies counts).                  |
+| mastodon-tl--symbols                               | A set of symbols (and 
fallback strings) to be used in timeline.              |
 | mastodon-tl--tag-timeline-tags                     | A list of up to four 
tags for use with `mastodon-tl-followed-tags-timeline'. |
-| mastodon-tl--tags-groups                           | A list containing lists 
of up to four tags each.                              |
-| mastodon-tl--timeline-posts-count                  | Number of posts to 
display when loading a timeline.                           |
-| mastodon-tl-position-after-update                  | Defines where `point' 
should be located after a timeline update.              |
-| mastodon-toot--attachment-height                   | Height of the attached 
images preview in the toot draft buffer.               |
-| mastodon-toot--completion-style-for-mentions       | The company completion 
style to use for mentions.                             |
-| mastodon-toot--default-media-directory             | The default directory 
when prompting for a media file to upload.              |
-| mastodon-toot--default-reply-visibility            | Default visibility 
settings when replying.                                    |
-| mastodon-toot--enable-completion                   | Whether to enable 
completion of mentions and hashtags.                        |
-| mastodon-toot--enable-custom-instance-emoji        | Whether to enable your 
instance's custom emoji by default.                    |
-| mastodon-toot--proportional-fonts-compose          | Nonnil to enable using 
proportional fonts in the compose buffer.              |
-| mastodon-toot--use-company-for-completion          | Whether to enable 
company for completion.                                     |
-| mastodon-toot-display-orig-in-reply-buffer         | Display a copy of the 
toot replied to in the compose buffer.                  |
-| mastodon-toot-mode-hook                            | Hook run after entering 
or leaving `mastodon-toot-mode'.                      |
-| mastodon-toot-orig-in-reply-length                 | Length to crop toot 
replied to in the compose buffer to.                      |
-| mastodon-toot-poll-use-transient                   | Whether to use the 
transient menu to create a poll.                           |
-| mastodon-toot-timestamp-format                     | Format to use for 
timestamps.                                                 |
-| mastodon-use-emojify                               | Whether to use 
emojify.el to display emojis.                                  |
+| mastodon-tl--tags-groups                           | A list containing lists 
of up to four tags each.                             |
+| mastodon-tl--timeline-posts-count                  | Number of posts to 
display when loading a timeline.                          |
+| mastodon-tl-position-after-update                  | Defines where `point' 
should be located after a timeline update.             |
+| mastodon-toot--attachment-height                   | Height of the attached 
images preview in the toot draft buffer.              |
+| mastodon-toot--completion-style-for-mentions       | The company completion 
style to use for mentions.                            |
+| mastodon-toot--default-media-directory             | The default directory 
when prompting for a media file to upload.             |
+| mastodon-toot--default-reply-visibility            | Default visibility 
settings when replying.                                   |
+| mastodon-toot--enable-completion                   | Whether to enable 
completion of mentions and hashtags.                       |
+| mastodon-toot--enable-custom-instance-emoji        | Whether to enable your 
instance's custom emoji by default.                   |
+| mastodon-toot--proportional-fonts-compose          | Nonnil to enable using 
proportional fonts in the compose buffer.             |
+| mastodon-toot--use-company-for-completion          | Whether to enable 
company for completion.                                    |
+| mastodon-toot-display-orig-in-reply-buffer         | Display a copy of the 
toot replied to in the compose buffer.                 |
+| mastodon-toot-mode-hook                            | Hook run after entering 
or leaving `mastodon-toot-mode'.                     |
+| mastodon-toot-orig-in-reply-length                 | Length to crop toot 
replied to in the compose buffer to.                     |
+| mastodon-toot-poll-use-transient                   | Whether to use the 
transient menu to create a poll.                          |
+| mastodon-toot-timestamp-format                     | Format to use for 
timestamps.                                                |
+| mastodon-use-emojify                               | Whether to use 
emojify.el to display emojis.                                 |
diff --git a/test/mastodon-auth-tests.el b/test/mastodon-auth-tests.el
index af410364cb..5ce9910534 100644
--- a/test/mastodon-auth-tests.el
+++ b/test/mastodon-auth-tests.el
@@ -75,3 +75,39 @@
     (with-mock
       (mock (mastodon-client--active-user))
       (should-error (mastodon-auth--access-token)))))
+
+(ert-deftest mastodon-auth-plstore-token-check ()
+  (let ((mastodon-instance-url "https://mastodon.example";)
+        (mastodon-active-user "test8000")
+        (user-details ;; order changed for new encrypted auth flow:
+         '( :client_id "id" :client_secret "secret"
+            :access_token "token"
+            :username "test8000@mastodon.example"
+            :instance "https://mastodon.example";))
+        ;; save token to plstore encrypted:
+        (mastodon-auth-use-auth-source nil)) ;; FIXME: test auth source
+    ;; setup plstore: store access token
+    (with-mock
+      (mock (mastodon-client) => '(:client_id "id" :client_secret "secret"))
+      (mock (mastodon-client--token-file) => "stubfile.plstore")
+      (should
+       (equal (mastodon-client--store-access-token "token")
+              user-details))
+      ;; should non-nil if we check with auth-source:
+      ;; because we saved with non auth-source:
+      (should
+       (equal
+        (let ((mastodon-auth-use-auth-source t))
+          (mastodon-auth--plstore-access-token-member :auth-source))
+        '(:secret-access_token t :username "test8000@mastodon.example"
+                               :instance "https://mastodon.example";)))
+      ;; should nil if we don't check with auth source:
+      (should
+       (equal
+        (mastodon-auth--plstore-access-token-member)
+        nil)))
+    ;; FIXME: ideally we would also mock up a non-encrypted plstore and
+    ;; test against it too, as that's the work we really want
+    ;; `mastodon-auth--plstore-access-token-member' to do
+    ;; but we don't currently have a way to mock one up.
+    (delete-file "stubfile.plstore")))
diff --git a/test/mastodon-client-tests.el b/test/mastodon-client-tests.el
index b302ed6e5b..83dc106d48 100644
--- a/test/mastodon-client-tests.el
+++ b/test/mastodon-client-tests.el
@@ -1,6 +1,8 @@
 ;;; mastodon-client-test.el --- Tests for mastodon-client.el  -*- 
lexical-binding: nil -*-
 
 (require 'el-mock)
+(require 'mastodon-client)
+(require 'mastodon-http)
 
 (ert-deftest mastodon-client--register ()
   "Should POST to /apps."
@@ -19,19 +21,22 @@
   "Should return client registration JSON."
   (with-temp-buffer
     (with-mock
-      (mock (mastodon-client--register) => (progn
-                                             (insert "\n\n{\"foo\":\"bar\"}")
-                                             (current-buffer)))
-      (should (equal (mastodon-client--fetch) '(:foo "bar"))))))
-
+     (mock (mastodon-client--register) => (progn
+                                            (insert "\n\n{\"foo\":\"bar\"}")
+                                            (current-buffer)))
+     (should (equal (mastodon-client--fetch) '(:foo "bar"))))))
+
+;; FIXME: broken by new encrypted plstore flow
+;; (asks for gpg passphrase)
+;; otherwise test passes
 (ert-deftest mastodon-client--store ()
   "Test the value `mastodon-client--store' returns/stores."
   (let ((mastodon-instance-url "http://mastodon.example";)
         (plist '(:client_id "id" :client_secret "secret")))
     (with-mock
-      (mock (mastodon-client--token-file) => "stubfile.plstore")
-      (mock (mastodon-client--fetch) => plist)
-      (should (equal (mastodon-client--store) plist)))
+     (mock (mastodon-client--token-file) => "stubfile.plstore")
+     (mock (mastodon-client--fetch) => plist)
+     (should (equal (mastodon-client--store) plist)))
     (let* ((plstore (plstore-open "stubfile.plstore"))
            (client (mastodon-client--remove-key-from-plstore
                     (plstore-get plstore "mastodon-http://mastodon.example";))))
@@ -40,48 +45,47 @@
       ;; clean up - delete the stubfile
       (delete-file "stubfile.plstore"))))
 
-
 (ert-deftest mastodon-client--read-finds-match ()
   "Should return mastodon client from `mastodon-token-file' if it exists."
   (let ((mastodon-instance-url "http://mastodon.example";))
     (with-mock
-      (mock (mastodon-client--token-file) => "fixture/client.plstore")
-      (should (equal (mastodon-client--read)
-                     '(:client_id "id2" :client_secret "secret2"))))))
+     (mock (mastodon-client--token-file) => "fixture/client.plstore")
+     (should (equal (mastodon-client--read)
+                    '(:client_id "id2" :client_secret "secret2"))))))
 
 (ert-deftest mastodon-client--general-read-finds-match ()
   (with-mock
-    (mock (mastodon-client--token-file) => "fixture/client.plstore")
-    (should (equal (mastodon-client--general-read 
"user-test8000@mastodon.example")
-                   '(:username "test8000@mastodon.example"
-                               :instance "http://mastodon.example";
-                               :client_id "id2" :client_secret "secret2"
-                               :access_token "token2")))))
+   (mock (mastodon-client--token-file) => "fixture/client.plstore")
+   (should (equal (mastodon-client--general-read 
"user-test8000@mastodon.example")
+                  '(:username "test8000@mastodon.example"
+                              :instance "http://mastodon.example";
+                              :client_id "id2" :client_secret "secret2"
+                              :access_token "token2")))))
 
 (ert-deftest mastodon-client--general-read-finds-no-match ()
   (with-mock
-    (mock (mastodon-client--token-file) => "fixture/client.plstore")
-    (should (equal (mastodon-client--general-read "nonexistant-key")
-                   nil))))
+   (mock (mastodon-client--token-file) => "fixture/client.plstore")
+   (should (equal (mastodon-client--general-read "nonexistant-key")
+                  nil))))
 
 (ert-deftest mastodon-client--general-read-empty-store ()
   (with-mock
-    (mock (mastodon-client--token-file) => "fixture/empty.plstore")
-    (should (equal (mastodon-client--general-read "something")
-                   nil))))
+   (mock (mastodon-client--token-file) => "fixture/empty.plstore")
+   (should (equal (mastodon-client--general-read "something")
+                  nil))))
 
 (ert-deftest mastodon-client--read-finds-no-match ()
   "Should return mastodon client from `mastodon-token-file' if it exists."
   (let ((mastodon-instance-url "http://mastodon.social";))
     (with-mock
-      (mock (mastodon-client--token-file) => "fixture/client.plstore")
-      (should (equal (mastodon-client--read) nil)))))
+     (mock (mastodon-client--token-file) => "fixture/client.plstore")
+     (should (equal (mastodon-client--read) nil)))))
 
 (ert-deftest mastodon-client--read-empty-store ()
   "Should return nil if mastodon client is not present in the plstore."
   (with-mock
-    (mock (mastodon-client--token-file) => "fixture/empty.plstore")
-    (should (equal (mastodon-client--read) nil))))
+   (mock (mastodon-client--token-file) => "fixture/empty.plstore")
+   (should (equal (mastodon-client--read) nil))))
 
 (ert-deftest mastodon-client--client-set-and-matching ()
   "Should return `mastondon-client' if `mastodon-client--client-details-alist' 
is non-nil and instance url is included."
@@ -95,32 +99,32 @@
   (let ((mastodon-instance-url "http://mastodon.example";)
         (mastodon-client--client-details-alist '(("http://other.example"; 
:wrong))))
     (with-mock
-      (mock (mastodon-client--read) => '(:client_id "foo" :client_secret 
"bar"))
-      (should (equal (mastodon-client) '(:client_id "foo" :client_secret 
"bar")))
-      (should (equal mastodon-client--client-details-alist
-                     '(("http://mastodon.example"; :client_id "foo" 
:client_secret "bar")
-                       ("http://other.example"; :wrong)))))))
+     (mock (mastodon-client--read) => '(:client_id "foo" :client_secret "bar"))
+     (should (equal (mastodon-client) '(:client_id "foo" :client_secret 
"bar")))
+     (should (equal mastodon-client--client-details-alist
+                    '(("http://mastodon.example"; :client_id "foo" 
:client_secret "bar")
+                      ("http://other.example"; :wrong)))))))
 
 (ert-deftest mastodon-client--client-unset ()
   "Should read from `mastodon-token-file' if available."
   (let ((mastodon-instance-url "http://mastodon.example";)
         (mastodon-client--client-details-alist nil))
     (with-mock
-      (mock (mastodon-client--read) => '(:client_id "foo" :client_secret 
"bar"))
-      (should (equal (mastodon-client) '(:client_id "foo" :client_secret 
"bar")))
-      (should (equal mastodon-client--client-details-alist
-                     '(("http://mastodon.example"; :client_id "foo" 
:client_secret "bar")))))))
+     (mock (mastodon-client--read) => '(:client_id "foo" :client_secret "bar"))
+     (should (equal (mastodon-client) '(:client_id "foo" :client_secret 
"bar")))
+     (should (equal mastodon-client--client-details-alist
+                    '(("http://mastodon.example"; :client_id "foo" 
:client_secret "bar")))))))
 
 (ert-deftest mastodon-client--client-unset-and-not-in-storage ()
   "Should store client data in plstore if it can't be read."
   (let ((mastodon-instance-url "http://mastodon.example";)
         (mastodon-client--client-details-alist nil))
     (with-mock
-      (mock (mastodon-client--read))
-      (mock (mastodon-client--store) => '(:client_id "foo" :client_secret 
"baz"))
-      (should (equal (mastodon-client) '(:client_id "foo" :client_secret 
"baz")))
-      (should (equal mastodon-client--client-details-alist
-                     '(("http://mastodon.example"; :client_id "foo" 
:client_secret "baz")))))))
+     (mock (mastodon-client--read))
+     (mock (mastodon-client--store) => '(:client_id "foo" :client_secret 
"baz"))
+     (should (equal (mastodon-client) '(:client_id "foo" :client_secret 
"baz")))
+     (should (equal mastodon-client--client-details-alist
+                    '(("http://mastodon.example"; :client_id "foo" 
:client_secret "baz")))))))
 
 (ert-deftest mastodon-client--form-user-from-vars ()
   (let ((mastodon-active-user "test9000")
@@ -134,41 +138,54 @@
         (mastodon-instance-url "https://mastodon.example";))
     ;; when the current user /is/ the active user
     (with-mock
-      (mock (mastodon-client--general-read "active-user") => '(:username 
"test9000@mastodon.example" :client_id "id1"))
-      (should (equal (mastodon-client--current-user-active-p)
-                     '(:username "test9000@mastodon.example" :client_id 
"id1"))))
+     (mock (mastodon-client--general-read "active-user") => '(:username 
"test9000@mastodon.example" :client_id "id1"))
+     (should (equal (mastodon-client--current-user-active-p)
+                    '(:username "test9000@mastodon.example" :client_id 
"id1"))))
     ;; when the current user is /not/ the active user
     (with-mock
-      (mock (mastodon-client--general-read "active-user") => '(:username 
"user@other.example" :client_id "id1"))
-      (should (null (mastodon-client--current-user-active-p))))))
+     (mock (mastodon-client--general-read "active-user") => '(:username 
"user@other.example" :client_id "id1"))
+     (should (null (mastodon-client--current-user-active-p))))))
 
+;; FIXME: broken by new encrypted plstore flow
+;; (asks for gpg passphrase)
+;; otherwise test passes
 (ert-deftest mastodon-client--store-access-token ()
   (let ((mastodon-instance-url "https://mastodon.example";)
         (mastodon-active-user "test8000")
-        (user-details
-         '(:username "test8000@mastodon.example"
-                     :instance "https://mastodon.example";
-                     :client_id "id" :client_secret "secret"
-                     :access_token "token")))
+        (user-details ;; order changed for new encrypted auth flow:
+         '( :client_id "id" :client_secret "secret"
+            :access_token "token"
+            :username "test8000@mastodon.example"
+            :instance "https://mastodon.example";))
+        (mastodon-auth-use-auth-source nil)) ;; FIXME: test auth source
     ;; test if mastodon-client--store-access-token /returns/ right
     ;; value
     (with-mock
-      (mock (mastodon-client) => '(:client_id "id" :client_secret "secret"))
-      (mock (mastodon-client--token-file) => "stubfile.plstore")
-      (should (equal (mastodon-client--store-access-token "token")
-                     user-details)))
+     (mock (mastodon-client) => '(:client_id "id" :client_secret "secret"))
+     (mock (mastodon-client--token-file) => "stubfile.plstore")
+     (should (equal (mastodon-client--store-access-token "token")
+                    user-details)))
     ;; test if mastodon-client--store-access-token /stores/ right value
     (with-mock
-      (mock (mastodon-client--token-file) => "stubfile.plstore")
-      (should (equal (mastodon-client--general-read
-                      "user-test8000@mastodon.example")
-                     user-details)))
+     (mock (mastodon-client--token-file) => "stubfile.plstore")
+     (should (equal (mastodon-client--general-read
+                     "user-test8000@mastodon.example")
+                    user-details)))
     (delete-file "stubfile.plstore")))
 
+;; FIXME: broken by new encrypted plstore flow
+;; (asks for gpg passphrase)
+;; otherwise test passes
 (ert-deftest mastodon-client--make-user-active ()
-  (let ((user-details '(:username "test@mastodon.example")))
+  ;; match new encrypted plstore return value:
+  (let ((user-details '( :access_token nil
+                         :client_id nil
+                         :client_secret nil
+                         :username "test@mastodon.example"))
+        (mastodon-auth-use-auth-source nil)) ;; FIXME: test auth source
     (with-mock
-      (mock (mastodon-client--token-file) => "stubfile.plstore")
-      (mastodon-client--make-user-active user-details)
-      (should (equal (mastodon-client--general-read "active-user")
-                     user-details)))))
+     (mock (mastodon-client--token-file) => "stubfile.plstore")
+     (mastodon-client--make-user-active user-details)
+     (should (equal (mastodon-client--general-read "active-user")
+                    user-details)))
+    (delete-file "stubfile.plstore")))

Reply via email to