Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Extract User API to handler structure #37

Merged
merged 2 commits into from
Nov 22, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 5 additions & 71 deletions apps/supabase_auth/lib/supabase/go_true.ex
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,11 @@ defmodule Supabase.GoTrue do
import Supabase.Client, only: [is_client: 1]

alias Supabase.Client
alias Supabase.Fetcher
alias Supabase.GoTrue.Endpoints
alias Supabase.GoTrue.PKCE
alias Supabase.GoTrue.Schemas.SignInRequest
alias Supabase.GoTrue.Schemas.SignInWithPassword
alias Supabase.GoTrue.Schemas.SignUpRequest
alias Supabase.GoTrue.Schemas.SignUpWithPassword
alias Supabase.GoTrue.Session
alias Supabase.GoTrue.User
alias Supabase.GoTrue.UserHandler

@opaque client :: pid | module

Expand All @@ -21,9 +17,7 @@ defmodule Supabase.GoTrue do
@impl true
def get_user(client, %Session{} = session) do
with {:ok, client} <- Client.retrieve_client(client),
uri = Endpoints.user(client),
headers = Fetcher.apply_client_headers(client, session.access_token),
{:ok, response} <- Fetcher.get(uri, headers) do
{:ok, response} <- UserHandler.get_user(client, session.access_token) do
User.parse(response)
end
end
Expand All @@ -32,76 +26,16 @@ defmodule Supabase.GoTrue do
def sign_in_with_password(client, credentials) when is_client(client) do
with {:ok, client} <- Client.retrieve_client(client),
{:ok, credentials} <- SignInWithPassword.parse(credentials),
attrs = %{
email: credentials.email,
phone: credentials.phone,
password: credentials.password
},
{:ok, params} <- SignInRequest.create(attrs, credentials.options) do
sign_in_request(params, client)
end
end

defp sign_in_request(%SignInRequest{} = request, %Client{} = client) do
headers = api_headers(client)
uri = Endpoints.sign_in(client, "password")

with {:ok, response} <- Fetcher.post(uri, request, headers) do
Session.parse(response, response["user"])
{:ok, response} <- UserHandler.sign_in_with_password(client, credentials) do
Session.parse(response)
end
end

@impl true
def sign_up(client, credentials) when is_client(client) do
with {:ok, client} <- Client.retrieve_client(client),
{:ok, credentials} <- SignUpWithPassword.parse(credentials) do
if client.auth.flow_type == :pkce do
sign_up_with_pkce(credentials, client)
else
sign_up_without_pkce(credentials, client)
end
end
end

defp sign_up_with_pkce(%SignUpWithPassword{} = credentials, %Client{} = client) do
code_verifier = PKCE.generate_verifier()
code_challenge = PKCE.generate_challenge(code_verifier)
code_challenge_method = "sha256"

attrs = %{
email: credentials.email,
phone: credentials.phone,
password: credentials.password,
code_challenge: code_challenge,
code_challenge_method: code_challenge_method
}

with {:ok, params} <- SignUpRequest.create(attrs, credentials.options),
{:ok, response} <- sign_up_request(params, client) do
{:ok, response, code_challenge}
end
end

defp sign_up_without_pkce(%SignUpWithPassword{} = credentials, %Client{} = client) do
attrs = %{email: credentials.email, phone: credentials.phone, password: credentials.password}

with {:ok, params} <- SignUpRequest.create(attrs, credentials.options),
{:ok, user} <- sign_up_request(params, client) do
{:ok, user, nil}
end
end

defp sign_up_request(%SignUpRequest{} = request, %Client{} = client) do
# add xform and redirect_to options to request
headers = api_headers(client)
uri = Endpoints.sign_up(client)

with {:ok, response} <- Fetcher.post(uri, request, headers) do
User.parse(response)
UserHandler.sign_up(client, credentials)
end
end

defp api_headers(%Client{} = client) do
Fetcher.apply_headers(client.conn.api_key, client.conn.access_token, client.global.headers)
end
end
45 changes: 0 additions & 45 deletions apps/supabase_auth/lib/supabase/go_true/endpoints.ex

This file was deleted.

Empty file.
36 changes: 23 additions & 13 deletions apps/supabase_auth/lib/supabase/go_true/schemas/sign_in_request.ex
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,6 @@ defmodule Supabase.GoTrue.Schemas.SignInRequest do

alias Supabase.GoTrue.Schemas.SignInWithPassword

@required_fields ~w[password]a
@optional_fields ~w[email phone]a

@derive Jason.Encoder
@primary_key false
embedded_schema do
Expand All @@ -18,24 +15,37 @@ defmodule Supabase.GoTrue.Schemas.SignInRequest do
field(:password, :string)

embeds_one :gotrue_meta_security, GoTrueMetaSecurity, primary_key: false do
@derive Jason.Encoder
field(:captcha_token, :string)
end
end

def create(attrs, nil) do
def create(%SignInWithPassword{} = signin) do
attrs = SignInWithPassword.to_sign_in_params(signin)
gotrue_meta = %__MODULE__.GoTrueMetaSecurity{captcha_token: signin.options.captcha_token}

%__MODULE__{}
|> cast(attrs, @required_fields ++ @optional_fields)
|> validate_required(@required_fields)
|> cast(attrs, [:email, :phone, :password])
|> put_embed(:gotrue_meta_security, gotrue_meta, required: true)
|> validate_required([:password])
|> validate_required_inclusion([:email, :phone])
|> apply_action(:insert)
end

def create(attrs, %SignInWithPassword.Options{} = options) do
gotrue_meta = %__MODULE__.GoTrueMetaSecurity{captcha_token: options.captcha_token}
defp validate_required_inclusion(%{valid?: false} = c, _), do: c

%__MODULE__{}
|> cast(attrs, @required_fields ++ @optional_fields)
|> put_embed(:gotrue_meta_security, gotrue_meta, required: true)
|> validate_required(@required_fields)
|> apply_action(:insert)
defp validate_required_inclusion(changeset, fields) do
if Enum.any?(fields, &present?(changeset, &1)) do
changeset
else
changeset
|> add_error(:email, "at least an email or phone is required")
|> add_error(:phone, "at least an email or phone is required")
end
end

defp present?(changeset, field) do
value = get_change(changeset, field)
value && value != ""
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,29 @@ defmodule Supabase.GoTrue.Schemas.SignInWithPassword do
end
end

def to_sign_in_params(%__MODULE__{} = signin) do
Map.take(signin, [:email, :phone, :password])
end

def parse(attrs) do
%__MODULE__{}
|> cast(attrs, ~w[email phone password]a)
|> cast_embed(:options, with: &options_changeset/2, required: false)
|> validate_required([:password])
|> maybe_put_default_options()
|> apply_action(:parse)
end

defp maybe_put_default_options(%{valid?: false} = c), do: c

defp maybe_put_default_options(changeset) do
if get_embed(changeset, :options) do
changeset
else
put_embed(changeset, :options, %__MODULE__.Options{})
end
end

defp options_changeset(options, attrs) do
cast(options, attrs, ~w[email_redirect_to data captcha_token]a)
end
Expand Down
24 changes: 15 additions & 9 deletions apps/supabase_auth/lib/supabase/go_true/schemas/sign_up_request.ex
Original file line number Diff line number Diff line change
Expand Up @@ -21,24 +21,30 @@ defmodule Supabase.GoTrue.Schemas.SignUpRequest do
field(:code_challenge_method, :string)

embeds_one :gotrue_meta_security, GoTrueMetaSecurity, primary_key: false do
@derive Jason.Encoder
field(:captcha_token, :string)
end
end

def create(attrs, nil) do
%__MODULE__{}
def changeset(signup \\ %__MODULE__{}, attrs, go_true_meta) do
signup
|> cast(attrs, @required_fields ++ @optional_fields)
|> put_embed(:gotrue_meta_security, go_true_meta)
|> validate_required(@required_fields)
|> apply_action(:insert)
end

def create(attrs, %SignUpWithPassword.Options{} = options) do
go_true_meta = %__MODULE__.GoTrueMetaSecurity{captcha_token: options.captcha_token}
def create(%SignUpWithPassword{} = signup) do
attrs = SignUpWithPassword.to_sign_up_params(signup)
go_true_meta = %__MODULE__.GoTrueMetaSecurity{captcha_token: signup.options.captcha_token}

%__MODULE__{}
|> cast(attrs, @required_fields ++ @optional_fields)
|> put_assoc(:data, go_true_meta)
|> validate_required(@required_fields)
|> apply_action(:insert)
changeset(attrs, go_true_meta)
end

def create(%SignUpWithPassword{} = signup, code_challenge, code_method) do
attrs = SignUpWithPassword.to_sign_up_params(signup, code_challenge, code_method)
go_true_meta = %__MODULE__.GoTrueMetaSecurity{captcha_token: signup.options.captcha_token}

changeset(attrs, go_true_meta)
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -32,15 +32,36 @@ defmodule Supabase.GoTrue.Schemas.SignUpWithPassword do
end
end

def to_sign_up_params(%__MODULE__{} = signup) do
Map.take(signup, [:email, :password, :phone])
end

def to_sign_up_params(%__MODULE__{} = signup, code_challenge, code_method) do
signup
|> to_sign_up_params()
|> Map.merge(%{code_challange: code_challenge, code_challenge_method: code_method})
end

@spec validate(map) :: Ecto.Changeset.t()
def validate(attrs) do
%__MODULE__{}
|> cast(attrs, [:email, :password, :phone])
|> cast_embed(:options, with: &options_changeset/2, required: false)
|> maybe_put_default_options()
|> validate_email_or_phone()
|> validate_required([:password])
end

defp maybe_put_default_options(%{valid?: false} = c), do: c

defp maybe_put_default_options(changeset) do
if get_embed(changeset, :options) do
changeset
else
put_embed(changeset, :options, %__MODULE__.Options{})
end
end

defp options_changeset(options, attrs) do
cast(options, attrs, ~w[email_redirect_to data captcha_token]a)
end
Expand Down
22 changes: 1 addition & 21 deletions apps/supabase_auth/lib/supabase/go_true/session.ex
Original file line number Diff line number Diff line change
Expand Up @@ -40,27 +40,7 @@ defmodule Supabase.GoTrue.Session do
%__MODULE__{}
|> cast(attrs, @required_fields ++ @optional_fields)
|> validate_required(@required_fields)
|> cast_embed(:user, required: false)
|> apply_action(:parse)
end

@spec parse(map, User.t() | map) :: {:ok, t} | {:error, Ecto.Changeset.t()}
def parse(attrs, user) do
with {:ok, session} <- parse(attrs) do
session
|> change()
|> maybe_put_user_assoc(user)
|> apply_action(:parse)
end
end

defp maybe_put_user_assoc(changeset, %User{} = user) do
put_embed(changeset, :user, user, required: true)
end

defp maybe_put_user_assoc(changeset, %{} = user) do
case User.parse(user) do
{:ok, user} -> maybe_put_user_assoc(changeset, user)
{:error, changeset} -> changeset
end
end
end
Loading