anderssorby / elm-mastodon / Mastodon.Login

Support for creating an App and logging in to a server.

See https://docs.joinmastodon.org/client/authorized/


type FetchAccountOrRedirect msg
    = FetchAccount (Task ( String, Mastodon.Request.Error ) ( String, String, Mastodon.Entity.Account ))
    | Redirect (Task ( String, Mastodon.Request.Error ) ( String, Mastodon.Entity.App, Platform.Cmd.Cmd msg ))

The result type of the loginTask function.

It's either a task to fetch the logged-in user's Mastodon.Entity.Account or a task to redirect to the authorization server to get a code.

The two Strings in FetchAccount are the server name and token. The String in Redirect is the server name.

loginTask : { client_name : String, server : String, applicationUri : String } -> Maybe String -> FetchAccountOrRedirect msg

Get a token and, if possible, the logged in Account from the server.

The Maybe String arg is a login token, called maybeToken below, if it is not Nothing.

Returns a Task to either fetch an Account for a known authorization token, or to redirect to the authentication server to create a code with which to mint the token.

If maybeToken is not Nothing, will attempt to get an Account using that token. If that fails, will return an error. If maybeToken is Nothing, will create a new App, and redirect to do authentication. When the application is restarted, with a code in the URL query, call getTokenTask to use that code and the saved App instance to mint a new token, and to use that to get an Account.

Usually, you will get a Token from persistent localStorage, pass that here, and successfully receive the Account back. No redirects necessary. But the redirects must happen at least once, and then whenever the authorization token expires. This is step 8 below. Only if that fails will this do step 3, or, if there was no persisted client id and secret, steps 1 through 3.

Currently, permission for all scopes is requested. Maybe the scopes should be passed as a parameter.

The full login procedure is as follows:

  1. Send "POST /api/v1/apps" to the Mastodon server, via a PostApp request,

  2. Receive the returned App, including client_id and client_secret. Save it in localStorage, keyed with the Mastodon server's host name (from applicationUri).

  3. Redirect to <server_url>/oauth/authorize?client_id=<client_id>&redirect_uri=<applicationUri>&response_type=code&scope=<scopes>

  4. The user enters login authorization information to the server web site.

  5. The server web site redirects to <applicationUri>?code=<code>

  6. Lookup the saved App instance using the Mastodon server's host name (which your top-level application must persist somewhere). Use that to POST to <server_url>/oauth/token:

    POST /oauth/token HTTP/1.1
    Host: <server_url>
    Authorization: Basic `(Base64.encode (<client_id> ++ ":" ++ <client_secret>))`
    Content-Type: application/x-www-form-urlencoded
    grant_type=authorization_code&code=<code>&redirect_uri=<applicationUri>&client_id=<client_id>&client_secret=<client_secret>
    

    The Authorization header isn't needed for new Mastodon API servers, but that's how it was done for old ones, so I'm hoping this will give some backward compatibility.

  7. Receive back token information, JSON-encoded:

    { "access_token":"cptLSO8ff7zKbBXlTTyH15bnxQS5b9erVUWi_n0_EGd",
      "token_type":"Bearer",
      "scope":"write,read,follow,push",
      "created_at":1561845912
    }
    
  8. Use the token_type and access_token to authenticate a request for the user's Account.

getTokenTask : { code : String, server : String, app : Mastodon.Entity.App } -> Task ( String, Mastodon.Request.Error ) ( String, Mastodon.Entity.Authorization, Mastodon.Entity.Account )

Continue after being restarted with a code and state in the URL query.

This continues from step 6 in the full login procedure description, which is included with the documentation for loginTask above.

Your application will usually persist the Authorization, so you can use it the next time the user starts the application, as a parameter to loginTask.

The String in the Task is the server name, e.g. "mastodon.social".

Internal functions.

appToAuthorizeUrl : String -> Mastodon.Entity.App -> String

Compute URL to redirect to for authentication.

Args are:

appToAuthorizeUrl server app

Returns:

https://<server>/oauth/authorize
   ?client_id=<app.client_id>
   &redirect_uri=<app.redirect_uri>
   &response_type=code
   &scope=<all scopes>

You will call this explicitly only when a token expires, and you need to mint a new one from a previously created App:

appToAuthorizeUrl server app
    |> Browser.Navigation.load