From a7eb06c48e832f1664b65231ed07a0468dd1b2ab Mon Sep 17 00:00:00 2001 From: Steffen Deusch Date: Sun, 13 Oct 2024 18:35:18 +0200 Subject: [PATCH 01/29] update server log example to mention bandit instead --- guides/introduction/up_and_running.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/guides/introduction/up_and_running.md b/guides/introduction/up_and_running.md index 8d07024b1c..df1871d49a 100644 --- a/guides/introduction/up_and_running.md +++ b/guides/introduction/up_and_running.md @@ -86,7 +86,7 @@ And finally, we'll start the Phoenix server: ```console $ mix phx.server -[info] Running HelloWeb.Endpoint with cowboy 2.9.0 at 127.0.0.1:4000 (http) +[info] Running HelloWeb.Endpoint with Bandit 1.5.7 at 127.0.0.1:4000 (http) [info] Access HelloWeb.Endpoint at http://localhost:4000 [watch] build finished, watching for changes... ... From edb4b18b511d2d6c69af5247f7a97f47ef1ca167 Mon Sep 17 00:00:00 2001 From: Steffen Deusch Date: Wed, 16 Oct 2024 17:03:34 +0200 Subject: [PATCH 02/29] Move flash_group to the end of the app template (#5953) Closes https://github.com/phoenixframework/phoenix_live_view/issues/3415. --- installer/templates/phx_web/components/layouts/app.html.heex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/installer/templates/phx_web/components/layouts/app.html.heex b/installer/templates/phx_web/components/layouts/app.html.heex index c50aa21d28..d164090735 100644 --- a/installer/templates/phx_web/components/layouts/app.html.heex +++ b/installer/templates/phx_web/components/layouts/app.html.heex @@ -26,7 +26,7 @@
- <.flash_group flash={@flash} /> <%%= @inner_content %>
+<.flash_group flash={@flash} /> From 65d2507ccd8619fc0beb0c6339a6c3f6967cdd16 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonatan=20K=C5=82osko?= Date: Tue, 22 Oct 2024 10:29:57 +0200 Subject: [PATCH 03/29] Implement mix listener to respect concurrent compilations (#5955) --- installer/templates/phx_single/mix.exs | 3 +- .../phx_umbrella/apps/app_name_web/mix.exs | 3 +- lib/phoenix/code_reloader.ex | 17 +++ lib/phoenix/code_reloader/mix_listener.ex | 72 +++++++++ lib/phoenix/code_reloader/server.ex | 138 ++++++++++++++---- 5 files changed, 204 insertions(+), 29 deletions(-) create mode 100644 lib/phoenix/code_reloader/mix_listener.ex diff --git a/installer/templates/phx_single/mix.exs b/installer/templates/phx_single/mix.exs index 60fe5b2677..8282523114 100644 --- a/installer/templates/phx_single/mix.exs +++ b/installer/templates/phx_single/mix.exs @@ -13,7 +13,8 @@ defmodule <%= @app_module %>.MixProject do elixirc_paths: elixirc_paths(Mix.env()), start_permanent: Mix.env() == :prod, aliases: aliases(), - deps: deps() + deps: deps(), + listeners: [Phoenix.CodeReloader] ] end diff --git a/installer/templates/phx_umbrella/apps/app_name_web/mix.exs b/installer/templates/phx_umbrella/apps/app_name_web/mix.exs index 12f347b4df..620aa84ec4 100644 --- a/installer/templates/phx_umbrella/apps/app_name_web/mix.exs +++ b/installer/templates/phx_umbrella/apps/app_name_web/mix.exs @@ -13,7 +13,8 @@ defmodule <%= @web_namespace %>.MixProject do elixirc_paths: elixirc_paths(Mix.env()), start_permanent: Mix.env() == :prod, aliases: aliases(), - deps: deps() + deps: deps(), + listeners: [Phoenix.CodeReloader] ] end diff --git a/lib/phoenix/code_reloader.ex b/lib/phoenix/code_reloader.ex index 809d55525b..433cc75476 100644 --- a/lib/phoenix/code_reloader.ex +++ b/lib/phoenix/code_reloader.ex @@ -31,6 +31,19 @@ defmodule Phoenix.CodeReloader do This function is a no-op and returns `:ok` if Mix is not available. + The reloader should also be configured as a Mix listener in project's + mix.exs file (since Elixir v1.18): + + def project do + [ + ..., + listeners: [Phoenix.CodeReloader] + ] + end + + This way the reloader can notice whenever the project is compiled + concurrently. + ## Options * `:reloadable_args` - additional CLI args to pass to the compiler tasks. @@ -57,6 +70,10 @@ defmodule Phoenix.CodeReloader do @spec sync :: :ok defdelegate sync, to: Phoenix.CodeReloader.Server + @doc false + @spec child_spec(keyword) :: Supervisor.child_spec() + defdelegate child_spec(opts), to: Phoenix.CodeReloader.MixListener + ## Plug @behaviour Plug diff --git a/lib/phoenix/code_reloader/mix_listener.ex b/lib/phoenix/code_reloader/mix_listener.ex new file mode 100644 index 0000000000..f136314ee9 --- /dev/null +++ b/lib/phoenix/code_reloader/mix_listener.ex @@ -0,0 +1,72 @@ +defmodule Phoenix.CodeReloader.MixListener do + @moduledoc false + + use GenServer + + @name __MODULE__ + + @spec start_link(keyword) :: GenServer.on_start() + def start_link(_opts) do + GenServer.start_link(__MODULE__, {}, name: @name) + end + + @spec started? :: boolean() + def started? do + Process.whereis(Phoenix.CodeReloader.MixListener) != nil + end + + @doc """ + Unloads all modules invalidated by external compilations. + + Only reloads modules from the given apps. + """ + @spec purge([atom()]) :: :ok + def purge(apps) do + GenServer.call(@name, {:purge, apps}, :infinity) + end + + @impl true + def init({}) do + {:ok, %{to_purge: %{}}} + end + + @impl true + def handle_call({:purge, apps}, _from, state) do + for app <- apps, modules = state.to_purge[app] do + purge_modules(modules) + end + + {:reply, :ok, %{state | to_purge: %{}}} + end + + @impl true + def handle_info({:modules_compiled, info}, state) do + if info.os_pid == System.pid() do + # Ignore compilations from ourselves, because the modules are + # already updated in memory + {:noreply, state} + else + %{changed: changed, removed: removed} = info.modules_diff + + state = + update_in(state.to_purge[info.app], fn to_purge -> + to_purge = to_purge || MapSet.new() + to_purge = Enum.into(changed, to_purge) + Enum.into(removed, to_purge) + end) + + {:noreply, state} + end + end + + def handle_info(_message, state) do + {:noreply, state} + end + + defp purge_modules(modules) do + for module <- modules do + :code.purge(module) + :code.delete(module) + end + end +end diff --git a/lib/phoenix/code_reloader/server.ex b/lib/phoenix/code_reloader/server.ex index 8f56f423c4..16ff92bf19 100644 --- a/lib/phoenix/code_reloader/server.ex +++ b/lib/phoenix/code_reloader/server.ex @@ -63,23 +63,39 @@ defmodule Phoenix.CodeReloader.Server do apps = endpoint.config(:reloadable_apps) || default_reloadable_apps() args = Keyword.get(opts, :reloadable_args, ["--no-all-warnings"]) - # We do a backup of the endpoint in case compilation fails. - # If so we can bring it back to finish the request handling. - backup = load_backup(endpoint) froms = all_waiting([from], endpoint) - {res, out} = - proxy_io(fn -> - try do - mix_compile(Code.ensure_loaded(Mix.Task), compilers, apps, args, state.timestamp) - catch - :exit, {:shutdown, 1} -> - :error - - kind, reason -> - IO.puts(Exception.format(kind, reason, __STACKTRACE__)) - :error - end + {backup, res, out} = + with_build_lock(fn -> + purge_fallback? = + if Phoenix.CodeReloader.MixListener.started?() do + Phoenix.CodeReloader.MixListener.purge(apps) + false + else + warn_missing_mix_listener() + true + end + + # We do a backup of the endpoint in case compilation fails. + # If so we can bring it back to finish the request handling. + backup = load_backup(endpoint) + + {res, out} = + proxy_io(fn -> + try do + task_loaded = Code.ensure_loaded(Mix.Task) + mix_compile(task_loaded, compilers, apps, args, state.timestamp, purge_fallback?) + catch + :exit, {:shutdown, 1} -> + :error + + kind, reason -> + IO.puts(Exception.format(kind, reason, __STACKTRACE__)) + :error + end + end) + + {backup, res, out} end) reply = @@ -175,7 +191,34 @@ defmodule Phoenix.CodeReloader.Server do defp purge_protocols(_path), do: :ok end - defp mix_compile({:module, Mix.Task}, compilers, apps_to_reload, compile_args, timestamp) do + defp warn_missing_mix_listener do + listeners_supported? = Version.match?(System.version(), ">= 1.18.0-dev") + + if listeners_supported? do + IO.warn(""" + a Mix listener expected by Phoenix.CodeReloader is missing. + + Please add the listener to your mix.exs configuration, like so: + + def project do + [ + ..., + listeners: [Phoenix.CodeReloader] + ] + end + + """) + end + end + + defp mix_compile( + {:module, Mix.Task}, + compilers, + apps_to_reload, + compile_args, + timestamp, + purge_fallback? + ) do config = Mix.Project.config() path = Mix.Project.consolidation_path(config) @@ -184,8 +227,25 @@ defmodule Phoenix.CodeReloader.Server do purge_protocols(path) end - mix_compile_deps(Mix.Dep.cached(), apps_to_reload, compile_args, compilers, timestamp, path) - mix_compile_project(config[:app], apps_to_reload, compile_args, compilers, timestamp, path) + mix_compile_deps( + Mix.Dep.cached(), + apps_to_reload, + compile_args, + compilers, + timestamp, + path, + purge_fallback? + ) + + mix_compile_project( + config[:app], + apps_to_reload, + compile_args, + compilers, + timestamp, + path, + purge_fallback? + ) if config[:consolidate_protocols] do # If we are consolidating protocols, we need to purge all of its modules @@ -198,30 +258,46 @@ defmodule Phoenix.CodeReloader.Server do :ok end - defp mix_compile({:error, _reason}, _, _, _, _) do + defp mix_compile({:error, _reason}, _, _, _, _, _) do raise "the Code Reloader is enabled but Mix is not available. If you want to " <> "use the Code Reloader in production or inside an escript, you must add " <> ":mix to your applications list. Otherwise, you must disable code reloading " <> "in such environments" end - defp mix_compile_deps(deps, apps_to_reload, compile_args, compilers, timestamp, path) do + defp mix_compile_deps( + deps, + apps_to_reload, + compile_args, + compilers, + timestamp, + path, + purge_fallback? + ) do for dep <- deps, dep.app in apps_to_reload do Mix.Dep.in_dependency(dep, fn _ -> - mix_compile_unless_stale_config(compilers, compile_args, timestamp, path) + mix_compile_unless_stale_config(compilers, compile_args, timestamp, path, purge_fallback?) end) end end - defp mix_compile_project(nil, _, _, _, _, _), do: :ok - - defp mix_compile_project(app, apps_to_reload, compile_args, compilers, timestamp, path) do + defp mix_compile_project(nil, _, _, _, _, _, _), do: :ok + + defp mix_compile_project( + app, + apps_to_reload, + compile_args, + compilers, + timestamp, + path, + purge_fallback? + ) do if app in apps_to_reload do - mix_compile_unless_stale_config(compilers, compile_args, timestamp, path) + mix_compile_unless_stale_config(compilers, compile_args, timestamp, path, purge_fallback?) end end - defp mix_compile_unless_stale_config(compilers, compile_args, timestamp, path) do + defp mix_compile_unless_stale_config(compilers, compile_args, timestamp, path, purge_fallback?) do manifests = Mix.Tasks.Compile.Elixir.manifests() configs = Mix.Project.config_files() config = Mix.Project.config() @@ -230,7 +306,8 @@ defmodule Phoenix.CodeReloader.Server do [] -> # If the manifests are more recent than the timestamp, # someone updated this app behind the scenes, so purge all beams. - if Mix.Utils.stale?(manifests, [timestamp]) do + # TODO: remove once we depend on Elixir 1.18 + if purge_fallback? and Mix.Utils.stale?(manifests, [timestamp]) do purge_modules(Path.join(Mix.Project.app_path(config), "ebin")) end @@ -351,4 +428,11 @@ defmodule Phoenix.CodeReloader.Server do Logger.configure(compile_time_application: logger_config_app) end end + + # TODO: remove once we depend on Elixir 1.18 + if Code.ensure_loaded?(Mix.Project) and function_exported?(Mix.Project, :with_build_lock, 1) do + defp with_build_lock(fun), do: Mix.Project.with_build_lock(fun) + else + defp with_build_lock(fun), do: fun.() + end end From e88f60368aa5ec2172c63a0104f5877c981ed2cb Mon Sep 17 00:00:00 2001 From: Pavel Shpak Date: Tue, 22 Oct 2024 11:31:22 +0300 Subject: [PATCH 04/29] Fix action rename example in controllers guides (#5948) --- guides/controllers.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/guides/controllers.md b/guides/controllers.md index 6ca3346427..3bb93589eb 100644 --- a/guides/controllers.md +++ b/guides/controllers.md @@ -47,7 +47,7 @@ defmodule HelloWeb.PageController do ... def index(conn, _params) do - render(conn, :index) + render(conn, :home) end end ``` From a2e7de1058a0972447d8a0e362166cc6b6911f57 Mon Sep 17 00:00:00 2001 From: Leif Metcalf Date: Tue, 22 Oct 2024 21:31:55 +1300 Subject: [PATCH 05/29] Update argon2_elixir (#5946) --- integration_test/mix.exs | 2 +- integration_test/mix.lock | 10 +++++----- lib/mix/tasks/phx.gen.auth/hashing_library.ex | 2 +- test/mix/tasks/phx.gen.auth_test.exs | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/integration_test/mix.exs b/integration_test/mix.exs index 81012c0c80..4c45c7fcfc 100644 --- a/integration_test/mix.exs +++ b/integration_test/mix.exs @@ -54,7 +54,7 @@ defmodule Phoenix.Integration.MixProject do {:swoosh, "~> 1.16"}, {:bandit, "~> 1.0"}, {:bcrypt_elixir, "~> 3.0"}, - {:argon2_elixir, "~> 3.0"}, + {:argon2_elixir, "~> 4.0"}, {:pbkdf2_elixir, "~> 2.0"}, {:tailwind, "~> 0.2"}, {:heroicons, diff --git a/integration_test/mix.lock b/integration_test/mix.lock index 59445ecf5d..59d00655d0 100644 --- a/integration_test/mix.lock +++ b/integration_test/mix.lock @@ -1,10 +1,10 @@ %{ - "argon2_elixir": {:hex, :argon2_elixir, "3.2.1", "f47740bf9f2a39ffef79ba48eb25dea2ee37bcc7eadf91d49615591d1a6fce1a", [:make, :mix], [{:comeonin, "~> 5.3", [hex: :comeonin, repo: "hexpm", optional: false]}, {:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "a813b78217394530b5fcf4c8070feee43df03ffef938d044019169c766315690"}, + "argon2_elixir": {:hex, :argon2_elixir, "4.1.0", "2f242afe47c373663cb404eb75e792f749507075ed737b49685a9f2edcb401df", [:make, :mix], [{:comeonin, "~> 5.3", [hex: :comeonin, repo: "hexpm", optional: false]}, {:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "2ecb6f2ca2cca34b28e546224661bf2a85714516d2713c7313c5ffe8bdade7cf"}, "bandit": {:hex, :bandit, "1.5.4", "8e56e7cfc06f3c57995be0d9bf4e45b972d8732f5c7e96ef8ec0735f52079527", [:mix], [{:hpax, "~> 0.2.0", [hex: :hpax, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:thousand_island, "~> 1.0", [hex: :thousand_island, repo: "hexpm", optional: false]}, {:websock, "~> 0.5", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "04c2b38874769af67fe7f10034f606ad6dda1d8f80c4d7a0c616b347584d5aff"}, "bcrypt_elixir": {:hex, :bcrypt_elixir, "3.1.0", "0b110a9a6c619b19a7f73fa3004aa11d6e719a67e672d1633dc36b6b2290a0f7", [:make, :mix], [{:comeonin, "~> 5.3", [hex: :comeonin, repo: "hexpm", optional: false]}, {:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "2ad2acb5a8bc049e8d5aa267802631912bb80d5f4110a178ae7999e69dca1bf7"}, - "castore": {:hex, :castore, "1.0.8", "dedcf20ea746694647f883590b82d9e96014057aff1d44d03ec90f36a5c0dc6e", [:mix], [], "hexpm", "0b2b66d2ee742cb1d9cb8c8be3b43c3a70ee8651f37b75a8b982e036752983f1"}, + "castore": {:hex, :castore, "1.0.9", "5cc77474afadf02c7c017823f460a17daa7908e991b0cc917febc90e466a375c", [:mix], [], "hexpm", "5ea956504f1ba6f2b4eb707061d8e17870de2bee95fb59d512872c2ef06925e7"}, "cc_precompiler": {:hex, :cc_precompiler, "0.1.10", "47c9c08d8869cf09b41da36538f62bc1abd3e19e41701c2cea2675b53c704258", [:mix], [{:elixir_make, "~> 0.7", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "f6e046254e53cd6b41c6bacd70ae728011aa82b2742a80d6e2214855c6e06b22"}, - "comeonin": {:hex, :comeonin, "5.4.0", "246a56ca3f41d404380fc6465650ddaa532c7f98be4bda1b4656b3a37cc13abe", [:mix], [], "hexpm", "796393a9e50d01999d56b7b8420ab0481a7538d0caf80919da493b4a6e51faf1"}, + "comeonin": {:hex, :comeonin, "5.5.0", "364d00df52545c44a139bad919d7eacb55abf39e86565878e17cebb787977368", [:mix], [], "hexpm", "6287fc3ba0aad34883cbe3f7949fc1d1e738e5ccdce77165bc99490aa69f47fb"}, "db_connection": {:hex, :db_connection, "2.6.0", "77d835c472b5b67fc4f29556dee74bf511bbafecdcaf98c27d27fa5918152086", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "c2f992d15725e721ec7fbc1189d4ecdb8afef76648c746a8e1cad35e3b8a35f3"}, "decimal": {:hex, :decimal, "2.1.1", "5611dca5d4b2c3dd497dec8f68751f1f1a54755e8ed2a966c2633cf885973ad6", [:mix], [], "hexpm", "53cfe5f497ed0e7771ae1a475575603d77425099ba5faef9394932b35020ffcc"}, "dns_cluster": {:hex, :dns_cluster, "0.1.3", "0bc20a2c88ed6cc494f2964075c359f8c2d00e1bf25518a6a6c7fd277c9b0c66", [:mix], [], "hexpm", "46cb7c4a1b3e52c7ad4cbe33ca5079fbde4840dedeafca2baf77996c2da1bc33"}, @@ -13,12 +13,12 @@ "ecto_sqlite3": {:hex, :ecto_sqlite3, "0.16.0", "1cdc8ea6319e7cb1bc273a36db0ecde69ad56b4dea3037689ad8c0afc6a91e16", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.11", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.11", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:exqlite, "~> 0.22", [hex: :exqlite, repo: "hexpm", optional: false]}], "hexpm", "73c9dd56830d67c951bc254c082cb0a7f9fa139d44866bc3186c8859d1b4d787"}, "elixir_make": {:hex, :elixir_make, "0.8.4", "4960a03ce79081dee8fe119d80ad372c4e7badb84c493cc75983f9d3bc8bde0f", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:certifi, "~> 2.0", [hex: :certifi, repo: "hexpm", optional: true]}], "hexpm", "6e7f1d619b5f61dfabd0a20aa268e575572b542ac31723293a4c1a567d5ef040"}, "esbuild": {:hex, :esbuild, "0.8.1", "0cbf919f0eccb136d2eeef0df49c4acf55336de864e63594adcea3814f3edf41", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "25fc876a67c13cb0a776e7b5d7974851556baeda2085296c14ab48555ea7560f"}, - "expo": {:hex, :expo, "0.5.2", "beba786aab8e3c5431813d7a44b828e7b922bfa431d6bfbada0904535342efe2", [:mix], [], "hexpm", "8c9bfa06ca017c9cb4020fabe980bc7fdb1aaec059fd004c2ab3bff03b1c599c"}, + "expo": {:hex, :expo, "1.1.0", "f7b9ed7fb5745ebe1eeedf3d6f29226c5dd52897ac67c0f8af62a07e661e5c75", [:mix], [], "hexpm", "fbadf93f4700fb44c331362177bdca9eeb8097e8b0ef525c9cc501cb9917c960"}, "exqlite": {:hex, :exqlite, "0.23.0", "6e851c937a033299d0784994c66da24845415072adbc455a337e20087bce9033", [:make, :mix], [{:cc_precompiler, "~> 0.1", [hex: :cc_precompiler, repo: "hexpm", optional: false]}, {:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:elixir_make, "~> 0.8", [hex: :elixir_make, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "404341cceec5e6466aaed160cf0b58be2019b60af82588c215e1224ebd3ec831"}, "file_system": {:hex, :file_system, "1.0.0", "b689cc7dcee665f774de94b5a832e578bd7963c8e637ef940cd44327db7de2cd", [:mix], [], "hexpm", "6752092d66aec5a10e662aefeed8ddb9531d79db0bc145bb8c40325ca1d8536d"}, "finch": {:hex, :finch, "0.18.0", "944ac7d34d0bd2ac8998f79f7a811b21d87d911e77a786bc5810adb75632ada4", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: false]}, {:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.3", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.4 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 0.2.6 or ~> 1.0", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "69f5045b042e531e53edc2574f15e25e735b522c37e2ddb766e15b979e03aa65"}, "floki": {:hex, :floki, "0.36.2", "a7da0193538c93f937714a6704369711998a51a6164a222d710ebd54020aa7a3", [:mix], [], "hexpm", "a8766c0bc92f074e5cb36c4f9961982eda84c5d2b8e979ca67f5c268ec8ed580"}, - "gettext": {:hex, :gettext, "0.24.0", "6f4d90ac5f3111673cbefc4ebee96fe5f37a114861ab8c7b7d5b30a1108ce6d8", [:mix], [{:expo, "~> 0.5.1", [hex: :expo, repo: "hexpm", optional: false]}], "hexpm", "bdf75cdfcbe9e4622dd18e034b227d77dd17f0f133853a1c73b97b3d6c770e8b"}, + "gettext": {:hex, :gettext, "0.26.1", "38e14ea5dcf962d1fc9f361b63ea07c0ce715a8ef1f9e82d3dfb8e67e0416715", [:mix], [{:expo, "~> 0.5.1 or ~> 1.0", [hex: :expo, repo: "hexpm", optional: false]}], "hexpm", "01ce56f188b9dc28780a52783d6529ad2bc7124f9744e571e1ee4ea88bf08734"}, "heroicons": {:git, "https://github.com/tailwindlabs/heroicons.git", "88ab3a0d790e6a47404cba02800a6b25d2afae50", [tag: "v2.1.1", sparse: "optimized", depth: 1]}, "hpax": {:hex, :hpax, "0.2.0", "5a58219adcb75977b2edce5eb22051de9362f08236220c9e859a47111c194ff5", [:mix], [], "hexpm", "bea06558cdae85bed075e6c036993d43cd54d447f76d8190a8db0dc5893fa2f1"}, "jason": {:hex, :jason, "1.4.3", "d3f984eeb96fe53b85d20e0b049f03e57d075b5acda3ac8d465c969a2536c17b", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "9a90e868927f7c777689baa16d86f4d0e086d968db5c05d917ccff6d443e58a3"}, diff --git a/lib/mix/tasks/phx.gen.auth/hashing_library.ex b/lib/mix/tasks/phx.gen.auth/hashing_library.ex index cd1d2b7c72..2d37afc265 100644 --- a/lib/mix/tasks/phx.gen.auth/hashing_library.ex +++ b/lib/mix/tasks/phx.gen.auth/hashing_library.ex @@ -40,7 +40,7 @@ defmodule Mix.Tasks.Phx.Gen.Auth.HashingLibrary do lib = %__MODULE__{ name: :argon2, module: Argon2, - mix_dependency: ~s|{:argon2_elixir, "~> 3.0"}|, + mix_dependency: ~s|{:argon2_elixir, "~> 4.0"}|, test_config: """ config :argon2_elixir, t_cost: 1, m_cost: 8 """ diff --git a/test/mix/tasks/phx.gen.auth_test.exs b/test/mix/tasks/phx.gen.auth_test.exs index d0944d8f76..e252d1167b 100644 --- a/test/mix/tasks/phx.gen.auth_test.exs +++ b/test/mix/tasks/phx.gen.auth_test.exs @@ -1050,7 +1050,7 @@ defmodule Mix.Tasks.Phx.Gen.AuthTest do assert_received {:mix_shell, :yes?, [@liveview_option_message]} assert_file("mix.exs", fn file -> - assert file =~ ~s|{:argon2_elixir, "~> 3.0"}| + assert file =~ ~s|{:argon2_elixir, "~> 4.0"}| end) assert_file("config/test.exs", fn file -> From acd8561a898b51f9180174be0969b98d8a9af171 Mon Sep 17 00:00:00 2001 From: Jatin Shridhar Date: Tue, 22 Oct 2024 14:20:17 +0530 Subject: [PATCH 06/29] [docs] add code example on how to render function components in controller (#5954) --- guides/components.md | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/guides/components.md b/guides/components.md index f0a3c18055..f78aa38a3d 100644 --- a/guides/components.md +++ b/guides/components.md @@ -62,11 +62,7 @@ Next we need to update `show.html.heex`: ``` -When we reload `http://localhost:4000/hello/Frank`, we should see the same content as before. - -Since templates are embedded inside the `HelloHTML` module, we were able to invoke the view function simply as `<.greet messenger="..." />`. - -If the component was defined elsewhere, we can also type ``. +When we reload `http://localhost:4000/hello/Frank`, we should see the same content as before. Since the `show.html.heex` template is embedded within the `HelloHTML` module, we were able to invoke the function component directly as `<.greet messenger="..." />`. If the component was defined elsewhere, we would need to give its full name: ``. By declaring attributes as required, Phoenix will warn at compile time if we call the `<.greet />` component without passing attributes. If an attribute is optional, you can specify the `:default` option with a value: @@ -74,19 +70,22 @@ By declaring attributes as required, Phoenix will warn at compile time if we cal attr :messenger, :string, default: nil ``` -Although this is a quick example, it shows the different roles function components play in Phoenix: - -* Function components can be defined as functions that receive `assigns` as argument and call the `~H` sigil, as we did in `greet/1` - -* Function components can be embedded from template files, that's how we load `show.html.heex` into `HelloWeb.HelloHTML` +Overall, function components are the essential building block of Phoenix rendering stack. The majority of the times, they are functions that receive a single argument called `assigns` and call the `~H` sigil, as we did in `greet/1`. They can also be invoked from templates, with compile-time validation of its attributes declared via `attr`. -* Function components can declare which attributes are expected, which are validated at compilation time +In fact, every template embedded into `HelloHTML` is a function component in itself. `show.html.heex` simply becomes a function component named `show`. This also means you can directly render function components directly from the controller, skipping the `show.html.heex` template: -* Function components can be directly rendered from controllers +```elixir +def HelloWeb.HelloController do + use HelloWeb, :controller -* Function components can be directly rendered from other function components, as we called `<.greet messenger={@messenger} />` from `show.html.heex` + def show(conn, %{"messenger" => messenger}) do + # Render the HelloWeb.HelloHTML.greet/1 component + render(conn, :greet, messenger: messenger) + end +end +``` -And there's more. Before we go deeper, let's fully understand the expressive power behind the HEEx template language. +Next, let's fully understand the expressive power behind the HEEx template language. ## HEEx From 31b23ca148e7e22480dad377a0a9af0fd51733cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patr=C3=ADcio=20dos=20Santos?= Date: Tue, 22 Oct 2024 09:58:21 +0100 Subject: [PATCH 07/29] phx.new - Fix deprecation warning when using Elixir ~> 1.18 (#5940) --- installer/lib/mix/tasks/phx.new.ex | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/installer/lib/mix/tasks/phx.new.ex b/installer/lib/mix/tasks/phx.new.ex index 899a9f84b7..01882b9d21 100644 --- a/installer/lib/mix/tasks/phx.new.ex +++ b/installer/lib/mix/tasks/phx.new.ex @@ -266,8 +266,14 @@ defmodule Mix.Tasks.Phx.New do Code.ensure_loaded?(Hex) end - defp rebar_available? do - Mix.Rebar.rebar_cmd(:rebar3) + if Version.match?(System.version(), "~> 1.18") do + defp rebar_available? do + true + end + else + defp rebar_available? do + Mix.Rebar.rebar_cmd(:rebar3) + end end defp print_missing_steps(steps) do From 2b3c13bec228bf70e857d17171ce16bbf88306b0 Mon Sep 17 00:00:00 2001 From: Josh Kalderimis Date: Tue, 22 Oct 2024 21:59:03 +1300 Subject: [PATCH 08/29] Remove an old and unused PubSub subscribe option (#5938) --- lib/phoenix/socket.ex | 2 +- lib/phoenix/transports/long_poll.ex | 2 +- lib/phoenix/transports/long_poll_server.ex | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/phoenix/socket.ex b/lib/phoenix/socket.ex index 77286a8db1..ce7920c315 100644 --- a/lib/phoenix/socket.ex +++ b/lib/phoenix/socket.ex @@ -509,7 +509,7 @@ defmodule Phoenix.Socket do defp result({:error, _}), do: :error def __init__({state, %{id: id, endpoint: endpoint} = socket}) do - _ = id && endpoint.subscribe(id, link: true) + _ = id && endpoint.subscribe(id) {:ok, {state, %{socket | transport_pid: self()}}} end diff --git a/lib/phoenix/transports/long_poll.ex b/lib/phoenix/transports/long_poll.ex index de3365cee9..97cb107a1f 100644 --- a/lib/phoenix/transports/long_poll.ex +++ b/lib/phoenix/transports/long_poll.ex @@ -226,7 +226,7 @@ defmodule Phoenix.Transports.LongPoll do defp client_ref(pid) when is_pid(pid), do: self() defp subscribe(endpoint, topic) when is_binary(topic), - do: Phoenix.PubSub.subscribe(endpoint.config(:pubsub_server), topic, link: true) + do: Phoenix.PubSub.subscribe(endpoint.config(:pubsub_server), topic) defp subscribe(_endpoint, pid) when is_pid(pid), do: :ok diff --git a/lib/phoenix/transports/long_poll_server.ex b/lib/phoenix/transports/long_poll_server.ex index 37e8c90bd0..e3a5f23d8d 100644 --- a/lib/phoenix/transports/long_poll_server.ex +++ b/lib/phoenix/transports/long_poll_server.ex @@ -33,7 +33,7 @@ defmodule Phoenix.Transports.LongPoll.Server do client_ref: nil } - :ok = PubSub.subscribe(state.pubsub_server, priv_topic, link: true) + :ok = PubSub.subscribe(state.pubsub_server, priv_topic) schedule_inactive_shutdown(state.window_ms) {:ok, state} From 842f2422bbcb7cce4f11adad4cf66abcb5d2fa63 Mon Sep 17 00:00:00 2001 From: Michael Crumm Date: Tue, 22 Oct 2024 02:01:31 -0700 Subject: [PATCH 09/29] Send download encodes reserved chars (#5928) Closes #5926. --- lib/phoenix/controller.ex | 2 +- test/phoenix/controller/controller_test.exs | 11 +++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/lib/phoenix/controller.ex b/lib/phoenix/controller.ex index 95cb139e5e..74f1d5bd46 100644 --- a/lib/phoenix/controller.ex +++ b/lib/phoenix/controller.ex @@ -1274,7 +1274,7 @@ defmodule Phoenix.Controller do end defp encode_filename(filename, false), do: filename - defp encode_filename(filename, true), do: URI.encode(filename) + defp encode_filename(filename, true), do: URI.encode(filename, &URI.char_unreserved?/1) defp get_disposition_type(:attachment), do: "attachment" defp get_disposition_type(:inline), do: "inline" diff --git a/test/phoenix/controller/controller_test.exs b/test/phoenix/controller/controller_test.exs index 279715fa72..4b2057f09c 100644 --- a/test/phoenix/controller/controller_test.exs +++ b/test/phoenix/controller/controller_test.exs @@ -537,6 +537,17 @@ defmodule Phoenix.Controller.ControllerTest do "world" end + test "sends file for download for filename with unreserved characters" do + conn = send_download(conn(:get, "/"), {:file, @hello_txt}, filename: "hello, world.json") + assert conn.status == 200 + assert get_resp_header(conn, "content-disposition") == + ["attachment; filename=\"hello%2C%20world.json\"; filename*=utf-8''hello%2C%20world.json"] + assert get_resp_header(conn, "content-type") == + ["application/json"] + assert conn.resp_body == + "world" + end + test "sends file supports UTF-8" do conn = send_download(conn(:get, "/"), {:file, @hello_txt}, filename: "测 试.txt") assert conn.status == 200 From 166a2c9410643919a9502c9ca67addeaceda21dc Mon Sep 17 00:00:00 2001 From: Parker Selbert Date: Tue, 22 Oct 2024 04:01:54 -0500 Subject: [PATCH 10/29] Add Slack community link to home template (#5911) The Elixir Slack has a large community and an active #phoenix channel, people should be directed there along with IRC and Discord. --- .../phx_web/controllers/page_html/home.html.heex | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/installer/templates/phx_web/controllers/page_html/home.html.heex b/installer/templates/phx_web/controllers/page_html/home.html.heex index fe4507e466..071a7104d3 100644 --- a/installer/templates/phx_web/controllers/page_html/home.html.heex +++ b/installer/templates/phx_web/controllers/page_html/home.html.heex @@ -201,6 +201,21 @@ Join our Discord server +
Date: Tue, 22 Oct 2024 11:03:23 +0200 Subject: [PATCH 11/29] Update Phoenix.Socket.assign/2,3 docs (#5905) Make wording similar to: * https://hexdocs.pm/phoenix_live_view/Phoenix.Component.html#assign/2 * https://hexdocs.pm/phoenix_live_view/Phoenix.Component.html#assign/3 --- lib/phoenix/socket.ex | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/lib/phoenix/socket.ex b/lib/phoenix/socket.ex index ce7920c315..7963ef31e8 100644 --- a/lib/phoenix/socket.ex +++ b/lib/phoenix/socket.ex @@ -332,24 +332,32 @@ defmodule Phoenix.Socket do ## USER API @doc """ - Adds key-value pairs to socket assigns. + Adds a `key`/`value` pair to `socket` assigns. - A single key-value pair may be passed, a keyword list or map - of assigns may be provided to be merged into existing socket - assigns. + See also `assign/2` to add multiple key/value pairs. ## Examples iex> assign(socket, :name, "Elixir") - iex> assign(socket, name: "Elixir", logo: "💧") """ def assign(%Socket{} = socket, key, value) do assign(socket, [{key, value}]) end - def assign(%Socket{} = socket, attrs) - when is_map(attrs) or is_list(attrs) do - %{socket | assigns: Map.merge(socket.assigns, Map.new(attrs))} + @doc """ + Adds key/value pairs to socket assigns. + + A keyword list or a map of assigns must be given as argument to be merged into existing assigns. + + ## Examples + + iex> assign(socket, name: "Elixir", logo: "💧") + iex> assign(socket, %{name: "Elixir"}) + + """ + def assign(%Socket{} = socket, keyword_or_map) + when is_map(keyword_or_map) or is_list(keyword_or_map) do + %{socket | assigns: Map.merge(socket.assigns, Map.new(keyword_or_map))} end @doc """ From 8a0c5c7f0317f132c5f3757acdde58123051540f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Tue, 22 Oct 2024 14:20:56 +0200 Subject: [PATCH 12/29] More info on contexts tips --- guides/contexts.md | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/guides/contexts.md b/guides/contexts.md index ec34394522..6de85c08c0 100644 --- a/guides/contexts.md +++ b/guides/contexts.md @@ -20,8 +20,6 @@ By giving modules that expose and group related functionality the name **context In Phoenix, contexts often encapsulate data access and data validation. They often talk to a database or APIs. Overall, think of them as boundaries to decouple and isolate parts of your application. Let's use these ideas to build out our web application. Our goal is to build an ecommerce system where we can showcase products, allow users to add products to their cart, and complete their orders. -> How to read this guide: Using the context generators is a great way for beginners and intermediate Elixir programmers alike to get up and running quickly while thoughtfully writing their applications. This guide focuses on those readers. - ### Adding a Catalog Context An ecommerce platform has wide-reaching coupling across a codebase so it's important to think about writing well-defined modules. With that in mind, our goal is to build a product catalog API that handles creating, updating, and deleting the products available in our system. We'll start off with the basic features of showcasing our products, and we will add shopping cart features later. We'll see how starting with a solid foundation with isolated boundaries allows us to grow our application naturally as we add functionality. @@ -30,9 +28,6 @@ Phoenix includes the `mix phx.gen.html`, `mix phx.gen.json`, `mix phx.gen.live`, In order to run the context generators, we need to come up with a module name that groups the related functionality that we're building. In the [Ecto guide](ecto.html), we saw how we can use Changesets and Repos to validate and persist user schemas, but we didn't integrate this with our application at large. In fact, we didn't think about where a "user" in our application should live at all. Let's take a step back and think about the different parts of our system. We know that we'll have products to showcase on pages for sale, along with descriptions, pricing, etc. Along with selling products, we know we'll need to support carting, order checkout, and so on. While the products being purchased are related to the cart and checkout processes, showcasing a product and managing the *exhibition* of our products is distinctly different than tracking what a user has placed in their cart or how an order is placed. A `Catalog` context is a natural place for the management of our product details and the showcasing of those products we have for sale. -> #### Naming things is hard {: .tip} -> If you're stuck when trying to come up with a context name when the grouped functionality in your system isn't yet clear, you can simply use the plural form of the resource you're creating. For example, a `Products` context for managing products. As you grow your application and the parts of your system become clear, you can simply rename the context to a more refined one. - To jump-start our catalog context, we'll use `mix phx.gen.html` which creates a context module that wraps up Ecto access for creating, updating, and deleting products, along with web files like controllers and templates for the web interface into our context. Run the following command at your project root: ```console @@ -60,7 +55,6 @@ Add the resource to your browser scope in lib/hello_web/router.ex: resources "/products", ProductController - Remember to update your repository by running migrations: $ mix ecto.migrate @@ -123,6 +117,14 @@ Views: 0 If we follow the "Back" link, we get a list of all products, which should contain the one we just created. Likewise, we can update this record or delete it. Now that we've seen how it works in the browser, it's time to take a look at the generated code. +> #### Naming things is hard {: .tip} +> +> When starting a web application, it may be hard to draw lines or name its different contexts, especially when the domain you are working with is not as well established as e-commerce. +> +> If you're stuck when defining or naming a context, you can simply create a new context using the plural form of the resource you're creating. For example, a `Products` context for managing products. You will find that, even in such cases, you will organically discover other resources that belong to the `Products` context, such as categories or image galleries. +> +> As your applications grows and the different parts of your system become clear, you can simply rename the context or move resources around. The beauty of Elixir modules is that they are stateless, so moving them around should be simply a matter of renaming the module names (and renaming the files for conssitency). + ## Starting with generators That little `mix phx.gen.html` command packed a surprising punch. We got a lot of functionality out-of-the-box for creating, updating, and deleting products in our catalog. This is far from a full-featured app, but remember, generators are first and foremost learning tools and a starting point for you to begin building real features. Code generation can't solve all your problems, but it will teach you the ins and outs of Phoenix and nudge you towards the proper mindset when designing your application. From 36e9b3f3df4bc6f7b99661188e6eb72bc87374c6 Mon Sep 17 00:00:00 2001 From: Steffen Deusch Date: Tue, 22 Oct 2024 15:58:41 +0200 Subject: [PATCH 13/29] require Elixir 1.15+ in new apps (#5957) Closes #5276. --- .github/workflows/ci.yml | 16 ++++++------ guides/deployment/heroku.md | 4 +-- guides/deployment/releases.md | 8 +++--- guides/introduction/installation.md | 6 ++--- installer/lib/mix/tasks/phx.new.ex | 25 +++---------------- installer/mix.exs | 2 +- .../templates/phx_single/config/config.exs | 2 +- installer/templates/phx_single/mix.exs | 2 +- .../phx_umbrella/apps/app_name/mix.exs | 2 +- .../phx_umbrella/apps/app_name_web/mix.exs | 2 +- .../templates/phx_umbrella/config/dev.exs | 2 +- .../phx_umbrella/config/extra_config.exs | 2 +- integration_test/docker.sh | 6 ++--- integration_test/mix.exs | 2 +- test/test_helper.exs | 2 +- 15 files changed, 33 insertions(+), 50 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index dc08e02056..c11117b32a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -22,8 +22,8 @@ jobs: - elixir: 1.14.5 otp: 25.3.2.9 - - elixir: 1.17.2 - otp: 27.0 + - elixir: 1.17.3 + otp: 27.1 lint: true installer: true @@ -111,13 +111,13 @@ jobs: matrix: include: # look for correct alpine image here: https://hub.docker.com/r/hexpm/elixir/tags - - elixir: 1.14.5 - otp: 25.3.2.12 - suffix: "alpine-3.19.1" + - elixir: 1.15.8 + otp: 24.3.4.17 + suffix: "alpine-3.20.3" - - elixir: 1.16.2 - otp: 26.2.5 - suffix: "alpine-3.19.1" + - elixir: 1.17.3 + otp: 27.1.2 + suffix: "alpine-3.20.3" container: image: hexpm/elixir:${{ matrix.elixir }}-erlang-${{ matrix.otp }}-${{ matrix.suffix }} diff --git a/guides/deployment/heroku.md b/guides/deployment/heroku.md index b2560b0880..9ba3838f22 100644 --- a/guides/deployment/heroku.md +++ b/guides/deployment/heroku.md @@ -97,11 +97,11 @@ The buildpack uses a predefined Elixir and Erlang version, but to avoid surprise ```console # Elixir version -elixir_version=1.14.0 +elixir_version=1.15.0 # Erlang version # https://github.com/HashNuke/heroku-buildpack-elixir-otp-builds/blob/master/otp-versions -erlang_version=24.3 +erlang_version=25.3 # Invoke assets.deploy defined in your mix.exs to deploy assets with esbuild # Note we nuke the esbuild executable from the image diff --git a/guides/deployment/releases.md b/guides/deployment/releases.md index c89ef2add7..7e4d1668fa 100644 --- a/guides/deployment/releases.md +++ b/guides/deployment/releases.md @@ -169,11 +169,11 @@ If you call `mix phx.gen.release --docker` you'll see a new file with these cont # - https://hub.docker.com/r/hexpm/elixir/tags - for the build image # - https://hub.docker.com/_/debian?tab=tags&page=1&name=bullseye-20230612-slim - for the release image # - https://pkgs.org/ - resource for finding needed packages -# - Ex: hexpm/elixir:1.14.5-erlang-25.3.2.4-debian-bullseye-20230612-slim +# - Ex: hexpm/elixir:1.15.8-erlang-25.3.2.15-debian-bookworm-20241016-slim # -ARG ELIXIR_VERSION=1.14.5 -ARG OTP_VERSION=25.3.2.4 -ARG DEBIAN_VERSION=bullseye-20230612-slim +ARG ELIXIR_VERSION=1.15.8 +ARG OTP_VERSION=25.3.2.15 +ARG DEBIAN_VERSION=bookworm-20241016-slim ARG BUILDER_IMAGE="hexpm/elixir:${ELIXIR_VERSION}-erlang-${OTP_VERSION}-debian-${DEBIAN_VERSION}" ARG RUNNER_IMAGE="debian:${DEBIAN_VERSION}" diff --git a/guides/introduction/installation.md b/guides/introduction/installation.md index e7e96a9f91..11b2c3969e 100644 --- a/guides/introduction/installation.md +++ b/guides/introduction/installation.md @@ -8,7 +8,7 @@ In order to build a Phoenix application, we will need a few dependencies install Please take a look at this list and make sure to install anything necessary for your system. Having dependencies installed in advance can prevent frustrating problems later on. -## Elixir 1.14 or later +## Elixir 1.15 or later Phoenix is written in Elixir, and our application code will also be written in Elixir. We won't get far in a Phoenix app without it! The Elixir site maintains a great [Installation Page](https://elixir-lang.org/install.html) to help. @@ -28,13 +28,13 @@ When we install Elixir using instructions from the Elixir [Installation Page](ht ## Phoenix -To check that we are on Elixir 1.14 and Erlang 24 or later, run: +To check that we are on Elixir 1.15 and Erlang 24 or later, run: ```console elixir -v Erlang/OTP 24 [erts-12.0] [source] [64-bit] [smp:8:8] [async-threads:10] [hipe] [kernel-poll:false] [dtrace] -Elixir 1.14.0 +Elixir 1.15.0 ``` Once we have Elixir and Erlang, we are ready to install the Phoenix application generator: diff --git a/installer/lib/mix/tasks/phx.new.ex b/installer/lib/mix/tasks/phx.new.ex index 01882b9d21..dc4807b652 100644 --- a/installer/lib/mix/tasks/phx.new.ex +++ b/installer/lib/mix/tasks/phx.new.ex @@ -223,9 +223,7 @@ defmodule Mix.Tasks.Phx.New do Task.async(fn -> cmd(project, cmd, log: false, cd: project.web_path) end) end) - if rebar_available?() do - cmd(project, "mix deps.compile") - end + cmd(project, "mix deps.compile") Task.await_many(tasks, :infinity) end @@ -254,28 +252,13 @@ defmodule Mix.Tasks.Phx.New do defp maybe_cd(path, func), do: path && File.cd!(path, func) defp install_mix(project, install?) do - if install? && hex_available?() do + if install? do cmd(project, "mix deps.get") else ["$ mix deps.get"] end end - # TODO: Elixir v1.15 automatically installs Hex/Rebar if missing, so we can simplify this. - defp hex_available? do - Code.ensure_loaded?(Hex) - end - - if Version.match?(System.version(), "~> 1.18") do - defp rebar_available? do - true - end - else - defp rebar_available? do - Mix.Rebar.rebar_cmd(:rebar3) - end - end - defp print_missing_steps(steps) do Mix.shell().info(""" @@ -395,9 +378,9 @@ defmodule Mix.Tasks.Phx.New do end defp elixir_version_check! do - unless Version.match?(System.version(), "~> 1.14") do + unless Version.match?(System.version(), "~> 1.15") do Mix.raise( - "Phoenix v#{@version} requires at least Elixir v1.14\n " <> + "Phoenix v#{@version} requires at least Elixir v1.15\n " <> "You have #{System.version()}. Please update accordingly" ) end diff --git a/installer/mix.exs b/installer/mix.exs index 98ca0634c6..518f3a2432 100644 --- a/installer/mix.exs +++ b/installer/mix.exs @@ -17,7 +17,7 @@ defmodule Phx.New.MixProject do # 4. test/test_helper.exs at the root # 5. installer/lib/mix/tasks/phx.new.ex # - @elixir_requirement "~> 1.14" + @elixir_requirement "~> 1.15" def project do [ diff --git a/installer/templates/phx_single/config/config.exs b/installer/templates/phx_single/config/config.exs index 661291d54a..97c1bbf53b 100644 --- a/installer/templates/phx_single/config/config.exs +++ b/installer/templates/phx_single/config/config.exs @@ -56,7 +56,7 @@ config :tailwind, ]<% end %> # Configures Elixir's Logger -config :logger, :console, +config :logger, :default_formatter, format: "$time $metadata[$level] $message\n", metadata: [:request_id] diff --git a/installer/templates/phx_single/mix.exs b/installer/templates/phx_single/mix.exs index 8282523114..ed631a2521 100644 --- a/installer/templates/phx_single/mix.exs +++ b/installer/templates/phx_single/mix.exs @@ -9,7 +9,7 @@ defmodule <%= @app_module %>.MixProject do config_path: "../../config/config.exs", deps_path: "../../deps", lockfile: "../../mix.lock",<% end %> - elixir: "~> 1.14", + elixir: "~> 1.15", elixirc_paths: elixirc_paths(Mix.env()), start_permanent: Mix.env() == :prod, aliases: aliases(), diff --git a/installer/templates/phx_umbrella/apps/app_name/mix.exs b/installer/templates/phx_umbrella/apps/app_name/mix.exs index b1510c8c23..5ba7b9323e 100644 --- a/installer/templates/phx_umbrella/apps/app_name/mix.exs +++ b/installer/templates/phx_umbrella/apps/app_name/mix.exs @@ -9,7 +9,7 @@ defmodule <%= @app_module %>.MixProject do config_path: "../../config/config.exs", deps_path: "../../deps", lockfile: "../../mix.lock", - elixir: "~> 1.14", + elixir: "~> 1.15", elixirc_paths: elixirc_paths(Mix.env()), start_permanent: Mix.env() == :prod, aliases: aliases(), diff --git a/installer/templates/phx_umbrella/apps/app_name_web/mix.exs b/installer/templates/phx_umbrella/apps/app_name_web/mix.exs index 620aa84ec4..f0bd3d0cd2 100644 --- a/installer/templates/phx_umbrella/apps/app_name_web/mix.exs +++ b/installer/templates/phx_umbrella/apps/app_name_web/mix.exs @@ -9,7 +9,7 @@ defmodule <%= @web_namespace %>.MixProject do config_path: "../../config/config.exs", deps_path: "../../deps", lockfile: "../../mix.lock", - elixir: "~> 1.14", + elixir: "~> 1.15", elixirc_paths: elixirc_paths(Mix.env()), start_permanent: Mix.env() == :prod, aliases: aliases(), diff --git a/installer/templates/phx_umbrella/config/dev.exs b/installer/templates/phx_umbrella/config/dev.exs index c48d060474..a049be27d4 100644 --- a/installer/templates/phx_umbrella/config/dev.exs +++ b/installer/templates/phx_umbrella/config/dev.exs @@ -1,7 +1,7 @@ import Config # Do not include metadata nor timestamps in development logs -config :logger, :console, format: "[$level] $message\n" +config :logger, :default_formatter, format: "[$level] $message\n" # Initialize plugs at runtime for faster development compilation config :phoenix, :plug_init_mode, :runtime<%= if @html do %> diff --git a/installer/templates/phx_umbrella/config/extra_config.exs b/installer/templates/phx_umbrella/config/extra_config.exs index 68d1c6605a..a34ed28f28 100644 --- a/installer/templates/phx_umbrella/config/extra_config.exs +++ b/installer/templates/phx_umbrella/config/extra_config.exs @@ -1,7 +1,7 @@ import Config # Configures Elixir's Logger -config :logger, :console, +config :logger, :default_formatter, format: "$time $metadata[$level] $message\n", metadata: [:request_id] diff --git a/integration_test/docker.sh b/integration_test/docker.sh index ab4441ca7c..c2a2965141 100755 --- a/integration_test/docker.sh +++ b/integration_test/docker.sh @@ -2,9 +2,9 @@ # adapt with versions from .github/versions/ci.yml if necessary; # you can also override these with environment variables -ELIXIR="${ELIXIR:-1.16.2}" -ERLANG="${ERLANG:-26.2.5}" -SUFFIX="${SUFFIX:-alpine-3.19.1}" +ELIXIR="${ELIXIR:-1.17.3}" +ERLANG="${ERLANG:-27.1.2}" +SUFFIX="${SUFFIX:-alpine-3.20.3}" # Get the directory of the script SCRIPT_DIR=$(dirname "$(readlink -f "$0")") diff --git a/integration_test/mix.exs b/integration_test/mix.exs index 4c45c7fcfc..731c09dde6 100644 --- a/integration_test/mix.exs +++ b/integration_test/mix.exs @@ -10,7 +10,7 @@ defmodule Phoenix.Integration.MixProject do [ app: :phoenix_integration, version: "0.1.0", - elixir: "~> 1.14", + elixir: "~> 1.15", elixirc_paths: elixirc_paths(Mix.env()), start_permanent: Mix.env() == :prod, deps: deps() diff --git a/test/test_helper.exs b/test/test_helper.exs index 3202405518..ae30db4b73 100644 --- a/test/test_helper.exs +++ b/test/test_helper.exs @@ -26,7 +26,7 @@ assert_timeout = String.to_integer( ) excludes = - if Version.match?(System.version(), "~> 1.14") do + if Version.match?(System.version(), "~> 1.15") do [] else [:mix_phx_new] From 94b0fc2f250a28a489b784c2577d5e0fd7c2f5e7 Mon Sep 17 00:00:00 2001 From: Steffen Deusch Date: Wed, 23 Oct 2024 09:28:42 +0200 Subject: [PATCH 14/29] Adjust logger config in dev.exs config template Relates to: #5957 Thank you, @champeric! --- installer/templates/phx_single/config/dev.exs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/installer/templates/phx_single/config/dev.exs b/installer/templates/phx_single/config/dev.exs index 73deeaf344..4dc897f64d 100644 --- a/installer/templates/phx_single/config/dev.exs +++ b/installer/templates/phx_single/config/dev.exs @@ -56,7 +56,7 @@ config :<%= @app_name %>, <%= @endpoint_module %>, config :<%= @app_name %>, dev_routes: true # Do not include metadata nor timestamps in development logs -config :logger, :console, format: "[$level] $message\n" +config :logger, :default_formatter, format: "[$level] $message\n" # Set a higher stacktrace during development. Avoid configuring such # in production as building large stacktraces may be expensive. From ed1331ea71bf458cc7909bca023564905a44fa90 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=2EYasoob=20Ullah=20Khalid=20=E2=98=BA?= Date: Wed, 23 Oct 2024 03:47:49 -0700 Subject: [PATCH 15/29] Update contexts.md (#5958) --- guides/contexts.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/guides/contexts.md b/guides/contexts.md index 6de85c08c0..333eaa209f 100644 --- a/guides/contexts.md +++ b/guides/contexts.md @@ -123,7 +123,7 @@ If we follow the "Back" link, we get a list of all products, which should contai > > If you're stuck when defining or naming a context, you can simply create a new context using the plural form of the resource you're creating. For example, a `Products` context for managing products. You will find that, even in such cases, you will organically discover other resources that belong to the `Products` context, such as categories or image galleries. > -> As your applications grows and the different parts of your system become clear, you can simply rename the context or move resources around. The beauty of Elixir modules is that they are stateless, so moving them around should be simply a matter of renaming the module names (and renaming the files for conssitency). +> As your applications grows and the different parts of your system become clear, you can simply rename the context or move resources around. The beauty of Elixir modules is that they are stateless, so moving them around should be simply a matter of renaming the module names (and renaming the files for consistency). ## Starting with generators From 7586cbee9e37afbe0b3cdbd560b9e6aa60d32bf6 Mon Sep 17 00:00:00 2001 From: jrko <31940806+jrko@users.noreply.github.com> Date: Fri, 25 Oct 2024 13:00:09 -0700 Subject: [PATCH 16/29] Mention migration step in phx.gen.auth docs (#5937) --- guides/authentication/mix_phx_gen_auth.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/guides/authentication/mix_phx_gen_auth.md b/guides/authentication/mix_phx_gen_auth.md index 952cca8d16..05b7cd533a 100644 --- a/guides/authentication/mix_phx_gen_auth.md +++ b/guides/authentication/mix_phx_gen_auth.md @@ -1,12 +1,14 @@ # mix phx.gen.auth +> This guide assumes that you have gone through the [introductory guides](overview.html) and have a Phoenix application [up and running](up_and_running.html). + The `mix phx.gen.auth` command generates a flexible, pre-built authentication system into your Phoenix app. This generator allows you to quickly move past the task of adding authentication to your codebase and stay focused on the real-world problem your application is trying to solve. ## Getting started > Before running this command, consider committing your work as it generates multiple files. -Let's start by running the following command from the root of our app (or `apps/my_app_web` in an umbrella app): +Let's start by running the following command from the root of our app: ```console $ mix phx.gen.auth Accounts User users @@ -28,10 +30,10 @@ Since this generator installed additional dependencies in `mix.exs`, let's fetch $ mix deps.get ``` -Now we need to verify the database connection details for the development and test environments in `config/` so the migrator and tests can run properly. Then run the following to create the database: +Now run the pending repository migrations: ```console -$ mix ecto.setup +$ mix ecto.migrate ``` Let's run the tests to make sure our new authentication system works as expected. @@ -40,7 +42,7 @@ Let's run the tests to make sure our new authentication system works as expected $ mix test ``` -And finally, let's start our Phoenix server and try it out. +And finally, let's start our Phoenix server and try it out (note the new `Register` and `Log in` links at the top right of the default page). ```console $ mix phx.server From 18287deff82ea4b5effd81dd599d520464504311 Mon Sep 17 00:00:00 2001 From: Tom Dudley Date: Tue, 29 Oct 2024 18:31:44 +0000 Subject: [PATCH 17/29] Fix some grammar in the JSON and APIs guide (#5962) --- guides/json_and_apis.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/guides/json_and_apis.md b/guides/json_and_apis.md index c6638dfdf8..5994229dc5 100644 --- a/guides/json_and_apis.md +++ b/guides/json_and_apis.md @@ -173,7 +173,7 @@ end This view is very simple. The `index` function receives all URLs, and converts them into a list of maps. Those maps are placed inside the data key at the root, exactly as we saw when interfacing with our application from `cURL`. In other words, our JSON view converts our complex data into simple Elixir data-structures. Once our view layer returns, Phoenix uses the `Jason` library to encode JSON and send the response to the client. -If you explore the remaining the controller, you will learn the `show` action is similar to the `index` one. For `create`, `update`, and `delete` actions, Phoenix uses one other important feature, called "Action fallback". +If you explore the remaining controller, you will learn the `show` action is similar to the `index` one. For `create`, `update`, and `delete` actions, Phoenix uses one other important feature, called "Action fallback". ## Action fallback From a14841b09aa90c7e2d3fd7002db514029f45895c Mon Sep 17 00:00:00 2001 From: Chris McCord Date: Wed, 30 Oct 2024 10:19:05 -0400 Subject: [PATCH 18/29] Do no generate unused phx-disable-with as it has no effect --- priv/templates/phx.gen.auth/login_live.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/priv/templates/phx.gen.auth/login_live.ex b/priv/templates/phx.gen.auth/login_live.ex index dd9003c97b..6a3c28389a 100644 --- a/priv/templates/phx.gen.auth/login_live.ex +++ b/priv/templates/phx.gen.auth/login_live.ex @@ -26,7 +26,7 @@ defmodule <%= inspect context.web_module %>.<%= inspect Module.concat(schema.web <:actions> - <.button phx-disable-with="Logging in..." class="w-full"> + <.button class="w-full"> Log in From c486cdfb7ca73e42e1408d346201554fccbbf4fb Mon Sep 17 00:00:00 2001 From: Tangui <29804907+tanguilp@users.noreply.github.com> Date: Thu, 31 Oct 2024 10:36:59 +0300 Subject: [PATCH 19/29] Add `check_csrf` option to socket transport options (#5952) One of check_origin or check_csrf must be enabled. --- lib/phoenix/endpoint.ex | 7 ++++ lib/phoenix/endpoint/supervisor.ex | 24 +++++++++++--- lib/phoenix/socket/transport.ex | 27 +++++++++------ lib/phoenix/transports/long_poll.ex | 6 +++- lib/phoenix/transports/websocket.ex | 6 +++- test/phoenix/endpoint/supervisor_test.exs | 40 +++++++++++++++++++++++ test/phoenix/socket/transport_test.exs | 28 +++++++++++++++- 7 files changed, 121 insertions(+), 17 deletions(-) diff --git a/lib/phoenix/endpoint.ex b/lib/phoenix/endpoint.ex index 465420ac29..a923be9a84 100644 --- a/lib/phoenix/endpoint.ex +++ b/lib/phoenix/endpoint.ex @@ -859,6 +859,13 @@ defmodule Phoenix.Endpoint do The MFA is invoked with the request `%URI{}` as the first argument, followed by arguments in the MFA list, and must return a boolean. + * `:check_csrf` - if the transport should perform CSRF check. To avoid + "Cross-Site WebSocket Hijacking", you must have at least one of + `check_origin` and `check_csrf` enabled. If you set both to `false`, + Phoenix will raise, but it is still possible to disable both by passing + a custom MFA to `check_origin`. In such cases, it is your responsibility + to ensure at least one of them is enabled. Defaults to `true` + * `:code_reloader` - enable or disable the code reloader. Defaults to your endpoint configuration diff --git a/lib/phoenix/endpoint/supervisor.ex b/lib/phoenix/endpoint/supervisor.ex index 5ee904dcb7..597f175d90 100644 --- a/lib/phoenix/endpoint/supervisor.ex +++ b/lib/phoenix/endpoint/supervisor.ex @@ -84,11 +84,10 @@ defmodule Phoenix.Endpoint.Supervisor do children = config_children(mod, secret_conf, default_conf) ++ pubsub_children(mod, conf) ++ - socket_children(mod, :child_spec) ++ + socket_children(mod, conf, :child_spec) ++ server_children(mod, conf, server?) ++ - socket_children(mod, :drainer_spec) ++ + socket_children(mod, conf, :drainer_spec) ++ watcher_children(mod, conf, server?) - Supervisor.init(children, strategy: :one_for_one) end @@ -118,8 +117,9 @@ defmodule Phoenix.Endpoint.Supervisor do end end - defp socket_children(endpoint, fun) do + defp socket_children(endpoint, conf, fun) do for {_, socket, opts} <- Enum.uniq_by(endpoint.__sockets__(), &elem(&1, 1)), + _ = check_origin_or_csrf_checked!(conf, opts), spec = apply_or_ignore(socket, fun, [[endpoint: endpoint] ++ opts]), spec != :ignore do spec @@ -135,6 +135,22 @@ defmodule Phoenix.Endpoint.Supervisor do end end + defp check_origin_or_csrf_checked!(endpoint_conf, socket_opts) do + check_origin = endpoint_conf[:check_origin] + + for {transport, transport_opts} <- socket_opts, is_list(transport_opts) do + check_origin = Keyword.get(transport_opts, :check_origin, check_origin) + + check_csrf = transport_opts[:check_csrf] + + if check_origin == false and check_csrf == false do + raise ArgumentError, + "one of :check_origin and :check_csrf must be set to non-false value for " <> + "transport #{inspect(transport)}" + end + end + end + defp config_children(mod, conf, default_conf) do args = {mod, conf, default_conf, name: Module.concat(mod, "Config")} [{Phoenix.Config, args}] diff --git a/lib/phoenix/socket/transport.ex b/lib/phoenix/socket/transport.ex index a1c5fd5467..c9ab5dd956 100644 --- a/lib/phoenix/socket/transport.ex +++ b/lib/phoenix/socket/transport.ex @@ -458,8 +458,9 @@ defmodule Phoenix.Socket.Transport do * `:user_agent` - the value of the "user-agent" request header + The CSRF check can be disabled by setting the `:check_csrf` option to `false`. """ - def connect_info(conn, endpoint, keys) do + def connect_info(conn, endpoint, keys, opts \\ []) do for key <- keys, into: %{} do case key do :peer_data -> @@ -478,7 +479,7 @@ defmodule Phoenix.Socket.Transport do {:user_agent, fetch_user_agent(conn)} {:session, session} -> - {:session, connect_session(conn, endpoint, session)} + {:session, connect_session(conn, endpoint, session, opts)} {key, val} -> {key, val} @@ -486,26 +487,24 @@ defmodule Phoenix.Socket.Transport do end end - defp connect_session(conn, endpoint, {key, store, {csrf_token_key, init}}) do + defp connect_session(conn, endpoint, {key, store, {csrf_token_key, init}}, opts) do conn = Plug.Conn.fetch_cookies(conn) + check_csrf = Keyword.get(opts, :check_csrf, true) - with csrf_token when is_binary(csrf_token) <- conn.params["_csrf_token"], - cookie when is_binary(cookie) <- conn.cookies[key], + with cookie when is_binary(cookie) <- conn.cookies[key], conn = put_in(conn.secret_key_base, endpoint.config(:secret_key_base)), {_, session} <- store.get(conn, cookie, init), - csrf_state when is_binary(csrf_state) <- - Plug.CSRFProtection.dump_state_from_session(session[csrf_token_key]), - true <- Plug.CSRFProtection.valid_state_and_csrf_token?(csrf_state, csrf_token) do + true <- not check_csrf or csrf_token_valid?(conn, session, csrf_token_key) do session else _ -> nil end end - defp connect_session(conn, endpoint, {:mfa, {module, function, args}}) do + defp connect_session(conn, endpoint, {:mfa, {module, function, args}}, opts) do case apply(module, function, args) do session_config when is_list(session_config) -> - connect_session(conn, endpoint, init_session(session_config)) + connect_session(conn, endpoint, init_session(session_config), opts) other -> raise ArgumentError, @@ -542,6 +541,14 @@ defmodule Phoenix.Socket.Transport do end end + defp csrf_token_valid?(conn, session, csrf_token_key) do + with csrf_token when is_binary(csrf_token) <- conn.params["_csrf_token"], + csrf_state when is_binary(csrf_state) <- + Plug.CSRFProtection.dump_state_from_session(session[csrf_token_key]) do + Plug.CSRFProtection.valid_state_and_csrf_token?(csrf_state, csrf_token) + end + end + defp check_origin_config(handler, endpoint, opts) do Phoenix.Config.cache(endpoint, {:check_origin, handler}, fn _ -> check_origin = diff --git a/lib/phoenix/transports/long_poll.ex b/lib/phoenix/transports/long_poll.ex index 97cb107a1f..3a9915bb4e 100644 --- a/lib/phoenix/transports/long_poll.ex +++ b/lib/phoenix/transports/long_poll.ex @@ -4,6 +4,7 @@ defmodule Phoenix.Transports.LongPoll do # 10MB @max_base64_size 10_000_000 + @connect_info_opts [:check_csrf] import Plug.Conn alias Phoenix.Socket.{V1, V2, Transport} @@ -136,7 +137,10 @@ defmodule Phoenix.Transports.LongPoll do (System.system_time(:millisecond) |> Integer.to_string()) keys = Keyword.get(opts, :connect_info, []) - connect_info = Transport.connect_info(conn, endpoint, keys) + + connect_info = + Transport.connect_info(conn, endpoint, keys, Keyword.take(opts, @connect_info_opts)) + arg = {endpoint, handler, opts, conn.params, priv_topic, connect_info} spec = {Phoenix.Transports.LongPoll.Server, arg} diff --git a/lib/phoenix/transports/websocket.ex b/lib/phoenix/transports/websocket.ex index f04cc4350d..dfc7bd2508 100644 --- a/lib/phoenix/transports/websocket.ex +++ b/lib/phoenix/transports/websocket.ex @@ -15,6 +15,8 @@ defmodule Phoenix.Transports.WebSocket do # @behaviour Plug + @connect_info_opts [:check_csrf] + import Plug.Conn alias Phoenix.Socket.{V1, V2, Transport} @@ -45,7 +47,9 @@ defmodule Phoenix.Transports.WebSocket do %{params: params} = conn -> keys = Keyword.get(opts, :connect_info, []) - connect_info = Transport.connect_info(conn, endpoint, keys) + + connect_info = + Transport.connect_info(conn, endpoint, keys, Keyword.take(opts, @connect_info_opts)) config = %{ endpoint: endpoint, diff --git a/test/phoenix/endpoint/supervisor_test.exs b/test/phoenix/endpoint/supervisor_test.exs index 235c5c0be4..3b2b958db6 100644 --- a/test/phoenix/endpoint/supervisor_test.exs +++ b/test/phoenix/endpoint/supervisor_test.exs @@ -180,4 +180,44 @@ defmodule Phoenix.Endpoint.SupervisorTest do end) end end + + describe "origin & CSRF checks config" do + defmodule TestSocket do + @behaviour Phoenix.Socket.Transport + def child_spec(_), do: :ignore + def connect(_), do: {:ok, []} + def init(state), do: {:ok, state} + def handle_in(_, state), do: {:ok, state} + def handle_info(_, state), do: {:ok, state} + def terminate(_, _), do: :ok + end + + defmodule SocketEndpoint do + use Phoenix.Endpoint, otp_app: :phoenix + + socket "/ws", TestSocket, websocket: [check_csrf: false, check_origin: false] + end + + Application.put_env(:phoenix, SocketEndpoint, []) + + test "fails when CSRF and origin checks both disabled in transport" do + assert_raise ArgumentError, ~r/one of :check_origin and :check_csrf must be set/, fn -> + Supervisor.init({:phoenix, SocketEndpoint, []}) + end + end + + defmodule SocketEndpointOriginCheckDisabled do + use Phoenix.Endpoint, otp_app: :phoenix + + socket "/ws", TestSocket, websocket: [check_csrf: false] + end + + Application.put_env(:phoenix, SocketEndpointOriginCheckDisabled, check_origin: false) + + test "fails when origin is disabled in endpoint config and CSRF disabled in transport" do + assert_raise ArgumentError, ~r/one of :check_origin and :check_csrf must be set/, fn -> + Supervisor.init({:phoenix, SocketEndpointOriginCheckDisabled, []}) + end + end + end end diff --git a/test/phoenix/socket/transport_test.exs b/test/phoenix/socket/transport_test.exs index 2a3e3df9f7..05700342dd 100644 --- a/test/phoenix/socket/transport_test.exs +++ b/test/phoenix/socket/transport_test.exs @@ -276,7 +276,7 @@ defmodule Phoenix.Socket.TransportTest do end end - describe "connect_info/3" do + describe "connect_info/4" do defp load_connect_info(connect_info) do [connect_info: connect_info] = Transport.load_config(connect_info: connect_info) connect_info @@ -330,5 +330,31 @@ defmodule Phoenix.Socket.TransportTest do |> Transport.connect_info(Endpoint, connect_info) end + test "loads the session when CSRF is disabled despite CSRF token not being provided" do + conn = conn(:get, "https://foo.com/") |> Endpoint.call([]) + session_cookie = conn.cookies["_hello_key"] + + connect_info = load_connect_info(session: {Endpoint, :session_config, []}) + + assert %{session: %{"from_session" => "123"}} = + conn(:get, "https://foo.com/") + |> put_req_cookie("_hello_key", session_cookie) + |> fetch_query_params() + |> Transport.connect_info(Endpoint, connect_info, check_csrf: false) + end + + test "doesn't load session when an invalid CSRF token is provided" do + conn = conn(:get, "https://foo.com/") |> Endpoint.call([]) + invalid_csrf_token = "some invalid CSRF token" + session_cookie = conn.cookies["_hello_key"] + + connect_info = load_connect_info(session: {Endpoint, :session_config, []}) + + assert %{session: nil} = + conn(:get, "https://foo.com/", _csrf_token: invalid_csrf_token) + |> put_req_cookie("_hello_key", session_cookie) + |> fetch_query_params() + |> Transport.connect_info(Endpoint, connect_info) + end end end From 57b352b896ca1d8fcbca62ec7e3d3ef59904a765 Mon Sep 17 00:00:00 2001 From: Leif Metcalf Date: Thu, 7 Nov 2024 02:46:01 +1100 Subject: [PATCH 20/29] Change select input border colour (#5966) --- installer/templates/phx_web/components/core_components.ex | 2 +- priv/templates/phx.gen.live/core_components.ex | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/installer/templates/phx_web/components/core_components.ex b/installer/templates/phx_web/components/core_components.ex index 83ac2a9f32..c93f726121 100644 --- a/installer/templates/phx_web/components/core_components.ex +++ b/installer/templates/phx_web/components/core_components.ex @@ -265,7 +265,7 @@ defmodule <%= @web_namespace %>.CoreComponents do From a4e9f1be1fc42e53b1b53d80a335723b112066bf Mon Sep 17 00:00:00 2001 From: Ostap Brehin Date: Wed, 6 Nov 2024 19:47:58 +0000 Subject: [PATCH 21/29] Update error component spacing (#5847) --- installer/templates/phx_web/components/core_components.ex | 2 +- priv/templates/phx.gen.live/core_components.ex | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/installer/templates/phx_web/components/core_components.ex b/installer/templates/phx_web/components/core_components.ex index c93f726121..1aa9858b70 100644 --- a/installer/templates/phx_web/components/core_components.ex +++ b/installer/templates/phx_web/components/core_components.ex @@ -339,7 +339,7 @@ defmodule <%= @web_namespace %>.CoreComponents do def error(assigns) do ~H""" -

+

<.icon name="hero-exclamation-circle-mini" class="mt-0.5 h-5 w-5 flex-none" /> <%%= render_slot(@inner_block) %>

diff --git a/priv/templates/phx.gen.live/core_components.ex b/priv/templates/phx.gen.live/core_components.ex index c93f726121..1aa9858b70 100644 --- a/priv/templates/phx.gen.live/core_components.ex +++ b/priv/templates/phx.gen.live/core_components.ex @@ -339,7 +339,7 @@ defmodule <%= @web_namespace %>.CoreComponents do def error(assigns) do ~H""" -

+

<.icon name="hero-exclamation-circle-mini" class="mt-0.5 h-5 w-5 flex-none" /> <%%= render_slot(@inner_block) %>

From d79ed216cb032b27764479fcd1e0ba4272c7da58 Mon Sep 17 00:00:00 2001 From: Chris McCord Date: Thu, 7 Nov 2024 15:35:30 -0500 Subject: [PATCH 22/29] Support new live_title default --- installer/templates/phx_web/components/layouts/root.html.heex | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/installer/templates/phx_web/components/layouts/root.html.heex b/installer/templates/phx_web/components/layouts/root.html.heex index ae33a8b500..12f7f1d87e 100644 --- a/installer/templates/phx_web/components/layouts/root.html.heex +++ b/installer/templates/phx_web/components/layouts/root.html.heex @@ -4,8 +4,8 @@ - <.live_title suffix=" · Phoenix Framework"> - <%%= assigns[:page_title] || "<%= @app_module %>" %> + <.live_title default="<%= @app_module %>" suffix=" · Phoenix Framework"> + <%%= assigns[:page_title] %>