diff --git a/app/components/UI/account/activity_date.html.erb b/app/components/UI/account/activity_date.html.erb index 672c6d77c..a1a330588 100644 --- a/app/components/UI/account/activity_date.html.erb +++ b/app/components/UI/account/activity_date.html.erb @@ -6,7 +6,7 @@ <%= check_box_tag "#{date}_entries_selection", class: ["checkbox checkbox--light hidden lg:block", "lg:hidden": entries.size == 0], id: "selection_entry_#{date}", - data: { + data: { action: "bulk-select#toggleGroupSelection", checkbox_toggle_target: "selectionEntry" } %> diff --git a/app/components/UI/account/activity_feed.html.erb b/app/components/UI/account/activity_feed.html.erb index 362e1af06..f46053401 100644 --- a/app/components/UI/account/activity_feed.html.erb +++ b/app/components/UI/account/activity_feed.html.erb @@ -77,7 +77,7 @@
<%= check_box_tag "selection_entry", class: "checkbox checkbox--light hidden lg:block", - data: { + data: { action: "bulk-select#togglePageSelection", checkbox_toggle_target: "selectionEntry" } %> diff --git a/app/controllers/settings/securities_controller.rb b/app/controllers/settings/securities_controller.rb index 756accf79..fd6791994 100644 --- a/app/controllers/settings/securities_controller.rb +++ b/app/controllers/settings/securities_controller.rb @@ -6,5 +6,6 @@ class Settings::SecuritiesController < ApplicationController [ "Home", root_path ], [ "Security", nil ] ] + @oidc_identities = Current.user.oidc_identities.order(:provider) end end diff --git a/app/controllers/settings/sso_identities_controller.rb b/app/controllers/settings/sso_identities_controller.rb index f42175c62..97f946a90 100644 --- a/app/controllers/settings/sso_identities_controller.rb +++ b/app/controllers/settings/sso_identities_controller.rb @@ -3,20 +3,12 @@ class Settings::SsoIdentitiesController < ApplicationController layout "settings" - def show - @oidc_identities = Current.user.oidc_identities.order(:provider) - @breadcrumbs = [ - [ t("settings.nav.home"), root_path ], - [ t(".page_title"), nil ] - ] - end - def destroy @identity = Current.user.oidc_identities.find(params[:id]) # Prevent unlinking last identity if user has no password if Current.user.oidc_identities.count == 1 && Current.user.password_digest.blank? - redirect_to settings_sso_identities_path, alert: t(".cannot_unlink_last") + redirect_to settings_security_path, alert: t(".cannot_unlink_last") return end @@ -30,6 +22,6 @@ class Settings::SsoIdentitiesController < ApplicationController request: request ) - redirect_to settings_sso_identities_path, notice: t(".success", provider: provider_name) + redirect_to settings_security_path, notice: t(".success", provider: provider_name) end end diff --git a/app/helpers/settings_helper.rb b/app/helpers/settings_helper.rb index 1428aeda0..a907cd7c8 100644 --- a/app/helpers/settings_helper.rb +++ b/app/helpers/settings_helper.rb @@ -6,7 +6,6 @@ module SettingsHelper { name: "Preferences", path: :settings_preferences_path }, { name: "Profile Info", path: :settings_profile_path }, { name: "Security", path: :settings_security_path }, - { name: "Connected Accounts", path: :settings_sso_identities_path, condition: :has_sso_connections? }, { name: "Billing", path: :settings_billing_path, condition: :not_self_hosted? }, # Transactions section { name: "Categories", path: :categories_path }, @@ -82,8 +81,4 @@ module SettingsHelper def self_hosted_and_admin? self_hosted? && admin_user? end - - def has_sso_connections? - Current.user&.oidc_identities&.exists? || AuthConfig.sso_providers.any? - end end diff --git a/app/policies/sso_provider_policy.rb b/app/policies/sso_provider_policy.rb index 5c975dc66..d68010415 100644 --- a/app/policies/sso_provider_policy.rb +++ b/app/policies/sso_provider_policy.rb @@ -34,6 +34,10 @@ class SsoProviderPolicy < ApplicationPolicy update? end + def test_connection? + user&.super_admin? + end + class Scope < ApplicationPolicy::Scope def resolve if user&.super_admin? diff --git a/app/views/accounts/show/_activity.html.erb b/app/views/accounts/show/_activity.html.erb index 7bd35639b..dbed8ad52 100644 --- a/app/views/accounts/show/_activity.html.erb +++ b/app/views/accounts/show/_activity.html.erb @@ -67,7 +67,7 @@
<%= check_box_tag "selection_entry", class: "checkbox checkbox--light hidden lg:block", - data: { + data: { action: "bulk-select#togglePageSelection", checkbox_toggle_target: "selectionEntry" } %> diff --git a/app/views/budget_categories/_budget_category.html.erb b/app/views/budget_categories/_budget_category.html.erb index 3ce90e6f4..a241101d2 100644 --- a/app/views/budget_categories/_budget_category.html.erb +++ b/app/views/budget_categories/_budget_category.html.erb @@ -6,7 +6,7 @@ <% if budget_category.initialized? %> <%# Category Header with Status Badge %>
-
<% end %> -
\ No newline at end of file +
diff --git a/app/views/entries/_entry_group.html.erb b/app/views/entries/_entry_group.html.erb index 7a0d14470..5499c9f84 100644 --- a/app/views/entries/_entry_group.html.erb +++ b/app/views/entries/_entry_group.html.erb @@ -6,7 +6,7 @@ <%= check_box_tag "#{date}_entries_selection", class: ["checkbox checkbox--light hidden lg:block", "lg:hidden": entries.size == 0], id: "selection_entry_#{date}", - data: { + data: { action: "bulk-select#toggleGroupSelection", checkbox_toggle_target: "selectionEntry" } %> diff --git a/app/views/import/configurations/_rule_import.html.erb b/app/views/import/configurations/_rule_import.html.erb index 7089a40ad..eb0f8be8b 100644 --- a/app/views/import/configurations/_rule_import.html.erb +++ b/app/views/import/configurations/_rule_import.html.erb @@ -12,4 +12,3 @@ <%= form.submit t("import.configurations.rule_import.process_button"), disabled: import.complete? %> <% end %>
- diff --git a/app/views/pages/dashboard/_outflows_donut.html.erb b/app/views/pages/dashboard/_outflows_donut.html.erb index 2657662c7..4b11de35c 100644 --- a/app/views/pages/dashboard/_outflows_donut.html.erb +++ b/app/views/pages/dashboard/_outflows_donut.html.erb @@ -78,7 +78,7 @@ action: "mouseenter->donut-chart#highlightSegment mouseleave->donut-chart#unhighlightSegment" } do %>
-

<%= t("recurring_transactions.title") %>

- <% unless @family.recurring_transactions_disabled? %> + <% unless @family.recurring_transactions_disabled? %> <%= render DS::Menu.new do |menu| %> <% menu.with_item( variant: "button", diff --git a/app/views/rules/index.html.erb b/app/views/rules/index.html.erb index 23613b959..f40020abe 100644 --- a/app/views/rules/index.html.erb +++ b/app/views/rules/index.html.erb @@ -117,12 +117,12 @@ <% @recent_runs.each do |run| %> - + "> <%= run.executed_at.strftime("%b %d, %Y %I:%M %p") %> - + "> <%= t("rules.recent_runs.execution_types.#{run.execution_type}") %> diff --git a/app/views/settings/llm_usages/show.html.erb b/app/views/settings/llm_usages/show.html.erb index 2e32e2f4c..387ebaf56 100644 --- a/app/views/settings/llm_usages/show.html.erb +++ b/app/views/settings/llm_usages/show.html.erb @@ -117,7 +117,7 @@ <% @llm_usages.each do |usage| %> - + "> <%= usage.created_at.strftime("%b %d, %Y %I:%M %p") %> diff --git a/app/views/settings/providers/show.html.erb b/app/views/settings/providers/show.html.erb index 4f84027ad..559a54972 100644 --- a/app/views/settings/providers/show.html.erb +++ b/app/views/settings/providers/show.html.erb @@ -26,7 +26,6 @@ <% end %> - <%= settings_section title: "Enable Banking (beta)", collapsible: true, open: false do %> <%= render "settings/providers/enable_banking_panel" %> diff --git a/app/views/settings/securities/show.html.erb b/app/views/settings/securities/show.html.erb index 1d3a7350f..f0cfb56bb 100644 --- a/app/views/settings/securities/show.html.erb +++ b/app/views/settings/securities/show.html.erb @@ -44,3 +44,58 @@
<% end %> + +<% if @oidc_identities.any? || AuthConfig.sso_providers.any? %> + <%= settings_section title: t(".sso_title"), subtitle: t(".sso_subtitle") do %> + <% if @oidc_identities.any? %> +
+ <% @oidc_identities.each do |identity| %> +
+
+
+ <%= icon identity.provider_config&.dig(:icon) || "key", class: "w-5 h-5 text-secondary" %> +
+
+

<%= identity.provider_config&.dig(:label) || identity.provider.titleize %>

+

<%= identity.info&.dig("email") || t(".sso_no_email") %>

+

+ <%= t(".sso_last_used") %>: + <%= identity.last_authenticated_at&.to_fs(:short) || t(".sso_never") %> +

+
+
+ <% if @oidc_identities.count > 1 || Current.user.password_digest.present? %> + <%= render DS::Button.new( + text: t(".sso_disconnect"), + variant: "outline", + size: "sm", + href: settings_sso_identity_path(identity), + method: :delete, + confirm: CustomConfirm.new( + title: t(".sso_confirm_title"), + body: t(".sso_confirm_body", provider: identity.provider_config&.dig(:label) || identity.provider.titleize), + btn_text: t(".sso_confirm_button"), + destructive: true + ) + ) %> + <% end %> +
+ <% end %> +
+ <% if @oidc_identities.count == 1 && Current.user.password_digest.blank? %> +
+
+ <%= icon "alert-triangle", class: "w-5 h-5 text-amber-600 shrink-0 mt-0.5" %> +

<%= t(".sso_warning_message") %>

+
+
+ <% end %> + <% else %> +
+ <%= icon "link", class: "w-12 h-12 mx-auto text-secondary mb-3" %> +

<%= t(".sso_no_identities") %>

+

<%= t(".sso_connect_hint") %>

+
+ <% end %> + <% end %> +<% end %> diff --git a/app/views/settings/sso_identities/show.html.erb b/app/views/settings/sso_identities/show.html.erb deleted file mode 100644 index b9046425e..000000000 --- a/app/views/settings/sso_identities/show.html.erb +++ /dev/null @@ -1,59 +0,0 @@ -<%= content_for :page_title, t(".page_title") %> - -<%= settings_section title: t(".identities_title"), subtitle: t(".identities_subtitle") do %> - <% if @oidc_identities.any? %> -
- <% @oidc_identities.each do |identity| %> -
-
-
- <%= icon identity.provider_config&.dig(:icon) || "key", class: "w-5 h-5 text-secondary" %> -
-
-

<%= identity.provider_config&.dig(:label) || identity.provider.titleize %>

-

<%= identity.info&.dig("email") || t(".no_email") %>

-

- <%= t(".last_used") %>: - <%= identity.last_authenticated_at&.to_fs(:short) || t(".never") %> -

-
-
- <% if @oidc_identities.count > 1 || Current.user.password_digest.present? %> - <%= render DS::Button.new( - text: t(".disconnect"), - variant: "outline", - size: "sm", - href: settings_sso_identity_path(identity), - method: :delete, - confirm: CustomConfirm.new( - title: t(".confirm_title"), - body: t(".confirm_body", provider: identity.provider_config&.dig(:label) || identity.provider.titleize), - btn_text: t(".confirm_button"), - destructive: true - ) - ) %> - <% end %> -
- <% end %> -
- <% else %> -
- <%= icon "link", class: "w-12 h-12 mx-auto text-secondary mb-3" %> -

<%= t(".no_identities") %>

- <% if AuthConfig.sso_providers.any? %> -

<%= t(".connect_hint") %>

- <% end %> -
- <% end %> -<% end %> - -<% if @oidc_identities.count == 1 && Current.user.password_digest.blank? %> - <%= settings_section title: t(".warning_title") do %> -
-
- <%= icon "alert-triangle", class: "w-5 h-5 text-amber-600 shrink-0 mt-0.5" %> -

<%= t(".warning_message") %>

-
-
- <% end %> -<% end %> diff --git a/app/views/shared/_demo_warning.html.erb b/app/views/shared/_demo_warning.html.erb index e1d003dac..ba3c08f23 100644 --- a/app/views/shared/_demo_warning.html.erb +++ b/app/views/shared/_demo_warning.html.erb @@ -10,4 +10,3 @@
- diff --git a/app/views/simplefin_items/_simplefin_item.html.erb b/app/views/simplefin_items/_simplefin_item.html.erb index 680a89d04..93510054c 100644 --- a/app/views/simplefin_items/_simplefin_item.html.erb +++ b/app/views/simplefin_items/_simplefin_item.html.erb @@ -125,8 +125,6 @@ ) %> <% end %> - - <%= render DS::Menu.new do |menu| %> <% menu.with_item( variant: "button", @@ -146,7 +144,6 @@ <%= render "accounts/index/account_groups", accounts: simplefin_item.accounts %> <% end %> - <%# Sync summary (collapsible) Prefer controller-provided map; fallback to latest sync stats so Turbo broadcasts can render the summary without requiring a full page refresh. %> diff --git a/app/views/transactions/index.html.erb b/app/views/transactions/index.html.erb index 58055f342..7251c4c0a 100644 --- a/app/views/transactions/index.html.erb +++ b/app/views/transactions/index.html.erb @@ -63,7 +63,7 @@
<%= check_box_tag "selection_entry", class: "checkbox checkbox--light hidden lg:block", - data: { + data: { action: "bulk-select#togglePageSelection", checkbox_toggle_target: "selectionEntry" } %> diff --git a/config/locales/views/settings/en.yml b/config/locales/views/settings/en.yml index 567310cc8..e84e9e056 100644 --- a/config/locales/views/settings/en.yml +++ b/config/locales/views/settings/en.yml @@ -89,6 +89,23 @@ en: securities: show: page_title: Security + mfa_title: Two-Factor Authentication + mfa_description: Add an extra layer of security to your account by requiring a code from your authenticator app when signing in + enable_mfa: Enable 2FA + disable_mfa: Disable 2FA + disable_mfa_confirm: Are you sure you want to disable two-factor authentication? + sso_title: Connected Accounts + sso_subtitle: Manage your single sign-on account connections + sso_disconnect: Disconnect + sso_last_used: Last used + sso_never: Never + sso_no_email: No email + sso_no_identities: No SSO accounts connected + sso_connect_hint: Log out and sign in with an SSO provider to connect an account. + sso_confirm_title: Disconnect Account? + sso_confirm_body: Are you sure you want to disconnect your %{provider} account? You can reconnect it later by signing in with that provider again. + sso_confirm_button: Disconnect + sso_warning_message: This is your only login method. You should set a password in your security settings before disconnecting, otherwise you may be locked out of your account. settings_nav: accounts_label: Accounts advanced_section_title: Advanced diff --git a/config/locales/views/settings/sso_identities/en.yml b/config/locales/views/settings/sso_identities/en.yml deleted file mode 100644 index c989ee974..000000000 --- a/config/locales/views/settings/sso_identities/en.yml +++ /dev/null @@ -1,22 +0,0 @@ ---- -en: - settings: - sso_identities: - show: - page_title: "Connected Accounts" - identities_title: "SSO Connections" - identities_subtitle: "Manage your single sign-on account connections" - disconnect: "Disconnect" - last_used: "Last used" - never: "Never" - no_email: "No email" - no_identities: "No SSO accounts connected" - connect_hint: "Log out and sign in with an SSO provider to connect an account." - confirm_title: "Disconnect Account?" - confirm_body: "Are you sure you want to disconnect your %{provider} account? You can reconnect it later by signing in with that provider again." - confirm_button: "Disconnect" - warning_title: "Important" - warning_message: "This is your only login method. You should set a password in your security settings before disconnecting, otherwise you may be locked out of your account." - destroy: - success: "Successfully disconnected %{provider}" - cannot_unlink_last: "Cannot disconnect your only login method. Please set a password first." diff --git a/config/routes.rb b/config/routes.rb index 9d0e8fa4a..f591ea2f4 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -91,7 +91,6 @@ Rails.application.routes.draw do end resource :billing, only: :show resource :security, only: :show - resource :sso_identities, only: :show resources :sso_identities, only: :destroy resource :api_key, only: [ :show, :new, :create, :destroy ] resource :ai_prompts, only: :show