diff --git a/elixir/apps/domain/lib/domain/auth/adapters/email.ex b/elixir/apps/domain/lib/domain/auth/adapters/email.ex index 4532229e9..be31f3eda 100644 --- a/elixir/apps/domain/lib/domain/auth/adapters/email.ex +++ b/elixir/apps/domain/lib/domain/auth/adapters/email.ex @@ -100,18 +100,25 @@ defmodule Domain.Auth.Adapters.Email do @impl true def verify_secret(%Identity{} = identity, %Context{} = context, encoded_token) do - with {:ok, token} <- Tokens.use_token(encoded_token, %{context | type: :email}) do + with {:ok, token} <- Tokens.use_token(encoded_token, %{context | type: :email}), + true <- token.identity_id == identity.id do {:ok, identity} = - Identity.Changeset.update_identity_provider_state(identity, %{ - last_used_token_id: token.id - }) - |> Repo.update() + Identity.Query.not_disabled() + |> Identity.Query.by_id(identity.id) + |> Repo.fetch_and_update(Identity.Query, + with: fn identity -> + Identity.Changeset.update_identity_provider_state(identity, %{ + last_used_token_id: token.id + }) + end + ) {:ok, _count} = Tokens.delete_all_tokens_by_type_and_assoc(:email, identity) {:ok, identity, nil} else {:error, :invalid_or_expired_token} -> {:error, :invalid_secret} + false -> {:error, :invalid_secret} end end end diff --git a/elixir/apps/domain/test/domain/auth/adapters/email_test.exs b/elixir/apps/domain/test/domain/auth/adapters/email_test.exs index 032a3468c..45b1cecb5 100644 --- a/elixir/apps/domain/test/domain/auth/adapters/email_test.exs +++ b/elixir/apps/domain/test/domain/auth/adapters/email_test.exs @@ -176,6 +176,22 @@ defmodule Domain.Auth.Adapters.EmailTest do assert token.deleted_at end + test "returns error when token belongs to a different identity", %{ + account: account, + provider: provider, + identity: identity, + context: context + } do + other_identity = Fixtures.Auth.create_identity(account: account, provider: provider) + {:ok, other_identity} = request_sign_in_token(other_identity, context) + + nonce = other_identity.provider_virtual_state.nonce + fragment = other_identity.provider_virtual_state.fragment + token = nonce <> fragment + + assert verify_secret(identity, context, token) == {:error, :invalid_secret} + end + test "returns error when token is expired", %{ context: context, identity: identity, diff --git a/elixir/apps/domain/test/domain/auth_test.exs b/elixir/apps/domain/test/domain/auth_test.exs index 40a441e0b..407259f2f 100644 --- a/elixir/apps/domain/test/domain/auth_test.exs +++ b/elixir/apps/domain/test/domain/auth_test.exs @@ -2989,6 +2989,26 @@ defmodule Domain.AuthTest do {:error, :unauthorized} end + test "returns error when secret belongs to a different identity invalid", %{ + account: account, + provider: provider, + user_agent: user_agent, + remote_ip: remote_ip + } do + nonce = "test_nonce_for_firezone" + context = %Auth.Context{type: :browser, user_agent: user_agent, remote_ip: remote_ip} + + identity = Fixtures.Auth.create_identity(account: account, provider: provider) + {:ok, identity} = Domain.Auth.Adapters.Email.request_sign_in_token(identity, context) + + identity2 = Fixtures.Auth.create_identity(account: account, provider: provider) + {:ok, identity2} = Domain.Auth.Adapters.Email.request_sign_in_token(identity2, context) + secret = identity2.provider_virtual_state.nonce <> identity2.provider_virtual_state.fragment + + assert sign_in(provider, identity.provider_identifier, nonce, secret, context) == + {:error, :unauthorized} + end + test "returns error when nonce is invalid", %{ account: account, provider: provider, diff --git a/elixir/mix.exs b/elixir/mix.exs index 3e19c093e..a1dfa7c18 100644 --- a/elixir/mix.exs +++ b/elixir/mix.exs @@ -1,11 +1,13 @@ defmodule Firezone.MixProject do use Mix.Project + @version "VERSION" |> File.read!() |> String.trim() + def project do [ name: :firezone, apps_path: "apps", - version: String.trim(File.read!("VERSION")), + version: @version, start_permanent: Mix.env() == :prod, test_coverage: [tool: ExCoveralls], preferred_cli_env: [