Implement access token authentication

Add whoami endpoint
This commit is contained in:
Pim Kunis 2021-06-26 00:29:33 +02:00
parent d81a42bf8a
commit dac1a429b9
8 changed files with 96 additions and 4 deletions

View file

@ -34,6 +34,7 @@ defmodule MatrixServer do
{get_errcode_status(errcode), %{errcode: errcode, error: get_errcode_error(errcode)}} {get_errcode_status(errcode), %{errcode: errcode, error: get_errcode_error(errcode)}}
end end
# TODO: make a plug for this?
def get_errcode_error("M_BAD_JSON"), do: "Bad request." def get_errcode_error("M_BAD_JSON"), do: "Bad request."
def get_errcode_error("M_USER_IN_USE"), do: "Username is already taken." def get_errcode_error("M_USER_IN_USE"), do: "Username is already taken."
def get_errcode_error("M_INVALID_USERNAME"), do: "Invalid username." def get_errcode_error("M_INVALID_USERNAME"), do: "Invalid username."

View file

@ -44,6 +44,15 @@ defmodule MatrixServer.Account do
|> Multi.run(:device_with_access_token, &Device.generate_access_token/2) |> Multi.run(:device_with_access_token, &Device.generate_access_token/2)
end end
def get_by_access_token(access_token) do
from(a in Account,
join: d in assoc(a, :devices),
where: d.access_token == ^access_token,
preload: [devices: d]
)
|> Repo.one()
end
def changeset(account, params \\ %{}) do def changeset(account, params \\ %{}) do
account account
|> cast(params, [:localpart, :password_hash]) |> cast(params, [:localpart, :password_hash])

View file

@ -23,8 +23,12 @@ defmodule MatrixServer.Device do
|> unique_constraint([:localpart, :device_id], name: :devices_pkey) |> unique_constraint([:localpart, :device_id], name: :devices_pkey)
end end
def generate_access_token(repo, %{device: %Device{localpart: localpart, device_id: device_id} = device}) do def generate_access_token(repo, %{
access_token = Phoenix.Token.encrypt(MatrixServerWeb.Endpoint, "access_token", {localpart, device_id}) device: %Device{localpart: localpart, device_id: device_id} = device
}) do
access_token =
Phoenix.Token.encrypt(MatrixServerWeb.Endpoint, "access_token", {localpart, device_id})
device device
|> change(%{access_token: access_token}) |> change(%{access_token: access_token})
|> repo.update() |> repo.update()

View file

@ -0,0 +1,45 @@
defmodule MatrixServerWeb.Authenticate do
import Plug.Conn
import Phoenix.Controller, only: [json: 2]
alias MatrixServer.Account
alias Plug.Conn
def init(options), do: options
def call(%Conn{params: %{"access_token" => access_token}} = conn, _opts) do
authenticate(conn, access_token)
end
def call(%Conn{req_headers: headers} = conn, _opts) do
case List.keyfind(headers, "authorization", 0) do
{_, "Bearer " <> access_token} ->
authenticate(conn, access_token)
_ ->
data = %{errcode: "M_MISSING_TOKEN", error: "Access token missing."}
conn
|> put_status(401)
|> json(data)
|> halt()
end
end
defp authenticate(conn, access_token) do
case Account.get_by_access_token(access_token) do
%Account{devices: [device]} = account ->
conn
|> assign(:account, account)
|> assign(:device, device)
nil ->
data = %{errcode: "M_UNKNOWN_TOKEN", error: "Invalid access token."}
conn
|> put_status(401)
|> json(data)
|> halt()
end
end
end

View file

@ -1,6 +1,10 @@
defmodule MatrixServerWeb.AccountController do defmodule MatrixServerWeb.AccountController do
use MatrixServerWeb, :controller use MatrixServerWeb, :controller
import MatrixServer, only: [get_mxid: 1]
alias MatrixServer.Account alias MatrixServer.Account
alias Plug.Conn
def available(conn, params) do def available(conn, params) do
localpart = Map.get(params, "username", "") localpart = Map.get(params, "username", "")
@ -21,4 +25,12 @@ defmodule MatrixServerWeb.AccountController do
|> put_status(status) |> put_status(status)
|> json(data) |> json(data)
end end
def whoami(%Conn{assigns: %{account: %Account{localpart: localpart}}} = conn, _params) do
data = %{user_id: get_mxid(localpart)}
conn
|> put_status(200)
|> json(data)
end
end end

View file

@ -1,12 +1,17 @@
defmodule MatrixServerWeb.Router do defmodule MatrixServerWeb.Router do
use MatrixServerWeb, :router use MatrixServerWeb, :router
pipeline :api do pipeline :public do
plug :accepts, ["json"] plug :accepts, ["json"]
end end
pipeline :authenticated do
plug :accepts, ["json"]
plug MatrixServerWeb.Authenticate
end
scope "/_matrix", MatrixServerWeb do scope "/_matrix", MatrixServerWeb do
pipe_through :api pipe_through :public
scope "/client/r0", as: :client do scope "/client/r0", as: :client do
post "/register", AuthController, :register post "/register", AuthController, :register
@ -16,4 +21,12 @@ defmodule MatrixServerWeb.Router do
get "/client/versions", InfoController, :versions get "/client/versions", InfoController, :versions
end end
scope "/_matrix", MatrixServerWeb do
pipe_through :authenticated
scope "/client/r0", as: :client do
get "/account/whoami", AccountController, :whoami
end
end
end end

View file

@ -13,6 +13,7 @@ defmodule MatrixServer.Repo.Migrations.AddDevicesTable do
null: false null: false
end end
# Compound primary already indexes device_id.
create index(:devices, [:localpart]) create index(:devices, [:localpart])
end end
end end

View file

@ -0,0 +1,7 @@
defmodule MatrixServer.Repo.Migrations.AddAccessTokenIndexToDevices do
use Ecto.Migration
def change do
create index(:devices, [:access_token], unique: true)
end
end