@@ -0,0 +1,29 @@ | |||||
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 |
@@ -0,0 +1,22 @@ | |||||
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 |
@@ -0,0 +1,32 @@ | |||||
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 |
@@ -0,0 +1,30 @@ | |||||
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 |
@@ -0,0 +1,44 @@ | |||||
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 |
@@ -0,0 +1,4 @@ | |||||
defmodule Pleroma.Web.OAuth.OAuthView do | |||||
use Pleroma.Web, :view | |||||
import Phoenix.HTML.Form | |||||
end |
@@ -0,0 +1,31 @@ | |||||
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 |
@@ -16,6 +16,7 @@ defmodule Pleroma.Web.Router do | |||||
pipeline :authenticated_api do | pipeline :authenticated_api do | ||||
plug :accepts, ["json"] | plug :accepts, ["json"] | ||||
plug :fetch_session | plug :fetch_session | ||||
plug Pleroma.Plugs.OAuthPlug | |||||
plug Pleroma.Plugs.AuthenticationPlug, %{fetcher: &Router.user_fetcher/1} | plug Pleroma.Plugs.AuthenticationPlug, %{fetcher: &Router.user_fetcher/1} | ||||
end | end | ||||
@@ -31,10 +32,27 @@ defmodule Pleroma.Web.Router do | |||||
plug :accepts, ["json"] | plug :accepts, ["json"] | ||||
end | 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 | scope "/api/v1", Pleroma.Web do | ||||
pipe_through :masto_config | pipe_through :masto_config | ||||
# TODO: Move this | # TODO: Move this | ||||
get "/instance", TwitterAPI.UtilController, :masto_instance | 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 | end | ||||
scope "/api", Pleroma.Web do | scope "/api", Pleroma.Web do | ||||
@@ -0,0 +1,11 @@ | |||||
<!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> |
@@ -0,0 +1,2 @@ | |||||
<h1>Successfully authorized</h1> | |||||
<h2>Token code is <%= @auth.token %></h2> |
@@ -0,0 +1,14 @@ | |||||
<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 %> |
@@ -0,0 +1,3 @@ | |||||
defmodule Pleroma.Web.LayoutView do | |||||
use Pleroma.Web, :view | |||||
end |
@@ -0,0 +1,16 @@ | |||||
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 |
@@ -0,0 +1,15 @@ | |||||
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 |
@@ -0,0 +1,15 @@ | |||||
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 |