Class: Account
Overview
== Schema Information
Table name: accounts
Database name: primary
id :integer not null, primary key
authentication_mode :integer default(0), not null
confirmation_sent_at :datetime
confirmation_token :string(255)
confirmed_at :datetime
current_sign_in_at :datetime
current_sign_in_ip :string(255)
disabled :boolean default(FALSE), not null
email :citext
encrypted_password :string(255) default("")
failed_attempts :integer default(0)
ignore_ip_visit_check :boolean default(FALSE), not null
inherited_role_ids :integer default([]), is an Array
inherited_role_names :string default([]), is an Array
invitation_accepted_at :datetime
invitation_created_at :datetime
invitation_limit :integer
invitation_sent_at :datetime
invitation_token :string(255)
invited_by_type :string(255)
is_guest :boolean
last_login :datetime
last_sign_in_at :datetime
last_sign_in_ip :string(255)
locked_at :datetime
login :citext not null
name :string(100) default("")
password_salt :string(255) default("")
remember_created_at :datetime
require_myp_migration :boolean default(FALSE)
reset_password_sent_at :datetime
reset_password_token :string(255)
return_path_for_invite :string(255)
sign_in_count :integer default(0)
unlock_token :string(255)
created_at :datetime
updated_at :datetime
invited_by_id :integer
my_projects_user_id :integer
party_id :integer
Indexes
index_accounts_on_confirmation_token (confirmation_token) UNIQUE
index_accounts_on_email (email)
index_accounts_on_inherited_role_names (inherited_role_names) USING gin
index_accounts_on_invitation_token (invitation_token)
index_accounts_on_invited_by_id (invited_by_id)
index_accounts_on_login (login) UNIQUE
index_accounts_on_my_projects_user_id (my_projects_user_id)
index_accounts_on_party_id (party_id)
index_accounts_on_reset_password_token (reset_password_token) UNIQUE
index_accounts_on_unlock_token (unlock_token) UNIQUE
Foreign Keys
accounts_party_id_fkey (party_id => parties.id)
Defined Under Namespace
Classes: Inviter
Constant Summary
collapse
- AUTH_INTERNAL =
0
- AUTH_GOOGLE =
1
- BCRYPT_HASH_PREFIX_RE =
Bcrypt-shape detector. Bcrypt outputs $2a$, $2b$, or $2y$
prefixes (variant + cost-factor + salt + hash). A non-bcrypt-shaped
encrypted_password is the legacy restful_authentication_sha1
hex digest.
/\A\$2[aby]\$/
- LEGACY_SHA1_STRETCHES =
Number of SHA1 rounds the legacy restful_authentication_sha1
encryptor was configured with (Devise.stretches = 10 historically,
before we repurposed stretches to mean bcrypt cost). Pinned here
so the verify-time reconstruction in legacy_sha1_digest doesn't
drift if Devise.stretches is bumped.
10
Models::Auditable::ALWAYS_IGNORED
Constants included
from Schedulable
Schedulable::SIMPLE_FORM_OPTIONS
Instance Attribute Summary collapse
#creator, #updater
Has and belongs to many
collapse
Delegated Instance Attributes
collapse
Class Method Summary
collapse
Instance Method Summary
collapse
#all_skipped_columns, #audit_reference_data, #should_not_save_version, #stamp_record
ransackable_associations, ransackable_attributes, ransackable_scopes, ransortable_attributes, #to_relation
config
#after_commit
#publish_event
Dynamic Method Handling
This class handles dynamic methods through the method_missing method
#method_missing(method_id, *arguments) ⇒ Object
597
598
599
600
601
602
603
604
605
606
607
|
# File 'app/models/account.rb', line 597
def method_missing(method_id, *arguments, &)
if /^is_\w+?/.match?(method_id.to_s)
self.class.send :define_method, method_id do
role_to_check = method_id.to_s.match(/^is_(\w+)?/)[1]
send(:has_role?, role_to_check)
end
send(method_id)
else
super
end
end
|
Instance Attribute Details
#authentication_mode ⇒ Object
121
|
# File 'app/models/account.rb', line 121
validates :authentication_mode, presence: true, inclusion: { in: [AUTH_INTERNAL, AUTH_GOOGLE] }
|
#email ⇒ Object
123
|
# File 'app/models/account.rb', line 123
validates :email, presence: true, email_format: true
|
#email_reset_instructions ⇒ Object
Returns the value of attribute email_reset_instructions.
136
137
138
|
# File 'app/models/account.rb', line 136
def email_reset_instructions
@email_reset_instructions
end
|
#invitation_code ⇒ Object
Returns the value of attribute invitation_code.
136
137
138
|
# File 'app/models/account.rb', line 136
def invitation_code
@invitation_code
end
|
#login ⇒ Object
122
|
# File 'app/models/account.rb', line 122
validates :login, presence: true, uniqueness: true
|
#marketing_sign_up ⇒ Object
Returns the value of attribute marketing_sign_up.
136
137
138
|
# File 'app/models/account.rb', line 136
def marketing_sign_up
@marketing_sign_up
end
|
#password ⇒ Object
from devise validatable, we only keep password validations, not email since they require uniqueness if present, which we do not
Validations:
-
Presence
({ if: :password_required? })
-
Confirmation
({ if: -> { password.present? } })
-
Length
({ within: Devise.password_length, allow_blank: true })
127
|
# File 'app/models/account.rb', line 127
validates :password, presence: { if: :password_required? }
|
#password_confirmation ⇒ Object
133
|
# File 'app/models/account.rb', line 133
validates :password_confirmation, presence: true, if: -> { password.present? }
|
#require_password ⇒ Object
Returns the value of attribute require_password.
136
137
138
|
# File 'app/models/account.rb', line 136
def require_password
@require_password
end
|
#skip_notification ⇒ Object
Returns the value of attribute skip_notification.
136
137
138
|
# File 'app/models/account.rb', line 136
def skip_notification
@skip_notification
end
|
Class Method Details
.account_api_signed_in(api_authentication_token) ⇒ Object
155
156
157
158
159
160
161
162
163
164
165
166
167
168
|
# File 'app/models/account.rb', line 155
def self.account_api_signed_in(api_authentication_token)
res = nil
api_auth = ApiAuthentication.find_by(api_authentication_token: api_authentication_token)
if api_auth
if api_auth.expired?
api_auth.destroy
res = nil
else
res = api_auth.account
end
end
res
end
|
.active_accounts ⇒ ActiveRecord::Relation<Account>
A relation of Accounts that are active accounts. Active Record Scope
113
|
# File 'app/models/account.rb', line 113
scope :active_accounts, -> { where.not(disabled: true) }
|
.email_logins ⇒ ActiveRecord::Relation<Account>
A relation of Accounts that are email logins. Active Record Scope
114
|
# File 'app/models/account.rb', line 114
scope :email_logins, -> { where("accounts.login LIKE '%@%'") }
|
.get_unique_login_for_email(email) ⇒ Object
170
171
172
173
174
175
176
177
178
179
180
181
182
183
|
# File 'app/models/account.rb', line 170
def self.get_unique_login_for_email(email)
login = email
if Account.where(login: email).exists?
name_part, = email.split('@')
base_login = name_part
counter = 0
login = base_login
while Account.where(login: login).exists?
counter += 1
login = "#{base_login}-#{counter}"
end
end
login
end
|
Instance Method Details
#ability ⇒ Object
278
279
280
|
# File 'app/models/account.rb', line 278
def ability
@ability ||= Ability.new(party)
end
|
#account_created_notify_reps_and_master_account ⇒ Object
484
485
486
487
488
|
# File 'app/models/account.rb', line 484
def account_created_notify_reps_and_master_account
return if party&.is_employee? || skip_notification
notify(activity: 'created_account') if party.present?
end
|
#active_for_authentication? ⇒ Boolean
189
190
191
192
|
# File 'app/models/account.rb', line 189
def active_for_authentication?
Rails.logger.debug { "active_for_authentication? disabled?: #{disabled?}" }
super && !disabled?
end
|
#after_database_authentication ⇒ Object
Devise calls this after a successful database authentication
(database_authenticatable strategy → resource.after_database_authentication).
We piggyback to drain the bcrypt-migration tail: if the password
we just verified came in via legacy SHA1 (state A) or bcrypt-wrap
(state B), valid_password? stashed the plaintext on
@needs_password_rehash; here we re-set the password through
Devise's bcrypt setter (writes pure-bcrypt to encrypted_password)
and clear the no-longer-needed password_salt. Saved without
validations because we don't want a model-level validation drift
to mask a successful auth and lock the user out.
Custom controllers that call valid_password? directly and then
sign_in(:account, …) (e.g. Auth::CustomerSessionsController#authenticate,
#finish_fast_checkout) bypass the Devise strategy and therefore
this callback — they should call consume_password_rehash!
explicitly after a successful sign-in.
519
520
521
522
|
# File 'app/models/account.rb', line 519
def after_database_authentication
super if defined?(super)
consume_password_rehash!
end
|
#api_authentications ⇒ ActiveRecord::Relation<ApiAuthentication>
109
|
# File 'app/models/account.rb', line 109
has_many :api_authentications, dependent: :destroy
|
#api_sign_in!(is_guest = false) ⇒ Object
Here we manage api_authentications which is used only for API authentication, we don't want it to mix or reset the usual web based authentication token which is used for employee 'login as this customer" login or "continue as guest" accounts for email link login and is reset on successful registration or social login authentication. We want API authentication - the only kind of api login mechanism via email/password, social login or continue as guest via a non web based app - to be independent
401
402
403
404
405
406
407
|
# File 'app/models/account.rb', line 401
def api_sign_in!(is_guest = false)
api_auth = api_authentications.build
api_auth.is_guest = is_guest
save!
api_auth.api_authentication_token
end
|
#api_sign_out!(api_authentication_token) ⇒ Object
409
410
411
412
413
|
# File 'app/models/account.rb', line 409
def api_sign_out!(api_authentication_token)
api_auth = api_authentications.find_by(api_authentication_token: api_authentication_token)
api_auth&.destroy
end
|
#apply_omniauth(omniauth) ⇒ Object
If you use a social media account login, then this extracts the name and stores it in the party
and builds the authentication
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
|
# File 'app/models/account.rb', line 351
def apply_omniauth(omniauth)
logger.debug "apply_omniauth(omniauth): party.id: #{party.id}"
logger.debug "apply_omniauth(omniauth): email: #{email}"
logger.debug "apply_omniauth(omniauth): omniauth: #{omniauth.inspect}"
if login.blank?
self.login = Authentication.(omniauth)
self.email = Authentication.(omniauth)
logger.debug "apply_omniauth(omniauth): email: #{email}"
logger.debug "apply_omniauth(omniauth): party.email: #{begin
party.email
rescue StandardError
'n/a'
end}"
end
if name.blank? || name.downcase.index('guest').present?
omniauth_name = Authentication.(omniauth)
logger.debug "apply_omniauth(omniauth): omniauth_name: #{omniauth_name}"
self.name = omniauth_name if omniauth_name
logger.debug "apply_omniauth(omniauth): SET name: #{name}"
logger.debug "apply_omniauth(omniauth): party.name: #{begin
party.name
rescue StandardError
'n/a'
end}"
end
authentications.build(provider: omniauth['provider'], uid: omniauth['uid'])
@pending_social_login_picture_url = Authentication.(omniauth)
end
|
#auth_token_required? ⇒ Boolean
444
445
446
|
# File 'app/models/account.rb', line 444
def auth_token_required?
login.present? && authentications.empty? && encrypted_password.blank? && !is_employee?
end
|
#authentication_internal? ⇒ Boolean
282
283
284
|
# File 'app/models/account.rb', line 282
def authentication_internal?
authentication_mode == AUTH_INTERNAL
end
|
#authentication_methods ⇒ Object
389
390
391
392
393
394
395
396
397
|
# File 'app/models/account.rb', line 389
def authentication_methods
auth_methods = []
auth_methods << 'password' if encrypted_password.present?
authentications.each do |auth|
auth_methods << auth.provider
end
auth_methods
end
|
#authentication_mode_name ⇒ Object
318
319
320
|
# File 'app/models/account.rb', line 318
def authentication_mode_name
%w[Internal Google][authentication_mode]
end
|
#authentications ⇒ ActiveRecord::Relation<Authentication>
108
|
# File 'app/models/account.rb', line 108
has_many :authentications, dependent: :destroy
|
#bcrypt_shaped_password? ⇒ Boolean
213
214
215
|
# File 'app/models/account.rb', line 213
def bcrypt_shaped_password?
encrypted_password.to_s.match?(BCRYPT_HASH_PREFIX_RE)
end
|
#can? ⇒ Object
151
|
# File 'app/models/account.rb', line 151
delegate :can?, :cannot?, to: :ability
|
#can_access_mcp? ⇒ Boolean
MCP (Model Context Protocol) access methods
Used for AI assistant integrations like Cursor/Claude
418
419
420
|
# File 'app/models/account.rb', line 418
def can_access_mcp?
is_employee? && has_role?('mcp_access')
end
|
#can_impersonate?(customer_account) ⇒ Boolean
304
305
306
307
308
309
|
# File 'app/models/account.rb', line 304
def can_impersonate?(customer_account)
is_manager? ||
is_customer_service_rep? ||
party_id == customer_account.customer.primary_sales_rep_id ||
party_id == customer_account.customer.secondary_sales_rep_id
end
|
#cannot? ⇒ Object
Alias for Ability#cannot?
151
|
# File 'app/models/account.rb', line 151
delegate :can?, :cannot?, to: :ability
|
#check_for_reset_instructions ⇒ Object
575
576
577
578
579
580
|
# File 'app/models/account.rb', line 575
def check_for_reset_instructions
return unless email_reset_instructions == '1'
self.email_password_reset_instructions = nil
send_reset_password_instructions
end
|
#confirmation_required? ⇒ Boolean
556
557
558
559
560
561
|
# File 'app/models/account.rb', line 556
def confirmation_required?
return false if party&.is_employee?
return false if authentications.present?
true
end
|
#consume_password_rehash! ⇒ Boolean
Idempotent rehash of a transitional-state password to pure bcrypt.
No-op when nothing was queued by valid_password? (e.g. the user
was already on pure bcrypt, or this method got called twice).
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
|
# File 'app/models/account.rb', line 528
def consume_password_rehash!
plain = @needs_password_rehash
@needs_password_rehash = nil
return false if plain.blank?
self.password = plain self.password_salt = nil prior_skip_notification = skip_notification
self.skip_notification = true
begin
saved = save(validate: false)
ensure
self.skip_notification = prior_skip_notification
end
Appsignal.increment_counter('password_rehashed_to_pure_bcrypt', 1) if saved && defined?(Appsignal)
saved
end
|
#customer ⇒ Object
286
|
# File 'app/models/account.rb', line 286
delegate :customer, to: :party
|
105
|
# File 'app/models/account.rb', line 105
belongs_to :employee, class_name: 'Employee', foreign_key: :party_id, optional: true
|
#fetch_inherited_role_names ⇒ Object
495
496
497
|
# File 'app/models/account.rb', line 495
def fetch_inherited_role_names
Role.where(id: inherited_role_ids).order(:name).pluck(:name)
end
|
#fully_enabled? ⇒ Boolean
322
323
324
|
# File 'app/models/account.rb', line 322
def fully_enabled?
login.present? && (encrypted_password.present? || authentications.present?)
end
|
#generate_mcp_token! ⇒ Object
422
423
424
425
426
|
# File 'app/models/account.rb', line 422
def generate_mcp_token!
raise 'Account does not have MCP access' unless can_access_mcp?
api_sign_in!
end
|
#has_role?(*roles_in_question, admin_check: true) ⇒ Boolean
311
312
313
314
315
316
|
# File 'app/models/account.rb', line 311
def has_role?(*roles_in_question, admin_check: true)
roles_array = roles_in_question.flatten.map(&:to_s).map(&:downcase)
(admin_check && inherited_role_names.include?('admin')) ||
inherited_role_names.map(&:downcase).intersect?(roles_array)
end
|
331
332
333
334
335
336
337
|
# File 'app/models/account.rb', line 331
def (_action)
if party.respond_to?(:primary_sales_rep?)
{ from: "'#{party.primary_sales_rep.name}' <#{party.primary_sales_rep.email}>" }
else
{}
end
end
|
#inactive_message ⇒ Object
194
195
196
197
198
|
# File 'app/models/account.rb', line 194
def inactive_message
return super unless disabled?
I18n.t('devise.failure.account_disabled', phone: CompanyConstants::PHONE[:usa])
end
|
#invitations ⇒ ActiveRecord::Relation<Invitation>
107
|
# File 'app/models/account.rb', line 107
has_many :invitations, class_name: self.class.to_s, as: :invited_by
|
#is_admin? ⇒ Boolean
296
297
298
|
# File 'app/models/account.rb', line 296
def is_admin?
inherited_role_names.include?('admin')
end
|
#is_customer? ⇒ Boolean
292
293
294
|
# File 'app/models/account.rb', line 292
def is_customer?
party.is_a?(Customer)
end
|
#is_employee? ⇒ Boolean
288
289
290
|
# File 'app/models/account.rb', line 288
def is_employee?
party.is_a?(Employee)
end
|
#is_manager? ⇒ Boolean
300
301
302
|
# File 'app/models/account.rb', line 300
def is_manager?
is_admin? || inherited_role_names.any? { |rn| rn =~ /_manager$/ && rn != 'item_manager' }
end
|
#legacy_sha1_password? ⇒ Boolean
State A: legacy restful_authentication_sha1 hex digest, no
bcrypt wrapping. The Stage 2 data migration (see migration plan)
converts every State-A row to State B; expected to be empty
post-migration.
230
231
232
|
# File 'app/models/account.rb', line 230
def legacy_sha1_password?
encrypted_password.present? && !bcrypt_shaped_password?
end
|
#login_is_email? ⇒ Boolean
185
186
187
|
# File 'app/models/account.rb', line 185
def login_is_email?
login =~ RFC822::EMAIL
end
|
#notify(options) ⇒ Object
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
|
# File 'app/models/account.rb', line 428
def notify(options)
mailers = []
case options[:activity]
when 'update_password'
mailers << AccountMailer.password_changed(id)
when 'update_email'
mailers << AccountMailer.email_changed(id, options[:old_email], options[:old_login], options[:old_email]) if options[:old_email] != email
mailers << AccountMailer.email_changed(id, options[:old_email], options[:old_login], email) if (options[:old_email] != email) || (options[:old_login] != login)
when 'created_account'
mailers << AccountMailer.created_account(id)
end
mailers.each { |m| m.deliver_later(wait: 10.seconds) }
end
|
#obfuscated_email ⇒ Object
499
500
501
|
# File 'app/models/account.rb', line 499
def obfuscated_email
email&.gsub(/(?<=.{2}).*@.*(?=\S{2})/, '****@****')
end
|
#omniauth_provider_icon_basenames ⇒ Object
381
382
383
|
# File 'app/models/account.rb', line 381
def omniauth_provider_icon_basenames
Authentication::PROVIDERS.select { |p, _h| authentications.find_by(provider: p) }.map { |_p, h| h[:icon] }
end
|
104
|
# File 'app/models/account.rb', line 104
belongs_to :party, optional: true
|
#password_required? ⇒ Boolean
339
340
341
342
343
344
345
346
347
|
# File 'app/models/account.rb', line 339
def password_required?
return false if authentication_mode == AUTH_GOOGLE
res = true
res = false if authentications.any?
res = false if encrypted_password.blank?
res = true if require_password
res && (!persisted? || !password.nil? || !password_confirmation.nil?)
end
|
#pending_confirmation? ⇒ Boolean
326
327
328
|
# File 'app/models/account.rb', line 326
def pending_confirmation?
invitation_token.present?
end
|
#push_login_to_email ⇒ Object
490
491
492
493
|
# File 'app/models/account.rb', line 490
def push_login_to_email
self.email = login if email.blank? && login_is_email?
end
|
#respond_to?(method_id, include_private = false) ⇒ Boolean
385
386
387
|
# File 'app/models/account.rb', line 385
def respond_to?(method_id, include_private = false)
/^is_\w+?/.match?(method_id.to_s) || super
end
|
#role_inheritance ⇒ Object
586
587
588
589
590
591
592
593
594
595
|
# File 'app/models/account.rb', line 586
def role_inheritance
roles.each do |r|
arids = r.ancestors_ids if (res = (role_ids & arids)).present?
role_names = Role.where(id: res).pluck(:name)
errors.add(:base, "Role #{r.name} is already inherited by role #{role_names.join(', ')} and cannot be assigned")
end
end
end
|
#roles ⇒ ActiveRecord::Relation<Role>
111
|
# File 'app/models/account.rb', line 111
has_and_belongs_to_many :roles, after_add: :touch_me, after_remove: :touch_me, inverse_of: :accounts
|
#set_default_marketing_preferences(locale) ⇒ Object
270
271
272
273
274
275
276
|
# File 'app/models/account.rb', line 270
def set_default_marketing_preferences(locale)
self.marketing_sign_up = if (locale == :'en-CA') || (locale == :'fr-CA')
false
else
true
end
end
|
#set_defaults ⇒ Object
563
564
565
566
|
# File 'app/models/account.rb', line 563
def set_defaults
self.authentication_mode ||= AUTH_INTERNAL
push_login_to_email
end
|
#signed_login_url(target_url) ⇒ Object
Build a magic-login URL — the embedded login_token authenticates this
account when the URL is visited. The token is a single-use
generates_token_for(:magic_login) value (see the declaration above):
purpose-tagged, 7-day TTL, and killed by the next sign-in. No DB column.
The TTL lives on the class-level declaration, not here — there's no
per-call override, so a caller that needs a different lifetime must add a
new generates_token_for purpose rather than pass a kwarg.
Example:
account.signed_login_url(retrieve_my_cart_url(host: WEB_HOSTNAME))
472
473
474
475
476
477
478
479
480
481
482
|
# File 'app/models/account.rb', line 472
def signed_login_url(target_url)
uri = Addressable::URI.parse(target_url)
new_query_ar = uri.query ? Addressable::URI.form_unencode(uri.query) : []
new_query_ar.reject! { |key, _| key == 'login_token' }
new_query_ar << ['login_token', generate_token_for(:magic_login)]
uri.query = Addressable::URI.form_encode(new_query_ar)
uri.to_s
end
|
#timeout_in ⇒ Object
609
610
611
612
613
614
615
616
617
|
# File 'app/models/account.rb', line 609
def timeout_in
return 30.minutes if auth_token_required?
return 8.hours if is_employee?
Devise.timeout_in
end
|
#touch_me(_role) ⇒ Object
582
583
584
|
# File 'app/models/account.rb', line 582
def touch_me(_role)
touch unless new_record?
end
|
568
569
570
571
572
573
|
# File 'app/models/account.rb', line 568
def update_matching_contact_point_if_needed
return unless email_changed? && valid?
cp = party.contact_points.where(detail: email_was, category: 'email').first
cp&.update_attribute!(:detail, email)
end
|
#valid_password?(password) ⇒ Boolean
Devise verifies passwords by computing bcrypt over the supplied
plaintext and comparing to encrypted_password. We override to
also accept the two transitional states from the bcrypt migration:
- State A (legacy SHA1): recompute the legacy
restful_authentication_sha1 digest from the supplied
plaintext + this row's password_salt + the project pepper,
and secure_compare against encrypted_password.
- State B (wrapped bcrypt): recompute the same legacy SHA1
digest, then bcrypt-verify it against encrypted_password.
This is the Dropbox-style wrap — bcrypt-cost protection
without re-hashing the user's plaintext.
On a successful match in either transitional state, stash the
plaintext on the instance so after_database_authentication
can rehash the row to pure bcrypt and clear the salt. Verifying
is read-only by contract (Devise calls it from many code paths,
not all of which want a side-effecting save), so the rehash is
deferred to the post-authentication callback.
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
|
# File 'app/models/account.rb', line 253
def valid_password?(password)
if legacy_sha1_password?
Appsignal.increment_counter('password_verify_state', 1, state: 'legacy_sha1') if defined?(Appsignal)
verified = Devise.secure_compare(encrypted_password, legacy_sha1_digest(password))
@needs_password_rehash = password if verified
verified
elsif wrapped_password?
Appsignal.increment_counter('password_verify_state', 1, state: 'wrapped_bcrypt') if defined?(Appsignal)
verified = ::BCrypt::Password.new(encrypted_password) == legacy_sha1_digest(password)
@needs_password_rehash = password if verified
verified
else
Appsignal.increment_counter('password_verify_state', 1, state: 'pure_bcrypt') if defined?(Appsignal)
super
end
end
|
#wrapped_password? ⇒ Boolean
State B (Dropbox-style wrap): bcrypt over the legacy SHA1 digest.
Detected by: bcrypt shape AND a still-populated password_salt
column (the salt is only needed to recompute the SHA1 pre-image
at verify time; we clear it once the row is rehashed to pure
bcrypt in rehash_legacy_password!).
222
223
224
|
# File 'app/models/account.rb', line 222
def wrapped_password?
bcrypt_shaped_password? && password_salt.present?
end
|