Packages
phoenix_kit
2.2.0
2.2.0
2.1.0
2.0.1
2.0.0
1.7.236
1.7.235
1.7.234
1.7.233
1.7.232
1.7.231
1.7.230
1.7.229
1.7.228
1.7.227
1.7.226
1.7.225
1.7.224
1.7.223
1.7.222
1.7.221
1.7.220
1.7.219
1.7.218
1.7.217
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_complete.ex
defmodule PhoenixKitWeb.Users.QrLoginComplete do
@moduledoc """
Completion endpoint for QR device-handoff login.
The desktop LiveView navigates here once its request is approved on the
phone, carrying the one-time login token. This controller exchanges that
token for the approved user's uuid (exactly once, via
`PhoenixKit.Users.QrLogin.consume/1`) and establishes the session with
PhoenixKit's own login machinery.
Keyfob's login token is single-use and short-lived, so a replayed or
stale URL lands on the login page rather than signing anyone in.
"""
use PhoenixKitWeb, :controller
require Logger
alias PhoenixKit.Users.Auth, as: Users
alias PhoenixKit.Users.QrLogin, as: QrLoginContext
alias PhoenixKit.Utils.Routes
alias PhoenixKitWeb.Users.Auth, as: UserAuth
# Guards ONLY the store call, and only against not answering.
#
# PR #699 pinned keyfob 0.1.1 because before it an unreachable store made
# `consume/2` exit, and this `with` matches return values — so the exit became
# a 500 on the finish URL where "this link is invalid or has expired" was both
# the honest answer and the same outcome. The pin fixes keyfob's OWN store.
# `Keyfob.Store` is a behaviour a host may implement over Redis, a database or
# a cluster cache, and #699's own reasoning for guarding `peek/1` is that core
# cannot assume a stranger's implementation is total. That argument applies
# here unchanged, and here the cost really is the 500 it describes.
#
# Deliberately NOT wrapped around the whole `with`: `log_in_user/3` writes the
# session, and turning a genuine failure there into "invalid or expired" would
# hide a real bug behind a message about the token.
defp safe_consume(token) do
QrLoginContext.consume(token)
rescue
error ->
Logger.warning(
"[PhoenixKit.QrLoginComplete] could not consume the token: #{inspect(error)}"
)
:error
catch
:exit, reason ->
Logger.warning(
"[PhoenixKit.QrLoginComplete] store did not answer: #{inspect(reason)} — " <>
"treating the link as expired"
)
:error
end
def complete(conn, %{"token" => token} = params) do
with true <- QrLoginContext.enabled?(),
{:ok, user_uuid} <- safe_consume(token),
%{} = user <- Users.get_user(user_uuid) do
conn
# log_in_user/3 reads :user_return_to from the session for its redirect,
# so stash the (sanitized) destination there before signing in.
|> maybe_store_return_to(params["return_to"])
|> put_flash(:info, gettext("Signed in with QR code."))
|> UserAuth.log_in_user(user, login_params(params))
else
_ ->
conn
|> put_flash(:error, gettext("This QR sign-in link is invalid or has expired."))
|> redirect(to: Routes.path("/users/log-in"))
end
end
def complete(conn, _params) do
conn
|> put_flash(:error, gettext("This QR sign-in link is invalid or has expired."))
|> redirect(to: Routes.path("/users/log-in"))
end
# Only "true" (from the browser's checkbox) opts into the persistent
# remember-me cookie; anything else keeps a session-only login.
defp login_params(%{"remember_me" => "true"}), do: %{"remember_me" => "true"}
defp login_params(_params), do: %{}
defp maybe_store_return_to(conn, return_to) do
if is_binary(return_to) and Routes.local_path?(return_to) do
put_session(conn, :user_return_to, return_to)
else
conn
end
end
end