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. """ 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