aboutsummaryrefslogtreecommitdiff
path: root/lisp
diff options
context:
space:
mode:
authormartianh <martianh@noreply.codeberg.org>2022-03-23 08:04:07 +0100
committermartianh <martianh@noreply.codeberg.org>2022-03-23 08:04:07 +0100
commitcecd5de060a56f13f7f7eb4528b341027812faab (patch)
treeb612438cd9e5b46c0300c2ae1dc1b007698226bb /lisp
parentc7b475160d2e7712e339e15adf168529f71b52c6 (diff)
parent56fa25df379623e79261b535cd724db3ed979d44 (diff)
Merge pull request '2FA login support' (#255) from Red_Starfish/mastodon-up.el:login into develop
Reviewed-on: https://codeberg.org/martianh/mastodon.el/pulls/255
Diffstat (limited to 'lisp')
-rw-r--r--lisp/mastodon-auth.el201
-rw-r--r--lisp/mastodon-client.el110
-rw-r--r--lisp/mastodon-http.el8
-rw-r--r--lisp/mastodon.el30
4 files changed, 272 insertions, 77 deletions
diff --git a/lisp/mastodon-auth.el b/lisp/mastodon-auth.el
index e582e4f..582f7bb 100644
--- a/lisp/mastodon-auth.el
+++ b/lisp/mastodon-auth.el
@@ -1,6 +1,7 @@
;;; mastodon-auth.el --- Auth functions for mastodon.el -*- lexical-binding: t -*-
;; Copyright (C) 2017-2019 Johnson Denen
+;; Copyright (C) 2021 Abhiseck Paira <abhiseckpaira@disroot.org>
;; Author: Johnson Denen <johnson.denen@gmail.com>
;; Maintainer: Marty Hiatt <martianhiatus@riseup.net>
;; Version: 0.10.0
@@ -39,21 +40,25 @@
(autoload 'mastodon-http--api "mastodon-http")
(autoload 'mastodon-http--get-json "mastodon-http")
(autoload 'mastodon-http--post "mastodon-http")
+(autoload 'mastodon-http--append-query-string "mastodon-http")
+(autoload 'mastodon-client--store-access-token "mastodon-client")
+(autoload 'mastodon-client--active-user "mastodon-client")
+(autoload 'mastodon-client--make-user-active "mastodon-client")
+(autoload 'mastodon-client--form-user-from-vars "mastodon-client")
(defvar mastodon-instance-url)
+(defvar mastodon-client-scopes)
+(defvar mastodon-client-redirect-uri)
+(defvar mastodon-active-user)
(defgroup mastodon-auth nil
"Authenticate with Mastodon."
:prefix "mastodon-auth-"
:group 'mastodon)
-(defcustom mastodon-auth-source-file ""
- "Filename to use to store user names and passwords.
-
-Leave empty to not permanently store any secrets.
-Otherwise set to e.g. \"~/.authinfo.gpg\" to have encrypted storage, or
-if you are happy with unencryped storage use e.g. \"~/authinfo\"."
- :group 'mastodon-auth
- :type 'string)
+(defvar mastodon-auth-source-file nil
+ "This variable is obsolete.
+This variable currently serves no purpose and will be removed in
+the future.")
(defvar mastodon-auth--token-alist nil
"Alist of User access tokens keyed by instance url.")
@@ -61,60 +66,95 @@ if you are happy with unencryped storage use e.g. \"~/authinfo\"."
(defvar mastodon-auth--acct-alist nil
"Alist of account accts (name@domain) keyed by instance url.")
+(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'.
+
+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.
+")
+
+(defun mastodon-auth--get-browser-login-url ()
+ "Return properly formed browser login url."
+ (mastodon-http--append-query-string
+ (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)))))
+
+(defvar mastodon-auth--explanation
+ (format
+ "
+1. A URL has been copied to your clipboard. Open this URL in a
+javascript capable browser and your browser will take you to your
+Mastodon instance's login page.
+
+2. Login to your account (%s) and authorize \"mastodon.el\".
+
+3. After authorization you will be presented an authorization
+code. Copy this code and paste it in the minibuffer prompt."
+ (mastodon-client--form-user-from-vars)))
+
+(defun mastodon-auth--show-notice (notice buffer-name &optional ask)
+ "Display NOTICE to user.
+NOTICE is displayed in vertical split occupying 50% of total
+width. The buffer name of the buffer being displayed in the
+window is BUFFER-NAME.
+
+When optional argument ASK is given which should be a string, use
+ASK as the minibuffer prompt. Return whatever user types in
+response to the prompt.
+
+When ASK is absent return nil."
+ (let ((buffer (get-buffer-create buffer-name))
+ (inhibit-read-only t)
+ ask-value window)
+ (set-buffer buffer)
+ (erase-buffer)
+ (insert notice)
+ (fill-region (point-min) (point-max))
+ (read-only-mode)
+ (setq window (select-window
+ (split-window (frame-root-window) nil 'left)
+ t))
+ (switch-to-buffer buffer t)
+ (when ask
+ (setq ask-value (read-string ask))
+ (kill-buffer buffer)
+ (delete-window window))
+ ask-value))
+
+(defun mastodon-auth--request-authorization-code ()
+ "Ask authorization code and return it."
+ (let ((url (mastodon-auth--get-browser-login-url))
+ authorization-code)
+ (kill-new url)
+ (setq authorization-code
+ (mastodon-auth--show-notice mastodon-auth--explanation
+ "*mastodon-notice*"
+ "Authorization Code: "))
+ authorization-code))
+
(defun mastodon-auth--generate-token ()
- "Make POST to generate auth token.
-
-If no auth-sources file, runs
-`mastodon-auth--generate-token-no-storing-credentials'. If
-auth-sources file exists, runs
-`mastodon-auth--generate-token-and-store'."
- (if (or (null mastodon-auth-source-file)
- (string= "" mastodon-auth-source-file))
- (mastodon-auth--generate-token-no-storing-credentials)
- (mastodon-auth--generate-token-and-store)))
-
-(defun mastodon-auth--generate-token-no-storing-credentials ()
- "Make POST to generate auth token, without using auth-sources file."
- (mastodon-http--post
- (concat mastodon-instance-url "/oauth/token")
- `(("client_id" . ,(plist-get (mastodon-client) :client_id))
- ("client_secret" . ,(plist-get (mastodon-client) :client_secret))
- ("grant_type" . "password")
- ("username" . ,(read-string "Email: " user-mail-address))
- ("password" . ,(read-passwd "Password: "))
- ("scope" . "read write follow"))
- nil
- :unauthenticated))
-
-(defun mastodon-auth--generate-token-and-store ()
- "Make POST to generate auth token.
-
-Reads and/or stores secrets in `MASTODON-AUTH-SOURCE-FILE'."
- (let* ((auth-sources (list mastodon-auth-source-file))
- (auth-source-creation-prompts
- '((user . "Enter email for %h: ")
- (secret . "Password: ")))
- (credentials-plist (nth 0 (auth-source-search
- :create t
- :host mastodon-instance-url
- :port 443
- :require '(:user :secret)))))
- (prog1
- (mastodon-http--post
- (concat mastodon-instance-url "/oauth/token")
- `(("client_id" . ,(plist-get (mastodon-client) :client_id))
- ("client_secret" . ,(plist-get (mastodon-client) :client_secret))
- ("grant_type" . "password")
- ("username" . ,(plist-get credentials-plist :user))
- ("password" . ,(let ((secret (plist-get credentials-plist :secret)))
- (if (functionp secret)
- (funcall secret)
- secret)))
- ("scope" . "read write follow"))
- nil
- :unauthenticated)
- (when (functionp (plist-get credentials-plist :save-function))
- (funcall (plist-get credentials-plist :save-function))))))
+ "Generate access_token for the user. Return response buffer."
+ (let ((authorization-code (mastodon-auth--request-authorization-code)))
+ (mastodon-http--post
+ (concat mastodon-instance-url "/oauth/token")
+ `(("grant_type" . "authorization_code")
+ ("client_secret" . ,(plist-get (mastodon-client) :client_secret))
+ ("client_id" . ,(plist-get (mastodon-client) :client_id))
+ ("code" . ,authorization-code)
+ ("redirect_uri" . ,mastodon-client-redirect-uri))
+ nil
+ :unauthenticated)))
(defun mastodon-auth--get-token ()
"Make a request to generate an auth token and return JSON response."
@@ -128,16 +168,33 @@ Reads and/or stores secrets in `MASTODON-AUTH-SOURCE-FILE'."
(json-read-from-string json-string))))
(defun mastodon-auth--access-token ()
- "Return exiting or generate new access token.
-
-If an access token for `mastodon-instance-url' is in
-`mastodon-auth--token-alist', return it.
-
-Otherwise, generate a token and pass it to
-`mastodon-auth--handle-token-reponse'."
- (if-let ((token (cdr (assoc mastodon-instance-url mastodon-auth--token-alist))))
- token
- (mastodon-auth--handle-token-response (mastodon-auth--get-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 already.
+ (alist-get mastodon-instance-url mastodon-auth--token-alist
+ nil nil 'equal))
+ ((plist-get (mastodon-client--active-user) :access_token)
+ ;; user variables needs to initialised by reading 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 'equal))
+ ((null mastodon-active-user)
+ ;; user not aware of 2FA related changes and has not set the
+ ;; `mastodon-active-user' properly. 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)))))
(defun mastodon-auth--handle-token-response (response)
"Add token RESPONSE to `mastodon-auth--token-alist'.
@@ -148,6 +205,8 @@ Handle any errors from the server."
(pcase response
((and (let token (plist-get response :access_token))
(guard token))
+ (mastodon-client--make-user-active
+ (mastodon-client--store-access-token token))
(cdar (push (cons mastodon-instance-url token)
mastodon-auth--token-alist)))
diff --git a/lisp/mastodon-client.el b/lisp/mastodon-client.el
index 42e8b1f..4fc8db7 100644
--- a/lisp/mastodon-client.el
+++ b/lisp/mastodon-client.el
@@ -1,6 +1,7 @@
;;; mastodon-client.el --- Client functions for mastodon.el -*- lexical-binding: t -*-
;; Copyright (C) 2017-2019 Johnson Denen
+;; Copyright (C) 2021 Abhiseck Paira <abhiseckpaira@disroot.org>
;; Author: Johnson Denen <johnson.denen@gmail.com>
;; Maintainer: Marty Hiatt <martianhiatus@riseup.net>
;; Version: 0.10.0
@@ -32,8 +33,10 @@
(require 'plstore)
(require 'json)
+(require 'url)
(defvar mastodon-instance-url)
+(defvar mastodon-active-user)
(autoload 'mastodon-http--api "mastodon-http")
(autoload 'mastodon-http--post "mastodon-http")
@@ -46,14 +49,26 @@
(defvar mastodon-client--client-details-alist nil
"An alist of Client id and secrets keyed by the instance url.")
+(defvar mastodon-client--active-user-details-plist nil
+ "A plist of active user details.")
+
+(defvar mastodon-client-scopes "read write follow"
+ "Scopes to pass to oauth during registration.")
+
+(defvar mastodon-client-website "https://codeberg.org/martianh/mastodon.el"
+ "Website of mastodon.el.")
+
+(defvar mastodon-client-redirect-uri "urn:ietf:wg:oauth:2.0:oob"
+ "Redirect_uri as required by oauth.")
+
(defun mastodon-client--register ()
"POST client to Mastodon."
(mastodon-http--post
(mastodon-http--api "apps")
- '(("client_name" . "mastodon.el")
- ("redirect_uris" . "urn:ietf:wg:oauth:2.0:oob")
- ("scopes" . "read write follow")
- ("website" . "https://github.com/jdenen/mastodon.el"))
+ `(("client_name" . "mastodon.el")
+ ("redirect_uris" . ,mastodon-client-redirect-uri)
+ ("scopes" . ,mastodon-client-scopes)
+ ("website" . ,mastodon-client-website))
nil
:unauthenticated))
@@ -88,11 +103,96 @@ Make `mastodon-client--fetch' call to determine client values."
(plstore-close plstore)
client))
+(defun mastodon-client--remove-key-from-plstore (plstore)
+ "Remove KEY from PLSTORE."
+ (cdr plstore))
+
+;; Actually it returns a plist with client-details if such details are
+;; already stored in mastodon.plstore
(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))))
- (cdr mastodon)))
+ (mastodon-client--remove-key-from-plstore mastodon)))
+
+(defun mastodon-client--general-read (key)
+ "Retrieve the plstore item keyed by KEY.
+Return plist without the KEY."
+ (let* ((plstore (plstore-open (mastodon-client--token-file)))
+ (plstore-item (plstore-get plstore key)))
+ (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)))
+
+(defun mastodon-client--store-access-token (token)
+ "Save TOKEN as :access_token in plstore of the current user.
+Return the plist after the operation."
+ (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)))
+ (print-length nil)
+ (print-level nil))
+ (plstore-put plstore (concat "user-" username) plstore-value nil)
+ (plstore-save plstore)
+ (plstore-close plstore)
+ plstore-value))
+
+(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)
+ (plstore-save plstore)
+ (plstore-close plstore)))
+
+(defun mastodon-client--form-user-from-vars ()
+ "Create a username from user variable. Return that username.
+
+Username in the form user@instance.com is formed from the
+variables `mastodon-instance-url' and `mastodon-active-user'."
+ (concat mastodon-active-user
+ "@"
+ (url-host (url-generic-parse-url mastodon-instance-url))))
+
+(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))
+
+(defun mastodon-client--current-user-active-p ()
+ "Return user-details if the current user is active.
+Otherwise return nil."
+ (let ((username (mastodon-client--form-user-from-vars))
+ (user-details (mastodon-client--general-read "active-user")))
+ (when (and user-details
+ (equal (plist-get user-details :username) username))
+ user-details)))
+
+(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)))
+ (setq mastodon-client--active-user-details-plist
+ active-user-details))
+ active-user-details))
(defun mastodon-client ()
"Return variable client secrets to use for `mastodon-instance-url'.
diff --git a/lisp/mastodon-http.el b/lisp/mastodon-http.el
index e288c18..35fd070 100644
--- a/lisp/mastodon-http.el
+++ b/lisp/mastodon-http.el
@@ -32,6 +32,7 @@
(require 'json)
(require 'request) ; for attachments upload
+(require 'url)
(defvar mastodon-instance-url)
(defvar mastodon-toot--media-attachment-ids)
@@ -156,6 +157,13 @@ Pass response buffer to CALLBACK function."
(with-temp-buffer
(mastodon-http--url-retrieve-synchronously url))))
+(defun mastodon-http--append-query-string (url params)
+ "Append PARAMS to URL as query strings and return it.
+
+PARAMS should be an alist as required `url-build-query-string'."
+ (let ((query-string (url-build-query-string params)))
+ (concat url "?" query-string)))
+
;; search functions:
(defun mastodon-http--process-json-search ()
"Process JSON returned by a search query to the server."
diff --git a/lisp/mastodon.el b/lisp/mastodon.el
index 30fc2d3..49abe26 100644
--- a/lisp/mastodon.el
+++ b/lisp/mastodon.el
@@ -1,6 +1,7 @@
;;; mastodon.el --- Client for Mastodon -*- lexical-binding: t -*-
;; Copyright (C) 2017-2019 Johnson Denen
+;; Copyright (C) 2021 Abhiseck Paira <abhiseckpaira@disroot.org>
;; Author: Johnson Denen <johnson.denen@gmail.com>
;; Maintainer: Marty Hiatt <martianhiatus@riseup.net>
;; Version: 0.10.0
@@ -97,7 +98,34 @@
:group 'external)
(defcustom mastodon-instance-url "https://mastodon.social"
- "Base URL for the Masto instance from which you toot."
+ "Base URL for the Mastodon instance you want to be active.
+
+For example, if your mastodon username is
+\"example_user@social.instance.org\", and you want this account
+to be active, the value of this variable should be
+\"https://social.instance.org\".
+
+Also for completeness, the value of `mastodon-active-user' should
+be \"example_user\".
+
+After setting these variables you should restart Emacs for these
+changes to take effect."
+ :group 'mastodon
+ :type 'string)
+
+(defcustom mastodon-active-user nil
+ "Username of the active user.
+
+For example, if your mastodon username is
+\"example_user@social.instance.org\", and you want this account
+to be active, the value of this variable should be
+\"example_user\".
+
+Also for completeness, the value of `mastodon-instance-url'
+should be \"https://social.instance.org\".
+
+After setting these variables you should restart Emacs for these
+changes to take effect."
:group 'mastodon
:type 'string)