defmodule HostasWeb.Auth.TokenController do import Ecto.Query, only: [from: 2] use HostasWeb, :controller alias Hostas.Repo alias Hostas.Denizen alias Hostas.Token @doc """ Generates an API token. Responds with the token if the user provides the correct password """ def create(conn, %{"handle" => handle, "password" => given_password}) do case Repo.one( from d in Denizen, where: d.handle == ^handle, select: %{id: d.id, password: d.password} ) do nil -> conn |> put_status(404) |> json(%{"error" => "No user with handle #{handle}"}) denizen -> %{id: denizen_id, password: real_password_hash} = denizen if Bcrypt.verify_pass(given_password, real_password_hash) do {:ok, token_struct} = Token.new(denizen_id) conn |> put_status(201) |> json(Map.take(token_struct, [:token, :expires])) else # Reject the request, as passwords don't match conn |> put_status(401) |> json(%{"error" => "Password mismatch"}) end end end def create(conn, _params) do conn |> put_status(422) |> json(%{"error" => "Missing required parameters"}) end @doc """ Responds with 200 OK if the requester's `Bearing` header contains a valid, non-expired API token, along with a payload with an `expires` key detailing when the key expires. """ def verify(conn, _params) do conn |> put_status(200) |> json(%{"expires" => conn.assigns[:token].expires}) end @doc """ Deletes the token the requester used in the `Bearing` header """ def revoke(conn, %{"id" => id_param}) do with {:parsed_id, {id, ""}} <- {:parsed_id, Integer.parse(id_param, 10)}, {:ok, token} <- fetch_token(id, conn), {:can_access, true} <- {:can_access, token.denizen_id == conn.assigns[:denizen].id} do Repo.delete_all(from t in Token, where: t.id == ^token.id) conn |> send_resp(200, "") else _ -> conn |> put_status(404) |> json(%{"error" => "Token not found"}) end end @doc """ Deletes the token the requester used in the `Bearing` header and responds with a new one if the old one was valid and unexpired """ def renew(_conn, _params) do :ok end defp fetch_token(id, conn) do if id == conn.assigns[:token].id do {:ok, conn.assigns[:token]} else case Repo.one(from t in Token, where: t.id == ^id) do nil -> {:error, :token_not_found} token -> {:ok, token} end end end end