You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
docuseal/app/models/user.rb

182 lines
5.6 KiB

# frozen_string_literal: true
# == Schema Information
#
# Table name: users
#
# id :bigint not null, primary key
# archived_at :datetime
# confirmation_sent_at :datetime
# confirmation_token :string
# confirmed_at :datetime
# consumed_timestep :integer
# current_sign_in_at :datetime
# current_sign_in_ip :string
# email :string not null
# encrypted_password :string not null
# failed_attempts :integer default(0), not null
# first_name :string
# last_name :string
# last_sign_in_at :datetime
# last_sign_in_ip :string
# locked_at :datetime
# otp_required_for_login :boolean default(FALSE), not null
# otp_secret :string
# remember_created_at :datetime
# reset_password_sent_at :datetime
# reset_password_token :string
# role :string not null
# sign_in_count :integer default(0), not null
# unconfirmed_email :string
# unlock_token :string
# uuid :string not null
# created_at :datetime not null
# updated_at :datetime not null
# account_id :bigint not null
#
# Indexes
#
# index_users_on_account_id (account_id)
# index_users_on_email (email) UNIQUE
# index_users_on_reset_password_token (reset_password_token) UNIQUE
# index_users_on_unlock_token (unlock_token) UNIQUE
# index_users_on_uuid (uuid) UNIQUE
#
# Foreign Keys
#
# fk_rails_... (account_id => accounts.id)
#
class User < ApplicationRecord
ROLES = [
ADMIN_ROLE = 'admin'
].freeze
EMAIL_REGEXP = /[^@;,<>\s]+@[^@;,<>\s]+/
FULL_EMAIL_REGEXP =
/\A[a-z0-9][.']?(?:(?:[a-z0-9_-]+[.+'])*[a-z0-9_-]+)*@(?:[a-z0-9]+[.-])*[a-z0-9]+\.[a-z]{2,}\z/i
has_one_attached :signature
has_one_attached :initials
belongs_to :account
has_one :access_token, dependent: :destroy
has_many :access_tokens, dependent: :destroy
has_many :mcp_tokens, dependent: :destroy
has_many :templates, dependent: :destroy, foreign_key: :author_id, inverse_of: :author
has_many :template_folders, dependent: :destroy, foreign_key: :author_id, inverse_of: :author
has_many :user_configs, dependent: :destroy
has_many :encrypted_configs, dependent: :destroy, class_name: 'EncryptedUserConfig'
has_many :email_messages, dependent: :destroy, foreign_key: :author_id, inverse_of: :author
# :omniauthable is included unconditionally so the Devise routes are
# always declared. Whether the strategy actually works (and whether the
# Google button is shown on the sign-in page) is gated by
# Wabosign.google_sso_enabled? at runtime — driven by ENV and/or the
# `google_sso_configs` EncryptedConfig record.
devise :two_factor_authenticatable, :recoverable, :rememberable,
:validatable, :trackable, :lockable, :omniauthable,
omniauth_providers: [:google_oauth2]
attribute :role, :string, default: ADMIN_ROLE
attribute :uuid, :string, default: -> { SecureRandom.uuid }
scope :active, -> { where(archived_at: nil) }
scope :archived, -> { where.not(archived_at: nil) }
scope :admins, -> { where(role: ADMIN_ROLE) }
validates :email, format: { with: /\A[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}\z/ }
def access_token
super || build_access_token.tap(&:save!)
end
def active_for_authentication?
super && !archived_at? && !account.archived_at?
end
def remember_me
true
end
def sidekiq?
return true if Rails.env.development?
role == 'admin'
end
def self.sign_in_after_reset_password
if PasswordsController::Current.user.present?
!PasswordsController::Current.user.otp_required_for_login
else
true
end
end
def initials
[first_name&.first, last_name&.first].compact_blank.join.upcase
end
def full_name
[first_name, last_name].compact_blank.join(' ')
end
def friendly_name
if full_name.present?
%("#{full_name.delete('"')}" <#{email}>)
else
email
end
end
def signed_in_via_sso?
provider == 'google_oauth2' && uid.present?
end
def self.from_google_omniauth(auth)
hd = auth.extra&.raw_info&.respond_to?(:hd) ? auth.extra.raw_info.hd : auth.extra&.raw_info&.dig('hd')
return nil unless Wabosign.google_domain_allowed?(hd)
email = auth.info.email.to_s.downcase
return nil if email.blank?
user = find_by('lower(email) = ?', email)
if user
return nil if user.provider.present? && user.uid != auth.uid
user.update!(provider: 'google_oauth2', uid: auth.uid) if user.provider.blank?
return user
end
account = default_sso_account
return nil if account.nil?
create!(
account: account,
email: email,
first_name: auth.info.first_name,
last_name: auth.info.last_name,
role: ADMIN_ROLE,
password: SecureRandom.hex(32),
provider: 'google_oauth2',
uid: auth.uid,
confirmed_at: Time.current
)
end
def self.default_sso_account
# ENV override always wins.
if Wabosign::GOOGLE_DEFAULT_ACCOUNT_ID.present?
return Account.find_by(id: Wabosign::GOOGLE_DEFAULT_ACCOUNT_ID)
end
# If an admin saved the Google SSO config via the UI, JIT-provision into
# that same account so admins land in the right tenant.
if (db_config = EncryptedConfig.find_by(key: EncryptedConfig::GOOGLE_SSO_KEY))
return db_config.account if db_config.account && db_config.account.archived_at.nil?
end
Account.order(:created_at).first
end
end