comfycamp/lib/comfycamp_web/controllers/oauth_controller.ex

166 lines
5.9 KiB
Elixir
Raw Normal View History

2024-09-10 01:40:05 +05:00
defmodule ComfycampWeb.OauthController do
use ComfycampWeb, :controller
alias Comfycamp.Accounts
2024-09-10 01:40:05 +05:00
alias Comfycamp.SSO
alias Comfycamp.SSO.OIDCApp
alias Comfycamp.SSO.IDToken
alias Comfycamp.Token
2024-09-10 01:40:05 +05:00
@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
2024-09-10 01:40:05 +05:00
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
2024-09-10 01:40:05 +05:00
end
@doc """
Generate an Authorization Code and redirect the user back to Relying Party.
"""
2024-09-10 01:40:05 +05:00
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
2024-09-10 01:40:05 +05:00
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,
2024-10-17 17:47:23 +05:00
redirect_uri: redirect_uri,
nonce: params["nonce"]
})
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, params = %{"code" => code_value, "redirect_uri" => redirect_uri}) do
with {:client_info, {:ok, client_id, client_secret}} <-
{:client_info, get_client_info(conn, params)},
{:code, code} <- {:code, SSO.get_oidc_code!(code_value)},
{:uri, ^redirect_uri} <- {:uri, code.redirect_uri},
{:app, oidc_app = %OIDCApp{enabled: true, client_id: ^client_id}} <-
{:app, SSO.get_oidc_app_by_secret!(client_secret)},
{:code_ref, ^client_id} <- {:code_ref, code.oidc_app.client_id} do
SSO.delete_oidc_code(code)
{access_token, refresh_token} = Accounts.generate_oauth_tokens(code.user)
2024-10-17 17:47:23 +05:00
id_token = IDToken.build_id_token(code.user, oidc_app.client_id, code.nonce)
{:ok, signed_id_token} = Token.sign(id_token, client_secret)
render(conn, :token,
access_token: Base.url_encode64(access_token),
refresh_token: Base.url_encode64(refresh_token),
id_token: signed_id_token
)
else
{:client_info, _} ->
render(conn, :error, description: "Нет client id или client secret")
{:code, _} ->
render(conn, :error, description: "Не удалось найти временный код")
{:uri, _} ->
render(conn, :error, description: "Redirect URI не совпадает с изначальным значением")
{:app, _} ->
render(conn, :error, description: "Приложение не найдено или отключено")
{:code_ref, _} ->
render(conn, :error, description: "Временный код выдан для другого приложения")
end
2024-09-10 01:40:05 +05:00
end
def openid_discovery(conn, _params) do
render(conn, :openid_discovery)
end
2024-10-17 14:13:23 +05:00
def user_info(conn, _params) do
render(conn, :user_info, user: conn.assigns.oauth_user)
end
@doc """
Extract client id and client secret from request parameters or headers.
Returns {:ok, "client_id", "client_secret"} on success.
"""
def get_client_info(_conn, %{"client_id" => client_id, "client_secret" => client_secret}) do
{:ok, client_id, client_secret}
end
def get_client_info(conn, _params) do
with [header] <- Plug.Conn.get_req_header(conn, "authorization"),
"Basic " <> b64 <- header,
{:ok, keys} <- Base.decode64(b64),
[client_id, client_secret] <- String.split(keys, ":") do
{:ok, client_id, client_secret}
else
_ -> {:error, "Invalid Authorization header"}
end
end
2024-09-10 01:40:05 +05:00
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