Fresh repository history for elektrine/tarakan hosted at https://git.elektrine.com/elektrine/tarakan.
189 lines
7.4 KiB
Elixir
189 lines
7.4 KiB
Elixir
defmodule TarakanWeb.ClientAuthorizationLive do
|
|
use TarakanWeb, :live_view
|
|
|
|
alias Tarakan.Accounts.ClientAuthorizations
|
|
|
|
@impl true
|
|
def mount(%{"user_code" => user_code}, _session, socket) do
|
|
actor_key = socket.assigns.current_scope.account.id
|
|
|
|
case ClientAuthorizations.get_for_browser(user_code, actor_key) do
|
|
{:ok, authorization} ->
|
|
{:ok,
|
|
socket
|
|
|> assign(:authorization, authorization)
|
|
|> assign(
|
|
:display_code,
|
|
ClientAuthorizations.display_user_code(authorization.user_code)
|
|
)
|
|
|> assign(:page_title, "Authorize Tarakan Client")}
|
|
|
|
# Throttled and missing look the same on purpose: telling a grinder which
|
|
# of its guesses were merely rate limited would hand back the oracle.
|
|
{:error, _reason} ->
|
|
{:ok,
|
|
socket
|
|
|> assign(:authorization, nil)
|
|
|> assign(:display_code, user_code)
|
|
|> assign(:page_title, "Login request expired")}
|
|
end
|
|
end
|
|
|
|
@impl true
|
|
def handle_event(action, _params, %{assigns: %{authorization: nil}} = socket)
|
|
when action in ["approve", "deny"] do
|
|
{:noreply, put_flash(socket, :error, "This login request has expired or was already used.")}
|
|
end
|
|
|
|
def handle_event("approve", _params, socket) do
|
|
account = socket.assigns.current_scope.account
|
|
|
|
case ClientAuthorizations.approve(socket.assigns.authorization, account) do
|
|
{:ok, authorization} ->
|
|
{:noreply,
|
|
socket
|
|
|> assign(:authorization, authorization)
|
|
|> put_flash(:info, "Connected. You can go back to the terminal.")}
|
|
|
|
{:error, _reason} ->
|
|
{:noreply,
|
|
socket
|
|
|> assign(:authorization, nil)
|
|
|> put_flash(:error, "This login request expired or was already used.")}
|
|
end
|
|
end
|
|
|
|
def handle_event("deny", _params, socket) do
|
|
account = socket.assigns.current_scope.account
|
|
|
|
case ClientAuthorizations.deny(socket.assigns.authorization, account) do
|
|
{:ok, authorization} ->
|
|
{:noreply,
|
|
socket
|
|
|> assign(:authorization, authorization)
|
|
|> put_flash(:info, "Denied.")}
|
|
|
|
{:error, _reason} ->
|
|
{:noreply, assign(socket, :authorization, nil)}
|
|
end
|
|
end
|
|
|
|
@impl true
|
|
def render(assigns) do
|
|
~H"""
|
|
<Layouts.app flash={@flash} current_scope={@current_scope} current_path={assigns[:current_path]}>
|
|
<Layouts.page id="client-authorization-page" width={:compact}>
|
|
<section class={[
|
|
"border-2 border-strong bg-panel p-6 shadow-[8px_8px_0_0_var(--color-rule)] sm:p-8"
|
|
]}>
|
|
<div class={["flex items-start gap-4"]}>
|
|
<div class={[
|
|
"flex size-11 shrink-0 items-center justify-center bg-signal text-ground"
|
|
]}>
|
|
<.icon name="hero-command-line" class={["size-6"]} />
|
|
</div>
|
|
<div class={["min-w-0"]}>
|
|
<h1 class={[
|
|
"font-display text-2xl font-medium uppercase tracking-[0.02em] text-ink"
|
|
]}>
|
|
Connect your terminal
|
|
</h1>
|
|
<p class={["mt-2 text-sm leading-6 text-ink-muted"]}>
|
|
Approving lets the client claim jobs as @{@current_scope.account.handle}.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
<%= cond do %>
|
|
<% is_nil(@authorization) -> %>
|
|
<div id="client-authorization-expired" class={["mt-8 border-2 border-rule p-5"]}>
|
|
<p class={["text-sm text-ink"]}>
|
|
That code is invalid or expired. Run <code class="font-mono">tarakan login</code>
|
|
again.
|
|
</p>
|
|
</div>
|
|
<% @authorization.status == "approved" -> %>
|
|
<div
|
|
id="client-authorization-approved"
|
|
class={["mt-8 border-2 border-phosphor bg-ground p-5"]}
|
|
>
|
|
<p class={["text-sm font-semibold text-ink"]}>Connected.</p>
|
|
<p class={["mt-1 text-sm text-ink-muted"]}>Back to your terminal.</p>
|
|
</div>
|
|
<% @authorization.status == "denied" -> %>
|
|
<div id="client-authorization-denied" class={["mt-8 border-2 border-rule p-5"]}>
|
|
<p class={["text-sm text-ink"]}>Denied. Nothing was granted.</p>
|
|
</div>
|
|
<% true -> %>
|
|
<div id="client-authorization-pending" class={["mt-8"]}>
|
|
<p class={["text-xs font-semibold text-ink-muted"]}>
|
|
Code
|
|
</p>
|
|
<%!-- Deliberately outside the two-token tracking scale (0.12em
|
|
labels / 0.02em display): this is a code the user transcribes
|
|
by hand, and the wide gaps keep characters from running together. --%>
|
|
<div
|
|
id="client-authorization-code"
|
|
class={[
|
|
"mt-2 border-2 border-strong bg-ground px-4 py-3 font-mono text-2xl tracking-[0.2em] text-ink sm:text-3xl"
|
|
]}
|
|
>
|
|
{@display_code}
|
|
</div>
|
|
|
|
<%!-- The name comes from whoever started the login, not from
|
|
Tarakan, so it is attributed rather than asserted: a
|
|
request that calls itself something official must not read
|
|
as though this page vouches for it. --%>
|
|
<p class={["mt-5 text-xs font-semibold text-ink-muted"]}>
|
|
Calls itself
|
|
</p>
|
|
<p
|
|
id="client-authorization-client-name"
|
|
class={[
|
|
"mt-1 border-2 border-rule bg-ground px-3 py-2 font-mono text-sm text-ink"
|
|
]}
|
|
>
|
|
{@authorization.client_name}
|
|
</p>
|
|
|
|
<p class={["mt-4 text-sm text-ink-muted"]}>
|
|
Approving lets it claim jobs and submit reviews as
|
|
<span class="font-semibold text-ink">@{@current_scope.account.handle}</span>
|
|
for about 7 days. Revoke anytime in settings.
|
|
</p>
|
|
<p class={["mt-2 text-sm text-ink-muted"]}>
|
|
Only approve this if you just ran
|
|
<code class="font-mono text-ink">tarakan login</code>
|
|
yourself and the code above matches your terminal. Nobody at Tarakan will
|
|
ever ask you for it.
|
|
</p>
|
|
|
|
<div class={["mt-6 grid gap-3 sm:grid-cols-2"]}>
|
|
<button
|
|
id="client-authorization-deny-button"
|
|
phx-click="deny"
|
|
class={[
|
|
"flex h-12 items-center justify-center border border-strong px-4 font-display text-sm uppercase tracking-[0.12em] text-ink transition hover:bg-ground"
|
|
]}
|
|
>
|
|
Deny
|
|
</button>
|
|
<button
|
|
id="client-authorization-approve-button"
|
|
phx-click="approve"
|
|
class={[
|
|
"clip-notch flex h-12 items-center justify-center bg-btn px-4 font-display text-sm uppercase tracking-[0.12em] text-btn-fg transition hover:opacity-90"
|
|
]}
|
|
>
|
|
Approve
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<% end %>
|
|
</section>
|
|
</Layouts.page>
|
|
</Layouts.app>
|
|
"""
|
|
end
|
|
end
|