Packages
phoenix_kit
1.4.1
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/users/oauth.ex
if Code.ensure_loaded?(Ueberauth) do
defmodule PhoenixKit.Users.OAuth do
@moduledoc """
OAuth authentication context for PhoenixKit.
Handles OAuth authentication flows for external providers like Google, Apple, GitHub.
This module requires the Ueberauth library to be installed. If Ueberauth is not available,
a fallback module with basic functionality will be used instead.
"""
import Ecto.Query, warn: false
alias PhoenixKit.RepoHelper, as: Repo
alias PhoenixKit.Users.Auth
alias PhoenixKit.Users.Auth.User
alias PhoenixKit.Users.OAuthProvider
@doc """
Handles OAuth callback from Ueberauth.
"""
def handle_oauth_callback(%Ueberauth.Auth{} = auth, opts \\ []) do
oauth_data = extract_oauth_data(auth)
track_geolocation = Keyword.get(opts, :track_geolocation, false)
ip_address = Keyword.get(opts, :ip_address)
referral_code = Keyword.get(opts, :referral_code)
Repo.transaction(fn ->
with {:ok, user, _status} <-
find_or_create_user(oauth_data, track_geolocation, ip_address),
{:ok, _provider} <- link_oauth_provider(user, oauth_data),
:ok <- maybe_process_referral_code(user, referral_code) do
user
else
{:error, reason} -> Repo.rollback(reason)
end
end)
end
@doc """
Finds an existing user by email or creates a new one from OAuth data.
"""
def find_or_create_user(oauth_data, track_geolocation \\ false, ip_address \\ nil) do
case Auth.get_user_by_email(oauth_data.email) do
%User{} = user ->
{:ok, user, :found}
nil ->
case register_oauth_user(oauth_data, track_geolocation, ip_address) do
{:ok, user} -> {:ok, user, :created}
{:error, reason} -> {:error, reason}
end
end
end
@doc """
Links an OAuth provider to a user account.
"""
def link_oauth_provider(%User{} = user, oauth_data) when is_map(oauth_data) do
attrs = %{
user_id: user.id,
provider: oauth_data.provider,
provider_uid: oauth_data.provider_uid,
provider_email: oauth_data.email,
access_token: oauth_data[:access_token],
refresh_token: oauth_data[:refresh_token],
token_expires_at: oauth_data[:token_expires_at],
raw_data: build_raw_data(oauth_data)
}
%OAuthProvider{}
|> OAuthProvider.changeset(attrs)
|> Repo.insert(
on_conflict: {:replace_all_except, [:id, :user_id, :provider, :inserted_at]},
conflict_target: [:user_id, :provider]
)
end
@doc """
Gets all OAuth providers for a user.
"""
def get_user_oauth_providers(user_id) when is_integer(user_id) do
from(p in OAuthProvider,
where: p.user_id == ^user_id,
order_by: [desc: p.inserted_at]
)
|> Repo.all()
end
@doc """
Unlinks an OAuth provider from a user.
"""
def unlink_oauth_provider(user_id, provider)
when is_integer(user_id) and is_binary(provider) do
case from(p in OAuthProvider, where: p.user_id == ^user_id and p.provider == ^provider)
|> Repo.one() do
nil -> {:error, :not_found}
provider_record -> Repo.delete(provider_record)
end
end
# Private functions
defp extract_oauth_data(%Ueberauth.Auth{} = auth) do
%{
provider: to_string(auth.provider),
provider_uid: auth.uid,
email: auth.info.email,
first_name: auth.info.first_name,
last_name: auth.info.last_name,
image: auth.info.image,
access_token: get_in(auth, [Access.key(:credentials), Access.key(:token)]),
refresh_token: get_in(auth, [Access.key(:credentials), Access.key(:refresh_token)]),
token_expires_at: get_token_expires_at(auth.credentials),
raw_info: get_in(auth, [Access.key(:extra), Access.key(:raw_info)])
}
end
defp get_token_expires_at(%{expires_at: expires_at}) when is_integer(expires_at) do
DateTime.from_unix!(expires_at)
end
defp get_token_expires_at(%{expires: true, expires_at: expires_at})
when is_integer(expires_at) do
DateTime.from_unix!(expires_at)
end
defp get_token_expires_at(_), do: nil
defp register_oauth_user(oauth_data, track_geolocation, ip_address) do
attrs = %{
email: oauth_data.email,
password: generate_random_password(),
first_name: oauth_data.first_name,
last_name: oauth_data.last_name,
confirmed_at: NaiveDateTime.utc_now() |> NaiveDateTime.truncate(:second)
}
if track_geolocation && ip_address do
Auth.register_user_with_geolocation(attrs, ip_address)
else
Auth.register_user(attrs)
end
end
defp generate_random_password do
:crypto.strong_rand_bytes(32)
|> Base.url_encode64(padding: false)
|> binary_part(0, 32)
end
defp build_raw_data(oauth_data) do
%{
image: oauth_data[:image],
raw_info: serialize_raw_info(oauth_data[:raw_info])
}
|> Enum.reject(fn {_k, v} -> is_nil(v) end)
|> Enum.into(%{})
end
defp serialize_raw_info(nil), do: %{}
defp serialize_raw_info(raw_info) when is_map(raw_info) do
Enum.into(raw_info, %{}, fn {key, value} ->
{key, serialize_value(value)}
end)
end
defp serialize_raw_info(raw_info), do: raw_info
# Serialize OAuth2.AccessToken if present
defp serialize_value(%OAuth2.AccessToken{} = token) do
%{
access_token: token.access_token,
refresh_token: token.refresh_token,
expires_at: token.expires_at,
token_type: token.token_type,
other_params: token.other_params
}
end
defp serialize_value(value), do: value
defp maybe_process_referral_code(_user, nil), do: :ok
defp maybe_process_referral_code(user, referral_code) when is_binary(referral_code) do
if Code.ensure_loaded?(PhoenixKit.ReferralCodes) do
case PhoenixKit.ReferralCodes.get_code_by_string(referral_code) do
nil -> :ok
code -> PhoenixKit.ReferralCodes.use_code(code.code, user.id)
end
end
:ok
end
end
else
# Fallback module when Ueberauth is not loaded
defmodule PhoenixKit.Users.OAuth do
@moduledoc """
Fallback OAuth context module when Ueberauth is not installed.
This module provides basic OAuth provider management without authentication capabilities.
To enable full OAuth authentication, install the required dependencies.
"""
import Ecto.Query, warn: false
alias PhoenixKit.RepoHelper, as: Repo
alias PhoenixKit.Users.OAuthProvider
@doc """
Returns an error when OAuth callback is attempted without Ueberauth.
"""
def handle_oauth_callback(_auth, _opts \\ []) do
{:error, :ueberauth_not_loaded}
end
@doc """
Gets all OAuth providers for a user.
"""
def get_user_oauth_providers(user_id) when is_integer(user_id) do
from(p in OAuthProvider,
where: p.user_id == ^user_id,
order_by: [desc: p.inserted_at]
)
|> Repo.all()
end
@doc """
Unlinks an OAuth provider from a user.
"""
def unlink_oauth_provider(user_id, provider)
when is_integer(user_id) and is_binary(provider) do
case from(p in OAuthProvider, where: p.user_id == ^user_id and p.provider == ^provider)
|> Repo.one() do
nil -> {:error, :not_found}
provider_record -> Repo.delete(provider_record)
end
end
end
end