131 lines
4.5 KiB
Elixir
131 lines
4.5 KiB
Elixir
defmodule ComfycampWeb.OauthController do
|
||
use ComfycampWeb, :controller
|
||
|
||
alias Comfycamp.Accounts
|
||
alias Comfycamp.SSO
|
||
alias Comfycamp.SSO.OIDCApp
|
||
alias Comfycamp.SSO.OIDCCode
|
||
alias Comfycamp.SSO.IDToken
|
||
alias Comfycamp.Token
|
||
|
||
@doc """
|
||
Check the request parameters and current user status,
|
||
then ask the user to confirm that he wants to share his info with Relying Party.
|
||
"""
|
||
def authorize(conn, %{"client_id" => client_id, "redirect_uri" => redirect_uri} = params) do
|
||
app = SSO.get_oidc_app!(client_id)
|
||
current_user = conn.assigns.current_user
|
||
|
||
with {:is_approved, true} <- {:is_approved, current_user.is_approved},
|
||
{:response_type, "code"} <- {:response_type, params["response_type"]},
|
||
{:is_enabled, true} <- {:is_enabled, app.enabled},
|
||
{:has_uri, true} <- {:has_uri, SSO.has_redirect_uri?(client_id, redirect_uri)} do
|
||
render(conn, :authorize,
|
||
page_title: "Подтвердите вход",
|
||
app_name: app.name,
|
||
params: URI.encode_query(params)
|
||
)
|
||
else
|
||
{:is_approved, false} ->
|
||
render(conn, :error,
|
||
description:
|
||
"Ваш аккаунт ещё не был одобрен, подождите немного или свяжитесь с администратором"
|
||
)
|
||
|
||
{:response_type, response_type} ->
|
||
render(conn, :error, description: "Неподдерживаемый response type: #{response_type}")
|
||
|
||
{:is_enabled, false} ->
|
||
render(conn, :error, description: "Приложение отключено")
|
||
|
||
{:has_uri, false} ->
|
||
render(conn, :error, description: "Redirect URI не зарегистрирован или отсутствует")
|
||
end
|
||
end
|
||
|
||
@doc """
|
||
Generate an Authorization Code and redirect the user back to Relying Party.
|
||
"""
|
||
def generate_code(conn, %{"client_id" => client_id, "redirect_uri" => redirect_uri} = params) do
|
||
app = SSO.get_oidc_app!(client_id)
|
||
current_user = conn.assigns.current_user
|
||
|
||
with {:is_approved, true} <- {:is_approved, current_user.is_approved},
|
||
{:is_enabled, true} <- {:is_enabled, app.enabled},
|
||
{:has_uri, true} <- {:has_uri, SSO.has_redirect_uri?(client_id, redirect_uri)} do
|
||
{:ok, code} =
|
||
SSO.create_oidc_code(%{
|
||
oidc_app_id: client_id,
|
||
user_id: conn.assigns.current_user.id,
|
||
redirect_uri: redirect_uri
|
||
})
|
||
|
||
uri = build_redirect_uri(redirect_uri, code.value, params["state"])
|
||
redirect(conn, external: uri)
|
||
else
|
||
{:is_approved, false} ->
|
||
render(conn, :error,
|
||
description:
|
||
"Ваш аккаунт ещё не был одобрен, подождите немного или свяжитесь с администратором"
|
||
)
|
||
|
||
{:is_enabled, false} ->
|
||
render(conn, :error, description: "Приложение отключено")
|
||
|
||
{:has_uri, false} ->
|
||
render(conn, :error, description: "Redirect URI не зарегистрирован или отсутствует")
|
||
end
|
||
end
|
||
|
||
def token(conn, %{
|
||
"code" => code_value,
|
||
"redirect_uri" => redirect_uri,
|
||
"client_id" => client_id,
|
||
"client_secret" => client_secret
|
||
}) do
|
||
# Check that code is still valid and redirect uri has not been altered.
|
||
%OIDCCode{redirect_uri: ^redirect_uri} = code = SSO.get_oidc_code!(code_value)
|
||
|
||
# Check that client provided a valid secret for an active OIDC app.
|
||
%OIDCApp{enabled: true, client_id: ^client_id} =
|
||
oidc_app = SSO.get_oidc_app_by_secret!(client_secret)
|
||
|
||
# Check that OIDC app is referenced by provided code.
|
||
^client_id = code.oidc_app.client_id
|
||
|
||
# Delete the code.
|
||
SSO.delete_oidc_code(code)
|
||
|
||
{access_token, refresh_token} = Accounts.generate_oauth_tokens(code.user)
|
||
|
||
id_token = IDToken.build_id_token(code.user, oidc_app.client_id)
|
||
{:ok, signed_id_token} = Token.sign(id_token)
|
||
|
||
render(conn, :token,
|
||
access_token: Base.url_encode64(access_token),
|
||
refresh_token: Base.url_encode64(refresh_token),
|
||
id_token: signed_id_token
|
||
)
|
||
end
|
||
|
||
defp build_redirect_uri(redirect_uri, code, state) do
|
||
parsed_uri = URI.parse(redirect_uri)
|
||
|
||
query =
|
||
build_query_params(code, state)
|
||
|> URI.encode_query()
|
||
|
||
%{parsed_uri | query: query}
|
||
|> URI.to_string()
|
||
end
|
||
|
||
defp build_query_params(code, state) do
|
||
params = %{"code" => code}
|
||
|
||
if state do
|
||
Map.put(params, "state", state)
|
||
else
|
||
params
|
||
end
|
||
end
|
||
end
|