Fixes missing Rauthy error message closes #289 #427
7 changed files with 163 additions and 12 deletions
|
|
@ -45,4 +45,11 @@ defmodule MvWeb.AuthOverrides do
|
||||||
Gettext.gettext(MvWeb.Gettext, "or")
|
Gettext.gettext(MvWeb.Gettext, "or")
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Hide AshAuthentication's Flash component since we use flash_group in root layout
|
||||||
|
# This prevents duplicate flash messages
|
||||||
|
override AshAuthentication.Phoenix.Components.Flash do
|
||||||
|
set :message_class_info, "hidden"
|
||||||
|
set :message_class_error, "hidden"
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,7 @@
|
||||||
</script>
|
</script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
<.flash_group flash={@flash} />
|
||||||
{@inner_content}
|
{@inner_content}
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
||||||
|
|
@ -57,7 +57,9 @@ defmodule MvWeb.AuthController do
|
||||||
handle_authentication_failed(conn, caused_by)
|
handle_authentication_failed(conn, caused_by)
|
||||||
|
|
||||||
_ ->
|
_ ->
|
||||||
redirect_with_error(conn, gettext("Incorrect email or password"))
|
conn
|
||||||
|
|> put_flash(:error, gettext("Incorrect email or password"))
|
||||||
|
|> redirect(to: ~p"/sign-in")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -74,14 +76,39 @@ defmodule MvWeb.AuthController do
|
||||||
handle_oidc_email_collision(conn, errors)
|
handle_oidc_email_collision(conn, errors)
|
||||||
|
|
||||||
_ ->
|
_ ->
|
||||||
redirect_with_error(conn, gettext("Unable to authenticate with OIDC. Please try again."))
|
conn
|
||||||
|
|> put_flash(:error, gettext("Unable to authenticate with OIDC. Please try again."))
|
||||||
|
|> redirect(to: ~p"/sign-in")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Handle Assent server unreachable errors (network/connectivity issues)
|
||||||
|
defp handle_rauthy_failure(conn, %Assent.ServerUnreachableError{} = err) do
|
||||||
|
# Use warning level: server unreachable is often transient, not a critical system error
|
||||||
|
Logger.warning("OIDC authentication server unreachable", safe_assent_meta(err))
|
||||||
|
|
||||||
|
conn
|
||||||
|
|> put_flash(:error, gettext("The authentication server is currently unavailable. Please try again later."))
|
||||||
|
|> redirect(to: ~p"/sign-in")
|
||||||
|
end
|
||||||
|
|
||||||
|
# Handle Assent invalid response errors (configuration or malformed responses)
|
||||||
|
defp handle_rauthy_failure(conn, %Assent.InvalidResponseError{} = err) do
|
||||||
|
# Use warning level: configuration errors are operational issues, not critical failures
|
||||||
|
Logger.warning("OIDC authentication invalid response", safe_assent_meta(err))
|
||||||
|
|
||||||
|
conn
|
||||||
|
|> put_flash(:error, gettext("Authentication configuration error. Please contact the administrator."))
|
||||||
|
|> redirect(to: ~p"/sign-in")
|
||||||
|
end
|
||||||
|
|
||||||
# Catch-all clause for any other error types
|
# Catch-all clause for any other error types
|
||||||
defp handle_rauthy_failure(conn, reason) do
|
defp handle_rauthy_failure(conn, reason) do
|
||||||
Logger.warning("Unhandled Rauthy failure reason: #{inspect(reason)}")
|
Logger.warning("Unhandled Rauthy failure reason: #{inspect(reason)}")
|
||||||
redirect_with_error(conn, gettext("Unable to authenticate with OIDC. Please try again."))
|
|
||||||
|
conn
|
||||||
|
|> put_flash(:error, gettext("Unable to authenticate with OIDC. Please try again."))
|
||||||
|
|> redirect(to: ~p"/sign-in")
|
||||||
end
|
end
|
||||||
|
|
||||||
# Handle generic AuthenticationFailed errors
|
# Handle generic AuthenticationFailed errors
|
||||||
|
|
@ -93,14 +120,20 @@ defmodule MvWeb.AuthController do
|
||||||
You can confirm your account using the link we sent to you, or by resetting your password.
|
You can confirm your account using the link we sent to you, or by resetting your password.
|
||||||
""")
|
""")
|
||||||
|
|
||||||
redirect_with_error(conn, message)
|
conn
|
||||||
|
|> put_flash(:error, message)
|
||||||
|
|> redirect(to: ~p"/sign-in")
|
||||||
else
|
else
|
||||||
redirect_with_error(conn, gettext("Authentication failed. Please try again."))
|
conn
|
||||||
|
|> put_flash(:error, gettext("Authentication failed. Please try again."))
|
||||||
|
|> redirect(to: ~p"/sign-in")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
defp handle_authentication_failed(conn, _other) do
|
defp handle_authentication_failed(conn, _other) do
|
||||||
redirect_with_error(conn, gettext("Authentication failed. Please try again."))
|
conn
|
||||||
|
|> put_flash(:error, gettext("Authentication failed. Please try again."))
|
||||||
|
|> redirect(to: ~p"/sign-in")
|
||||||
end
|
end
|
||||||
|
|
||||||
# Handle OIDC email collision - user needs to verify password to link accounts
|
# Handle OIDC email collision - user needs to verify password to link accounts
|
||||||
|
|
@ -112,7 +145,10 @@ defmodule MvWeb.AuthController do
|
||||||
nil ->
|
nil ->
|
||||||
# Check if it's a "different OIDC account" error or email uniqueness error
|
# Check if it's a "different OIDC account" error or email uniqueness error
|
||||||
error_message = extract_meaningful_error_message(errors)
|
error_message = extract_meaningful_error_message(errors)
|
||||||
redirect_with_error(conn, error_message)
|
|
||||||
|
conn
|
||||||
|
|> put_flash(:error, error_message)
|
||||||
|
|> redirect(to: ~p"/sign-in")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -177,13 +213,46 @@ defmodule MvWeb.AuthController do
|
||||||
|> redirect(to: ~p"/auth/link-oidc-account")
|
|> redirect(to: ~p"/auth/link-oidc-account")
|
||||||
end
|
end
|
||||||
|
|
||||||
# Generic error redirect helper
|
# Extract safe metadata from Assent errors for logging
|
||||||
defp redirect_with_error(conn, message) do
|
# Never logs sensitive data: no tokens, secrets, or full request URLs
|
||||||
conn
|
# Returns keyword list for Logger.warning/2
|
||||||
|> put_flash(:error, message)
|
defp safe_assent_meta(%{request_url: url} = err) when is_binary(url) do
|
||||||
|> redirect(to: ~p"/sign-in")
|
[
|
||||||
|
request_url: redact_url(url),
|
||||||
|
http_adapter: Map.get(err, :http_adapter)
|
||||||
|
]
|
||||||
|
|> Enum.filter(fn {_key, value} -> not is_nil(value) end)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp safe_assent_meta(%{response: %{status_code: status_code}} = err) do
|
||||||
|
[
|
||||||
|
status_code: status_code,
|
||||||
|
http_adapter: Map.get(err, :http_adapter)
|
||||||
|
]
|
||||||
|
|> Enum.filter(fn {_key, value} -> not is_nil(value) end)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp safe_assent_meta(err) do
|
||||||
|
# Only extract safe, simple fields
|
||||||
|
[
|
||||||
|
http_adapter: Map.get(err, :http_adapter)
|
||||||
|
]
|
||||||
|
|> Enum.filter(fn {_key, value} -> not is_nil(value) end)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Redact URL to only show scheme and host, hiding path, query, and fragments
|
||||||
|
defp redact_url(url) when is_binary(url) do
|
||||||
|
case URI.parse(url) do
|
||||||
|
%URI{scheme: scheme, host: host} when not is_nil(scheme) and not is_nil(host) ->
|
||||||
|
"#{scheme}://#{host}"
|
||||||
|
|
||||||
|
_ ->
|
||||||
|
"[redacted]"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp redact_url(_), do: "[redacted]"
|
||||||
|
|
||||||
def sign_out(conn, _params) do
|
def sign_out(conn, _params) do
|
||||||
return_to = get_session(conn, :return_to) || ~p"/"
|
return_to = get_session(conn, :return_to) || ~p"/"
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -582,6 +582,16 @@ msgstr "Ein Konto mit dieser E-Mail existiert bereits. Bitte verifiziere dein Pa
|
||||||
msgid "Unable to authenticate with OIDC. Please try again."
|
msgid "Unable to authenticate with OIDC. Please try again."
|
||||||
msgstr "OIDC-Authentifizierung fehlgeschlagen. Bitte versuche es erneut."
|
msgstr "OIDC-Authentifizierung fehlgeschlagen. Bitte versuche es erneut."
|
||||||
|
|
||||||
|
#: lib/mv_web/controllers/auth_controller.ex
|
||||||
|
#, elixir-autogen, elixir-format
|
||||||
|
msgid "The authentication server is currently unavailable. Please try again later."
|
||||||
|
msgstr "Der Authentifizierungsserver ist derzeit nicht erreichbar. Bitte versuche es später erneut."
|
||||||
|
|
||||||
|
#: lib/mv_web/controllers/auth_controller.ex
|
||||||
|
#, elixir-autogen, elixir-format
|
||||||
|
msgid "Authentication configuration error. Please contact the administrator."
|
||||||
|
msgstr "Authentifizierungskonfigurationsfehler. Bitte kontaktiere den Administrator."
|
||||||
|
|
||||||
#: lib/mv_web/controllers/auth_controller.ex
|
#: lib/mv_web/controllers/auth_controller.ex
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Unable to sign in. Please try again."
|
msgid "Unable to sign in. Please try again."
|
||||||
|
|
|
||||||
|
|
@ -583,6 +583,16 @@ msgstr ""
|
||||||
msgid "Unable to authenticate with OIDC. Please try again."
|
msgid "Unable to authenticate with OIDC. Please try again."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: lib/mv_web/controllers/auth_controller.ex
|
||||||
|
#, elixir-autogen, elixir-format
|
||||||
|
msgid "The authentication server is currently unavailable. Please try again later."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: lib/mv_web/controllers/auth_controller.ex
|
||||||
|
#, elixir-autogen, elixir-format
|
||||||
|
msgid "Authentication configuration error. Please contact the administrator."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: lib/mv_web/controllers/auth_controller.ex
|
#: lib/mv_web/controllers/auth_controller.ex
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Unable to sign in. Please try again."
|
msgid "Unable to sign in. Please try again."
|
||||||
|
|
|
||||||
|
|
@ -583,6 +583,16 @@ msgstr ""
|
||||||
msgid "Unable to authenticate with OIDC. Please try again."
|
msgid "Unable to authenticate with OIDC. Please try again."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: lib/mv_web/controllers/auth_controller.ex
|
||||||
|
#, elixir-autogen, elixir-format
|
||||||
|
msgid "The authentication server is currently unavailable. Please try again later."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: lib/mv_web/controllers/auth_controller.ex
|
||||||
|
#, elixir-autogen, elixir-format
|
||||||
|
msgid "Authentication configuration error. Please contact the administrator."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: lib/mv_web/controllers/auth_controller.ex
|
#: lib/mv_web/controllers/auth_controller.ex
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Unable to sign in. Please try again."
|
msgid "Unable to sign in. Please try again."
|
||||||
|
|
|
||||||
|
|
@ -248,4 +248,48 @@ defmodule MvWeb.AuthControllerTest do
|
||||||
|
|
||||||
assert to =~ "/auth/user/password/sign_in_with_token"
|
assert to =~ "/auth/user/password/sign_in_with_token"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# OIDC/Rauthy error handling tests
|
||||||
|
describe "handle_rauthy_failure/2" do
|
||||||
|
test "Assent.ServerUnreachableError redirects to sign-in with error flash", %{
|
||||||
|
conn: authenticated_conn
|
||||||
|
} do
|
||||||
|
conn = build_unauthenticated_conn(authenticated_conn)
|
||||||
|
# Create a mock Assent.ServerUnreachableError struct
|
||||||
|
error = %Assent.ServerUnreachableError{request_url: "https://auth.example.com/callback?token=secret123"}
|
||||||
|
|
||||||
|
conn = MvWeb.AuthController.failure(conn, {:rauthy, :callback}, error)
|
||||||
|
|
||||||
|
assert redirected_to(conn) == ~p"/sign-in"
|
||||||
|
assert get_flash(conn, :error) == "The authentication server is currently unavailable. Please try again later."
|
||||||
|
end
|
||||||
|
|
||||||
|
test "Assent.InvalidResponseError redirects to sign-in with error flash", %{
|
||||||
|
conn: authenticated_conn
|
||||||
|
} do
|
||||||
|
conn = build_unauthenticated_conn(authenticated_conn)
|
||||||
|
# Create a mock Assent.InvalidResponseError struct
|
||||||
|
error = %Assent.InvalidResponseError{
|
||||||
|
response: %{status_code: 400, body: "invalid_request"},
|
||||||
|
request_url: "https://auth.example.com/callback"
|
||||||
|
}
|
||||||
|
|
||||||
|
conn = MvWeb.AuthController.failure(conn, {:rauthy, :callback}, error)
|
||||||
|
|
||||||
|
assert redirected_to(conn) == ~p"/sign-in"
|
||||||
|
assert get_flash(conn, :error) == "Authentication configuration error. Please contact the administrator."
|
||||||
|
end
|
||||||
|
|
||||||
|
test "unknown reason triggers catch-all and redirects to sign-in with error flash", %{
|
||||||
|
conn: authenticated_conn
|
||||||
|
} do
|
||||||
|
conn = build_unauthenticated_conn(authenticated_conn)
|
||||||
|
unknown_reason = :oops
|
||||||
|
|
||||||
|
conn = MvWeb.AuthController.failure(conn, {:rauthy, :callback}, unknown_reason)
|
||||||
|
|
||||||
|
assert redirected_to(conn) == ~p"/sign-in"
|
||||||
|
assert get_flash(conn, :error) == "Unable to authenticate with OIDC. Please try again."
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue