Add security measures for SSO-only users: block password resets, enforce SSO authentication, and refactor validations for JIT provisioning. (#569)

Co-authored-by: Josh Waldrep <joshua.waldrep5+github@gmail.com>
This commit is contained in:
LPW
2026-01-07 14:17:23 -05:00
committed by GitHub
parent 66671d9e1f
commit 02e203e8ee
9 changed files with 191 additions and 10 deletions

View File

@@ -1,5 +1,7 @@
class User < ApplicationRecord
has_secure_password
# Allow nil password for SSO-only users (JIT provisioning).
# Custom validation ensures password is present for non-SSO registration.
has_secure_password validations: false
belongs_to :family
belongs_to :last_viewed_chat, class_name: "Chat", optional: true
@@ -17,6 +19,11 @@ class User < ApplicationRecord
validate :ensure_valid_profile_image
validates :default_period, inclusion: { in: Period::PERIODS.keys }
validates :default_account_order, inclusion: { in: AccountOrder::ORDERS.keys }
# Password is required on create unless the user is being created via SSO JIT.
# SSO JIT users have password_digest = nil and authenticate via OIDC only.
validates :password, presence: true, on: :create, unless: :skip_password_validation?
validates :password, length: { minimum: 8 }, allow_nil: true
normalizes :email, with: ->(email) { email.strip.downcase }
normalizes :unconfirmed_email, with: ->(email) { email&.strip&.downcase }
@@ -104,6 +111,20 @@ class User < ApplicationRecord
ai_enabled && ai_available?
end
# SSO-only users have OIDC identities but no local password.
# They cannot use password reset or local login.
def sso_only?
password_digest.nil? && oidc_identities.exists?
end
# Check if user has a local password set (can authenticate locally)
def has_local_password?
password_digest.present?
end
# Attribute to skip password validation during SSO JIT provisioning
attr_accessor :skip_password_validation
# Deactivation
validate :can_deactivate, if: -> { active_changed? && !active }
after_update_commit :purge_later, if: -> { saved_change_to_active?(from: true, to: false) }
@@ -258,6 +279,10 @@ class User < ApplicationRecord
end
private
def skip_password_validation?
skip_password_validation == true
end
def default_dashboard_section_order
%w[cashflow_sankey outflows_donut net_worth_chart balance_sheet]
end