+
+ defp get_app_from_request(conn, params) do
+ conn
+ |> fetch_client_credentials(params)
+ |> fetch_client
+ end
+
+ defp fetch_client({id, secret}) when is_binary(id) and is_binary(secret) do
+ Repo.get_by(App, client_id: id, client_secret: secret)
+ end
+
+ defp fetch_client({_id, _secret}), do: nil
+
+ defp fetch_client_credentials(conn, params) do
+ # Per RFC 6749, HTTP Basic is preferred to body params
+ with ["Basic " <> encoded] <- get_req_header(conn, "authorization"),
+ {:ok, decoded} <- Base.decode64(encoded),
+ [id, secret] <-
+ Enum.map(
+ String.split(decoded, ":"),
+ fn s -> URI.decode_www_form(s) end
+ ) do
+ {id, secret}
+ else
+ _ -> {params["client_id"], params["client_secret"]}
+ end
+ end
+
+ # Special case: Local MastodonFE
+ defp redirect_uri(conn, "."), do: mastodon_api_url(conn, :login)
+
+ defp redirect_uri(_conn, redirect_uri), do: redirect_uri
+
+ defp get_session_registration_id(conn), do: get_session(conn, :registration_id)
+
+ defp put_session_registration_id(conn, registration_id),
+ do: put_session(conn, :registration_id, registration_id)
+
+ defp response_token(%User{} = user, token, opts \\ %{}) do
+ %{
+ token_type: "Bearer",
+ access_token: token.token,
+ refresh_token: token.refresh_token,
+ expires_in: @expires_in,
+ scope: Enum.join(token.scopes, " "),
+ me: user.ap_id
+ }
+ |> Map.merge(opts)
+ end
+
+ @spec validate_scopes(App.t(), map()) ::
+ {:ok, list()} | {:error, :missing_scopes | :unsupported_scopes}
+ defp validate_scopes(app, params) do
+ params
+ |> Scopes.fetch_scopes(app.scopes)
+ |> Scopes.validates(app.scopes)
+ end