-
-
-
-
- Deploy your application
-
+
+
+
+
+
+
+
+
+ Mitgliederverwaltung
+
+
+
+
+
diff --git a/lib/mv_web/live_user_auth.ex b/lib/mv_web/live_user_auth.ex
new file mode 100644
index 0000000..67bef70
--- /dev/null
+++ b/lib/mv_web/live_user_auth.ex
@@ -0,0 +1,46 @@
+defmodule MvWeb.LiveUserAuth do
+ @moduledoc """
+ Helpers for authenticating users in LiveViews.
+ """
+
+ import Phoenix.Component
+ use MvWeb, :verified_routes
+
+ # This is used for nested liveviews to fetch the current user.
+ # To use, place the following at the top of that liveview:
+ # on_mount {MvWeb.LiveUserAuth, :current_user}
+ def on_mount(:current_user, _params, session, socket) do
+ return_to = session[:return_to]
+
+ socket =
+ socket
+ |> assign(:return_to, return_to)
+ |> AshAuthentication.Phoenix.LiveSession.assign_new_resources(session)
+
+ {:cont, session, socket}
+ end
+
+ def on_mount(:live_user_optional, _params, _session, socket) do
+ if socket.assigns[:current_user] do
+ {:cont, socket}
+ else
+ {:cont, assign(socket, :current_user, nil)}
+ end
+ end
+
+ def on_mount(:live_user_required, _params, _session, socket) do
+ if socket.assigns[:current_user] do
+ {:cont, socket}
+ else
+ {:halt, Phoenix.LiveView.redirect(socket, to: ~p"/sign-in")}
+ end
+ end
+
+ def on_mount(:live_no_user, _params, _session, socket) do
+ if socket.assigns[:current_user] do
+ {:halt, Phoenix.LiveView.redirect(socket, to: ~p"/")}
+ else
+ {:cont, assign(socket, :current_user, nil)}
+ end
+ end
+end
diff --git a/lib/mv_web/router.ex b/lib/mv_web/router.ex
index f2cde75..595a2da 100644
--- a/lib/mv_web/router.ex
+++ b/lib/mv_web/router.ex
@@ -1,6 +1,10 @@
defmodule MvWeb.Router do
use MvWeb, :router
+ use AshAuthentication.Phoenix.Router
+
+ import AshAuthentication.Plug.Helpers
+
pipeline :browser do
plug :accepts, ["html"]
plug :fetch_session
@@ -8,36 +12,92 @@ defmodule MvWeb.Router do
plug :put_root_layout, html: {MvWeb.Layouts, :root}
plug :protect_from_forgery
plug :put_secure_browser_headers
+ plug :load_from_session
plug :set_locale
end
pipeline :api do
plug :accepts, ["json"]
+ plug :load_from_bearer
+ plug :set_actor, :user
end
scope "/", MvWeb do
pipe_through :browser
- get "/", PageController, :home
- live "/members", MemberLive.Index, :index
- live "/members/new", MemberLive.Index, :new
- live "/members/:id/edit", MemberLive.Index, :edit
- live "/members/:id", MemberLive.Show, :show
- live "/members/:id/show/edit", MemberLive.Show, :edit
+ ash_authentication_live_session :authenticated_routes do
+ # in each liveview, add one of the following at the top of the module:
+ #
+ # If an authenticated user must be present:
+ # on_mount {MvWeb.LiveUserAuth, :live_user_required}
+ #
+ # If an authenticated user *may* be present:
+ # on_mount {MvWeb.LiveUserAuth, :live_user_optional}
+ #
+ # If an authenticated user must *not* be present:
+ # on_mount {MvWeb.LiveUserAuth, :live_no_user}
+ end
+ end
- live "/property_types", PropertyTypeLive.Index, :index
- live "/property_types/new", PropertyTypeLive.Index, :new
- live "/property_types/:id/edit", PropertyTypeLive.Index, :edit
- live "/property_types/:id", PropertyTypeLive.Show, :show
- live "/property_types/:id/show/edit", PropertyTypeLive.Show, :edit
+ scope "/", MvWeb do
+ pipe_through :browser
- live "/properties", PropertyLive.Index, :index
- live "/properties/new", PropertyLive.Index, :new
- live "/properties/:id/edit", PropertyLive.Index, :edit
- live "/properties/:id", PropertyLive.Show, :show
- live "/properties/:id/show/edit", PropertyLive.Show, :edit
+ @doc """
+ AshAuthentication-specific: We define that all routes can only be accessed when the user is signed in.
+ """
+ ash_authentication_live_session :authentication_required,
+ on_mount: {MvWeb.LiveUserAuth, :live_user_required} do
+ get "/", PageController, :home
- post "/set_locale", LocaleController, :set_locale
+ live "/members", MemberLive.Index, :index
+ live "/members/new", MemberLive.Index, :new
+ live "/members/:id/edit", MemberLive.Index, :edit
+ live "/members/:id", MemberLive.Show, :show
+ live "/members/:id/show/edit", MemberLive.Show, :edit
+
+ live "/property_types", PropertyTypeLive.Index, :index
+ live "/property_types/new", PropertyTypeLive.Index, :new
+ live "/property_types/:id/edit", PropertyTypeLive.Index, :edit
+ live "/property_types/:id", PropertyTypeLive.Show, :show
+ live "/property_types/:id/show/edit", PropertyTypeLive.Show, :edit
+
+ live "/properties", PropertyLive.Index, :index
+ live "/properties/new", PropertyLive.Index, :new
+ live "/properties/:id/edit", PropertyLive.Index, :edit
+ live "/properties/:id", PropertyLive.Show, :show
+ live "/properties/:id/show/edit", PropertyLive.Show, :edit
+
+ post "/set_locale", LocaleController, :set_locale
+ end
+
+ # ASHAUTHENTICATION GENERATED AUTH ROUTES
+ auth_routes AuthController, Mv.Accounts.User, path: "/auth"
+ sign_out_route AuthController
+
+ # Remove these if you'd like to use your own authentication views
+ sign_in_route register_path: "/register",
+ reset_path: "/reset",
+ auth_routes_prefix: "/auth",
+ on_mount: [{MvWeb.LiveUserAuth, :live_no_user}],
+ overrides: [MvWeb.AuthOverrides, AshAuthentication.Phoenix.Overrides.Default],
+ gettext_backend: {MvWeb.Gettext, "default"}
+
+ # Remove this if you do not want to use the reset password feature
+ reset_route auth_routes_prefix: "/auth",
+ overrides: [MvWeb.AuthOverrides, AshAuthentication.Phoenix.Overrides.Default],
+ gettext_backend: {MvWeb.Gettext, "default"}
+
+ # Remove this if you do not use the confirmation strategy
+ confirm_route Mv.Accounts.User, :confirm_new_user,
+ auth_routes_prefix: "/auth",
+ overrides: [MvWeb.AuthOverrides, AshAuthentication.Phoenix.Overrides.Default],
+ gettext_backend: {MvWeb.Gettext, "default"}
+
+ # Remove this if you do not use the magic link strategy.
+ # magic_sign_in_route(Mv.Accounts.User, :magic_link,
+ # auth_routes_prefix: "/auth",
+ # overrides: [MvWeb.AuthOverrides, AshAuthentication.Phoenix.Overrides.Default]
+ # )
end
# Other scopes may use custom stacks.
diff --git a/mix.exs b/mix.exs
index a1e30ab..c16e11b 100644
--- a/mix.exs
+++ b/mix.exs
@@ -40,6 +40,9 @@ defmodule Mv.MixProject do
{:ash_postgres, "~> 2.0"},
{:ash_phoenix, "~> 2.0"},
{:ash, "~> 3.0"},
+ {:bcrypt_elixir, "~> 3.0"},
+ {:ash_authentication, "~> 4.9"},
+ {:ash_authentication_phoenix, "~> 2.10"},
{:igniter, "~> 0.6", only: [:dev, :test]},
{:phoenix, "~> 1.7.20"},
{:phoenix_ecto, "~> 4.5"},
@@ -92,7 +95,8 @@ defmodule Mv.MixProject do
"tailwind mv --minify",
"esbuild mv --minify",
"phx.digest"
- ]
+ ],
+ "phx.routes": ["phx.routes", "ash_authentication.phoenix.routes"]
]
end
end
diff --git a/mix.lock b/mix.lock
index 962f445..51b4604 100644
--- a/mix.lock
+++ b/mix.lock
@@ -1,13 +1,18 @@
%{
"ash": {:hex, :ash, "3.5.19", "defd1c6b94475352a7b69f430b792fb64e3a9f7ca030195737bb97dc0f1311b5", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.6.4 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 0.11", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.2.65 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, ">= 0.2.6 and < 1.0.0-0", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "ded976230b1ef823aeb25008cc62de6545bf3ad6208cf1f3badb598fa6c01375"},
"ash_admin": {:hex, :ash_admin, "0.13.9", "8a7c0f52be4aa490e4a59137bc40e3abafba9e1977f800bb2edae3f331ef1ebb", [:mix], [{:ash, ">= 3.4.63 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:ash_phoenix, ">= 2.1.8 and < 3.0.0-0", [hex: :ash_phoenix, repo: "hexpm", optional: false]}, {:gettext, "~> 0.26", [hex: :gettext, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.7", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 4.1", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:phoenix_live_view, "~> 1.0", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: false]}], "hexpm", "1373e1749d6b5b21c7ff7d7fc79ac932f6f8d1bd0d154a80758eab168948ea37"},
+ "ash_authentication": {:hex, :ash_authentication, "4.9.3", "2347b7982e3b00ae1165a4ef6875e05540204e933922e302bd3ac2be4c043e20", [:mix], [{:argon2_elixir, "~> 4.0", [hex: :argon2_elixir, repo: "hexpm", optional: true]}, {:ash, ">= 3.4.29 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:ash_postgres, "~> 2.0", [hex: :ash_postgres, repo: "hexpm", optional: true]}, {:assent, "~> 0.2.13", [hex: :assent, repo: "hexpm", optional: false]}, {:bcrypt_elixir, "~> 3.0", [hex: :bcrypt_elixir, repo: "hexpm", optional: false]}, {:castore, "~> 1.0", [hex: :castore, repo: "hexpm", optional: false]}, {:finch, "~> 0.19", [hex: :finch, repo: "hexpm", optional: false]}, {:igniter, "~> 0.4", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:joken, "~> 2.5", [hex: :joken, repo: "hexpm", optional: false]}, {:plug, "~> 1.13", [hex: :plug, repo: "hexpm", optional: false]}, {:spark, "~> 2.0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}], "hexpm", "1641988f6c67b7d7517caed9e6cb0f6bd906bbb994e2831022b6ad7cecf45ad0"},
+ "ash_authentication_phoenix": {:hex, :ash_authentication_phoenix, "2.10.1", "6facb8e14d7e93c3268b8cb5300d42d3802bd754d241f4215f2c5fc1d34c4c94", [:mix], [{:ash, "~> 3.0", [hex: :ash, repo: "hexpm", optional: false]}, {:ash_authentication, ">= 4.9.1 and < 5.0.0-0", [hex: :ash_authentication, repo: "hexpm", optional: false]}, {:ash_phoenix, "~> 2.0", [hex: :ash_phoenix, repo: "hexpm", optional: false]}, {:bcrypt_elixir, "~> 3.0", [hex: :bcrypt_elixir, repo: "hexpm", optional: false]}, {:gettext, "~> 0.26", [hex: :gettext, repo: "hexpm", optional: true]}, {:igniter, ">= 0.5.25 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.6", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:phoenix_html_helpers, "~> 1.0", [hex: :phoenix_html_helpers, repo: "hexpm", optional: false]}, {:phoenix_live_view, "~> 1.0", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: false]}, {:slugify, "~> 1.3", [hex: :slugify, repo: "hexpm", optional: false]}], "hexpm", "efc27905b29476cacb67562658d5b38ca0656b3c81c4bcb40a117a2d8d686433"},
"ash_phoenix": {:hex, :ash_phoenix, "2.3.6", "c2bea1673af52f305b2fe0c04999bd1f0dc8e127d4757a3d7f42d0b9dea16a7a", [:mix], [{:ash, ">= 3.5.13 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:igniter, "~> 0.6", [hex: :igniter, repo: "hexpm", optional: true]}, {:inertia, "~> 2.3", [hex: :inertia, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.5.6 or ~> 1.6", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:phoenix_live_view, "~> 0.20.3 or ~> 1.0-rc.1", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}, {:spark, ">= 2.2.29 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}], "hexpm", "6923dca70fe1d533864134999f4d9c5c59ef745a6b50982d42d60c18966474cd"},
"ash_postgres": {:hex, :ash_postgres, "2.6.6", "f60f806e3e969669329dfd33068bf602f3d7f214e0bbb36c241433f34cbff2e0", [:mix], [{:ash, ">= 3.5.13 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:ash_sql, ">= 0.2.72 and < 1.0.0-0", [hex: :ash_sql, repo: "hexpm", optional: false]}, {:ecto, ">= 3.12.1 and < 4.0.0-0", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.12", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:igniter, "~> 0.6", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:postgrex, ">= 0.0.0", [hex: :postgrex, repo: "hexpm", optional: false]}], "hexpm", "3133800432273f9e6effb6f8464fe81da22c5b577aa73291f63fd229f4bb43fb"},
"ash_sql": {:hex, :ash_sql, "0.2.80", "7717dca3794d7461b8302b107f039bce2c57773840177528cf94c7c264ed763b", [:mix], [{:ash, "~> 3.5", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "036f96b78bf612a1d1fe798b8795ab1e6ecef81e41ca473b1533b139dd0202ab"},
+ "assent": {:hex, :assent, "0.2.13", "11226365d2d8661d23e9a2cf94d3255e81054ff9d88ac877f28bfdf38fa4ef31", [:mix], [{:certifi, ">= 0.0.0", [hex: :certifi, repo: "hexpm", optional: true]}, {:finch, "~> 0.15", [hex: :finch, repo: "hexpm", optional: true]}, {:jose, "~> 1.8", [hex: :jose, repo: "hexpm", optional: true]}, {:mint, "~> 1.0", [hex: :mint, repo: "hexpm", optional: true]}, {:req, "~> 0.4", [hex: :req, repo: "hexpm", optional: true]}, {:ssl_verify_fun, ">= 0.0.0", [hex: :ssl_verify_fun, repo: "hexpm", optional: true]}], "hexpm", "bf9f351b01dd6bceea1d1f157f05438f6765ce606e6eb8d29296003d29bf6eab"},
"bandit": {:hex, :bandit, "1.7.0", "d1564f30553c97d3e25f9623144bb8df11f3787a26733f00b21699a128105c0c", [:mix], [{:hpax, "~> 1.0", [hex: :hpax, repo: "hexpm", optional: false]}, {:plug, "~> 1.18", [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", "3e2f7a98c7a11f48d9d8c037f7177cd39778e74d55c7af06fe6227c742a8168a"},
+ "bcrypt_elixir": {:hex, :bcrypt_elixir, "3.3.2", "d50091e3c9492d73e17fc1e1619a9b09d6a5ef99160eb4d736926fd475a16ca3", [:make, :mix], [{:comeonin, "~> 5.3", [hex: :comeonin, repo: "hexpm", optional: false]}, {:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "471be5151874ae7931911057d1467d908955f93554f7a6cd1b7d804cac8cef53"},
"bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"},
"castore": {:hex, :castore, "1.0.14", "4582dd7d630b48cf5e1ca8d3d42494db51e406b7ba704e81fbd401866366896a", [:mix], [], "hexpm", "7bc1b65249d31701393edaaac18ec8398d8974d52c647b7904d01b964137b9f4"},
"circular_buffer": {:hex, :circular_buffer, "0.4.1", "477f370fd8cfe1787b0a1bade6208bbd274b34f1610e41f1180ba756a7679839", [:mix], [], "hexpm", "633ef2e059dde0d7b89bbab13b1da9d04c6685e80e68fbdf41282d4fae746b72"},
+ "comeonin": {:hex, :comeonin, "5.5.1", "5113e5f3800799787de08a6e0db307133850e635d34e9fab23c70b6501669510", [:mix], [], "hexpm", "65aac8f19938145377cee73973f192c5645873dcf550a8a6b18187d17c13ccdb"},
"credo": {:hex, :credo, "1.7.12", "9e3c20463de4b5f3f23721527fcaf16722ec815e70ff6c60b86412c695d426c1", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "8493d45c656c5427d9c729235b99d498bd133421f3e0a683e5c1b561471291e5"},
"db_connection": {:hex, :db_connection, "2.7.0", "b99faa9291bb09892c7da373bb82cba59aefa9b36300f6145c5f201c7adf48ec", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "dcf08f31b2701f857dfc787fbad78223d61a32204f217f15e881dd93e4bdd3ff"},
"decimal": {:hex, :decimal, "2.3.0", "3ad6255aa77b4a3c4f818171b12d237500e63525c2fd056699967a3e7ea20f62", [:mix], [], "hexpm", "a4d66355cb29cb47c3cf30e71329e58361cfcb37c34235ef3bf1d7bf3773aeac"},
@@ -15,6 +20,7 @@
"ecto": {:hex, :ecto, "3.12.6", "8bf762dc5b87d85b7aca7ad5fe31ef8142a84cea473a3381eb933bd925751300", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "4c0cba01795463eebbcd9e4b5ef53c1ee8e68b9c482baef2a80de5a61e7a57fe"},
"ecto_commons": {:hex, :ecto_commons, "0.3.6", "7b1d9e59396cf8c8cbe5a26d50d03f9b6d0fe6c640210dd503622f276f1e59bb", [:mix], [{:burnex, "~> 3.0", [hex: :burnex, repo: "hexpm", optional: true]}, {:ecto, "~> 3.4", [hex: :ecto, repo: "hexpm", optional: false]}, {:ex_phone_number, "~> 0.2", [hex: :ex_phone_number, repo: "hexpm", optional: false]}, {:luhn, "~> 0.3.0", [hex: :luhn, repo: "hexpm", optional: false]}], "hexpm", "3f12981a1e398f206c5d2014e7b732b7ec91b110b9cb84875cb5b28fc75d7a0a"},
"ecto_sql": {:hex, :ecto_sql, "3.12.1", "c0d0d60e85d9ff4631f12bafa454bc392ce8b9ec83531a412c12a0d415a3a4d0", [:mix], [{:db_connection, "~> 2.4.1 or ~> 2.5", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.12", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.7", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.19 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "aff5b958a899762c5f09028c847569f7dfb9cc9d63bdb8133bff8a5546de6bf5"},
+ "elixir_make": {:hex, :elixir_make, "0.9.0", "6484b3cd8c0cee58f09f05ecaf1a140a8c97670671a6a0e7ab4dc326c3109726", [:mix], [], "hexpm", "db23d4fd8b757462ad02f8aa73431a426fe6671c80b200d9710caf3d1dd0ffdb"},
"esbuild": {:hex, :esbuild, "0.10.0", "b0aa3388a1c23e727c5a3e7427c932d89ee791746b0081bbe56103e9ef3d291f", [:mix], [{:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "468489cda427b974a7cc9f03ace55368a83e1a7be12fba7e30969af78e5f8c70"},
"ets": {:hex, :ets, "0.9.0", "79c6a6c205436780486f72d84230c6cba2f8a9920456750ddd1e47389107d5fd", [:mix], [], "hexpm", "2861fdfb04bcaeff370f1a5904eec864f0a56dcfebe5921ea9aadf2a481c822b"},
"ex_phone_number": {:hex, :ex_phone_number, "0.4.5", "2065cc48c3e9d1ed9821f50877c32f2f6898362cb990f44147ca217c5d1374ed", [:mix], [{:sweet_xml, "~> 0.7", [hex: :sweet_xml, repo: "hexpm", optional: false]}], "hexpm", "67163f8706f8cbfef1b1f4b9230c461f19786d0d79fd0b22cbeeefc6f0b99d4a"},
@@ -30,6 +36,8 @@
"inflex": {:hex, :inflex, "2.1.0", "a365cf0821a9dacb65067abd95008ca1b0bb7dcdd85ae59965deef2aa062924c", [:mix], [], "hexpm", "14c17d05db4ee9b6d319b0bff1bdf22aa389a25398d1952c7a0b5f3d93162dd8"},
"iterex": {:hex, :iterex, "0.1.2", "58f9b9b9a22a55cbfc7b5234a9c9c63eaac26d276b3db80936c0e1c60355a5a6", [:mix], [], "hexpm", "2e103b8bcc81757a9af121f6dc0df312c9a17220f302b1193ef720460d03029d"},
"jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"},
+ "joken": {:hex, :joken, "2.6.2", "5daaf82259ca603af4f0b065475099ada1b2b849ff140ccd37f4b6828ca6892a", [:mix], [{:jose, "~> 1.11.10", [hex: :jose, repo: "hexpm", optional: false]}], "hexpm", "5134b5b0a6e37494e46dbf9e4dad53808e5e787904b7c73972651b51cce3d72b"},
+ "jose": {:hex, :jose, "1.11.10", "a903f5227417bd2a08c8a00a0cbcc458118be84480955e8d251297a425723f83", [:mix, :rebar3], [], "hexpm", "0d6cd36ff8ba174db29148fc112b5842186b68a90ce9fc2b3ec3afe76593e614"},
"libgraph": {:hex, :libgraph, "0.16.0", "3936f3eca6ef826e08880230f806bfea13193e49bf153f93edcf0239d4fd1d07", [:mix], [], "hexpm", "41ca92240e8a4138c30a7e06466acc709b0cbb795c643e9e17174a178982d6bf"},
"live_debugger": {:hex, :live_debugger, "0.2.4", "2e0b02874ca562ba2d8cebb9e024c25c0ae9c1f4ee499135a70814e1dea6183e", [:mix], [{:igniter, ">= 0.5.40 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:phoenix_live_view, "~> 0.20.4 or ~> 1.0", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}], "hexpm", "bfd0db143be54ccf2872f15bfd2209fbec1083d0b06b81b4cedeecb2fa9ac208"},
"luhn": {:hex, :luhn, "0.3.3", "5aa0c6a32c2db4b9db9f9b883ba8301c1ae169d57199b9e6cb1ba2707bc51d96", [:mix], [], "hexpm", "3e823a913a25aab51352c727f135278d22954874d5f0835be81ed4fec3daf78d"},
@@ -42,6 +50,7 @@
"phoenix": {:hex, :phoenix, "1.7.21", "14ca4f1071a5f65121217d6b57ac5712d1857e40a0833aff7a691b7870fc9a3b", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.7", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:websock_adapter, "~> 0.5.3", [hex: :websock_adapter, repo: "hexpm", optional: false]}], "hexpm", "336dce4f86cba56fed312a7d280bf2282c720abb6074bdb1b61ec8095bdd0bc9"},
"phoenix_ecto": {:hex, :phoenix_ecto, "4.6.4", "dcf3483ab45bab4c15e3a47c34451392f64e433846b08469f5d16c2a4cd70052", [:mix], [{:ecto, "~> 3.5", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.14.2 or ~> 3.0 or ~> 4.1", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.9", [hex: :plug, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.16 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}], "hexpm", "f5b8584c36ccc9b903948a696fc9b8b81102c79c7c0c751a9f00cdec55d5f2d7"},
"phoenix_html": {:hex, :phoenix_html, "4.2.1", "35279e2a39140068fc03f8874408d58eef734e488fc142153f055c5454fd1c08", [:mix], [], "hexpm", "cff108100ae2715dd959ae8f2a8cef8e20b593f8dfd031c9cba92702cf23e053"},
+ "phoenix_html_helpers": {:hex, :phoenix_html_helpers, "1.0.1", "7eed85c52eff80a179391036931791ee5d2f713d76a81d0d2c6ebafe1e11e5ec", [:mix], [{:phoenix_html, "~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "cffd2385d1fa4f78b04432df69ab8da63dc5cf63e07b713a4dcf36a3740e3090"},
"phoenix_live_dashboard": {:hex, :phoenix_live_dashboard, "0.8.7", "405880012cb4b706f26dd1c6349125bfc903fb9e44d1ea668adaf4e04d4884b7", [:mix], [{:ecto, "~> 3.6.2 or ~> 3.7", [hex: :ecto, repo: "hexpm", optional: true]}, {:ecto_mysql_extras, "~> 0.5", [hex: :ecto_mysql_extras, repo: "hexpm", optional: true]}, {:ecto_psql_extras, "~> 0.7", [hex: :ecto_psql_extras, repo: "hexpm", optional: true]}, {:ecto_sqlite3_extras, "~> 1.1.7 or ~> 1.2.0", [hex: :ecto_sqlite3_extras, repo: "hexpm", optional: true]}, {:mime, "~> 1.6 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:phoenix_live_view, "~> 0.19 or ~> 1.0", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}, {:telemetry_metrics, "~> 0.6 or ~> 1.0", [hex: :telemetry_metrics, repo: "hexpm", optional: false]}], "hexpm", "3a8625cab39ec261d48a13b7468dc619c0ede099601b084e343968309bd4d7d7"},
"phoenix_live_reload": {:hex, :phoenix_live_reload, "1.6.0", "2791fac0e2776b640192308cc90c0dbcf67843ad51387ed4ecae2038263d708d", [:mix], [{:file_system, "~> 0.2.10 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.4", [hex: :phoenix, repo: "hexpm", optional: false]}], "hexpm", "b3a1fa036d7eb2f956774eda7a7638cf5123f8f2175aca6d6420a7f95e598e1c"},
"phoenix_live_view": {:hex, :phoenix_live_view, "1.0.17", "beeb16d83a7d3760f7ad463df94e83b087577665d2acc0bf2987cd7d9778068f", [:mix], [{:floki, "~> 0.36", [hex: :floki, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.6.15 or ~> 1.7.0 or ~> 1.8.0-rc", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 3.3 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:plug, "~> 1.15", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "a4ca05c1eb6922c4d07a508a75bfa12c45e5f4d8f77ae83283465f02c53741e1"},
@@ -54,6 +63,7 @@
"reactor": {:hex, :reactor, "0.15.4", "ef0c56a901c132529a14ab59fed0ccb4fcecb24308fb189a94c908255d4fdafc", [:mix], [{:igniter, "~> 0.4", [hex: :igniter, repo: "hexpm", optional: true]}, {:iterex, "~> 0.1", [hex: :iterex, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:libgraph, "~> 0.16", [hex: :libgraph, repo: "hexpm", optional: false]}, {:spark, "~> 2.0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.2", [hex: :telemetry, repo: "hexpm", optional: false]}, {:yaml_elixir, "~> 2.11", [hex: :yaml_elixir, repo: "hexpm", optional: false]}, {:ymlr, "~> 5.0", [hex: :ymlr, repo: "hexpm", optional: false]}], "hexpm", "783bf62fd0c72ded033afabdb8b6190b7048769771a2a97256e6f0bf4fb0a891"},
"req": {:hex, :req, "0.5.10", "a3a063eab8b7510785a467f03d30a8d95f66f5c3d9495be3474b61459c54376c", [:mix], [{:brotli, "~> 0.3.1", [hex: :brotli, repo: "hexpm", optional: true]}, {:ezstd, "~> 1.0", [hex: :ezstd, repo: "hexpm", optional: true]}, {:finch, "~> 0.17", [hex: :finch, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mime, "~> 2.0.6 or ~> 2.1", [hex: :mime, repo: "hexpm", optional: false]}, {:nimble_csv, "~> 1.0", [hex: :nimble_csv, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "8a604815743f8a2d3b5de0659fa3137fa4b1cffd636ecb69b30b2b9b2c2559be"},
"rewrite": {:hex, :rewrite, "1.1.2", "f5a5d10f5fed1491a6ff48e078d4585882695962ccc9e6c779bae025d1f92eda", [:mix], [{:glob_ex, "~> 0.1", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.0", [hex: :sourceror, repo: "hexpm", optional: false]}, {:text_diff, "~> 0.1", [hex: :text_diff, repo: "hexpm", optional: false]}], "hexpm", "7f8b94b1e3528d0a47b3e8b7bfeca559d2948a65fa7418a9ad7d7712703d39d4"},
+ "slugify": {:hex, :slugify, "1.3.1", "0d3b8b7e5c1eeaa960e44dce94382bee34a39b3ea239293e457a9c5b47cc6fd3", [:mix], [], "hexpm", "cb090bbeb056b312da3125e681d98933a360a70d327820e4b7f91645c4d8be76"},
"sobelow": {:hex, :sobelow, "0.14.0", "dd82aae8f72503f924fe9dd97ffe4ca694d2f17ec463dcfd365987c9752af6ee", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "7ecf91e298acfd9b24f5d761f19e8f6e6ac585b9387fb6301023f1f2cd5eed5f"},
"sourceror": {:hex, :sourceror, "1.10.0", "38397dedbbc286966ec48c7af13e228b171332be1ad731974438c77791945ce9", [:mix], [], "hexpm", "29dbdfc92e04569c9d8e6efdc422fc1d815f4bd0055dc7c51b8800fb75c4b3f1"},
"spark": {:hex, :spark, "2.2.65", "4c10d109c108417ce394158f330be09ef184878bde45de6462397fbda68cec29", [:mix], [{:igniter, ">= 0.3.64 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: true]}, {:sourceror, "~> 1.2", [hex: :sourceror, repo: "hexpm", optional: true]}], "hexpm", "d66d5070a77f4c69cb4f007e941ac17d5d751ce71190fcd6e6e5fb42ba86f101"},
diff --git a/priv/gettext/de/LC_MESSAGES/default.po b/priv/gettext/de/LC_MESSAGES/default.po
index aa33cc3..7e6d755 100644
--- a/priv/gettext/de/LC_MESSAGES/default.po
+++ b/priv/gettext/de/LC_MESSAGES/default.po
@@ -25,9 +25,9 @@ msgstr "Bist du sicher?"
msgid "Attempting to reconnect"
msgstr "Verbindung wird wiederhergestellt"
-#: lib/mv_web/member_live/form_component.ex:48
+#: lib/mv_web/member_live/form_component.ex:50
#: lib/mv_web/member_live/index.ex:25
-#: lib/mv_web/member_live/show.ex:30
+#: lib/mv_web/member_live/show.ex:32
#, elixir-autogen, elixir-format
msgid "City"
msgstr "Stadt"
@@ -43,12 +43,12 @@ msgid "Edit"
msgstr "Bearbeiten"
#: lib/mv_web/member_live/index.ex:76
-#: lib/mv_web/member_live/show.ex:91
+#: lib/mv_web/member_live/show.ex:93
#, elixir-autogen, elixir-format
msgid "Edit Member"
msgstr "Mitglied bearbeiten"
-#: lib/mv_web/member_live/form_component.ex:41
+#: lib/mv_web/member_live/form_component.ex:43
#: lib/mv_web/member_live/index.ex:24
#: lib/mv_web/member_live/show.ex:23
#, elixir-autogen, elixir-format
@@ -60,7 +60,7 @@ msgstr "E-Mail"
msgid "Error!"
msgstr "Fehler!"
-#: lib/mv_web/member_live/form_component.ex:39
+#: lib/mv_web/member_live/form_component.ex:41
#: lib/mv_web/member_live/index.ex:22
#: lib/mv_web/member_live/show.ex:21
#, elixir-autogen, elixir-format
@@ -72,14 +72,14 @@ msgstr "Vorname"
msgid "Hang in there while we get back on track"
msgstr "Bitte warten, wir stellen die Verbindung wieder her."
-#: lib/mv_web/member_live/form_component.ex:45
+#: lib/mv_web/member_live/form_component.ex:47
#: lib/mv_web/member_live/index.ex:26
-#: lib/mv_web/member_live/show.ex:27
+#: lib/mv_web/member_live/show.ex:29
#, elixir-autogen, elixir-format
msgid "Join Date"
msgstr "Beitrittsdatum"
-#: lib/mv_web/member_live/form_component.ex:40
+#: lib/mv_web/member_live/form_component.ex:42
#: lib/mv_web/member_live/index.ex:23
#: lib/mv_web/member_live/show.ex:22
#, elixir-autogen, elixir-format
@@ -124,76 +124,76 @@ msgstr "Keine Internetverbindung gefunden"
msgid "close"
msgstr "schließen"
-#: lib/mv_web/member_live/form_component.ex:42
+#: lib/mv_web/member_live/form_component.ex:44
#: lib/mv_web/member_live/show.ex:24
#, elixir-autogen, elixir-format
msgid "Birth Date"
msgstr "Geburtsdatum"
-#: lib/mv_web/member_live/form_component.ex:53
-#: lib/mv_web/member_live/show.ex:36
+#: lib/mv_web/member_live/form_component.ex:55
+#: lib/mv_web/member_live/show.ex:38
#, elixir-autogen, elixir-format
msgid "Custom Properties"
msgstr "Eigene Eigenschaften"
-#: lib/mv_web/member_live/form_component.ex:46
-#: lib/mv_web/member_live/show.ex:28
+#: lib/mv_web/member_live/form_component.ex:48
+#: lib/mv_web/member_live/show.ex:30
#, elixir-autogen, elixir-format
msgid "Exit Date"
msgstr "Austrittsdatum"
-#: lib/mv_web/member_live/form_component.ex:50
-#: lib/mv_web/member_live/show.ex:32
+#: lib/mv_web/member_live/form_component.ex:52
+#: lib/mv_web/member_live/show.ex:34
#, elixir-autogen, elixir-format
msgid "House Number"
msgstr "Hausnummer"
-#: lib/mv_web/member_live/form_component.ex:47
-#: lib/mv_web/member_live/show.ex:29
+#: lib/mv_web/member_live/form_component.ex:49
+#: lib/mv_web/member_live/show.ex:31
#, elixir-autogen, elixir-format
msgid "Notes"
msgstr "Notizen"
-#: lib/mv_web/member_live/form_component.ex:43
+#: lib/mv_web/member_live/form_component.ex:45
#: lib/mv_web/member_live/show.ex:25
#, elixir-autogen, elixir-format
msgid "Paid"
msgstr "Bezahlt"
-#: lib/mv_web/member_live/form_component.ex:44
-#: lib/mv_web/member_live/show.ex:26
+#: lib/mv_web/member_live/form_component.ex:46
+#: lib/mv_web/member_live/show.ex:28
#, elixir-autogen, elixir-format
msgid "Phone Number"
msgstr "Telefonnummer"
-#: lib/mv_web/member_live/form_component.ex:51
-#: lib/mv_web/member_live/show.ex:33
+#: lib/mv_web/member_live/form_component.ex:53
+#: lib/mv_web/member_live/show.ex:35
#, elixir-autogen, elixir-format
msgid "Postal Code"
msgstr "Postleitzahl"
-#: lib/mv_web/member_live/form_component.ex:73
+#: lib/mv_web/member_live/form_component.ex:75
#, elixir-autogen, elixir-format
msgid "Save Member"
msgstr "Mitglied speichern"
-#: lib/mv_web/member_live/form_component.ex:73
+#: lib/mv_web/member_live/form_component.ex:75
#, elixir-autogen, elixir-format
msgid "Saving..."
msgstr "Speichern..."
-#: lib/mv_web/member_live/form_component.ex:49
-#: lib/mv_web/member_live/show.ex:31
+#: lib/mv_web/member_live/form_component.ex:51
+#: lib/mv_web/member_live/show.ex:33
#, elixir-autogen, elixir-format
msgid "Street"
msgstr "Straße"
-#: lib/mv_web/member_live/form_component.ex:29
+#: lib/mv_web/member_live/form_component.ex:30
#, elixir-autogen, elixir-format
msgid "Use this form to manage member records and their properties."
msgstr "Dieses Formular dient zur Verwaltung von Mitgliedern und deren Eigenschaften."
-#: lib/mv_web/member_live/show.ex:50
+#: lib/mv_web/member_live/show.ex:52
#, elixir-autogen, elixir-format
msgid "Back to members"
msgstr "Zurück zur Mitgliederliste"
@@ -208,12 +208,12 @@ msgstr "Mitglied bearbeiten"
msgid "Id"
msgstr "ID"
-#: lib/mv_web/member_live/show.ex:25
+#: lib/mv_web/member_live/show.ex:26
#, elixir-autogen, elixir-format
msgid "No"
msgstr "Nein"
-#: lib/mv_web/member_live/show.ex:90
+#: lib/mv_web/member_live/show.ex:92
#, elixir-autogen, elixir-format, fuzzy
msgid "Show Member"
msgstr "Mitglied anzeigen"
@@ -223,22 +223,52 @@ msgstr "Mitglied anzeigen"
msgid "This is a member record from your database."
msgstr "Dies ist ein Mitglied aus deiner Datenbank."
-#: lib/mv_web/member_live/show.ex:25
+#: lib/mv_web/member_live/show.ex:26
#, elixir-autogen, elixir-format
msgid "Yes"
msgstr "Ja"
-#: lib/mv_web/member_live/form_component.ex:107
-#, elixir-autogen, elixir-format
-msgid "Member %{action} successfully"
-msgstr "Mitglied %{action} erfolgreich"
-
-#: lib/mv_web/member_live/form_component.ex:100
+#: lib/mv_web/member_live/form_component.ex:102
#, elixir-autogen, elixir-format
msgid "create"
msgstr "erstellt"
-#: lib/mv_web/member_live/form_component.ex:101
+#: lib/mv_web/member_live/form_component.ex:103
#, elixir-autogen, elixir-format
msgid "update"
msgstr "aktualisiert"
+
+#: lib/mv_web/controllers/auth_controller.ex:43
+#, elixir-autogen, elixir-format
+msgid "Incorrect email or password"
+msgstr ""
+
+#: lib/mv_web/member_live/form_component.ex:109
+#, elixir-autogen, elixir-format
+msgid "Member %{action} successfully"
+msgstr "Mitglied %{action} erfolgreich"
+
+#: lib/mv_web/controllers/auth_controller.ex:14
+#, elixir-autogen, elixir-format
+msgid "You are now signed in"
+msgstr ""
+
+#: lib/mv_web/controllers/auth_controller.ex:56
+#, elixir-autogen, elixir-format
+msgid "You are now signed out"
+msgstr ""
+
+#: lib/mv_web/controllers/auth_controller.ex:36
+#, elixir-autogen, elixir-format
+msgid "You have already signed in another way, but have not confirmed your account.\nYou can confirm your account using the link we sent to you, or by resetting your password.\n"
+msgstr ""
+
+#: lib/mv_web/controllers/auth_controller.ex:12
+#, elixir-autogen, elixir-format
+msgid "Your email address has now been confirmed"
+msgstr ""
+
+#: lib/mv_web/controllers/auth_controller.ex:13
+#, elixir-autogen, elixir-format
+msgid "Your password has successfully been reset"
+msgstr ""
diff --git a/priv/gettext/de/LC_MESSAGES/errors.po b/priv/gettext/de/LC_MESSAGES/errors.po
index c0fba6d..844c4f5 100644
--- a/priv/gettext/de/LC_MESSAGES/errors.po
+++ b/priv/gettext/de/LC_MESSAGES/errors.po
@@ -110,24 +110,3 @@ msgstr ""
msgid "must be equal to %{number}"
msgstr ""
-
-msgid "is not a valid email"
-msgstr "ist keine gültige E-Mail-Adresse"
-
-msgid "cannot be in the future"
-msgstr "darf nicht in der Zukunft liegen"
-
-msgid "must be present"
-msgstr "muss ausgefüllt sein"
-
-msgid "is not a valid phone number"
-msgstr "ist keine gültige Telefonnummer"
-
-msgid "length must be greater than or equal to 5"
-msgstr "Die Länge muss mindestens 5 Zeichen betragen"
-
-msgid "cannot be before join date"
-msgstr "darf nicht vor dem Eintrittsdatum liegen"
-
-msgid "must consist of 5 digits"
-msgstr "muss aus 5 Ziffern bestehen"
diff --git a/priv/gettext/default.pot b/priv/gettext/default.pot
index f5b79d7..aad2ae9 100644
--- a/priv/gettext/default.pot
+++ b/priv/gettext/default.pot
@@ -26,9 +26,9 @@ msgstr ""
msgid "Attempting to reconnect"
msgstr ""
-#: lib/mv_web/member_live/form_component.ex:48
+#: lib/mv_web/member_live/form_component.ex:50
#: lib/mv_web/member_live/index.ex:25
-#: lib/mv_web/member_live/show.ex:30
+#: lib/mv_web/member_live/show.ex:32
#, elixir-autogen, elixir-format
msgid "City"
msgstr ""
@@ -44,12 +44,12 @@ msgid "Edit"
msgstr ""
#: lib/mv_web/member_live/index.ex:76
-#: lib/mv_web/member_live/show.ex:91
+#: lib/mv_web/member_live/show.ex:93
#, elixir-autogen, elixir-format
msgid "Edit Member"
msgstr ""
-#: lib/mv_web/member_live/form_component.ex:41
+#: lib/mv_web/member_live/form_component.ex:43
#: lib/mv_web/member_live/index.ex:24
#: lib/mv_web/member_live/show.ex:23
#, elixir-autogen, elixir-format
@@ -61,7 +61,7 @@ msgstr ""
msgid "Error!"
msgstr ""
-#: lib/mv_web/member_live/form_component.ex:39
+#: lib/mv_web/member_live/form_component.ex:41
#: lib/mv_web/member_live/index.ex:22
#: lib/mv_web/member_live/show.ex:21
#, elixir-autogen, elixir-format
@@ -73,14 +73,14 @@ msgstr ""
msgid "Hang in there while we get back on track"
msgstr ""
-#: lib/mv_web/member_live/form_component.ex:45
+#: lib/mv_web/member_live/form_component.ex:47
#: lib/mv_web/member_live/index.ex:26
-#: lib/mv_web/member_live/show.ex:27
+#: lib/mv_web/member_live/show.ex:29
#, elixir-autogen, elixir-format
msgid "Join Date"
msgstr ""
-#: lib/mv_web/member_live/form_component.ex:40
+#: lib/mv_web/member_live/form_component.ex:42
#: lib/mv_web/member_live/index.ex:23
#: lib/mv_web/member_live/show.ex:22
#, elixir-autogen, elixir-format
@@ -125,76 +125,76 @@ msgstr ""
msgid "close"
msgstr ""
-#: lib/mv_web/member_live/form_component.ex:42
+#: lib/mv_web/member_live/form_component.ex:44
#: lib/mv_web/member_live/show.ex:24
#, elixir-autogen, elixir-format
msgid "Birth Date"
msgstr ""
-#: lib/mv_web/member_live/form_component.ex:53
-#: lib/mv_web/member_live/show.ex:36
+#: lib/mv_web/member_live/form_component.ex:55
+#: lib/mv_web/member_live/show.ex:38
#, elixir-autogen, elixir-format
msgid "Custom Properties"
msgstr ""
-#: lib/mv_web/member_live/form_component.ex:46
-#: lib/mv_web/member_live/show.ex:28
+#: lib/mv_web/member_live/form_component.ex:48
+#: lib/mv_web/member_live/show.ex:30
#, elixir-autogen, elixir-format
msgid "Exit Date"
msgstr ""
-#: lib/mv_web/member_live/form_component.ex:50
-#: lib/mv_web/member_live/show.ex:32
+#: lib/mv_web/member_live/form_component.ex:52
+#: lib/mv_web/member_live/show.ex:34
#, elixir-autogen, elixir-format
msgid "House Number"
msgstr ""
-#: lib/mv_web/member_live/form_component.ex:47
-#: lib/mv_web/member_live/show.ex:29
-#, elixir-autogen, elixir-format
-msgid "Notes"
-msgstr ""
-
-#: lib/mv_web/member_live/form_component.ex:43
-#: lib/mv_web/member_live/show.ex:25
-#, elixir-autogen, elixir-format
-msgid "Paid"
-msgstr ""
-
-#: lib/mv_web/member_live/form_component.ex:44
-#: lib/mv_web/member_live/show.ex:26
-#, elixir-autogen, elixir-format
-msgid "Phone Number"
-msgstr ""
-
-#: lib/mv_web/member_live/form_component.ex:51
-#: lib/mv_web/member_live/show.ex:33
-#, elixir-autogen, elixir-format
-msgid "Postal Code"
-msgstr ""
-
-#: lib/mv_web/member_live/form_component.ex:73
-#, elixir-autogen, elixir-format
-msgid "Save Member"
-msgstr ""
-
-#: lib/mv_web/member_live/form_component.ex:73
-#, elixir-autogen, elixir-format
-msgid "Saving..."
-msgstr ""
-
#: lib/mv_web/member_live/form_component.ex:49
#: lib/mv_web/member_live/show.ex:31
#, elixir-autogen, elixir-format
+msgid "Notes"
+msgstr ""
+
+#: lib/mv_web/member_live/form_component.ex:45
+#: lib/mv_web/member_live/show.ex:25
+#, elixir-autogen, elixir-format
+msgid "Paid"
+msgstr ""
+
+#: lib/mv_web/member_live/form_component.ex:46
+#: lib/mv_web/member_live/show.ex:28
+#, elixir-autogen, elixir-format
+msgid "Phone Number"
+msgstr ""
+
+#: lib/mv_web/member_live/form_component.ex:53
+#: lib/mv_web/member_live/show.ex:35
+#, elixir-autogen, elixir-format
+msgid "Postal Code"
+msgstr ""
+
+#: lib/mv_web/member_live/form_component.ex:75
+#, elixir-autogen, elixir-format
+msgid "Save Member"
+msgstr ""
+
+#: lib/mv_web/member_live/form_component.ex:75
+#, elixir-autogen, elixir-format
+msgid "Saving..."
+msgstr ""
+
+#: lib/mv_web/member_live/form_component.ex:51
+#: lib/mv_web/member_live/show.ex:33
+#, elixir-autogen, elixir-format
msgid "Street"
msgstr ""
-#: lib/mv_web/member_live/form_component.ex:29
+#: lib/mv_web/member_live/form_component.ex:30
#, elixir-autogen, elixir-format
msgid "Use this form to manage member records and their properties."
msgstr ""
-#: lib/mv_web/member_live/show.ex:50
+#: lib/mv_web/member_live/show.ex:52
#, elixir-autogen, elixir-format
msgid "Back to members"
msgstr ""
@@ -209,12 +209,12 @@ msgstr ""
msgid "Id"
msgstr ""
-#: lib/mv_web/member_live/show.ex:25
+#: lib/mv_web/member_live/show.ex:26
#, elixir-autogen, elixir-format
msgid "No"
msgstr ""
-#: lib/mv_web/member_live/show.ex:90
+#: lib/mv_web/member_live/show.ex:92
#, elixir-autogen, elixir-format
msgid "Show Member"
msgstr ""
@@ -224,22 +224,52 @@ msgstr ""
msgid "This is a member record from your database."
msgstr ""
-#: lib/mv_web/member_live/show.ex:25
+#: lib/mv_web/member_live/show.ex:26
#, elixir-autogen, elixir-format
msgid "Yes"
msgstr ""
-#: lib/mv_web/member_live/form_component.ex:107
-#, elixir-autogen, elixir-format
-msgid "Mitglied %{action} erfolgreich"
-msgstr ""
-
-#: lib/mv_web/member_live/form_component.ex:100
+#: lib/mv_web/member_live/form_component.ex:102
#, elixir-autogen, elixir-format
msgid "create"
msgstr ""
-#: lib/mv_web/member_live/form_component.ex:101
+#: lib/mv_web/member_live/form_component.ex:103
#, elixir-autogen, elixir-format
msgid "update"
msgstr ""
+
+#: lib/mv_web/controllers/auth_controller.ex:43
+#, elixir-autogen, elixir-format
+msgid "Incorrect email or password"
+msgstr ""
+
+#: lib/mv_web/member_live/form_component.ex:109
+#, elixir-autogen, elixir-format
+msgid "Member %{action} successfully"
+msgstr ""
+
+#: lib/mv_web/controllers/auth_controller.ex:14
+#, elixir-autogen, elixir-format
+msgid "You are now signed in"
+msgstr ""
+
+#: lib/mv_web/controllers/auth_controller.ex:56
+#, elixir-autogen, elixir-format
+msgid "You are now signed out"
+msgstr ""
+
+#: lib/mv_web/controllers/auth_controller.ex:36
+#, elixir-autogen, elixir-format
+msgid "You have already signed in another way, but have not confirmed your account.\nYou can confirm your account using the link we sent to you, or by resetting your password.\n"
+msgstr ""
+
+#: lib/mv_web/controllers/auth_controller.ex:12
+#, elixir-autogen, elixir-format
+msgid "Your email address has now been confirmed"
+msgstr ""
+
+#: lib/mv_web/controllers/auth_controller.ex:13
+#, elixir-autogen, elixir-format
+msgid "Your password has successfully been reset"
+msgstr ""
diff --git a/priv/gettext/en/LC_MESSAGES/default.po b/priv/gettext/en/LC_MESSAGES/default.po
index 6173d39..3317236 100644
--- a/priv/gettext/en/LC_MESSAGES/default.po
+++ b/priv/gettext/en/LC_MESSAGES/default.po
@@ -26,9 +26,9 @@ msgstr ""
msgid "Attempting to reconnect"
msgstr ""
-#: lib/mv_web/member_live/form_component.ex:48
+#: lib/mv_web/member_live/form_component.ex:50
#: lib/mv_web/member_live/index.ex:25
-#: lib/mv_web/member_live/show.ex:30
+#: lib/mv_web/member_live/show.ex:32
#, elixir-autogen, elixir-format
msgid "City"
msgstr ""
@@ -44,12 +44,12 @@ msgid "Edit"
msgstr ""
#: lib/mv_web/member_live/index.ex:76
-#: lib/mv_web/member_live/show.ex:91
+#: lib/mv_web/member_live/show.ex:93
#, elixir-autogen, elixir-format
msgid "Edit Member"
msgstr ""
-#: lib/mv_web/member_live/form_component.ex:41
+#: lib/mv_web/member_live/form_component.ex:43
#: lib/mv_web/member_live/index.ex:24
#: lib/mv_web/member_live/show.ex:23
#, elixir-autogen, elixir-format
@@ -61,7 +61,7 @@ msgstr ""
msgid "Error!"
msgstr ""
-#: lib/mv_web/member_live/form_component.ex:39
+#: lib/mv_web/member_live/form_component.ex:41
#: lib/mv_web/member_live/index.ex:22
#: lib/mv_web/member_live/show.ex:21
#, elixir-autogen, elixir-format
@@ -73,14 +73,14 @@ msgstr ""
msgid "Hang in there while we get back on track"
msgstr ""
-#: lib/mv_web/member_live/form_component.ex:45
+#: lib/mv_web/member_live/form_component.ex:47
#: lib/mv_web/member_live/index.ex:26
-#: lib/mv_web/member_live/show.ex:27
+#: lib/mv_web/member_live/show.ex:29
#, elixir-autogen, elixir-format
msgid "Join Date"
msgstr ""
-#: lib/mv_web/member_live/form_component.ex:40
+#: lib/mv_web/member_live/form_component.ex:42
#: lib/mv_web/member_live/index.ex:23
#: lib/mv_web/member_live/show.ex:22
#, elixir-autogen, elixir-format
@@ -125,76 +125,76 @@ msgstr ""
msgid "close"
msgstr ""
-#: lib/mv_web/member_live/form_component.ex:42
+#: lib/mv_web/member_live/form_component.ex:44
#: lib/mv_web/member_live/show.ex:24
#, elixir-autogen, elixir-format
msgid "Birth Date"
msgstr ""
-#: lib/mv_web/member_live/form_component.ex:53
-#: lib/mv_web/member_live/show.ex:36
+#: lib/mv_web/member_live/form_component.ex:55
+#: lib/mv_web/member_live/show.ex:38
#, elixir-autogen, elixir-format
msgid "Custom Properties"
msgstr ""
-#: lib/mv_web/member_live/form_component.ex:46
-#: lib/mv_web/member_live/show.ex:28
+#: lib/mv_web/member_live/form_component.ex:48
+#: lib/mv_web/member_live/show.ex:30
#, elixir-autogen, elixir-format
msgid "Exit Date"
msgstr ""
-#: lib/mv_web/member_live/form_component.ex:50
-#: lib/mv_web/member_live/show.ex:32
+#: lib/mv_web/member_live/form_component.ex:52
+#: lib/mv_web/member_live/show.ex:34
#, elixir-autogen, elixir-format
msgid "House Number"
msgstr ""
-#: lib/mv_web/member_live/form_component.ex:47
-#: lib/mv_web/member_live/show.ex:29
-#, elixir-autogen, elixir-format
-msgid "Notes"
-msgstr ""
-
-#: lib/mv_web/member_live/form_component.ex:43
-#: lib/mv_web/member_live/show.ex:25
-#, elixir-autogen, elixir-format
-msgid "Paid"
-msgstr ""
-
-#: lib/mv_web/member_live/form_component.ex:44
-#: lib/mv_web/member_live/show.ex:26
-#, elixir-autogen, elixir-format
-msgid "Phone Number"
-msgstr ""
-
-#: lib/mv_web/member_live/form_component.ex:51
-#: lib/mv_web/member_live/show.ex:33
-#, elixir-autogen, elixir-format
-msgid "Postal Code"
-msgstr ""
-
-#: lib/mv_web/member_live/form_component.ex:73
-#, elixir-autogen, elixir-format, fuzzy
-msgid "Save Member"
-msgstr ""
-
-#: lib/mv_web/member_live/form_component.ex:73
-#, elixir-autogen, elixir-format
-msgid "Saving..."
-msgstr ""
-
#: lib/mv_web/member_live/form_component.ex:49
#: lib/mv_web/member_live/show.ex:31
#, elixir-autogen, elixir-format
+msgid "Notes"
+msgstr ""
+
+#: lib/mv_web/member_live/form_component.ex:45
+#: lib/mv_web/member_live/show.ex:25
+#, elixir-autogen, elixir-format
+msgid "Paid"
+msgstr ""
+
+#: lib/mv_web/member_live/form_component.ex:46
+#: lib/mv_web/member_live/show.ex:28
+#, elixir-autogen, elixir-format
+msgid "Phone Number"
+msgstr ""
+
+#: lib/mv_web/member_live/form_component.ex:53
+#: lib/mv_web/member_live/show.ex:35
+#, elixir-autogen, elixir-format
+msgid "Postal Code"
+msgstr ""
+
+#: lib/mv_web/member_live/form_component.ex:75
+#, elixir-autogen, elixir-format, fuzzy
+msgid "Save Member"
+msgstr ""
+
+#: lib/mv_web/member_live/form_component.ex:75
+#, elixir-autogen, elixir-format
+msgid "Saving..."
+msgstr ""
+
+#: lib/mv_web/member_live/form_component.ex:51
+#: lib/mv_web/member_live/show.ex:33
+#, elixir-autogen, elixir-format
msgid "Street"
msgstr ""
-#: lib/mv_web/member_live/form_component.ex:29
+#: lib/mv_web/member_live/form_component.ex:30
#, elixir-autogen, elixir-format
msgid "Use this form to manage member records and their properties."
msgstr ""
-#: lib/mv_web/member_live/show.ex:50
+#: lib/mv_web/member_live/show.ex:52
#, elixir-autogen, elixir-format
msgid "Back to members"
msgstr ""
@@ -209,12 +209,12 @@ msgstr ""
msgid "Id"
msgstr ""
-#: lib/mv_web/member_live/show.ex:25
+#: lib/mv_web/member_live/show.ex:26
#, elixir-autogen, elixir-format
msgid "No"
msgstr ""
-#: lib/mv_web/member_live/show.ex:90
+#: lib/mv_web/member_live/show.ex:92
#, elixir-autogen, elixir-format, fuzzy
msgid "Show Member"
msgstr ""
@@ -224,22 +224,52 @@ msgstr ""
msgid "This is a member record from your database."
msgstr ""
-#: lib/mv_web/member_live/show.ex:25
+#: lib/mv_web/member_live/show.ex:26
#, elixir-autogen, elixir-format
msgid "Yes"
msgstr ""
-#: lib/mv_web/member_live/form_component.ex:107
-#, elixir-autogen, elixir-format
-msgid "Member %{action} successfully"
-msgstr ""
-
-#: lib/mv_web/member_live/form_component.ex:100
+#: lib/mv_web/member_live/form_component.ex:102
#, elixir-autogen, elixir-format
msgid "create"
msgstr ""
-#: lib/mv_web/member_live/form_component.ex:101
+#: lib/mv_web/member_live/form_component.ex:103
#, elixir-autogen, elixir-format
msgid "update"
msgstr ""
+
+#: lib/mv_web/controllers/auth_controller.ex:43
+#, elixir-autogen, elixir-format
+msgid "Incorrect email or password"
+msgstr ""
+
+#: lib/mv_web/member_live/form_component.ex:109
+#, elixir-autogen, elixir-format
+msgid "Member %{action} successfully"
+msgstr ""
+
+#: lib/mv_web/controllers/auth_controller.ex:14
+#, elixir-autogen, elixir-format
+msgid "You are now signed in"
+msgstr ""
+
+#: lib/mv_web/controllers/auth_controller.ex:56
+#, elixir-autogen, elixir-format
+msgid "You are now signed out"
+msgstr ""
+
+#: lib/mv_web/controllers/auth_controller.ex:36
+#, elixir-autogen, elixir-format
+msgid "You have already signed in another way, but have not confirmed your account.\nYou can confirm your account using the link we sent to you, or by resetting your password.\n"
+msgstr ""
+
+#: lib/mv_web/controllers/auth_controller.ex:12
+#, elixir-autogen, elixir-format
+msgid "Your email address has now been confirmed"
+msgstr ""
+
+#: lib/mv_web/controllers/auth_controller.ex:13
+#, elixir-autogen, elixir-format
+msgid "Your password has successfully been reset"
+msgstr ""
diff --git a/priv/gettext/en/LC_MESSAGES/errors.po b/priv/gettext/en/LC_MESSAGES/errors.po
index 60c1037..844c4f5 100644
--- a/priv/gettext/en/LC_MESSAGES/errors.po
+++ b/priv/gettext/en/LC_MESSAGES/errors.po
@@ -110,12 +110,3 @@ msgstr ""
msgid "must be equal to %{number}"
msgstr ""
-
-msgid "length must be greater than or equal to 5"
-msgstr "length must be greater than or equal to 5"
-
-msgid "cannot be before join date"
-msgstr "cannot be before join date"
-
-msgid "must consist of 5 digits"
-msgstr "must consist of 5 digits"
diff --git a/priv/repo/migrations/20250620110849_add_accounts_domain_extensions.exs b/priv/repo/migrations/20250620110849_add_accounts_domain_extensions.exs
new file mode 100644
index 0000000..f77419c
--- /dev/null
+++ b/priv/repo/migrations/20250620110849_add_accounts_domain_extensions.exs
@@ -0,0 +1,19 @@
+defmodule Mv.Repo.Migrations.AddAccountsDomainExtensions do
+ @moduledoc """
+ Installs any extensions that are mentioned in the repo's `installed_extensions/0` callback
+
+ This file was autogenerated with `mix ash_postgres.generate_migrations`
+ """
+
+ use Ecto.Migration
+
+ def up do
+ execute("CREATE EXTENSION IF NOT EXISTS \"citext\"")
+ end
+
+ def down do
+ # Uncomment this if you actually want to uninstall the extensions
+ # when this migration is rolled back:
+ # execute("DROP EXTENSION IF EXISTS \"citext\"")
+ end
+end
diff --git a/priv/repo/migrations/20250620110850_add_accounts_domain.exs b/priv/repo/migrations/20250620110850_add_accounts_domain.exs
new file mode 100644
index 0000000..4c7c54b
--- /dev/null
+++ b/priv/repo/migrations/20250620110850_add_accounts_domain.exs
@@ -0,0 +1,58 @@
+defmodule Mv.Repo.Migrations.AddAccountsDomain do
+ @moduledoc """
+ Updates resources based on their most recent snapshots.
+
+ This file was autogenerated with `mix ash_postgres.generate_migrations`
+ """
+
+ use Ecto.Migration
+
+ def up do
+ create table(:users, primary_key: false) do
+ add :id, :uuid, null: false, default: fragment("gen_random_uuid()"), primary_key: true
+ add :email, :citext, null: false
+ add :hashed_password, :text
+ add :oidc_id, :text
+
+ add :member_id,
+ references(:members,
+ column: :id,
+ name: "users_member_id_fkey",
+ type: :uuid,
+ prefix: "public"
+ )
+ end
+
+ create unique_index(:users, [:email], name: "users_unique_email_index")
+
+ create unique_index(:users, [:oidc_id], name: "users_unique_oidc_id_index")
+
+ create table(:tokens, primary_key: false) do
+ add :updated_at, :utc_datetime_usec,
+ null: false,
+ default: fragment("(now() AT TIME ZONE 'utc')")
+
+ add :created_at, :utc_datetime_usec,
+ null: false,
+ default: fragment("(now() AT TIME ZONE 'utc')")
+
+ add :extra_data, :map
+ add :purpose, :text, null: false
+ add :expires_at, :utc_datetime, null: false
+ add :subject, :text, null: false
+ add :jti, :text, null: false, primary_key: true
+ end
+ end
+
+ def down do
+ drop table(:tokens)
+
+ drop_if_exists unique_index(:users, [:oidc_id], name: "users_unique_oidc_id_index")
+
+ drop_if_exists unique_index(:users, [:email], name: "users_unique_email_index")
+
+ drop constraint(:users, "users_member_id_fkey")
+
+ drop table(:users)
+ end
+end
diff --git a/priv/resource_snapshots/repo/extensions.json b/priv/resource_snapshots/repo/extensions.json
index 33001db..323661b 100644
--- a/priv/resource_snapshots/repo/extensions.json
+++ b/priv/resource_snapshots/repo/extensions.json
@@ -1,6 +1,7 @@
{
"ash_functions_version": 5,
"installed": [
- "ash-functions"
+ "ash-functions",
+ "citext"
]
}
\ No newline at end of file
diff --git a/priv/resource_snapshots/repo/tokens/20250620110850.json b/priv/resource_snapshots/repo/tokens/20250620110850.json
new file mode 100644
index 0000000..c702eff
--- /dev/null
+++ b/priv/resource_snapshots/repo/tokens/20250620110850.json
@@ -0,0 +1,103 @@
+{
+ "attributes": [
+ {
+ "allow_nil?": false,
+ "default": "fragment(\"(now() AT TIME ZONE 'utc')\")",
+ "generated?": false,
+ "precision": null,
+ "primary_key?": false,
+ "references": null,
+ "scale": null,
+ "size": null,
+ "source": "updated_at",
+ "type": "utc_datetime_usec"
+ },
+ {
+ "allow_nil?": false,
+ "default": "fragment(\"(now() AT TIME ZONE 'utc')\")",
+ "generated?": false,
+ "precision": null,
+ "primary_key?": false,
+ "references": null,
+ "scale": null,
+ "size": null,
+ "source": "created_at",
+ "type": "utc_datetime_usec"
+ },
+ {
+ "allow_nil?": true,
+ "default": "nil",
+ "generated?": false,
+ "precision": null,
+ "primary_key?": false,
+ "references": null,
+ "scale": null,
+ "size": null,
+ "source": "extra_data",
+ "type": "map"
+ },
+ {
+ "allow_nil?": false,
+ "default": "nil",
+ "generated?": false,
+ "precision": null,
+ "primary_key?": false,
+ "references": null,
+ "scale": null,
+ "size": null,
+ "source": "purpose",
+ "type": "text"
+ },
+ {
+ "allow_nil?": false,
+ "default": "nil",
+ "generated?": false,
+ "precision": null,
+ "primary_key?": false,
+ "references": null,
+ "scale": null,
+ "size": null,
+ "source": "expires_at",
+ "type": "utc_datetime"
+ },
+ {
+ "allow_nil?": false,
+ "default": "nil",
+ "generated?": false,
+ "precision": null,
+ "primary_key?": false,
+ "references": null,
+ "scale": null,
+ "size": null,
+ "source": "subject",
+ "type": "text"
+ },
+ {
+ "allow_nil?": false,
+ "default": "nil",
+ "generated?": false,
+ "precision": null,
+ "primary_key?": true,
+ "references": null,
+ "scale": null,
+ "size": null,
+ "source": "jti",
+ "type": "text"
+ }
+ ],
+ "base_filter": null,
+ "check_constraints": [],
+ "custom_indexes": [],
+ "custom_statements": [],
+ "has_create_action": true,
+ "hash": "EA1475C339B5BE2728560EFB2AF911275B2F65C2CE66CD1C093FAB5D9183BB11",
+ "identities": [],
+ "multitenancy": {
+ "attribute": null,
+ "global": null,
+ "strategy": null
+ },
+ "repo": "Elixir.Mv.Repo",
+ "schema": null,
+ "table": "tokens"
+}
\ No newline at end of file
diff --git a/priv/resource_snapshots/repo/users/20250620110850.json b/priv/resource_snapshots/repo/users/20250620110850.json
new file mode 100644
index 0000000..54688a8
--- /dev/null
+++ b/priv/resource_snapshots/repo/users/20250620110850.json
@@ -0,0 +1,127 @@
+{
+ "attributes": [
+ {
+ "allow_nil?": false,
+ "default": "fragment(\"gen_random_uuid()\")",
+ "generated?": false,
+ "precision": null,
+ "primary_key?": true,
+ "references": null,
+ "scale": null,
+ "size": null,
+ "source": "id",
+ "type": "uuid"
+ },
+ {
+ "allow_nil?": false,
+ "default": "nil",
+ "generated?": false,
+ "precision": null,
+ "primary_key?": false,
+ "references": null,
+ "scale": null,
+ "size": null,
+ "source": "email",
+ "type": "citext"
+ },
+ {
+ "allow_nil?": true,
+ "default": "nil",
+ "generated?": false,
+ "precision": null,
+ "primary_key?": false,
+ "references": null,
+ "scale": null,
+ "size": null,
+ "source": "hashed_password",
+ "type": "text"
+ },
+ {
+ "allow_nil?": true,
+ "default": "nil",
+ "generated?": false,
+ "precision": null,
+ "primary_key?": false,
+ "references": null,
+ "scale": null,
+ "size": null,
+ "source": "oidc_id",
+ "type": "text"
+ },
+ {
+ "allow_nil?": true,
+ "default": "nil",
+ "generated?": false,
+ "precision": null,
+ "primary_key?": false,
+ "references": {
+ "deferrable": false,
+ "destination_attribute": "id",
+ "destination_attribute_default": null,
+ "destination_attribute_generated": null,
+ "index?": false,
+ "match_type": null,
+ "match_with": null,
+ "multitenancy": {
+ "attribute": null,
+ "global": null,
+ "strategy": null
+ },
+ "name": "users_member_id_fkey",
+ "on_delete": null,
+ "on_update": null,
+ "primary_key?": true,
+ "schema": "public",
+ "table": "members"
+ },
+ "scale": null,
+ "size": null,
+ "source": "member_id",
+ "type": "uuid"
+ }
+ ],
+ "base_filter": null,
+ "check_constraints": [],
+ "custom_indexes": [],
+ "custom_statements": [],
+ "has_create_action": true,
+ "hash": "03EBA1A8BCE47C4706E2D718E00364465E08C9A3999988D49FC1B89DEC5D717C",
+ "identities": [
+ {
+ "all_tenants?": false,
+ "base_filter": null,
+ "index_name": "users_unique_email_index",
+ "keys": [
+ {
+ "type": "atom",
+ "value": "email"
+ }
+ ],
+ "name": "unique_email",
+ "nils_distinct?": true,
+ "where": null
+ },
+ {
+ "all_tenants?": false,
+ "base_filter": null,
+ "index_name": "users_unique_oidc_id_index",
+ "keys": [
+ {
+ "type": "atom",
+ "value": "oidc_id"
+ }
+ ],
+ "name": "unique_oidc_id",
+ "nils_distinct?": true,
+ "where": null
+ }
+ ],
+ "multitenancy": {
+ "attribute": null,
+ "global": null,
+ "strategy": null
+ },
+ "repo": "Elixir.Mv.Repo",
+ "schema": null,
+ "table": "users"
+}
\ No newline at end of file
diff --git a/test/mv_web/controllers/auth_controller_test.exs b/test/mv_web/controllers/auth_controller_test.exs
new file mode 100644
index 0000000..e18ade2
--- /dev/null
+++ b/test/mv_web/controllers/auth_controller_test.exs
@@ -0,0 +1,48 @@
+defmodule MvWeb.AuthControllerTest do
+ use MvWeb.ConnCase, async: true
+
+ test "GET /sign-in shows sign in form", %{conn: conn} do
+ conn = get(conn, ~p"/sign-in")
+ assert html_response(conn, 200) =~ "Sign in"
+ end
+
+ test "POST /sign-in with valid credentials redirects to home", %{conn: conn} do
+ # Create a test user first
+ conn = conn_with_oidc_user(conn)
+ conn = get(conn, ~p"/sign-in")
+
+ assert redirected_to(conn) == ~p"/"
+ end
+
+ test "POST /sign-in with invalid credentials shows error", %{conn: conn} do
+ conn =
+ post(conn, ~p"/auth/sign_in", %{
+ "user" => %{
+ "email" => "wrong@example.com",
+ "password" => "wrongpassword"
+ }
+ })
+
+ assert conn.status == 404
+ end
+
+ test "GET /sign-out redirects to home", %{conn: conn} do
+ # First sign in a user
+ conn = conn_with_oidc_user(conn)
+
+ # Then sign out
+ conn = get(conn, ~p"/sign-out")
+ assert redirected_to(conn) == ~p"/"
+ end
+
+ test "unauthenticated user accessing protected route gets redirected to sign-in", %{conn: conn} do
+ conn = get(conn, ~p"/members")
+ assert redirected_to(conn) == ~p"/sign-in"
+ end
+
+ test "authenticated user can access protected route", %{conn: conn} do
+ conn = conn_with_oidc_user(conn)
+ conn = get(conn, ~p"/members")
+ assert conn.status == 200
+ end
+end
diff --git a/test/mv_web/controllers/page_controller_test.exs b/test/mv_web/controllers/page_controller_test.exs
index 702cd78..ce3195b 100644
--- a/test/mv_web/controllers/page_controller_test.exs
+++ b/test/mv_web/controllers/page_controller_test.exs
@@ -2,7 +2,9 @@ defmodule MvWeb.PageControllerTest do
use MvWeb.ConnCase
test "GET /", %{conn: conn} do
+ conn = conn_with_oidc_user(conn)
+
conn = get(conn, ~p"/")
- assert html_response(conn, 200) =~ "Peace of mind from prototype to production"
+ assert html_response(conn, 200) =~ "Mitgliederverwaltung"
end
end
diff --git a/test/mv_web/member_live/index_test.exs b/test/mv_web/member_live/index_test.exs
index b5a5968..ce47a43 100644
--- a/test/mv_web/member_live/index_test.exs
+++ b/test/mv_web/member_live/index_test.exs
@@ -3,6 +3,7 @@ defmodule MvWeb.MemberLive.IndexTest do
import Phoenix.LiveViewTest
test "shows translated title in German", %{conn: conn} do
+ conn = conn_with_oidc_user(conn)
conn = Plug.Test.init_test_session(conn, locale: "de")
{:ok, _view, html} = live(conn, "/members")
# Expected German title
@@ -10,6 +11,7 @@ defmodule MvWeb.MemberLive.IndexTest do
end
test "shows translated title in English", %{conn: conn} do
+ conn = conn_with_oidc_user(conn)
Gettext.put_locale(MvWeb.Gettext, "en")
{:ok, _view, html} = live(conn, "/members")
# Expected English title
@@ -17,18 +19,21 @@ defmodule MvWeb.MemberLive.IndexTest do
end
test "shows translated button text in German", %{conn: conn} do
+ conn = conn_with_oidc_user(conn)
conn = Plug.Test.init_test_session(conn, locale: "de")
{:ok, _view, html} = live(conn, "/members/new")
assert html =~ "Speichern"
end
test "shows translated button text in English", %{conn: conn} do
+ conn = conn_with_oidc_user(conn)
Gettext.put_locale(MvWeb.Gettext, "en")
{:ok, _view, html} = live(conn, "/members/new")
assert html =~ "Save"
end
test "shows translated flash message after creating a member in German", %{conn: conn} do
+ conn = conn_with_oidc_user(conn)
conn = Plug.Test.init_test_session(conn, locale: "de")
{:ok, view, _html} = live(conn, "/members")
view |> element("a", "Neues Mitglied") |> render_click()
@@ -44,6 +49,7 @@ defmodule MvWeb.MemberLive.IndexTest do
end
test "shows translated flash message after creating a member in English", %{conn: conn} do
+ conn = conn_with_oidc_user(conn)
conn = Plug.Test.init_test_session(conn, locale: "en")
{:ok, view, _html} = live(conn, "/members")
view |> element("a", "New Member") |> render_click()
diff --git a/test/support/conn_case.ex b/test/support/conn_case.ex
index 7101531..d1804b7 100644
--- a/test/support/conn_case.ex
+++ b/test/support/conn_case.ex
@@ -31,6 +31,38 @@ defmodule MvWeb.ConnCase do
end
end
+ @doc """
+ Creates a test user and returns the user struct.
+ """
+ def create_test_user(attrs \\ %{}) do
+ email = "user@example.com"
+ password = "password"
+ {:ok, hashed_password} = AshAuthentication.BcryptProvider.hash(password)
+
+ Ash.Seed.seed!(Mv.Accounts.User, %{
+ email: email,
+ hashed_password: hashed_password
+ })
+ end
+
+ @doc """
+ Signs in a user via OIDC for testing by creating a session with the user's token.
+ """
+ def sign_in_user_via_oidc(conn, user) do
+ # Mock OIDC sign-in by creating a token directly
+ conn
+ |> Phoenix.ConnTest.init_test_session(%{})
+ |> AshAuthentication.Plug.Helpers.store_in_session(user)
+ end
+
+ @doc """
+ Signs in a user via OIDC and returns a connection with the user authenticated.
+ """
+ def conn_with_oidc_user(conn, user_attrs \\ %{}) do
+ user = create_test_user(user_attrs)
+ sign_in_user_via_oidc(conn, user)
+ end
+
setup tags do
Mv.DataCase.setup_sandbox(tags)
{:ok, conn: Phoenix.ConnTest.build_conn()}