defmodule DiogramosWeb.Api.V1.SessionController do
@moduledoc """
Magic-link login for native clients, and revocation of the calling
device's own token.
The QR-pairing path (the primary one) never touches this controller —
the token is minted from an authenticated browser session in
`UserLive.Settings` and carried to the phone by the QR code.
"""
use DiogramosWeb, :controller
alias Diogramos.Accounts
action_fallback DiogramosWeb.Api.FallbackController
@doc """
Sends a magic link if the address belongs to a registered user.
Always replies 204 — a different answer for unknown addresses would
turn this into an account-enumeration oracle.
"""
def request_magic_link(conn, %{"email" => email}) when is_binary(email) do
if user = Accounts.get_user_by_email(email) do
# /m/:token rather than the web login path: Android claims that
# route as an App Link, so tapping the link in the phone's inbox
# opens the app instead of a browser. Opened anywhere else it
# redirects into the same web flow.
Accounts.deliver_login_instructions(user, &url(~p"/m/#{&1}"))
end
send_resp(conn, :no_content, "")
end
def request_magic_link(conn, _params) do
conn
|> put_status(:unprocessable_entity)
|> put_view(json: DiogramosWeb.Api.ErrorJSON)
|> render(:error, %{code: "unprocessable", message: "An email is required."})
end
@doc """
Trades a magic-link token for a long-lived API token.
The magic-link token is single-use: it is deleted on redemption, same as
the browser login flow.
"""
def redeem(conn, %{"token" => token} = params) when is_binary(token) do
case Accounts.login_user_by_magic_link(token) do
{:ok, {user, _expired_tokens}} ->
api_token = Accounts.create_api_token(user, %{label: params["label"]})
conn
|> put_status(:created)
|> json(%{token: api_token, user: user_payload(user)})
_ ->
conn
|> put_status(:unauthorized)
|> put_view(json: DiogramosWeb.Api.ErrorJSON)
|> render(:error, %{
code: "unauthenticated",
message: "That link is invalid or has expired."
})
end
end
def redeem(conn, _params) do
conn
|> put_status(:unprocessable_entity)
|> put_view(json: DiogramosWeb.Api.ErrorJSON)
|> render(:error, %{code: "unprocessable", message: "A token is required."})
end
@doc "Revokes the token this request authenticated with."
def delete(conn, _params) do
user = conn.assigns.current_scope.user
case Accounts.delete_api_token(user, conn.assigns.current_api_token_id) do
:ok -> send_resp(conn, :no_content, "")
error -> error
end
end
defp user_payload(user) do
%{id: user.id, email: user.email, confirmed_at: user.confirmed_at}
end
end
neiam
/diogramos
diogramos
public · Issues · Pulls · Labels · Forks · Compare · Actions success · Badges · Packages
⭐
Log in to mark this repository.
2.8 KiB · text
History
dc2edab