File: secure_password.rb

package info (click to toggle)
rails 2%3A7.2.2.1%2Bdfsg-7
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 43,352 kB
  • sloc: ruby: 349,799; javascript: 30,703; yacc: 46; sql: 43; sh: 29; makefile: 27
file content (60 lines) | stat: -rw-r--r-- 2,558 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
# frozen_string_literal: true

module ActiveRecord
  module SecurePassword
    extend ActiveSupport::Concern

    include ActiveModel::SecurePassword

    module ClassMethods
      # Given a set of attributes, finds a record using the non-password
      # attributes, and then authenticates that record using the password
      # attributes. Returns the record if authentication succeeds; otherwise,
      # returns +nil+.
      #
      # Regardless of whether a record is found, +authenticate_by+ will
      # cryptographically digest the given password attributes. This behavior
      # helps mitigate timing-based enumeration attacks, wherein an attacker can
      # determine if a passworded record exists even without knowing the
      # password.
      #
      # Raises an ArgumentError if the set of attributes doesn't contain at
      # least one password and one non-password attribute.
      #
      # ==== Examples
      #
      #   class User < ActiveRecord::Base
      #     has_secure_password
      #   end
      #
      #   User.create(name: "John Doe", email: "jdoe@example.com", password: "abc123")
      #
      #   User.authenticate_by(email: "jdoe@example.com", password: "abc123").name # => "John Doe" (in 373.4ms)
      #   User.authenticate_by(email: "jdoe@example.com", password: "wrong")       # => nil (in 373.9ms)
      #   User.authenticate_by(email: "wrong@example.com", password: "abc123")     # => nil (in 373.6ms)
      #
      #   User.authenticate_by(email: "jdoe@example.com", password: nil) # => nil (no queries executed)
      #   User.authenticate_by(email: "jdoe@example.com", password: "")  # => nil (no queries executed)
      #
      #   User.authenticate_by(email: "jdoe@example.com") # => ArgumentError
      #   User.authenticate_by(password: "abc123")        # => ArgumentError
      def authenticate_by(attributes)
        passwords, identifiers = attributes.to_h.partition do |name, value|
          !has_attribute?(name) && has_attribute?("#{name}_digest")
        end.map(&:to_h)

        raise ArgumentError, "One or more password arguments are required" if passwords.empty?
        raise ArgumentError, "One or more finder arguments are required" if identifiers.empty?

        return if passwords.any? { |name, value| value.nil? || value.empty? }

        if record = find_by(identifiers)
          record if passwords.count { |name, value| record.public_send(:"authenticate_#{name}", value) } == passwords.size
        else
          new(passwords)
          nil
        end
      end
    end
  end
end