mitgliederverwaltung/docs/admin-bootstrap-and-oidc-role-sync.md
Simon c381b86b5e
All checks were successful
continuous-integration/drone/push Build is passing
Improve oidc only mode (#474)
## 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>
2026-03-16 19:09:07 +01:00

3.4 KiB
Raw Blame History

Admin Bootstrap and OIDC Role Sync

Overview

  • Admin bootstrap: In production, the Docker entrypoint runs migrate, then Mv.Release.run_seeds/0 (bootstrap seeds; set RUN_DEV_SEEDS=true to also run dev seeds), then seed_admin/0 from ENV, then the server. Password can be changed without redeploy via bin/mv eval "Mv.Release.seed_admin()".
  • OIDC role sync: Optional mapping from OIDC groups (e.g. from Authentik profile scope) to the Admin role. Users in the configured admin group get the Admin role on registration and on each sign-in.

Admin Bootstrap (Part A)

Environment Variables

  • RUN_DEV_SEEDS If set to "true", run_seeds/0 also runs dev seeds (members, groups, sample data). Otherwise only bootstrap seeds run.
  • ADMIN_EMAIL Email of the admin user to create/update. If unset, seed_admin/0 does nothing.
  • ADMIN_PASSWORD Password for the admin user. If unset (and no file), no new user is created; if a user with ADMIN_EMAIL already exists (e.g. OIDC-only), their role is set to Admin (no password change).
  • ADMIN_PASSWORD_FILE Path to a file containing the password (e.g. Docker secret).

Release Tasks

  • Mv.Release.run_seeds/0 Runs bootstrap seeds (fee types, custom fields, roles, settings). If RUN_DEV_SEEDS env is "true", also runs dev seeds (members, groups, sample data). Idempotent.
  • Mv.Release.seed_admin/0 Reads ADMIN_EMAIL and password from ADMIN_PASSWORD or ADMIN_PASSWORD_FILE. If both email and password are set: creates or updates the user with the Admin role. If only ADMIN_EMAIL is set: sets the Admin role on an existing user with that email (for OIDC-only admins); does not create a user. Idempotent.

Entrypoint

  • rel/overlays/bin/docker-entrypoint.sh After migrate, runs run_seeds(), then seed_admin(), then starts the server.

Seeds (Dev/Test)

  • priv/repo/seeds.exs Uses ADMIN_PASSWORD or ADMIN_PASSWORD_FILE when set; otherwise fallback "testpassword" only in dev/test.

OIDC Role Sync (Part B)

Configuration

  • OIDC_ADMIN_GROUP_NAME OIDC group name that maps to the Admin role. If unset, no role sync.
  • OIDC_GROUPS_CLAIM JWT claim name for group list (default "groups").
  • Module: Mv.OidcRoleSyncConfig (oidc_admin_group_name/0, oidc_groups_claim/0).

Sign-in page (OIDC-only mode)

  • OIDC_ONLY (or Settings → OIDC → "Only OIDC sign-in") When set to true/1/yes and OIDC is configured, the sign-in page shows only the Single Sign-On button (password login is hidden). ENV takes precedence over Settings.
  • Redirect loop fix: After an OIDC failure (e.g. provider down), the app redirects to /sign-in?oidc_failed=1. The plug OidcOnlySignInRedirect does not redirect that request back to OIDC, so the sign-in page is shown with the error (no endless redirect).

Sync Logic

  • Mv.OidcRoleSync.apply_admin_role_from_user_info(user, user_info) If admin group configured, sets user role to Admin or Mitglied based on user_info groups.

Where It Runs

  1. Registration: register_with_oidc after_action calls OidcRoleSync.
  2. Sign-in: sign_in_with_oidc prepare after_action calls OidcRoleSync for each user.

Internal Action

  • User.set_role_from_oidc_sync Internal update (role_id only). Used by OidcRoleSync; not exposed.

See Also

  • .env.example Admin and OIDC group env vars.
  • lib/mv/release.ex seed_admin/0.
  • lib/mv/oidc_role_sync.ex Sync implementation.
  • docs/oidc-account-linking.md OIDC account linking.