comfycamp/lib/comfycamp_web/controllers/oauth_controller.ex

165 lines
5.9 KiB
Elixir
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

defmodule ComfycampWeb.OauthController do
use ComfycampWeb, :controller
alias Comfycamp.Accounts
alias Comfycamp.SSO
alias Comfycamp.SSO.OIDCApp
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,
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)
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
end
def openid_discovery(conn, _params) do
render(conn, :openid_discovery)
end
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
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