- Add jump_credo_checks ~> 0.4 with all 20 checks enabled - Fix all standard Credo issues: 139 @spec (113 done, 26 remain), 4 refactoring, 3 alias usage, 9 System.cmd env, 5 unsafe_to_atom, 2 max line length, 9 assert_receive timeout - Fix 170+ jump_credo_checks warnings: - 117 TopLevelAliasImportRequire: move nested alias/import to module top - 32 UseObanProWorker: switch to Oban.Pro.Worker - 4 DoctestIExExamples: add doctests / create test file - ~20 WeakAssertion: strengthen type-check assertions - Various ConditionalAssertion, AssertReceiveTimeout fixes - Exclude vendor/ from Credo analysis - Remaining: 175 warnings (mostly opinionated WeakAssertion, AvoidSocketAssignsInTest), 26 @spec annotations
69 lines
2.1 KiB
Elixir
69 lines
2.1 KiB
Elixir
defmodule MicrowavepropWeb.Api.V1.AuthController do
|
|
@moduledoc """
|
|
Email/password login that mints a long-lived `/api/v1` bearer token.
|
|
|
|
This is the only `/api/v1` endpoint that accepts a password — every
|
|
other endpoint authenticates with the bearer token returned here.
|
|
"""
|
|
|
|
use Phoenix.Controller, formats: [:json]
|
|
|
|
alias Microwaveprop.Accounts
|
|
alias MicrowavepropWeb.Api.ErrorJSON
|
|
alias MicrowavepropWeb.Api.V1.TokenJSON
|
|
|
|
plug :accepts, ["json"]
|
|
|
|
@doc """
|
|
POST /api/v1/auth/tokens
|
|
|
|
Body: `{"email": "...", "password": "...", "name": "device label",
|
|
"expires_at": "ISO8601" (optional)}`. Returns the plaintext token
|
|
and the persisted record.
|
|
"""
|
|
@spec create(Plug.Conn.t(), map()) :: Plug.Conn.t()
|
|
def create(conn, params) do
|
|
with {:ok, email} <- fetch_string(params, "email"),
|
|
{:ok, password} <- fetch_string(params, "password"),
|
|
{:ok, name} <- fetch_string(params, "name"),
|
|
%Microwaveprop.Accounts.User{} = user <-
|
|
Accounts.get_user_by_email_and_password(email, password) do
|
|
token_attrs = %{name: name, expires_at: parse_expiry(params["expires_at"])}
|
|
|
|
case Accounts.create_api_token(user, token_attrs) do
|
|
{:ok, {plaintext, record}} ->
|
|
conn
|
|
|> put_status(:created)
|
|
|> json(TokenJSON.show_with_plaintext(record, plaintext))
|
|
|
|
{:error, changeset} ->
|
|
ErrorJSON.send_changeset(conn, changeset)
|
|
end
|
|
else
|
|
nil ->
|
|
ErrorJSON.send_problem(conn, 401, "unauthorized", "Invalid email or password.")
|
|
|
|
{:error, field} ->
|
|
ErrorJSON.send_problem(conn, 400, "bad_request", "Missing or invalid `#{field}`.")
|
|
end
|
|
end
|
|
|
|
defp fetch_string(params, key) do
|
|
case Map.get(params, key) do
|
|
value when is_binary(value) and byte_size(value) > 0 -> {:ok, value}
|
|
_ -> {:error, key}
|
|
end
|
|
end
|
|
|
|
defp parse_expiry(nil), do: nil
|
|
defp parse_expiry(""), do: nil
|
|
|
|
defp parse_expiry(string) when is_binary(string) do
|
|
case DateTime.from_iso8601(string) do
|
|
{:ok, dt, _offset} -> dt
|
|
{:error, _} -> :invalid
|
|
end
|
|
end
|
|
|
|
defp parse_expiry(_), do: :invalid
|
|
end
|