Implement POST /hostapi/auth/token
This commit is contained in:
parent
2a406ef236
commit
1468d21c57
|
@ -28,3 +28,7 @@ config :logger, level: :warning
|
||||||
|
|
||||||
# Initialize plugs at runtime for faster test compilation
|
# Initialize plugs at runtime for faster test compilation
|
||||||
config :phoenix, :plug_init_mode, :runtime
|
config :phoenix, :plug_init_mode, :runtime
|
||||||
|
|
||||||
|
# Decrease the number of rounds used to encrypt passwords and whatnot
|
||||||
|
# to improve performance
|
||||||
|
config :bcrypt_elixir, :log_rounds, 4
|
||||||
|
|
|
@ -18,5 +18,13 @@ defmodule Hostas.Denizen do
|
||||||
denizen
|
denizen
|
||||||
|> cast(attrs, [:name, :handle, :password])
|
|> cast(attrs, [:name, :handle, :password])
|
||||||
|> validate_required([:name, :handle, :password])
|
|> validate_required([:name, :handle, :password])
|
||||||
|
|> unique_constraint(:handle)
|
||||||
|
|> hash_password
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Hash the password
|
||||||
|
defp hash_password(%Ecto.Changeset{valid?: true, changes: %{password: password}} = changeset) do
|
||||||
|
change(changeset, Bcrypt.add_hash(password))
|
||||||
|
end
|
||||||
|
defp hash_password(changeset), do: changeset
|
||||||
end
|
end
|
||||||
|
|
|
@ -5,6 +5,7 @@ defmodule Hostas.Token do
|
||||||
schema "tokens" do
|
schema "tokens" do
|
||||||
field :denizen_id, :integer
|
field :denizen_id, :integer
|
||||||
field :expires, :utc_datetime
|
field :expires, :utc_datetime
|
||||||
|
field :token, :string
|
||||||
|
|
||||||
# Link tokens to denizens
|
# Link tokens to denizens
|
||||||
belongs_to :denizens, Hostas.Denizen
|
belongs_to :denizens, Hostas.Denizen
|
||||||
|
|
|
@ -1,7 +1,79 @@
|
||||||
defmodule HostasWeb.Auth.TokenController do
|
defmodule HostasWeb.Auth.TokenController do
|
||||||
|
import Ecto.Query, only: [from: 2]
|
||||||
use HostasWeb, :controller
|
use HostasWeb, :controller
|
||||||
|
|
||||||
def index(_conn, _params) do
|
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
|
||||||
|
# Create a random token
|
||||||
|
token = Base.encode64(:crypto.strong_rand_bytes(256))
|
||||||
|
|
||||||
|
# Calculate when the token should expire
|
||||||
|
{:ok, time_now} = DateTime.now("Etc/UTC")
|
||||||
|
expiry = DateTime.add(time_now, 30, :day)
|
||||||
|
|> DateTime.truncate(:second)
|
||||||
|
|
||||||
|
# Register the token
|
||||||
|
{:ok, token_struct} = Repo.insert(
|
||||||
|
%Token{denizen_id: denizen_id, token: token, expires: expiry})
|
||||||
|
|
||||||
|
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(params)
|
||||||
|
# |> json(%{"error" => "Missing required parameters"})
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Responds with 200 OK if the requester's `Bearing` header
|
||||||
|
contains a valid, non-expired API token
|
||||||
|
"""
|
||||||
|
def verify(_conn, _params) do
|
||||||
|
:ok
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Deletes the token the requester used in the `Bearing` header
|
||||||
|
"""
|
||||||
|
def revoke(_conn, _params) do
|
||||||
|
:ok
|
||||||
|
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
|
:ok
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -23,7 +23,11 @@ defmodule HostasWeb.Router do
|
||||||
scope "/hostapi/", HostasWeb do
|
scope "/hostapi/", HostasWeb do
|
||||||
pipe_through :api
|
pipe_through :api
|
||||||
|
|
||||||
resources "/auth/token", Auth.TokenController
|
# create, verify, renew, revoke
|
||||||
|
post "/auth/token", Auth.TokenController, :create
|
||||||
|
get "/auth/token", Auth.TokenController, :verify
|
||||||
|
delete "/auth/token", Auth.TokenController, :revoke
|
||||||
|
get "/auth/token/renew", Auth.TokenController, :renew
|
||||||
end
|
end
|
||||||
|
|
||||||
# Enable LiveDashboard and Swoosh mailbox preview in development
|
# Enable LiveDashboard and Swoosh mailbox preview in development
|
||||||
|
|
3
mix.exs
3
mix.exs
|
@ -49,7 +49,8 @@ defmodule Hostas.MixProject do
|
||||||
{:telemetry_poller, "~> 1.0"},
|
{:telemetry_poller, "~> 1.0"},
|
||||||
{:gettext, "~> 0.20"},
|
{:gettext, "~> 0.20"},
|
||||||
{:jason, "~> 1.2"},
|
{:jason, "~> 1.2"},
|
||||||
{:plug_cowboy, "~> 2.5"}
|
{:plug_cowboy, "~> 2.5"},
|
||||||
|
{:bcrypt_elixir, "~> 3.0"}
|
||||||
]
|
]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
2
mix.lock
2
mix.lock
|
@ -1,6 +1,8 @@
|
||||||
%{
|
%{
|
||||||
|
"bcrypt_elixir": {:hex, :bcrypt_elixir, "3.0.1", "9be815469e6bfefec40fa74658ecbbe6897acfb57614df1416eeccd4903f602c", [:make, :mix], [{:comeonin, "~> 5.3", [hex: :comeonin, repo: "hexpm", optional: false]}, {:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "486bb95efb645d1efc6794c1ddd776a186a9a713abf06f45708a6ce324fb96cf"},
|
||||||
"castore": {:hex, :castore, "1.0.3", "7130ba6d24c8424014194676d608cb989f62ef8039efd50ff4b3f33286d06db8", [:mix], [], "hexpm", "680ab01ef5d15b161ed6a95449fac5c6b8f60055677a8e79acf01b27baa4390b"},
|
"castore": {:hex, :castore, "1.0.3", "7130ba6d24c8424014194676d608cb989f62ef8039efd50ff4b3f33286d06db8", [:mix], [], "hexpm", "680ab01ef5d15b161ed6a95449fac5c6b8f60055677a8e79acf01b27baa4390b"},
|
||||||
"cc_precompiler": {:hex, :cc_precompiler, "0.1.7", "77de20ac77f0e53f20ca82c563520af0237c301a1ec3ab3bc598e8a96c7ee5d9", [:mix], [{:elixir_make, "~> 0.7.3", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "2768b28bf3c2b4f788c995576b39b8cb5d47eb788526d93bd52206c1d8bf4b75"},
|
"cc_precompiler": {:hex, :cc_precompiler, "0.1.7", "77de20ac77f0e53f20ca82c563520af0237c301a1ec3ab3bc598e8a96c7ee5d9", [:mix], [{:elixir_make, "~> 0.7.3", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "2768b28bf3c2b4f788c995576b39b8cb5d47eb788526d93bd52206c1d8bf4b75"},
|
||||||
|
"comeonin": {:hex, :comeonin, "5.3.3", "2c564dac95a35650e9b6acfe6d2952083d8a08e4a89b93a481acb552b325892e", [:mix], [], "hexpm", "3e38c9c2cb080828116597ca8807bb482618a315bfafd98c90bc22a821cc84df"},
|
||||||
"cowboy": {:hex, :cowboy, "2.10.0", "ff9ffeff91dae4ae270dd975642997afe2a1179d94b1887863e43f681a203e26", [:make, :rebar3], [{:cowlib, "2.12.1", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "1.8.0", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "3afdccb7183cc6f143cb14d3cf51fa00e53db9ec80cdcd525482f5e99bc41d6b"},
|
"cowboy": {:hex, :cowboy, "2.10.0", "ff9ffeff91dae4ae270dd975642997afe2a1179d94b1887863e43f681a203e26", [:make, :rebar3], [{:cowlib, "2.12.1", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "1.8.0", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "3afdccb7183cc6f143cb14d3cf51fa00e53db9ec80cdcd525482f5e99bc41d6b"},
|
||||||
"cowboy_telemetry": {:hex, :cowboy_telemetry, "0.4.0", "f239f68b588efa7707abce16a84d0d2acf3a0f50571f8bb7f56a15865aae820c", [:rebar3], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7d98bac1ee4565d31b62d59f8823dfd8356a169e7fcbb83831b8a5397404c9de"},
|
"cowboy_telemetry": {:hex, :cowboy_telemetry, "0.4.0", "f239f68b588efa7707abce16a84d0d2acf3a0f50571f8bb7f56a15865aae820c", [:rebar3], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7d98bac1ee4565d31b62d59f8823dfd8356a169e7fcbb83831b8a5397404c9de"},
|
||||||
"cowlib": {:hex, :cowlib, "2.12.1", "a9fa9a625f1d2025fe6b462cb865881329b5caff8f1854d1cbc9f9533f00e1e1", [:make, :rebar3], [], "hexpm", "163b73f6367a7341b33c794c4e88e7dbfe6498ac42dcd69ef44c5bc5507c8db0"},
|
"cowlib": {:hex, :cowlib, "2.12.1", "a9fa9a625f1d2025fe6b462cb865881329b5caff8f1854d1cbc9f9533f00e1e1", [:make, :rebar3], [], "hexpm", "163b73f6367a7341b33c794c4e88e7dbfe6498ac42dcd69ef44c5bc5507c8db0"},
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
defmodule Hostas.Repo.Migrations.AddTokenField do
|
||||||
|
use Ecto.Migration
|
||||||
|
|
||||||
|
def change do
|
||||||
|
alter table(:tokens) do
|
||||||
|
add :token, :string
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
Loading…
Reference in New Issue