PhoenixKit.Users.QrLogin (phoenix_kit v1.7.199)

Copy Markdown View Source

QR device-handoff login ("scan to sign in") for PhoenixKit.

This is the PhoenixKit-side integration of the keyfob library. Keyfob owns the rendezvous — request lifecycle, expiry, single-use enforcement, the secret split, PubSub signaling. This module wires it into PhoenixKit: the internal PubSub, the qr_login_enabled setting, device-metadata extraction for the confirm screen, and activity logging on approval.

The flow

  1. A signed-out browser opens /users/qr-login (PhoenixKitWeb.Users.QrLogin) and a QR encoding the phone-confirm URL is shown. The LiveView waits.
  2. The user's already-signed-in phone scans it, opens the confirm URL (PhoenixKitWeb.Users.QrLoginConfirm), reviews the requesting device, and taps Approve — which calls approve/2 with the phone user's uuid.
  3. Approval mints a one-time login token delivered over PubSub only to the waiting browser, which navigates to the completion controller (PhoenixKitWeb.Users.QrLoginComplete).
  4. The controller calls consume/1 ({:ok, user_uuid}, exactly once) and logs that user in with PhoenixKit's own session machinery.

Approval always happens on the trusted (phone) device; the browser gets nothing until the phone approves. See keyfob's docs for the threat model (QR-jacking, the secret split).

All keyfob calls are routed through PhoenixKit's internal PubSub (:phoenix_kit_internal_pubsub) so the desktop LiveView and the phone confirm LiveView rendezvous on the same bus. The default Keyfob.Store.ETS (started in PhoenixKit.Supervisor) holds requests.

Summary

Functions

See Keyfob.approve/3. user_ref is the approving user's uuid.

See Keyfob.consume/2. Returns {:ok, user_uuid} exactly once.

See Keyfob.create_request/1. Merges the shared PubSub option.

Extracts a device-description map from the requesting browser's connected socket, shown verbatim on the phone confirm screen so the human can recognise (or reject) the sign-in.

Whether QR device-handoff login is enabled (setting qr_login_enabled, default false).

Shared keyfob options — routes every call through PhoenixKit's internal PubSub.

Formats a best-effort "City, Country" (or just "Country") string for an IP, or nil when the lookup fails, is unavailable, or is still running after 1500ms. Never raises — a geo backend hiccup must not crash (or stall) the QR mint that shows the code.

Records a user.qr_login_approved activity for the approving user.

The PubSub name every keyfob call in this integration uses.

Functions

approve(token, user_ref)

See Keyfob.approve/3. user_ref is the approving user's uuid.

consume(token)

See Keyfob.consume/2. Returns {:ok, user_uuid} exactly once.

create_request(opts \\ [])

See Keyfob.create_request/1. Merges the shared PubSub option.

deny(token)

See Keyfob.deny/2.

device_meta(socket)

@spec device_meta(Phoenix.LiveView.Socket.t()) :: map()

Extracts a device-description map from the requesting browser's connected socket, shown verbatim on the phone confirm screen so the human can recognise (or reject) the sign-in.

Keys: :browser, :os, :ip, :location — any of which may be absent when the underlying connect-info (or geo lookup) is unavailable — plus :requested_at, an absolute UTC timestamp of when the code was minted so the approver can sanity-check "did I just do this?".

The geo lookup is a best-effort, timeout-bounded call on the requesting browser's IP (same machinery registration/login-alerts already use); it runs at QR-mint time so the location is baked into the request the phone later reads.

enabled?()

@spec enabled?() :: boolean()

Whether QR device-handoff login is enabled (setting qr_login_enabled, default false).

keyfob_opts()

@spec keyfob_opts() :: keyword()

Shared keyfob options — routes every call through PhoenixKit's internal PubSub.

location_for(ip)

@spec location_for(String.t() | nil) :: String.t() | nil

Formats a best-effort "City, Country" (or just "Country") string for an IP, or nil when the lookup fails, is unavailable, or is still running after 1500ms. Never raises — a geo backend hiccup must not crash (or stall) the QR mint that shows the code.

log_approval(user, meta \\ %{})

@spec log_approval(map(), map()) :: :ok

Records a user.qr_login_approved activity for the approving user.

Actor and target are the same user (they approved their own sign-in), so no notification is generated — this is an audit-trail entry only. Errors are swallowed: a logging failure must never block the sign-in.

peek(token)

See Keyfob.peek/2.

pubsub()

@spec pubsub() :: atom()

The PubSub name every keyfob call in this integration uses.

subscribe(token)

See Keyfob.subscribe/2.

unsubscribe(token)

See Keyfob.unsubscribe/2.