--- /dev/null
+defmodule Pleroma.App do
+ use Ecto.Schema
+ import Ecto.{Changeset}
+
+ schema "apps" do
+ field :client_name, :string
+ field :redirect_uris, :string
+ field :scopes, :string
+ field :website, :string
+ field :client_id, :string
+ field :client_secret, :string
+
+ timestamps()
+ end
+
+ def register_changeset(struct, params \\ %{}) do
+ changeset = struct
+ |> cast(params, [:client_name, :redirect_uris, :scopes, :website])
+ |> validate_required([:client_name, :redirect_uris, :scopes])
+
+ if changeset.valid? do
+ changeset
+ |> put_change(:client_id, :crypto.strong_rand_bytes(32) |> Base.url_encode64)
+ |> put_change(:client_secret, :crypto.strong_rand_bytes(32) |> Base.url_encode64)
+ else
+ changeset
+ end
+ end
+end
--- /dev/null
+defmodule Pleroma.Plugs.OAuthPlug do
+ import Plug.Conn
+ alias Pleroma.User
+ alias Pleroma.Repo
+ alias Pleroma.Web.OAuth.Token
+
+ def init(options) do
+ options
+ end
+
+ def call(%{assigns: %{user: %User{}}} = conn, _), do: conn
+ def call(conn, opts) do
+ with ["Bearer " <> header] <- get_req_header(conn, "authorization"),
+ %Token{user_id: user_id} <- Repo.get_by(Token, token: header),
+ %User{} = user <- Repo.get(User, user_id) do
+ conn
+ |> assign(:user, user)
+ else
+ _ -> conn
+ end
+ end
+end
--- /dev/null
+defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
+ use Pleroma.Web, :controller
+ alias Pleroma.{Repo, App}
+
+ def create_app(conn, params) do
+ with cs <- App.register_changeset(%App{}, params) |> IO.inspect,
+ {:ok, app} <- Repo.insert(cs) |> IO.inspect do
+ res = %{
+ id: app.id,
+ client_id: app.client_id,
+ client_secret: app.client_secret
+ }
+
+ json(conn, res)
+ end
+ end
+
+ def verify_credentials(%{assigns: %{user: user}} = conn, params) do
+ account = %{
+ id: user.id,
+ username: user.nickname,
+ acct: user.nickname,
+ display_name: user.name,
+ locked: false,
+ created_at: user.inserted_at,
+ note: user.bio,
+ url: ""
+ }
+
+ json(conn, account)
+ end
+end
--- /dev/null
+defmodule Pleroma.Web.OAuth.Authorization do
+ use Ecto.Schema
+
+ alias Pleroma.{App, User, Repo}
+ alias Pleroma.Web.OAuth.Authorization
+
+ schema "oauth_authorizations" do
+ field :token, :string
+ field :valid_until, :naive_datetime
+ field :used, :boolean, default: false
+ belongs_to :user, Pleroma.User
+ belongs_to :app, Pleroma.App
+
+ timestamps()
+ end
+
+ def create_authorization(%App{} = app, %User{} = user) do
+ token = :crypto.strong_rand_bytes(32) |> Base.url_encode64
+
+ authorization = %Authorization{
+ token: token,
+ used: false,
+ user_id: user.id,
+ app_id: app.id,
+ valid_until: NaiveDateTime.add(NaiveDateTime.utc_now, 60 * 10)
+ }
+
+ Repo.insert(authorization)
+ end
+end
--- /dev/null
+defmodule Pleroma.Web.OAuth.OAuthController do
+ use Pleroma.Web, :controller
+
+ alias Pleroma.Web.OAuth.{Authorization, Token}
+ alias Pleroma.{Repo, User, App}
+ alias Comeonin.Pbkdf2
+
+ def authorize(conn, params) do
+ render conn, "show.html", %{
+ response_type: params["response_type"],
+ client_id: params["client_id"],
+ scope: params["scope"],
+ redirect_uri: params["redirect_uri"]
+ }
+ end
+
+ def create_authorization(conn, %{"authorization" => %{"name" => name, "password" => password, "client_id" => client_id}} = params) do
+ with %User{} = user <- User.get_cached_by_nickname(name),
+ true <- Pbkdf2.checkpw(password, user.password_hash),
+ %App{} = app <- Pleroma.Repo.get_by(Pleroma.App, client_id: client_id),
+ {:ok, auth} <- Authorization.create_authorization(app, user) do
+ render conn, "results.html", %{
+ auth: auth
+ }
+ end
+ end
+
+ # TODO CRITICAL
+ # - Check validity of auth token
+ def token_exchange(conn, %{"grant_type" => "authorization_code"} = params) do
+ with %App{} = app <- Repo.get_by(App, client_id: params["client_id"], client_secret: params["client_secret"]),
+ %Authorization{} = auth <- Repo.get_by(Authorization, token: params["code"], app_id: app.id),
+ {:ok, token} <- Token.create_token(app, Repo.get(User, auth.user_id)) do
+ response = %{
+ token_type: "Bearer",
+ access_token: token.token,
+ refresh_token: token.refresh_token,
+ expires_in: 60 * 10,
+ scope: "read write follow"
+ }
+ json(conn, response)
+ end
+ end
+end
--- /dev/null
+defmodule Pleroma.Web.OAuth.OAuthView do
+ use Pleroma.Web, :view
+ import Phoenix.HTML.Form
+end
--- /dev/null
+defmodule Pleroma.Web.OAuth.Token do
+ use Ecto.Schema
+
+ alias Pleroma.{App, User, Repo}
+ alias Pleroma.Web.OAuth.Token
+
+ schema "oauth_tokens" do
+ field :token, :string
+ field :refresh_token, :string
+ field :valid_until, :naive_datetime
+ belongs_to :user, Pleroma.User
+ belongs_to :app, Pleroma.App
+
+ timestamps()
+ end
+
+ def create_token(%App{} = app, %User{} = user) do
+ token = :crypto.strong_rand_bytes(32) |> Base.url_encode64
+ refresh_token = :crypto.strong_rand_bytes(32) |> Base.url_encode64
+
+ token = %Token{
+ token: token,
+ refresh_token: refresh_token,
+ user_id: user.id,
+ app_id: app.id,
+ valid_until: NaiveDateTime.add(NaiveDateTime.utc_now, 60 * 10)
+ }
+
+ Repo.insert(token)
+ end
+end
pipeline :authenticated_api do
plug :accepts, ["json"]
plug :fetch_session
+ plug Pleroma.Plugs.OAuthPlug
plug Pleroma.Plugs.AuthenticationPlug, %{fetcher: &Router.user_fetcher/1}
end
plug :accepts, ["json"]
end
+ pipeline :oauth do
+ plug :accepts, ["html", "json"]
+ end
+
+ scope "/oauth", Pleroma.Web.OAuth do
+ get "/authorize", OAuthController, :authorize
+ post "/authorize", OAuthController, :create_authorization
+ post "/token", OAuthController, :token_exchange
+ end
+
scope "/api/v1", Pleroma.Web do
pipe_through :masto_config
# TODO: Move this
get "/instance", TwitterAPI.UtilController, :masto_instance
+ post "/apps", MastodonAPI.MastodonAPIController, :create_app
+ end
+
+ scope "/api/v1", Pleroma.Web.MastodonAPI do
+ pipe_through :authenticated_api
+
+ get "/accounts/verify_credentials", MastodonAPIController, :verify_credentials
end
scope "/api", Pleroma.Web do
--- /dev/null
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset=utf-8 />
+ <title>Pleroma</title>
+ </head>
+ <body>
+ <h1>Welcome to Pleroma</h1>
+ <%= render @view_module, @view_template, assigns %>
+ </body>
+</html>
--- /dev/null
+<h1>Successfully authorized</h1>
+<h2>Token code is <%= @auth.token %></h2>
--- /dev/null
+<h2>OAuth Authorization</h2>
+<%= form_for @conn, o_auth_path(@conn, :authorize), [as: "authorization"], fn f -> %>
+<%= label f, :name, "Name" %>
+<%= text_input f, :name %>
+<br>
+<%= label f, :password, "Password" %>
+<%= password_input f, :password %>
+<br>
+<%= hidden_input f, :client_id, value: @client_id %>
+<%= hidden_input f, :response_type, value: @response_type %>
+<%= hidden_input f, :redirect_uri, value: @redirect_uri %>
+<%= hidden_input f, :scope, value: @scope %>
+<%= submit "Authorize" %>
+<% end %>
--- /dev/null
+defmodule Pleroma.Web.LayoutView do
+ use Pleroma.Web, :view
+end
--- /dev/null
+defmodule Pleroma.Repo.Migrations.AddMastodonApps do
+ use Ecto.Migration
+
+ def change do
+ create table(:apps) do
+ add :client_name, :string
+ add :redirect_uris, :string
+ add :scopes, :string
+ add :website, :string
+ add :client_id, :string
+ add :client_secret, :string
+
+ timestamps()
+ end
+ end
+end
--- /dev/null
+defmodule Pleroma.Repo.Migrations.CreateOAuthAuthorizations do
+ use Ecto.Migration
+
+ def change do
+ create table(:oauth_authorizations) do
+ add :app_id, references(:apps)
+ add :user_id, references(:users)
+ add :token, :string
+ add :valid_until, :naive_datetime
+ add :used, :boolean, default: false
+
+ timestamps()
+ end
+ end
+end
--- /dev/null
+defmodule Pleroma.Repo.Migrations.CreateOAuthToken do
+ use Ecto.Migration
+
+ def change do
+ create table(:oauth_tokens) do
+ add :app_id, references(:apps)
+ add :user_id, references(:users)
+ add :token, :string
+ add :refresh_token, :string
+ add :valid_until, :naive_datetime
+
+ timestamps()
+ end
+ end
+end