defmodule Supabase.GoTrue.UserHandler do @moduledoc false alias Supabase.Client alias Supabase.Fetcher alias Supabase.GoTrue.PKCE alias Supabase.GoTrue.Schemas.SignInRequest alias Supabase.GoTrue.Schemas.SignInWithIdToken alias Supabase.GoTrue.Schemas.SignInWithOauth alias Supabase.GoTrue.Schemas.SignInWithOTP alias Supabase.GoTrue.Schemas.SignInWithPassword alias Supabase.GoTrue.Schemas.SignInWithSSO alias Supabase.GoTrue.Schemas.SignUpRequest alias Supabase.GoTrue.Schemas.SignUpWithPassword alias Supabase.GoTrue.Schemas.VerifyOTP alias Supabase.GoTrue.User @single_user_uri "/user" @sign_in_uri "/token" @sign_up_uri "/signup" @oauth_uri "/authorize" @sso_uri "/sso" @otp_uri "/otp" @verify_otp_uri "/verify" @reset_pass_uri "/recover" @resend_signup_uri "/resend" def get_user(%Client{} = client, access_token) do headers = Fetcher.apply_client_headers(client, access_token) client |> Client.retrieve_auth_url(@single_user_uri) |> Fetcher.get(nil, headers, resolve_json: true) end def verify_otp(%Client{} = client, %{} = params) do with {:ok, request} <- VerifyOTP.to_request(params) do headers = Fetcher.apply_client_headers(client) endpoint = Client.retrieve_auth_url(client, @verify_otp_uri) endpoint = append_query(endpoint, %{redirect_to: get_in(request, [:options, :redirect_to])}) Fetcher.post(endpoint, request, headers, resolve_json: true) end end def sign_in_with_otp(%Client{} = client, %SignInWithOTP{} = signin) when client.auth.flow_type == :pkce do {challenge, method} = generate_pkce() with {:ok, request} <- SignInRequest.create(signin, challenge, method), headers = Fetcher.apply_client_headers(client), endpoint = Client.retrieve_auth_url(client, @otp_uri), endpoint = append_query(endpoint, %{redirect_to: request.redirect_to}), {:ok, response} <- Fetcher.post(endpoint, request, headers, resolve_json: true) do if is_nil(signin.email), do: {:ok, response["data"]["message_id"]}, else: :ok end end def sign_in_with_otp(%Client{} = client, %SignInWithOTP{} = signin) do with {:ok, request} <- SignInRequest.create(signin), headers = Fetcher.apply_client_headers(client), endpoint = Client.retrieve_auth_url(client, @otp_uri), endpoint = append_query(endpoint, %{redirect_to: request.redirect_to}), {:ok, response} <- Fetcher.post(endpoint, request, headers, resolve_json: true) do if is_nil(signin.email), do: {:ok, response["data"]["message_id"]}, else: :ok end end def sign_in_with_sso(%Client{} = client, %SignInWithSSO{} = signin) when client.auth.flow_type == :pkce do {challenge, method} = generate_pkce() with {:ok, request} <- SignInRequest.create(signin, challenge, method), headers = Fetcher.apply_client_headers(client), endpoint = Client.retrieve_auth_url(client, @sso_uri), endpoint = append_query(endpoint, %{redirect_to: request.redirect_to}), {:ok, response} <- Fetcher.post(endpoint, request, headers, resolve_json: true) do {:ok, response["data"]["url"]} end end def sign_in_with_sso(%Client{} = client, %SignInWithSSO{} = signin) do with {:ok, request} <- SignInRequest.create(signin), headers = Fetcher.apply_client_headers(client), endpoint = Client.retrieve_auth_url(client, @sso_uri), endpoint = append_query(endpoint, %{redirect_to: request.redirect_to}), {:ok, response} <- Fetcher.post(endpoint, request, headers, resolve_json: true) do {:ok, response["data"]["url"]} end end @grant_types ~w[password id_token] def sign_in_with_password(%Client{} = client, %SignInWithPassword{} = signin) do with {:ok, request} <- SignInRequest.create(signin) do sign_in_request(client, request, "password") end end def sign_in_with_id_token(%Client{} = client, %SignInWithIdToken{} = signin) do with {:ok, request} <- SignInRequest.create(signin) do sign_in_request(client, request, "id_token") end end defp sign_in_request(%Client{} = client, %SignInRequest{} = request, grant_type) when grant_type in @grant_types do headers = Fetcher.apply_client_headers(client) client |> Client.retrieve_auth_url(@sign_in_uri) |> append_query(%{grant_type: grant_type, redirect_to: request.redirect_to}) |> Fetcher.post(request, headers, resolve_json: true) end def sign_up(%Client{} = client, %SignUpWithPassword{} = signup) when client.auth.flow_type == :pkce do {challenge, method} = generate_pkce() with {:ok, request} <- SignUpRequest.create(signup, challenge, method), headers = Fetcher.apply_client_headers(client), endpoint = Client.retrieve_auth_url(client, @sign_up_uri), {:ok, response} <- Fetcher.post(endpoint, request, headers, resolve_json: true), {:ok, user} <- User.parse(response) do {:ok, user, challenge} end end def sign_up(%Client{} = client, %SignUpWithPassword{} = signup) do with {:ok, request} <- SignUpRequest.create(signup), headers = Fetcher.apply_client_headers(client), endpoint = Client.retrieve_auth_url(client, @sign_up_uri), {:ok, response} <- Fetcher.post(endpoint, request, headers, resolve_json: true) do User.parse(response) end end def recover_password(%Client{} = client, email, %{} = opts) when client.auth.flow_type == :pkce do {challenge, method} = generate_pkce() body = %{ email: email, code_challenge: challenge, code_challenge_method: method, go_true_meta_security: %{captcha_token: opts[:captcha_token]} } headers = Fetcher.apply_client_headers(client) endpoint = Client.retrieve_auth_url(client, @reset_pass_uri) endpoint = append_query(endpoint, %{redirect_to: opts[:redirect_to]}) with {:ok, _} <- Fetcher.post(endpoint, body, headers) do :ok end end def recover_password(%Client{} = client, email, %{} = opts) do body = %{ email: email, go_true_meta_security: %{captcha_token: opts[:captcha_token]} } headers = Fetcher.apply_client_headers(client) endpoint = Client.retrieve_auth_url(client, @reset_pass_uri) endpoint = append_query(endpoint, %{redirect_to: opts[:redirect_to]}) with {:ok, _} <- Fetcher.post(endpoint, body, headers) do :ok end end def resend_signup(%Client{} = client, email, %{} = opts) do body = %{ email: email, type: opts.type, go_true_meta_security: %{captcha_token: opts[:captcha_token]} } headers = Fetcher.apply_client_headers(client) endpoint = Client.retrieve_auth_url(client, @resend_signup_uri) endpoint = append_query(endpoint, %{redirect_to: opts[:redirect_to]}) with {:ok, _} <- Fetcher.post(endpoint, body, headers) do :ok end end def update_user(%Client{} = client, conn, %{} = params) when client.auth.flow_type == :pkce do {challenge, method} = generate_pkce() access_token = case conn do %Plug.Conn{} -> Plug.Conn.get_session(conn, :user_token) %Phoenix.LiveView.Socket{} -> conn.assigns.user_token end body = Map.merge(params, %{code_challenge: challenge, code_challenge_method: method}) headers = Fetcher.apply_client_headers(client, access_token) endpoint = Client.retrieve_auth_url(client, @single_user_uri) endpoint = append_query(endpoint, %{redirect_to: params[:email_redirect_to]}) session = %{"user_token" => access_token} auth_module = Supabase.GoTrue.get_auth_module!() with {:ok, _} <- Fetcher.put(endpoint, body, headers) do case conn do %Plug.Conn{} -> {:ok, apply(auth_module, :fetch_current_user, [conn, nil])} %Phoenix.LiveView.Socket{} -> {:ok, apply(auth_module, :mount_current_user, [session, conn])} end end end def update_user(%Client{} = client, conn, %{} = params) do access_token = case conn do %Plug.Conn{} -> Plug.Conn.get_session(conn, :user_token) %Phoenix.LiveView.Socket{} -> conn.assigns.user_token end headers = Fetcher.apply_client_headers(client, access_token) endpoint = Client.retrieve_auth_url(client, @single_user_uri) endpoint = append_query(endpoint, %{redirect_to: params[:email_redirect_to]}) session = %{"user_token" => access_token} auth_module = Supabase.GoTrue.get_auth_module!() with {:ok, _} <- Fetcher.put(endpoint, params, headers) do case conn do %Plug.Conn{} -> {:ok, apply(auth_module, :fetch_current_user, [conn, nil])} %Phoenix.LiveView.Socket{} -> {:ok, apply(auth_module, :mount_current_user, [session, conn])} end end end def get_url_for_provider(%Client{} = client, %SignInWithOauth{} = oauth) when client.auth.flow_type == :pkce do {challenge, method} = generate_pkce() pkce_query = %{code_challenge: challenge, code_challenge_method: method} oauth_query = SignInWithOauth.options_to_query(oauth) client |> Client.retrieve_auth_url(@oauth_uri) |> append_query(Map.merge(pkce_query, oauth_query)) end def get_url_for_provider(%Client{} = client, %SignInWithOauth{} = oauth) do oauth_query = SignInWithOauth.options_to_query(oauth) client |> Client.retrieve_auth_url(@oauth_uri) |> append_query(oauth_query) end defp append_query(%URI{} = uri, query) do query = Map.filter(query, &(not is_nil(elem(&1, 1)))) encoded = URI.encode_query(query) URI.append_query(uri, encoded) end defp append_query(uri, query) when is_binary(uri) do append_query(URI.new!(uri), query) end defp generate_pkce do verifier = PKCE.generate_verifier() challenge = PKCE.generate_challenge(verifier) method = if verifier == challenge, do: "plain", else: "s256" {challenge, method} end end