require Logger defmodule MvWeb.AuthController do use MvWeb, :controller use AshAuthentication.Phoenix.Controller def success(conn, activity, user, _token) do return_to = get_session(conn, :return_to) || ~p"/" message = case activity do {:confirm_new_user, :confirm} -> gettext("Your email address has now been confirmed") {:password, :reset} -> gettext("Your password has successfully been reset") _ -> gettext("You are now signed in") end conn |> delete_session(:return_to) |> store_in_session(user) # If your resource has a different name, update the assign name here (i.e :current_admin) |> assign(:current_user, user) |> put_flash(:info, message) |> redirect(to: return_to) end def failure(conn, activity, reason) do # Log the error for debugging Logger.warning( "Authentication failure - Activity: #{inspect(activity)}, Reason: #{inspect(reason)}" ) case {activity, reason} do # OIDC registration with existing email requires password verification (direct error) {{:rauthy, :register}, %Ash.Error.Invalid{errors: errors}} -> handle_oidc_email_collision(conn, errors) # OIDC registration with existing email (wrapped in AuthenticationFailed) {{:rauthy, :register}, %AshAuthentication.Errors.AuthenticationFailed{ caused_by: %Ash.Error.Invalid{errors: errors} }} -> handle_oidc_email_collision(conn, errors) # OIDC sign-in failure (wrapped) {{:rauthy, :sign_in}, %AshAuthentication.Errors.AuthenticationFailed{caused_by: caused_by}} -> # Check if it's actually a registration issue case caused_by do %Ash.Error.Invalid{errors: errors} -> handle_oidc_email_collision(conn, errors) _ -> # Real sign-in failure conn |> put_flash(:error, gettext("Unable to sign in with OIDC. Please try again.")) |> redirect(to: ~p"/sign-in") end # OIDC callback failure (can be either sign-in or registration) {{:rauthy, :callback}, %AshAuthentication.Errors.AuthenticationFailed{caused_by: caused_by}} -> case caused_by do %Ash.Error.Invalid{errors: errors} -> handle_oidc_email_collision(conn, errors) _ -> conn |> put_flash(:error, gettext("Unable to authenticate with OIDC. Please try again.")) |> redirect(to: ~p"/sign-in") end {_, %AshAuthentication.Errors.AuthenticationFailed{ caused_by: %Ash.Error.Forbidden{ errors: [%AshAuthentication.Errors.CannotConfirmUnconfirmedUser{}] } }} -> message = gettext(""" You have already signed in another way, but have not confirmed your account. You can confirm your account using the link we sent to you, or by resetting your password. """) conn |> put_flash(:error, message) |> redirect(to: ~p"/sign-in") _ -> message = gettext("Incorrect email or password") conn |> put_flash(:error, message) |> redirect(to: ~p"/sign-in") end end # Handle OIDC email collision - user needs to verify password defp handle_oidc_email_collision(conn, errors) do password_verification_error = Enum.find(errors, fn err -> match?(%Mv.Accounts.User.Errors.PasswordVerificationRequired{}, err) end) case password_verification_error do %Mv.Accounts.User.Errors.PasswordVerificationRequired{ user_id: user_id, oidc_user_info: oidc_user_info } -> # Store the OIDC info in session for the linking flow conn |> put_session(:oidc_linking_user_id, user_id) |> put_session(:oidc_linking_user_info, oidc_user_info) |> put_flash( :info, gettext( "An account with this email already exists. Please verify your password to link your OIDC account." ) ) |> redirect(to: ~p"/auth/link-oidc-account") _ -> # Other validation errors - show generic error conn |> put_flash(:error, gettext("Unable to sign in. Please try again.")) |> redirect(to: ~p"/sign-in") end end def sign_out(conn, _params) do return_to = get_session(conn, :return_to) || ~p"/" conn |> clear_session(:mv) |> put_flash(:info, gettext("You are now signed out")) |> redirect(to: return_to) end end