Improve oidc only mode (#474)
All checks were successful
continuous-integration/drone/push Build is passing
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>
This commit is contained in:
parent
9b0f269ab6
commit
c381b86b5e
23 changed files with 579 additions and 54 deletions
|
|
@ -54,7 +54,7 @@ defmodule MvWeb.Plugs.CheckPagePermission do
|
|||
conn
|
||||
|> fetch_session()
|
||||
|> fetch_flash()
|
||||
|> put_flash(:error, "You don't have permission to access this page.")
|
||||
|> maybe_put_access_denied_flash(user)
|
||||
|> redirect(to: redirect_to)
|
||||
|> halt()
|
||||
end
|
||||
|
|
@ -75,6 +75,13 @@ defmodule MvWeb.Plugs.CheckPagePermission do
|
|||
|
||||
defp redirect_target(user), do: redirect_target_for_user(user)
|
||||
|
||||
# Only set "no permission" flash when user is logged in; unauthenticated users get redirect only, no flash.
|
||||
defp maybe_put_access_denied_flash(conn, nil), do: conn
|
||||
|
||||
defp maybe_put_access_denied_flash(conn, _user) do
|
||||
put_flash(conn, :error, "You don't have permission to access this page.")
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns true if the path is public (no auth/permission check).
|
||||
Used by LiveView hook to skip redirect on sign-in etc.
|
||||
|
|
|
|||
73
lib/mv_web/plugs/oidc_only_sign_in_redirect.ex
Normal file
73
lib/mv_web/plugs/oidc_only_sign_in_redirect.ex
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
defmodule MvWeb.Plugs.OidcOnlySignInRedirect do
|
||||
@moduledoc """
|
||||
When OIDC-only mode is active:
|
||||
- GET /sign-in redirects to the OIDC flow when OIDC is configured (sign-in page skipped).
|
||||
- GET /sign-in?oidc_failed=1 is not redirected, so the sign-in page is shown after an OIDC
|
||||
failure (avoids redirect loop when the provider is down or misconfigured).
|
||||
- GET /auth/user/password/sign_in_with_token is rejected (redirect to /sign-in with error)
|
||||
so password sign-in cannot complete.
|
||||
"""
|
||||
import Plug.Conn
|
||||
import Phoenix.Controller
|
||||
|
||||
alias Mv.Config
|
||||
|
||||
def init(opts), do: opts
|
||||
|
||||
def call(conn, _opts) do
|
||||
conn
|
||||
|> maybe_redirect_sign_in_to_oidc()
|
||||
|> maybe_reject_password_token_sign_in()
|
||||
end
|
||||
|
||||
defp maybe_redirect_sign_in_to_oidc(conn) do
|
||||
if conn.request_path != "/sign-in" or conn.method != "GET" do
|
||||
conn
|
||||
else
|
||||
conn = fetch_query_params(conn)
|
||||
maybe_redirect_sign_in_to_oidc_checked(conn)
|
||||
end
|
||||
end
|
||||
|
||||
defp maybe_redirect_sign_in_to_oidc_checked(conn) do
|
||||
cond do
|
||||
# Show sign-in page when returning from OIDC failure to avoid redirect loop.
|
||||
conn.query_params["oidc_failed"] -> conn
|
||||
Config.oidc_only?() and Config.oidc_configured?() -> redirect_and_halt(conn)
|
||||
true -> conn
|
||||
end
|
||||
end
|
||||
|
||||
defp redirect_and_halt(conn) do
|
||||
conn
|
||||
|> redirect(to: "/auth/user/oidc")
|
||||
|> halt()
|
||||
end
|
||||
|
||||
defp maybe_reject_password_token_sign_in(conn) do
|
||||
if conn.halted, do: conn, else: reject_password_token_sign_in_if_applicable(conn)
|
||||
end
|
||||
|
||||
defp reject_password_token_sign_in_if_applicable(conn) do
|
||||
path = conn.request_path
|
||||
|
||||
password_token_path? =
|
||||
path =~ ~r|/auth/user/password/sign_in_with_token| and conn.method == "GET"
|
||||
|
||||
if password_token_path? and Config.oidc_only?() do
|
||||
message =
|
||||
Gettext.dgettext(
|
||||
MvWeb.Gettext,
|
||||
"default",
|
||||
"Only sign-in via Single Sign-On (SSO) is allowed."
|
||||
)
|
||||
|
||||
conn
|
||||
|> put_flash(:error, message)
|
||||
|> redirect(to: "/sign-in")
|
||||
|> halt()
|
||||
else
|
||||
conn
|
||||
end
|
||||
end
|
||||
end
|
||||
Loading…
Add table
Add a link
Reference in a new issue