Skip to content

Commit a2bf877

Browse files
committed
refactor: use UeberauthOidcc as a library
1 parent ff7f2ec commit a2bf877

File tree

7 files changed

+375
-273
lines changed

7 files changed

+375
-273
lines changed

lib/ueberauth/strategy/google.ex

+96-91
Original file line numberDiff line numberDiff line change
@@ -6,21 +6,31 @@ defmodule Ueberauth.Strategy.Google do
66
use Ueberauth.Strategy,
77
uid_field: :sub,
88
default_scope: "email",
9-
hd: nil,
10-
userinfo_endpoint: "https://www.googleapis.com/oauth2/v3/userinfo"
9+
hd: nil
1110

12-
alias Ueberauth.Auth.Info
13-
alias Ueberauth.Auth.Credentials
1411
alias Ueberauth.Auth.Extra
1512

13+
@session_key "ueberauth_strategy_google"
14+
1615
@doc """
1716
Handles initial request for Google authentication.
1817
"""
1918
def handle_request!(conn) do
20-
scopes = conn.params["scope"] || option(conn, :default_scope)
21-
22-
params =
23-
[scope: scopes]
19+
scopes =
20+
String.split(
21+
conn.params["scope"] || option(conn, :default_scope),
22+
" "
23+
)
24+
25+
scopes =
26+
if "openid" in scopes do
27+
scopes
28+
else
29+
["openid"] ++ scopes
30+
end
31+
32+
authorization_params =
33+
[]
2434
|> with_optional(:hd, conn)
2535
|> with_optional(:prompt, conn)
2636
|> with_optional(:access_type, conn)
@@ -30,33 +40,39 @@ defmodule Ueberauth.Strategy.Google do
3040
|> with_param(:prompt, conn)
3141
|> with_param(:login_hint, conn)
3242
|> with_param(:hl, conn)
33-
|> with_state_param(conn)
3443

35-
opts = oauth_client_options_from_conn(conn)
36-
redirect!(conn, Ueberauth.Strategy.Google.OAuth.authorize_url!(params, opts))
44+
opts =
45+
conn
46+
|> options_from_conn()
47+
|> Map.put(:scopes, scopes)
48+
|> Map.put(:authorization_params, Map.new(authorization_params))
49+
50+
case UeberauthOidcc.Request.handle_request(opts, conn) do
51+
{:ok, conn} ->
52+
conn
53+
54+
{:error, conn, reason} ->
55+
UeberauthOidcc.Error.set_described_error(conn, reason, "error")
56+
end
3757
end
3858

3959
@doc """
4060
Handles the callback from Google.
4161
"""
42-
def handle_callback!(%Plug.Conn{params: %{"code" => code}} = conn) do
43-
params = [code: code]
44-
opts = oauth_client_options_from_conn(conn)
62+
def handle_callback!(%Plug.Conn{} = conn) do
63+
opts = options_from_conn(conn)
4564

46-
case Ueberauth.Strategy.Google.OAuth.get_access_token(params, opts) do
47-
{:ok, token} ->
48-
fetch_user(conn, token)
65+
case UeberauthOidcc.Callback.handle_callback(opts, conn) do
66+
{:ok, conn, token, userinfo} ->
67+
conn
68+
|> put_private(:google_token, token)
69+
|> put_private(:google_user, userinfo)
4970

50-
{:error, {error_code, error_description}} ->
51-
set_errors!(conn, [error(error_code, error_description)])
71+
{:error, conn, reason} ->
72+
UeberauthOidcc.Error.set_described_error(conn, reason, "error")
5273
end
5374
end
5475

55-
@doc false
56-
def handle_callback!(conn) do
57-
set_errors!(conn, [error("missing_code", "No code received")])
58-
end
59-
6076
@doc false
6177
def handle_cleanup!(conn) do
6278
conn
@@ -81,87 +97,49 @@ defmodule Ueberauth.Strategy.Google do
8197
"""
8298
def credentials(conn) do
8399
token = conn.private.google_token
84-
scope_string = token.other_params["scope"] || ""
85-
scopes = String.split(scope_string, " ")
86-
87-
%Credentials{
88-
expires: !!token.expires_at,
89-
expires_at: token.expires_at,
90-
scopes: scopes,
91-
token_type: Map.get(token, :token_type),
92-
refresh_token: token.refresh_token,
93-
token: token.access_token
94-
}
100+
credentials = UeberauthOidcc.Auth.credentials(token)
101+
%{credentials | other: %{}}
95102
end
96103

97104
@doc """
98105
Fetches the fields to populate the info section of the `Ueberauth.Auth` struct.
99106
"""
100107
def info(conn) do
108+
token = conn.private.google_token
101109
user = conn.private.google_user
102110

103-
%Info{
104-
email: user["email"],
105-
first_name: user["given_name"],
106-
image: user["picture"],
107-
last_name: user["family_name"],
108-
name: user["name"],
109-
birthday: user["birthday"],
110-
urls: %{
111-
profile: user["profile"],
112-
website: user["hd"]
113-
}
111+
info = UeberauthOidcc.Auth.info(token, user)
112+
113+
%{
114+
info
115+
| birthday: info.birthday || user["birthday"],
116+
urls: Map.put_new(info.urls, :website, user["hd"])
114117
}
115118
end
116119

117120
@doc """
118121
Stores the raw information (including the token) obtained from the google callback.
119122
"""
120123
def extra(conn) do
124+
creds = credentials(conn)
125+
126+
# create a struct with the same format as the old token, even if we don't depend on OAuth2
127+
google_token = %{
128+
__struct__: OAuth2.AccessToken,
129+
access_token: creds.token,
130+
refresh_token: creds.refresh_token,
131+
expires_at: creds.expires_at,
132+
token_type: "Bearer"
133+
}
134+
121135
%Extra{
122136
raw_info: %{
123-
token: conn.private.google_token,
137+
token: google_token,
124138
user: conn.private.google_user
125139
}
126140
}
127141
end
128142

129-
defp fetch_user(conn, token) do
130-
conn = put_private(conn, :google_token, token)
131-
132-
# userinfo_endpoint from https://accounts.google.com/.well-known/openid-configuration
133-
# the userinfo_endpoint may be overridden in options when necessary.
134-
resp = Ueberauth.Strategy.Google.OAuth.get(token, get_userinfo_endpoint(conn))
135-
136-
case resp do
137-
{:ok, %OAuth2.Response{status_code: 401, body: _body}} ->
138-
set_errors!(conn, [error("token", "unauthorized")])
139-
140-
{:ok, %OAuth2.Response{status_code: status_code, body: user}}
141-
when status_code in 200..399 ->
142-
put_private(conn, :google_user, user)
143-
144-
{:error, %OAuth2.Response{status_code: status_code}} ->
145-
set_errors!(conn, [error("OAuth2", status_code)])
146-
147-
{:error, %OAuth2.Error{reason: reason}} ->
148-
set_errors!(conn, [error("OAuth2", reason)])
149-
end
150-
end
151-
152-
defp get_userinfo_endpoint(conn) do
153-
case option(conn, :userinfo_endpoint) do
154-
{:system, varname, default} ->
155-
System.get_env(varname) || default
156-
157-
{:system, varname} ->
158-
System.get_env(varname) || Keyword.get(default_options(), :userinfo_endpoint)
159-
160-
other ->
161-
other
162-
end
163-
end
164-
165143
defp with_param(opts, key, conn) do
166144
if value = conn.params[to_string(key)], do: Keyword.put(opts, key, value), else: opts
167145
end
@@ -170,18 +148,45 @@ defmodule Ueberauth.Strategy.Google do
170148
if option(conn, key), do: Keyword.put(opts, key, option(conn, key)), else: opts
171149
end
172150

173-
defp oauth_client_options_from_conn(conn) do
174-
base_options = [redirect_uri: callback_url(conn)]
175-
request_options = conn.private[:ueberauth_request_options].options
151+
defp options_from_conn(conn) do
152+
base_options = [
153+
issuer: UeberauthGoogle.ProviderConfiguration,
154+
userinfo: true,
155+
session_key: @session_key
156+
]
176157

177-
case {request_options[:client_id], request_options[:client_secret]} do
178-
{nil, _} -> base_options
179-
{_, nil} -> base_options
180-
{id, secret} -> [client_id: id, client_secret: secret] ++ base_options
181-
end
158+
request_options = conn.private[:ueberauth_request_options].options
159+
oauth_options = Application.get_env(:ueberauth, Ueberauth.Strategy.Google.OAuth) || []
160+
161+
[
162+
base_options,
163+
request_options,
164+
oauth_options
165+
]
166+
|> UeberauthOidcc.Config.merge_and_expand_configuration()
167+
|> generate_client_secret()
168+
|> fix_token_url()
182169
end
183170

184171
defp option(conn, key) do
185172
Keyword.get(options(conn), key, Keyword.get(default_options(), key))
186173
end
174+
175+
defp generate_client_secret(%{client_secret: {mod, fun}} = opts) do
176+
Map.put(opts, :client_secret, apply(mod, fun, [Keyword.new(opts)]))
177+
end
178+
179+
defp generate_client_secret(opts) do
180+
opts
181+
end
182+
183+
defp fix_token_url(%{token_url: token_endpoint} = opts) do
184+
opts
185+
|> Map.put(:token_endpoint, token_endpoint)
186+
|> Map.delete(:token_url)
187+
end
188+
189+
defp fix_token_url(opts) do
190+
opts
191+
end
187192
end

lib/ueberauth/strategy/google/oauth.ex

-106
This file was deleted.

lib/ueberauth_google/application.ex

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
defmodule UeberauthGoogle.Application do
2+
@moduledoc false
3+
4+
use Application
5+
6+
@impl true
7+
def start(_type, _args) do
8+
children = [
9+
{Oidcc.ProviderConfiguration.Worker,
10+
%{
11+
name: UeberauthGoogle.ProviderConfiguration,
12+
issuer: "https://accounts.google.com"
13+
}}
14+
]
15+
16+
opts = [strategy: :one_for_one, name: UeberauthGoogle.Supervisor]
17+
Supervisor.start_link(children, opts)
18+
end
19+
end

0 commit comments

Comments
 (0)