PhoenixKit.Users.LoginAlerts (phoenix_kit v2.22.8)

Copy Markdown View Source

New-login security alerts ("we noticed a new login to your account").

On every login (PhoenixKitWeb.Users.Auth.log_in_user/3), the request's (ip_address, user_agent_hash) pair is checked against PhoenixKit.Users.Auth.KnownDevice rows for that user. An unrecognized pair is a new device: it's persisted and a user.new_login_detected activity entry is logged either way — but the email (PhoenixKit.Users.Auth.UserNotifier.deliver_new_login_alert/2, gated behind new_login_alert_enabled) and the in-app notification are skipped when this is the very first KnownDevice row the account has ever had.

That first-device skip exists because registration ends by logging the new user in through this exact path (log_in_user/3), and an account with no device history yet cannot help but treat its own signup as "a new device" — without it, every signup on an installation with alerts on immediately received a "we noticed a new login" security email about the login it just performed to finish registering. The device is still recorded (so the second login, from anywhere else, correctly reads as new), and the activity entry still logs for the audit trail — only the two reader-facing alarms are suppressed.

A recognized pair just bumps last_seen_at — no alert, no email.

Sends synchronously (matching every other PhoenixKit auth email — confirmation, password reset, magic link — none of which are queued through Oban): a "new device" login is inherently rare per user (every subsequent login from the same device is silent), so the odd extra round-trip on a first-time login doesn't justify background-job infrastructure this feature would otherwise be the only user of. A send failure is logged and swallowed — it must never block sign-in.

Summary

Functions

Records a login from conn for user, alerting on a new device.

Whether new-login alerts are enabled (setting new_login_alert_enabled, default false).

Best-effort "City, Country" string for ip_address, or nil.

Functions

check(user, conn)

@spec check(map(), Plug.Conn.t()) :: :ok

Records a login from conn for user, alerting on a new device.

No-ops entirely (no DB write, no email) when the feature is disabled. Never raises — a failure here must never block sign-in.

enabled?()

@spec enabled?() :: boolean()

Whether new-login alerts are enabled (setting new_login_alert_enabled, default false).

location_for(ip_address)

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

Best-effort "City, Country" string for ip_address, or nil.

Never raises; a lookup failure (disabled, rate-limited, invalid IP) just means the alert email omits the location line.