diff --git a/app/controllers/admin/sso_providers_controller.rb b/app/controllers/admin/sso_providers_controller.rb index d47864f43..97bc86fa3 100644 --- a/app/controllers/admin/sso_providers_controller.rb +++ b/app/controllers/admin/sso_providers_controller.rb @@ -7,6 +7,12 @@ module Admin def index authorize SsoProvider @sso_providers = policy_scope(SsoProvider).order(:name) + + # Load runtime providers (from YAML/env) that might not be in the database + # This helps show users that legacy providers are active but not manageable via UI + @runtime_providers = Rails.configuration.x.auth.sso_providers || [] + db_provider_names = @sso_providers.pluck(:name) + @legacy_providers = @runtime_providers.reject { |p| db_provider_names.include?(p[:name].to_s) } end def show diff --git a/app/views/admin/sso_providers/_form.html.erb b/app/views/admin/sso_providers/_form.html.erb index 61b8a6c17..590ba740e 100644 --- a/app/views/admin/sso_providers/_form.html.erb +++ b/app/views/admin/sso_providers/_form.html.erb @@ -55,9 +55,13 @@ - <%= form.check_box :enabled, - label: "Enable this provider", - checked: sso_provider.enabled? %> +
+
+

<%= t("admin.sso_providers.form.enabled_label") %>

+

<%= t("admin.sso_providers.form.enabled_help") %>

+
+ <%= form.toggle :enabled %> +
diff --git a/app/views/admin/sso_providers/index.html.erb b/app/views/admin/sso_providers/index.html.erb index b99272db6..006dc2647 100644 --- a/app/views/admin/sso_providers/index.html.erb +++ b/app/views/admin/sso_providers/index.html.erb @@ -62,6 +62,44 @@
<% end %> + <% if @legacy_providers.any? %> + <%= settings_section title: t("admin.sso_providers.index.legacy_providers_title"), collapsible: true, open: true do %> +
+
+ <%= icon "alert-triangle", class: "w-5 h-5 text-amber-600 shrink-0" %> +

+ <%= t("admin.sso_providers.index.legacy_providers_notice") %> +

+
+
+ +
+ <% @legacy_providers.each do |provider| %> +
+
+ <% provider_icon = provider[:icon].presence || "key" %> + <%= icon provider_icon, class: "w-5 h-5 text-secondary" %> +
+

<%= provider[:label].presence || provider[:name] %>

+

+ <%= provider[:strategy].to_s.titleize %> · <%= provider[:name] %> + <% if provider[:issuer].present? %> + · <%= provider[:issuer] %> + <% end %> +

+
+
+
+ + <%= t("admin.sso_providers.index.env_configured") %> + +
+
+ <% end %> +
+ <% end %> + <% end %> + <%= settings_section title: "Configuration Mode", collapsible: true, open: false do %>
diff --git a/config/locales/views/admin/sso_providers/en.yml b/config/locales/views/admin/sso_providers/en.yml index c59c26380..ff26989aa 100644 --- a/config/locales/views/admin/sso_providers/en.yml +++ b/config/locales/views/admin/sso_providers/en.yml @@ -18,6 +18,9 @@ en: actions: "Actions" enabled: "Enabled" disabled: "Disabled" + legacy_providers_title: "Environment-Configured Providers" + legacy_providers_notice: "These providers are configured via environment variables or YAML and cannot be managed through this interface. To manage them here, migrate them to database-backed providers by enabling AUTH_PROVIDERS_SOURCE=db and recreating them in the UI." + env_configured: "Env/YAML" new: title: "Add SSO Provider" description: "Configure a new single sign-on authentication provider" @@ -51,6 +54,7 @@ en: icon_placeholder: "e.g., key, google, github" icon_help: "Lucide icon name (optional)" enabled_label: "Enable this provider" + enabled_help: "Users can sign in with this provider when enabled" issuer_label: "Issuer" issuer_placeholder: "https://accounts.google.com" issuer_help: "OIDC issuer URL (will validate .well-known/openid-configuration endpoint)" diff --git a/docs/hosting/oidc.md b/docs/hosting/oidc.md index 04be10bed..3aa676b8a 100644 --- a/docs/hosting/oidc.md +++ b/docs/hosting/oidc.md @@ -1,6 +1,6 @@ -# Configuring OpenID Connect and SSO providers +# Configuring OpenID Connect, SAML, and SSO Providers -This guide shows how to enable OpenID Connect (OIDC) and other single sign-on (SSO) providers for Sure using Google, GitHub, or another OIDC‑compatible identity provider (e.g. Keycloak, Authentik). +This guide shows how to enable OpenID Connect (OIDC), SAML 2.0, and other single sign-on (SSO) providers for Sure using Google, GitHub, or another identity provider (e.g. Keycloak, Authentik, Okta, Azure AD). It also documents the new `config/auth.yml` and environment variables that control: @@ -174,6 +174,26 @@ To enable Google: - `http://localhost:3000/auth//callback` +### 3.5 Bootstrapping the first super‑admin + +The first `super_admin` must be set via Rails console. Access the console in your container/pod or directly on the server: + +```bash +bin/rails console +``` + +Then promote a user: + +```ruby +# Set super_admin role +User.find_by(email: "admin@example.com").update!(role: :super_admin) + +# Verify +User.find_by(email: "admin@example.com").role # => "super_admin" +``` + +Once set, super‑admins can promote other users via the web UI at `/admin/users`. + --- ## 4. Example configurations @@ -419,6 +439,20 @@ To switch back to YAML-based configuration: 2. Restart the application 3. Providers will be loaded from `config/auth.yml` +### 6.6 JIT provisioning settings + +Each provider has a **Default Role** field (defaults to `member`) that sets the role for JIT-created users. + +**Role mapping from IdP groups:** + +Expand **"Role Mapping"** in the admin UI to map IdP group names to Sure roles. Enter comma-separated group names for each role: + +- **Super Admin Groups**: `Platform-Admins, IdP-Superusers` +- **Admin Groups**: `Team-Leads, Managers` +- **Member Groups**: `Everyone` or leave blank + +Mapping is case-sensitive and matches exact group claim values from the IdP. When a user belongs to multiple mapped groups, the highest role wins (`super_admin` > `admin` > `member`). If no groups match, the Default Role is used. + --- ## 7. Troubleshooting @@ -484,4 +518,50 @@ Each provider requires a callback URL configured in your identity provider: --- +## 9. SAML 2.0 Support + +Sure supports SAML 2.0 via database-backed providers. Select **"SAML 2.0"** as the strategy when adding a provider at `/admin/sso_providers`. + +Configure with either: +- **IdP Metadata URL** (recommended) - auto-fetches configuration +- **Manual config** - IdP SSO URL + certificate + +In your IdP, set: +- **ACS URL**: `https://yourdomain.com/auth//callback` +- **Entity ID**: `https://yourdomain.com` (your `APP_URL`) +- **Name ID**: Email Address + +--- + +## 10. User Administration + +Super‑admins can manage user roles at `/admin/users`. + +Roles: `member` (standard), `admin` (family admin), `super_admin` (platform admin). + +Note: Super‑admins cannot change their own role. + +--- + +## 11. Audit Logging + +SSO events are logged to `sso_audit_logs`: `login`, `login_failed`, `logout`, `logout_idp` (federated logout), `link`, `unlink`, `jit_account_created`. + +Query via console: + +```ruby +SsoAuditLog.by_event("login").recent.limit(50) +SsoAuditLog.by_event("login_failed").where("created_at > ?", 24.hours.ago) +``` + +--- + +## 12. User SSO Identity Management + +Users manage linked SSO identities at **Settings > Security**. + +SSO-only users (no password) cannot unlink their last identity. + +--- + For additional help, see the main [hosting documentation](../README.md) or open an issue on GitHub.