Packages
potionx
0.8.11
0.8.15
0.8.14
0.8.13
0.8.12
0.8.11
0.8.10
0.8.9
0.8.8
0.8.7
0.8.6
0.8.5
0.8.4
0.8.3
0.8.2
0.8.1
0.8.0
0.7.2
0.7.1
0.7.0
0.6.4
0.6.3
0.6.2
0.6.1
0.6.0
0.5.3
0.5.2
0.5.1
0.5.0
0.4.16
0.4.15
0.4.14
0.4.13
0.4.12
0.4.11
0.4.10
0.4.9
0.4.8
0.4.7
0.4.6
0.4.5
0.4.4
0.4.3
0.4.2
0.4.1
0.4.0
0.3.0
0.2.18
0.2.17
0.2.16
0.2.15
0.2.14
0.2.13
0.2.12
0.2.11
0.2.10
0.2.9
0.2.8
0.2.7
0.2.6
0.2.5
0.2.4
0.2.3
0.2.2
0.2.1
0.1.1
0.1.0
Potionx is a set of generators and functions that speeds up the process of setting up and deploying a full-stack application that uses Elixir with GraphQL for the server-side component and Vue for the frontend component.
Current section
Files
Jump to
Current section
Files
lib/potionx/auth/auth_resolvers.ex
defmodule Potionx.Auth.Resolvers do
alias Potionx.Context.Service
def auth_config() do
Application.get_env(:potionx, :auth)
end
@spec before_send(Plug.Conn.t(), Absinthe.Blueprint.t()) :: any
def before_send(
conn,
%Absinthe.Blueprint{
execution: %{
context: %{
assigns: %{
tokens_to_cookies: true
},
session: session
}
}
}
) when not is_nil(session) do
Potionx.Auth.handle_user_session_cookies(session, conn)
end
def before_send(
conn,
%Absinthe.Blueprint{
execution: %{
context: %{
assigns: %{
sign_out: true
},
session: session
}
}
}
) when not is_nil(session) do
conn
|> Potionx.Auth.delete_cookie(%{
name: Potionx.Auth.token_config().access_token.name,
ttl_seconds: session.ttl_access_seconds
})
|> Potionx.Auth.delete_cookie(%{
name: Potionx.Auth.token_config().renewal_token.name,
ttl_seconds: session.ttl_renewal_seconds
})
|> Potionx.Auth.delete_cookie(%{
name: Potionx.Auth.token_config().frontend.name,
ttl_seconds: session.ttl_access_seconds
})
end
def before_send(conn, _) do
conn
end
def call(conn, opts) do
callback(conn, opts)
end
def callback(%{assigns: %{context: %Service{session: %{id: _} = session} = ctx}} = conn, opts) do
after_login_path = Keyword.get(opts, :after_login_path) || "/"
redirect_path = Keyword.get(opts, :redirect_path) || "/login"
scheme = Keyword.get(opts, :scheme) || "https"
session_service = Keyword.fetch!(opts, :session_service)
redirect_url = get_redirect_url(conn, Map.get(session.data, "redirect_url") || after_login_path, scheme)
conn
|> verify_providers_match(session)
|> process_callback(conn, opts)
|> parse_callback_response(session.sign_in_provider)
|> create_user_session(session, session_service, %{ctx | changes: Map.put(ctx.changes, :redirect_url, redirect_url)})
|> Potionx.Auth.handle_user_session_cookies(conn)
|> case do
%Plug.Conn{} = conn ->
conn
|> Plug.Conn.put_resp_content_type("text/html")
|> Plug.Conn.send_resp(
200,
"""
<html>
<head><meta http-equiv="refresh" content="0;URL='#{redirect_url}'"/></head>
<body></body>
</html>
"""
)
{:error, _, msg, _} ->
{:error, msg}
err -> err
end
|> case do
{:error, msg} ->
url = Enum.join([redirect_path, "?msg=", msg], "")
conn
|> Plug.Conn.put_resp_content_type("text/html")
|> Plug.Conn.assign(:potionx_auth_error, msg)
|> Plug.Conn.send_resp(
401,
"""
<html>
<head><meta http-equiv="refresh" content="0;URL='#{url}'"/></head>
<body></body>
</html>
"""
)
res -> res
end
end
def callback(conn, opts) do
redirect_path = Keyword.get(opts, :redirect_path, "/login")
url = Enum.join([redirect_path, "?msg=missing_session"], "")
conn
|> Plug.Conn.assign(:potionx_auth_error, "missing_session")
|> Plug.Conn.send_resp(
401,
"""
<html>
<head><meta http-equiv="refresh" content="1;URL='#{url}'"/></head>
<body></body>
</html>
"""
)
end
def create_user_session(
{:ok, user_identity_params, user_params},
previous_session,
session_service,
%Service{changes: %{redirect_url: redirect_url}, ip: ip}
) do
session_service.create(
%Service{
changes: %{
identity: %{user_identity_params | "uid" => to_string(user_identity_params["uid"])},
session: %{
ip: ip,
sign_in_provider: previous_session.sign_in_provider,
ttl_access_seconds: Potionx.Auth.token_config().access_token.ttl_seconds,
uuid_access: Ecto.UUID.generate(),
uuid_renewal: Ecto.UUID.generate(),
ttl_renewal_seconds: Potionx.Auth.token_config().renewal_token.ttl_seconds
},
user: %{
email: user_params["email"],
email_verified: user_params["email_verified"],
file_url: user_params["picture"],
name_first: user_params["given_name"],
name_last: user_params["family_name"],
locale: user_params["locale"],
name: user_params["name"],
redirect_url: redirect_url
}
}
},
previous_session
)
|> case do
{:ok, %{session: session}} -> session
err -> err
end
end
def create_user_session(err, _, _, _), do: err
@doc """
Prepare redirect_url, ensure redirect can only lead back to log in domain
"""
def get_redirect_url(conn, redirect_url, scheme) do
redirect_uri = URI.parse(redirect_url)
URI.parse(Plug.Conn.request_url(conn))
|> Map.put(:query, redirect_uri.query)
|> Map.put(:path, redirect_uri.path)
|> Map.put(:port, scheme === "https" && 443 || conn.port)
|> Map.put(:scheme, scheme)
|> to_string
end
defp handle_user_identity_params({user_identity_params, user_params}, other_params, provider) do
user_identity_params = Map.put(user_identity_params, "provider", provider)
other_params = for {key, value} <- other_params, into: %{}, do: {Atom.to_string(key), value}
user_identity_params =
user_identity_params
|> Map.put("provider", provider)
|> Map.merge(other_params)
{:ok, user_identity_params, user_params}
end
def init(opts), do: opts
def middleware_renew(%{context: ctx, value: value} = res, _) when is_map(value) do
%{
res |
context: %{
ctx |
assigns: %{tokens_to_cookies: true},
session: Map.get(value, :session)
},
value: Map.delete(value, :session)
}
end
def middleware_renew(res, _), do: res
def middleware_sign_in(%{context: ctx, value: value} = res, _) when is_map(value) do
%{
res |
context: %{
ctx |
assigns: %{tokens_to_cookies: true},
session: Map.get(value, :session)
},
value: Map.delete(value, :session)
}
end
def middleware_sign_in(res, _), do: res
def middleware_sign_out(%{context: ctx, value: value} = res, _) when is_map(value) do
%{
res |
context: %{
ctx |
assigns: %{sign_out: true}
}
}
end
def middleware_sign_out(res, _), do: res
defp normalize_username(%{"preferred_username" => username} = params) do
params
|> Map.delete("preferred_username")
|> Map.put("username", username)
end
defp normalize_username(params), do: params
defp parse_callback_response({:ok, %{user: user} = response}, provider) do
other_params =
response
|> Map.delete(:user)
|> Map.put(:userinfo, user)
user
|> normalize_username()
|> split_user_identity_params()
|> handle_user_identity_params(other_params, provider)
end
defp parse_callback_response({:error, error}, _provider), do: {:error, error}
def process_callback(session, conn), do: process_callback(session, conn, [])
def process_callback(%{data: data, sign_in_provider: provider}, conn, opts) do
strategies = Keyword.get(opts, :strategies) || auth_config()[:strategies]
strategy_config = Keyword.fetch!(strategies, String.to_existing_atom(provider))
redirect_uri =
URI.parse(Plug.Conn.request_url(conn))
|> Map.replace!(:fragment, nil)
|> Map.replace!(:query, nil)
|> case do
%{host: "localhost"} = url -> url
url -> %{url | port: 443, scheme: "https"}
end
Keyword.fetch!(strategy_config, :strategy).callback(
Keyword.put(
strategy_config,
:session_params,
data
)
|> Keyword.put(
:http_adapter,
Assent.HTTPAdapter.Mint
)
|> Keyword.put(
:redirect_uri,
redirect_uri
),
conn.params
)
end
def process_callback(err, _conn, _opts) do
err
end
def resolve_renew(opts) do
session_service = Keyword.get(opts, :session_service)
if !session_service do
raise "Potionx.Auth.Resolvers requires a session_service"
end
fn
_parent, _, %{context: %Service{session: %{id: id}}} ->
session_service.patch(
%Service{
changes: %{
ttl_access_seconds: Potionx.Auth.token_config().access_token.ttl_seconds,
uuid_access: Ecto.UUID.generate(),
uuid_renewal: Ecto.UUID.generate(),
ttl_renewal_seconds: Potionx.Auth.token_config().renewal_token.ttl_seconds
},
filters: %{
id: id
}
}
)
|> case do
{:ok, %{session_patch: session}} ->
{:ok, %{session: session}}
err -> err
end
_parent, _, _ ->
{:ok, %{error: "missing_session"}}
end
end
def resolve_sign_in(opts \\ []) do
session_service = Keyword.get(opts, :session_service)
if !session_service do
raise "Potionx.Auth.Resolvers requires a session_service"
end
fn _parent, %{provider: provider} = args, %{context: %Service{request_url: url} = ctx} ->
strategies = Keyword.get(opts, :strategies) || auth_config()[:strategies]
redirect_uri =
URI.parse(url)
|> Map.replace!(:path, "/api/v1/auth/#{provider}/callback")
|> Map.replace!(:fragment, nil)
|> Map.replace!(:query, nil)
|> case do
%{host: "localhost"} = url -> url
url -> %{url | port: 443, scheme: "https"}
end
strategies
|> Keyword.fetch(String.to_existing_atom(provider))
|> case do
{:ok, config} ->
strategy = Keyword.fetch!(config, :strategy)
config
|> Keyword.delete(:strategy)
|> Keyword.put(:redirect_uri, redirect_uri)
|> Keyword.put(
:http_adapter,
Assent.HTTPAdapter.Mint
)
|> strategy.authorize_url()
|> case do
{:ok, %{session_params: params, url: url}} ->
session_service.create(
%Service{
changes: %{
data: Map.merge(args, params),
ip: ctx.ip,
sign_in_provider: provider,
ttl_access_seconds: Potionx.Auth.token_config().sign_in_token.ttl_seconds,
uuid_access: Ecto.UUID.generate()
}
},
nil
)
|> case do
{:ok, %{session: session}} ->
{:ok, %{session: session, url: url}}
err -> err
end
err -> err
end
_ ->
{:ok, %{error: "Missing Provider"}}
end
end
end
def resolve_sign_out(opts \\ []) do
session_service = Keyword.get(opts, :session_service)
if !session_service do
raise "Potionx.Auth.Assent resolve function requires a session_service"
end
fn
_parent, _, %{context: %{session: nil}} ->
{:ok, %{error: "not_signed_in"}}
_parent, _, %{context: %{session: session}} ->
session_service.delete(%Service{filters: %{id: session.id}})
|> case do
{:ok, _} -> {:ok, %{}}
err -> err
end
end
end
defp split_user_identity_params(%{"sub" => uid} = params) do
user_params = Map.delete(params, "sub")
{%{"uid" => uid}, user_params}
end
def verify_providers_match(%{path_params: %{"provider" => provider2}}, %{sign_in_provider: provider} = session) do
if provider === provider2 do
session
else
{:error, "provider_mismatch"}
end
end
end