Packages
phoenix_kit
1.7.211
1.7.216
1.7.215
1.7.214
1.7.213
1.7.212
1.7.211
1.7.210
1.7.209
1.7.208
1.7.207
1.7.206
1.7.205
1.7.204
1.7.203
1.7.202
1.7.201
1.7.200
1.7.199
1.7.198
1.7.197
1.7.196
1.7.194
1.7.193
1.7.192
1.7.191
1.7.190
1.7.189
1.7.187
1.7.186
1.7.185
1.7.184
1.7.183
1.7.182
1.7.181
1.7.180
1.7.179
1.7.178
1.7.177
1.7.176
1.7.175
1.7.174
1.7.173
1.7.172
1.7.171
1.7.170
1.7.169
1.7.168
1.7.167
1.7.166
1.7.165
1.7.164
1.7.162
1.7.161
1.7.160
1.7.159
1.7.157
1.7.156
1.7.155
1.7.154
1.7.153
1.7.152
1.7.151
1.7.150
1.7.149
1.7.146
1.7.145
1.7.144
1.7.143
1.7.138
1.7.133
1.7.132
1.7.131
1.7.130
1.7.128
1.7.126
1.7.125
1.7.121
1.7.120
1.7.119
1.7.118
1.7.117
1.7.116
1.7.115
1.7.114
1.7.113
1.7.112
1.7.111
1.7.110
1.7.109
1.7.108
1.7.107
1.7.106
1.7.105
1.7.104
1.7.103
1.7.102
1.7.101
1.7.100
1.7.99
1.7.98
1.7.97
1.7.96
1.7.95
1.7.94
1.7.93
1.7.92
1.7.91
1.7.90
1.7.89
1.7.88
1.7.87
1.7.86
1.7.85
1.7.84
1.7.83
1.7.82
1.7.81
1.7.80
1.7.79
1.7.78
1.7.77
1.7.76
1.7.75
1.7.74
1.7.71
1.7.70
1.7.69
1.7.66
1.7.65
1.7.64
1.7.63
1.7.62
1.7.61
1.7.59
1.7.58
1.7.57
1.7.56
1.7.55
1.7.54
1.7.53
1.7.52
1.7.51
1.7.49
1.7.44
1.7.43
1.7.42
1.7.41
1.7.39
1.7.38
1.7.37
1.7.36
1.7.34
1.7.33
1.7.31
1.7.30
1.7.29
1.7.28
1.7.27
1.7.26
1.7.25
1.7.24
1.7.23
1.7.22
1.7.21
1.7.20
1.7.19
1.7.18
1.7.17
1.7.16
1.7.15
1.7.14
1.7.13
1.7.12
1.7.11
1.7.10
1.7.9
1.7.8
1.7.7
1.7.6
1.7.5
1.7.4
1.7.3
1.7.2
1.7.1
1.7.0
1.6.20
1.6.19
1.6.18
1.6.17
1.6.16
1.6.15
1.6.14
1.6.13
1.6.12
1.6.11
1.6.10
1.6.9
1.6.8
1.6.7
1.6.6
1.6.5
1.6.4
1.6.3
1.5.2
1.5.1
1.5.0
1.4.9
1.4.8
1.4.7
1.4.6
1.4.5
1.4.4
1.4.3
1.4.2
1.4.1
1.4.0
1.3.2
1.3.1
1.3.0
1.2.10
1.2.9
1.2.8
1.2.7
1.2.5
1.2.4
1.2.2
1.2.1
1.2.0
1.1.0
1.0.0
A foundation for building Elixir Phoenix apps — SaaS, social networks, ERP systems, marketplaces, and more
Current section
Files
Jump to
Current section
Files
lib/phoenix_kit_web/users/qr_login_confirm.ex
defmodule PhoenixKitWeb.Users.QrLoginConfirm do
@moduledoc """
Phone-side approval screen for QR device-handoff login.
Opened on the user's already-signed-in phone (via the QR the desktop
shows). It displays the requesting device and, on the user's **explicit**
Approve, calls `PhoenixKit.Users.QrLogin.approve/2` — minting the login
token that signs the waiting browser in. There is no auto-approve: this
screen is the defense against QR-jacking.
Authenticated route — the phone must already be signed in. If it isn't,
the authenticated pipeline redirects to the login page first.
"""
use PhoenixKitWeb, :live_view
alias PhoenixKit.Users.QrLogin, as: QrLoginContext
alias PhoenixKit.Utils.Routes
def mount(%{"token" => token}, _session, socket) do
socket =
socket
|> assign(:token, token)
|> assign(:project_title, PhoenixKit.Settings.get_project_title())
|> assign(:page_title, gettext("Approve sign-in"))
cond do
# Disabling the setting must act as an immediate kill switch — even
# for a request minted while it was on.
not QrLoginContext.enabled?() ->
{:ok,
socket
|> put_flash(:error, gettext("QR code sign-in is not available."))
|> redirect(to: Routes.path("/"))}
# peek/2 is a read-only, idempotent ETS lookup, but gate it behind
# connected? anyway so a future non-ETS (e.g. DB/Redis) keyfob store
# doesn't get queried on both the dead and the connected mount.
connected?(socket) ->
{state, meta} =
case QrLoginContext.peek(token) do
{:ok, %{state: :pending, meta: meta}} -> {:pending, meta}
{:ok, %{state: :approved, meta: meta}} -> {:approved, meta}
# not_found / expired both present as "expired" to the user —
# the code is no longer actionable either way.
_ -> {:expired, %{}}
end
{:ok, socket |> assign(:kf_state, state) |> assign(:meta, meta)}
true ->
{:ok, socket |> assign(:kf_state, :pending) |> assign(:meta, %{})}
end
end
def handle_event("keyfob_approve", _params, socket) do
if QrLoginContext.enabled?() do
user = socket.assigns.phoenix_kit_current_user
case QrLoginContext.approve(socket.assigns.token, user.uuid) do
:ok ->
QrLoginContext.log_approval(user, socket.assigns.meta)
{:noreply, assign(socket, :kf_state, :approved)}
{:error, reason} ->
{:noreply,
socket
|> assign(:kf_state, error_state(reason))
|> put_flash(:error, error_message(reason))}
end
else
# The setting was disabled after this page loaded — refuse the
# approval instead of letting an in-flight flow complete.
{:noreply,
socket
|> assign(:kf_state, :expired)
|> put_flash(:error, gettext("QR code sign-in is not available."))}
end
end
def handle_event("keyfob_deny", _params, socket) do
QrLoginContext.deny(socket.assigns.token)
{:noreply, assign(socket, :kf_state, :denied)}
end
# A no-longer-pending request (expired / consumed / already approved on
# another device) renders as "expired"; a genuine deny is separate.
defp error_state(:not_pending), do: :expired
defp error_state(:expired), do: :expired
defp error_state(_), do: :expired
defp error_message(:not_pending),
do: gettext("This sign-in was already handled.")
defp error_message(_),
do: gettext("This sign-in request expired or is no longer valid.")
defp confirm_labels do
%{
title: gettext("Approve this sign-in?"),
subtitle: gettext("A browser is asking to sign in as you."),
browser: gettext("Browser"),
os: gettext("Device"),
ip: gettext("IP address"),
location: gettext("Location"),
requested_at: gettext("Requested"),
warning:
gettext("Only approve if this is you. Approving signs that browser in to your account."),
approve: gettext("Approve"),
deny: gettext("Deny"),
approved: gettext("Signed in"),
approved_hint: gettext("You can close this page."),
denied: gettext("Request denied."),
expired: gettext("This request expired or was already used.")
}
end
end