All checks were successful
continuous-integration/drone/push Build is passing
## Description of the implemented changes The changes were: - [x] Bugfixing - [x] New Feature - [ ] Breaking Change - [x] Refactoring **OIDC-only mode improvements and UX tweaks (success toasts, unauthenticated redirect).** ## What has been changed? ### OIDC-only mode (new feature) - **Admin settings:** "Only OIDC sign-in" is an immediate toggle at the top of the OIDC section (no save button). Enabling it also turns off "Allow direct registration". When OIDC-only is on, the registration checkbox is disabled and shows a tooltip (DaisyUI `<.tooltip>`). - **Backend:** Password sign-in is forbidden via Ash policy (`OidcOnlyActive` check). Password registration is blocked via validation `OidcOnlyBlocksPasswordRegistration`. New plug `OidcOnlySignInRedirect`: when OIDC-only and OIDC are configured, GET `/sign-in` redirects to the OIDC flow; GET `/auth/user/password/sign_in_with_token` is rejected with redirect + flash. `AuthController.success/4` also rejects password sign-in when OIDC-only. - **Tests:** GlobalSettingsLive (OIDC-only UI), AuthController (redirect and password sign-in rejection), User authentication (register_with_password blocked when OIDC-only). ### UX / behaviour (no new feature flag) - **Success toasts:** Success flash messages auto-dismiss after 5 seconds via JS hook `FlashAutoDismiss` and optional `auto_clear_ms` on `<.flash>` (used for success in root layout and `flash_group`). - **Unauthenticated users:** Redirect to sign-in without the "You don't have permission to access this page" flash; that message is only shown to logged-in users who lack access. Logic in `LiveHelpers` and `CheckPagePermission` plug; test updated accordingly. ### Other - Layouts: comment about unprocessed join-request count no longer uses "TODO" (Credo). - Gettext: German translation for "Home" (Startseite); POT/PO kept in sync. - CHANGELOG: Unreleased section updated with the above. ## Definition of Done ### Code Quality - [x] No new technical depths - [x] Linting passed - [x] Documentation is added where needed (module docs, comments where non-obvious) ### Accessibility - [x] New elements are properly defined with html-tags (labels, aria-label on checkboxes) - [x] Colour contrast follows WCAG criteria (unchanged) - [x] Aria labels are added when needed (e.g. oidc-only and registration checkboxes) - [x] Everything is accessible by keyboard (toggles and buttons unchanged) - [x] Tab-Order is comprehensible - [x] All interactive elements have a visible focus (existing patterns) ### Testing - [x] Tests for new code are written (OIDC-only UI, auth controller, user auth; SMTP config builder and mailer) - [x] All tests pass - [ ] axe-core dev tools show no critical or major issues (not re-run for this PR; suggest spot-check on settings and sign-in) ## Additional Notes - **OIDC-only:** When the `OIDC_ONLY` env var is set, the toggle is read-only and shows "(From OIDC_ONLY)". When OIDC is not configured, the toggle is disabled. - **Invalidation:** Enabling OIDC-only sets `registration_enabled: false` in one update; disabling OIDC-only only updates `oidc_only` (registration left as-is). - **Review focus:** Plug order in router (OidcOnlySignInRedirect), policy/validation order in User, and that all OIDC-only paths (form, plug, controller) stay consistent. Reviewed-on: #474 Co-authored-by: Simon <s.thiessen@local-it.org> Co-committed-by: Simon <s.thiessen@local-it.org>
178 lines
6.1 KiB
Elixir
178 lines
6.1 KiB
Elixir
defmodule MvWeb.GlobalSettingsLiveTest do
|
|
use MvWeb.ConnCase, async: true
|
|
import Phoenix.LiveViewTest
|
|
alias Mv.Membership
|
|
|
|
describe "Global Settings LiveView" do
|
|
setup %{conn: conn} do
|
|
user = create_test_user(%{email: "admin@example.com"})
|
|
conn = conn_with_oidc_user(conn, user)
|
|
{:ok, conn: conn, user: user}
|
|
end
|
|
|
|
test "renders the global settings page", %{conn: conn} do
|
|
{:ok, _view, html} = live(conn, ~p"/settings")
|
|
|
|
assert html =~ "Club Settings"
|
|
assert html =~ "Settings"
|
|
end
|
|
|
|
test "displays current club name", %{conn: conn} do
|
|
# Set initial club name
|
|
{:ok, settings} = Membership.get_settings()
|
|
{:ok, _updated} = Membership.update_settings(settings, %{club_name: "Test Club"})
|
|
|
|
{:ok, _view, html} = live(conn, ~p"/settings")
|
|
|
|
assert html =~ "Test Club"
|
|
end
|
|
|
|
test "can update club name via form", %{conn: conn} do
|
|
{:ok, view, _html} = live(conn, ~p"/settings")
|
|
|
|
# Submit form with new club name
|
|
assert view
|
|
|> form("#settings-form", %{setting: %{club_name: "Updated Club Name"}})
|
|
|> render_submit()
|
|
|
|
# Check for success message
|
|
assert render(view) =~ "Settings updated successfully"
|
|
assert render(view) =~ "Updated Club Name"
|
|
end
|
|
|
|
test "shows error when club_name is empty", %{conn: conn} do
|
|
{:ok, view, _html} = live(conn, ~p"/settings")
|
|
|
|
# Submit form with empty club name
|
|
html =
|
|
view
|
|
|> form("#settings-form", %{setting: %{club_name: ""}})
|
|
|> render_submit()
|
|
|
|
assert html =~ "must be present"
|
|
end
|
|
|
|
test "shows error when club_name is missing", %{conn: conn} do
|
|
{:ok, view, _html} = live(conn, ~p"/settings")
|
|
|
|
# Submit form with club_name explicitly set to empty string
|
|
# (Phoenix forms will keep existing value if field is omitted)
|
|
html =
|
|
view
|
|
|> form("#settings-form", %{setting: %{club_name: ""}})
|
|
|> render_submit()
|
|
|
|
assert html =~ "must be present"
|
|
end
|
|
end
|
|
|
|
describe "SMTP / E-Mail section" do
|
|
setup %{conn: conn} do
|
|
user = create_test_user(%{email: "admin@example.com"})
|
|
conn = conn_with_oidc_user(conn, user)
|
|
{:ok, conn: conn, user: user}
|
|
end
|
|
|
|
test "renders SMTP section with host/port fields and test email area", %{conn: conn} do
|
|
{:ok, _view, html} = live(conn, ~p"/settings")
|
|
# Section title (Gettext key: SMTP or E-Mail per concept)
|
|
assert html =~ "SMTP" or html =~ "E-Mail"
|
|
end
|
|
|
|
test "shows Send test email button when SMTP is configured", %{conn: conn} do
|
|
{:ok, view, _html} = live(conn, ~p"/settings")
|
|
# When Mv.Config.smtp_configured?() is true, button and recipient input should be present
|
|
# In test env SMTP is typically not configured; we only assert the section exists
|
|
html = render(view)
|
|
assert html =~ "SMTP" or html =~ "E-Mail"
|
|
end
|
|
|
|
test "send test email with valid address shows success or error result", %{conn: conn} do
|
|
{:ok, view, _html} = live(conn, ~p"/settings")
|
|
|
|
if has_element?(view, "[data-testid='smtp-test-email-form']") do
|
|
# Submit the test-email form (phx-submit) with a valid recipient address
|
|
view
|
|
|> form("[data-testid='smtp-test-email-form']", %{"to_email" => "test@example.com"})
|
|
|> render_submit()
|
|
|
|
# Result area must appear regardless of success or error
|
|
assert has_element?(view, "[data-testid='smtp-test-result']")
|
|
else
|
|
assert render(view) =~ "Settings"
|
|
end
|
|
end
|
|
|
|
test "shows warning when SMTP is not configured in production", %{conn: conn} do
|
|
# Concept: in prod, show warning "SMTP is not configured. Transactional emails..."
|
|
# In test we only check that the section exists; warning visibility is env-dependent
|
|
{:ok, _view, html} = live(conn, ~p"/settings")
|
|
assert html =~ "SMTP" or html =~ "E-Mail" or html =~ "Settings"
|
|
end
|
|
end
|
|
|
|
describe "Authentication section when OIDC-only is enabled" do
|
|
setup %{conn: conn} do
|
|
user = create_test_user(%{email: "admin@example.com"})
|
|
conn = conn_with_oidc_user(conn, user)
|
|
{:ok, settings} = Membership.get_settings()
|
|
original_oidc_only = Map.get(settings, :oidc_only, false)
|
|
{:ok, _} = Membership.update_settings(settings, %{oidc_only: true})
|
|
{:ok, conn: conn, original_oidc_only: original_oidc_only}
|
|
end
|
|
|
|
@describetag :ui
|
|
test "registration checkbox is disabled when OIDC-only is enabled", %{
|
|
conn: conn,
|
|
original_oidc_only: original
|
|
} do
|
|
try do
|
|
{:ok, view, _html} = live(conn, ~p"/settings")
|
|
assert has_element?(view, "#registration-enabled-checkbox[disabled]")
|
|
after
|
|
{:ok, s} = Membership.get_settings()
|
|
Membership.update_settings(s, %{oidc_only: original})
|
|
end
|
|
end
|
|
|
|
@describetag :ui
|
|
test "OIDC-only hint is visible when OIDC-only is enabled", %{
|
|
conn: conn,
|
|
original_oidc_only: original
|
|
} do
|
|
try do
|
|
{:ok, view, _html} = live(conn, ~p"/settings")
|
|
assert has_element?(view, "[data-testid='oidc-only-registration-hint']")
|
|
after
|
|
{:ok, s} = Membership.get_settings()
|
|
Membership.update_settings(s, %{oidc_only: original})
|
|
end
|
|
end
|
|
|
|
test "when OIDC-only is disabled, registration checkbox is enabled and can be toggled", %{
|
|
conn: conn,
|
|
original_oidc_only: original
|
|
} do
|
|
try do
|
|
{:ok, settings} = Membership.get_settings()
|
|
Membership.update_settings(settings, %{oidc_only: false})
|
|
|
|
{:ok, view, _html} = live(conn, ~p"/settings")
|
|
refute has_element?(view, "#registration-enabled-checkbox[disabled]")
|
|
|
|
initial_checked =
|
|
view |> element("#registration-enabled-checkbox") |> render() =~ "checked"
|
|
|
|
view
|
|
|> element("#registration-enabled-checkbox")
|
|
|> render_click()
|
|
|
|
new_checked = view |> element("#registration-enabled-checkbox") |> render() =~ "checked"
|
|
assert new_checked != initial_checked
|
|
after
|
|
{:ok, s} = Membership.get_settings()
|
|
Membership.update_settings(s, %{oidc_only: original})
|
|
end
|
|
end
|
|
end
|
|
end
|