omniauth-identity 3.1.4 → 3.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -5,25 +5,73 @@ require "nobrainer"
5
5
  module OmniAuth
6
6
  module Identity
7
7
  module Models
8
- # NoBrainer is an ORM adapter for RethinkDB:
9
- # http://nobrainer.io/
10
- # NOTE: NoBrainer is based on ActiveModel.
8
+ # NoBrainer is an ORM adapter for RethinkDB: http://nobrainer.io/
9
+ #
10
+ # This module provides OmniAuth Identity functionality for NoBrainer models,
11
+ # including secure password handling and authentication key management.
12
+ #
13
+ # @example Usage
14
+ # class User
15
+ # include NoBrainer::Document
16
+ #
17
+ # include OmniAuth::Identity::Models::NoBrainer
18
+ #
19
+ # field :email
20
+ # field :password_digest
21
+ # end
22
+ #
23
+ # user = User.new(email: 'user@example.com', password: 'password')
24
+ # user.save
25
+ #
26
+ # # Authenticate a user
27
+ # authenticated_user = User.locate(email: 'user@example.com')
28
+ # authenticated_user.authenticate('password') # => user or false
29
+ #
30
+ # @note NoBrainer is based on ActiveModel, so validations are enabled by default.
11
31
  module NoBrainer
12
- def self.included(base)
13
- base.class_eval do
14
- include(::OmniAuth::Identity::Model)
15
- include(::OmniAuth::Identity::SecurePassword)
32
+ class << self
33
+ # Called when this module is included in a model class.
34
+ #
35
+ # This method extends the base class with OmniAuth Identity functionality,
36
+ # including secure password support and authentication key validation.
37
+ #
38
+ # @param base [Class] the model class including this module
39
+ # @return [void]
40
+ def included(base)
41
+ base.class_eval do
42
+ include(::OmniAuth::Identity::Model)
43
+ include(::OmniAuth::Identity::SecurePassword)
16
44
 
17
- # validations: true (default) incurs a dependency on ActiveModel, but NoBrainer is ActiveModel based.
18
- has_secure_password
45
+ # validations: true (default) incurs a dependency on ActiveModel, but NoBrainer is ActiveModel based.
46
+ has_secure_password
19
47
 
20
- def self.auth_key=(key)
21
- super
22
- validates_uniqueness_of(key, case_sensitive: false)
23
- end
48
+ class << self
49
+ # @!method self.auth_key=(key)
50
+ # Sets the authentication key for the model and adds uniqueness validation.
51
+ #
52
+ # @param key [Symbol, String] the attribute to use as the authentication key
53
+ # @return [void]
54
+ # @example
55
+ # class User
56
+ # include OmniAuth::Identity::Models::NoBrainer
57
+ # self.auth_key = :email
58
+ # end
59
+ def auth_key=(key)
60
+ super
61
+ validates_uniqueness_of(key, case_sensitive: false)
62
+ end
24
63
 
25
- def self.locate(search_hash)
26
- where(search_hash).first
64
+ # @!method self.locate(search_hash)
65
+ # Finds a record by the given search criteria.
66
+ #
67
+ # @param search_hash [Hash] the attributes to search for
68
+ # @return [Object, nil] the first matching record or nil
69
+ # @example
70
+ # User.locate(email: 'user@example.com')
71
+ def locate(search_hash)
72
+ where(search_hash).first
73
+ end
74
+ end
27
75
  end
28
76
  end
29
77
  end
@@ -0,0 +1,164 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bcrypt"
4
+
5
+ require "rom"
6
+ require "rom-sql"
7
+
8
+ module OmniAuth
9
+ module Identity
10
+ module Models
11
+ # ROM adapter for OmniAuth::Identity
12
+ # This provides a reusable adapter that makes ROM entities compatible with OmniAuth::Identity
13
+ #
14
+ # Usage:
15
+ # class Identity
16
+ # include OmniAuth::Identity::Models::Rom
17
+ #
18
+ # # Configure the ROM container and relation using the DSL (no `self.`):
19
+ # rom_container -> { MyDatabase.rom } # accepts a proc or a container object
20
+ # rom_relation_name :identities # optional, defaults to :identities
21
+ # owner_relation_name :owners # optional, for loading associated owner
22
+ # # Uses OmniAuth::Identity::Model.auth_key to set the auth key (defaults to :email)
23
+ # auth_key :email
24
+ # # Optional: override the password digest field name (defaults to :password_digest)
25
+ # password_field :password_digest
26
+ # end
27
+ module Rom
28
+ class << self
29
+ def included(base)
30
+ # Align with other adapters: rely on OmniAuth::Identity::Model for API
31
+ base.include(::OmniAuth::Identity::Model)
32
+ base.extend(ClassMethods)
33
+
34
+ base.class_eval do
35
+ # OmniAuth::Identity required instance API
36
+ # Authenticates the instance with the provided password
37
+ # Returns self on success, false otherwise (to match Model.authenticate contract)
38
+ define_method(:authenticate) do |password|
39
+ digest_key = self.class.password_field
40
+ password_digest = @identity_data[digest_key]
41
+ return false unless password_digest
42
+
43
+ begin
44
+ BCrypt::Password.new(password_digest) == password && self
45
+ rescue BCrypt::Errors::InvalidHash
46
+ false
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
52
+
53
+ module ClassMethods
54
+ # Default ROM relation name when none is configured
55
+ DEFAULT_RELATION_NAME = :identities
56
+
57
+ ROM_CONFIG_MUTEX = Mutex.new
58
+ private_constant :ROM_CONFIG_MUTEX
59
+
60
+ # Configuration DSL
61
+ # These methods act like the DSL on `OmniAuth::Identity::Model` (e.g. `auth_key`) —
62
+ # when called with an argument they set the configuration, and when called
63
+ # without an argument they return the current value (with sensible defaults).
64
+ def rom_container(value = false)
65
+ ROM_CONFIG_MUTEX.synchronize do
66
+ @rom_container = value unless value == false
67
+ container = @rom_container
68
+ container.respond_to?(:call) ? container.call : container
69
+ end
70
+ end
71
+
72
+ def rom_relation_name(value = false)
73
+ ROM_CONFIG_MUTEX.synchronize do
74
+ @rom_relation_name = value unless value == false
75
+ @rom_relation_name || DEFAULT_RELATION_NAME
76
+ end
77
+ end
78
+
79
+ def owner_relation_name(value = false)
80
+ ROM_CONFIG_MUTEX.synchronize do
81
+ @owner_relation_name = value unless value == false
82
+ @owner_relation_name
83
+ end
84
+ end
85
+
86
+ def password_field(value = false)
87
+ ROM_CONFIG_MUTEX.synchronize do
88
+ @password_field = value unless value == false
89
+ (@password_field || :password_digest).to_sym
90
+ end
91
+ end
92
+
93
+ # Align with other adapters: use Model.auth_key (getter/setter) for the login attribute
94
+ # Model.auth_key returns a String; convert to Symbol for ROM queries when needed.
95
+ def auth_key_symbol
96
+ (auth_key || "email").to_sym
97
+ end
98
+
99
+ # Locate an identity given conditions (Hash) or a raw key value.
100
+ # Mirrors other adapters by accepting a conditions hash.
101
+ # Returns an instance or nil.
102
+ def locate(conditions)
103
+ key_value = if conditions.is_a?(Hash)
104
+ conditions[auth_key_symbol] || conditions[auth_key.to_s]
105
+ else
106
+ conditions
107
+ end
108
+ return if key_value.nil?
109
+
110
+ relation = rom_container.relations[rom_relation_name]
111
+ identity_data = relation.where(auth_key_symbol => key_value).one
112
+ return unless identity_data
113
+
114
+ if owner_relation_name && identity_data[:owner_id]
115
+ owner_data = rom_container.relations[owner_relation_name].where(id: identity_data[:owner_id]).one
116
+ new(identity_data, owner_data)
117
+ else
118
+ new(identity_data)
119
+ end
120
+ end
121
+ end
122
+
123
+ # Instance shape matches other adapters for downstream usage
124
+ attr_reader :uid, :email, :name, :info, :owner
125
+
126
+ def initialize(identity_data, owner_data = nil)
127
+ @identity_data = identity_data
128
+ @owner_data = owner_data
129
+ @uid = identity_data[:id]
130
+ @email = identity_data[:email]
131
+
132
+ # Prefer owner name if available, else fall back to email
133
+ @name = if owner_data && owner_data[:name]
134
+ owner_data[:name]
135
+ else
136
+ identity_data[:email]
137
+ end
138
+
139
+ @info = {
140
+ "email" => @email,
141
+ "name" => @name,
142
+ }
143
+
144
+ @owner = owner_data
145
+ end
146
+
147
+ # Hash-like access to underlying ROM tuple
148
+ def [](key)
149
+ @identity_data[key]
150
+ end
151
+
152
+ # Convenience accessor for owner id
153
+ def owner_id
154
+ @identity_data[:owner_id]
155
+ end
156
+
157
+ # OmniAuth info hash
158
+ def to_hash
159
+ @info
160
+ end
161
+ end
162
+ end
163
+ end
164
+ end
@@ -6,52 +6,116 @@ module OmniAuth
6
6
  module Identity
7
7
  module Models
8
8
  # Sequel is an ORM adapter for the following databases:
9
- # ADO, Amalgalite, IBM_DB, JDBC, MySQL, Mysql2, ODBC, Oracle, PostgreSQL, SQLAnywhere, SQLite3, and TinyTDS
9
+ # ADO, Amalgalite, IBM_DB, JDBC, MySQL, Mysql2, ODBC, Oracle, PostgreSQL, SQLAnywhere, SQLite3, and TinyTDS
10
10
  # The homepage is: http://sequel.jeremyevans.net/
11
- # NOTE: Sequel is *not* based on ActiveModel, but supports the API we need, except for `persisted?`:
12
- # * create
13
- # * save
11
+ #
12
+ # This module provides OmniAuth Identity functionality for Sequel models,
13
+ # including secure password handling and authentication key management.
14
+ #
15
+ # @example Usage
16
+ # class User < Sequel::Model(:users)
17
+ # include OmniAuth::Identity::Models::Sequel
18
+ # end
19
+ #
20
+ # # Schema example:
21
+ # # DB.create_table :users do
22
+ # # primary_key :id
23
+ # # String :email, null: false, unique: true
24
+ # # String :password_digest, null: false
25
+ # # end
26
+ #
27
+ # user = User.new(email: 'user@example.com', password: 'password')
28
+ # user.save
29
+ #
30
+ # # Authenticate a user
31
+ # authenticated_user = User.locate(email: 'user@example.com')
32
+ # authenticated_user.authenticate('password') # => user or false
33
+ #
34
+ # @note Sequel is *not* based on ActiveModel, but supports the API we need, except for `persisted?`.
35
+ # @note Validations are enabled by default in versions < 4, but may change to disabled in v4 if ActiveModel is not present.
14
36
  module Sequel
15
- def self.included(base)
16
- base.class_eval do
17
- # NOTE: Using the deprecated :validations_class_methods because it defines
18
- # validates_confirmation_of, while current :validation_helpers does not.
19
- # plugin :validation_helpers
20
- plugin(:validation_class_methods)
21
-
22
- include(::OmniAuth::Identity::Model)
23
- include(::OmniAuth::Identity::SecurePassword)
24
-
25
- # `validations: true` (default) would normally incur a dependency on ActiveModel.
26
- # Starting in v3.1 we check if ActiveModel is defined before we actually set validations.
27
- # If ActiveModel isn't defined, it may be unexpected that validations are not being set,
28
- # so this will result in a warning deprecation until release of v4,
29
- # at which point the default (for Sequel ORM only) will change to `validations: false`
30
- has_secure_password(validations: OmniAuth::Identity::Version.major < 4)
31
-
32
- class << self
33
- def auth_key=(key)
34
- super
35
- # Sequel version of validates_uniqueness_of! Does not incur ActiveRecord dependency!
36
- validates_uniqueness_of(:key, case_sensitive: false)
37
- end
37
+ # Called when this module is included in a model class.
38
+ #
39
+ # This method extends the base class with OmniAuth Identity functionality,
40
+ # including secure password support and authentication key validation.
41
+ #
42
+ # @param base [Class] the model class including this module
43
+ # @return [void]
44
+ class << self
45
+ def included(base)
46
+ base.class_eval do
47
+ # NOTE: Using the deprecated :validations_class_methods because it defines
48
+ # validates_confirmation_of, while current :validation_helpers does not.
49
+ # plugin :validation_helpers
50
+ plugin(:validation_class_methods)
51
+
52
+ include(::OmniAuth::Identity::Model)
53
+ include(::OmniAuth::Identity::SecurePassword)
54
+
55
+ # `validations: true` (default) would normally incur a dependency on ActiveModel.
56
+ # Starting in v3.1 we check if ActiveModel is defined before we actually set validations.
57
+ # If ActiveModel isn't defined, it may be unexpected that validations are not being set,
58
+ # so this will result in a warning deprecation until release of v4,
59
+ # at which point the default (for Sequel ORM only) will change to `validations: false`
60
+ has_secure_password(validations: OmniAuth::Identity::Version.major < 4)
61
+
62
+ class << self
63
+ # @!method self.auth_key=(key)
64
+ # Sets the authentication key for the model and adds uniqueness validation.
65
+ #
66
+ # @param key [Symbol, String] the attribute to use as the authentication key
67
+ # @return [void]
68
+ # @example
69
+ # class User < Sequel::Model(:users)
70
+ # include OmniAuth::Identity::Models::Sequel
71
+ # self.auth_key = :email
72
+ # end
73
+ def auth_key=(key)
74
+ super
75
+ # Sequel version of validates_uniqueness_of! Does not incur ActiveRecord dependency!
76
+ validates_uniqueness_of(:key, case_sensitive: false)
77
+ end
38
78
 
39
- # @param arguments [any] -
40
- # Filtering is probably the most common dataset modifying action done in Sequel.
41
- # Both the where and filter methods filter the dataset by modifying the dataset’s WHERE clause.
42
- # Both accept a wide variety of input formats, which are passed as arguments below.
43
- # See: https://sequel.jeremyevans.net/rdoc/files/doc/querying_rdoc.html#label-Filters
44
- def locate(arguments)
45
- where(arguments).first
79
+ # @!method self.locate(arguments)
80
+ # Finds a record by the given search criteria.
81
+ #
82
+ # Filtering is probably the most common dataset modifying action done in Sequel.
83
+ # Both the where and filter methods filter the dataset by modifying the dataset's WHERE clause.
84
+ # Both accept a wide variety of input formats.
85
+ # See: https://sequel.jeremyevans.net/rdoc/files/doc/querying_rdoc.html#label-Filters
86
+ #
87
+ # @param arguments [any] the search criteria
88
+ # @return [Sequel::Model, nil] the first matching record or nil
89
+ # @example
90
+ # User.locate(email: 'user@example.com')
91
+ def locate(arguments)
92
+ where(arguments).first
93
+ end
46
94
  end
47
- end
48
95
 
49
- def persisted?
50
- exists?
51
- end
96
+ # @!method persisted?
97
+ # Checks if the record exists in the database.
98
+ #
99
+ # @return [Boolean] true if the record exists, false otherwise
100
+ # @example
101
+ # user = User.new
102
+ # user.persisted? # => false
103
+ # user.save
104
+ # user.persisted? # => true
105
+ def persisted?
106
+ exists?
107
+ end
52
108
 
53
- def save
54
- super
109
+ # @!method save
110
+ # Saves the record to the database.
111
+ #
112
+ # @return [Boolean] true if saved successfully, false otherwise
113
+ # @example
114
+ # user = User.new(email: 'user@example.com', password: 'password')
115
+ # user.save # => true
116
+ def save
117
+ super
118
+ end
55
119
  end
56
120
  end
57
121
  end
@@ -9,21 +9,47 @@ module OmniAuth
9
9
  # include SecurePassword. The only difference is that instead of
10
10
  # using ActiveSupport::Concern, it checks to see if there is already
11
11
  # a has_secure_password method.
12
+ #
13
+ # Provides secure password hashing and authentication using BCrypt.
14
+ #
15
+ # @example Basic Usage
16
+ # class User
17
+ # include OmniAuth::Identity::SecurePassword
18
+ #
19
+ # has_secure_password
20
+ # end
21
+ #
22
+ # user = User.new(password: 'secret')
23
+ # user.authenticate('secret') # => user
12
24
  module SecurePassword
13
- def self.included(base)
14
- base.extend(ClassMethods) unless base.respond_to?(:has_secure_password)
15
- end
16
-
25
+ # @!attribute [r] MAX_PASSWORD_LENGTH_ALLOWED
17
26
  # BCrypt hash function can handle maximum 72 bytes, and if we pass
18
27
  # password of length more than 72 bytes it ignores extra characters.
19
28
  # Hence need to put a restriction on password length.
29
+ # @return [Integer] The maximum allowed password length in bytes.
20
30
  MAX_PASSWORD_LENGTH_ALLOWED = BCrypt::Engine::MAX_SECRET_BYTESIZE
21
31
 
32
+ MIN_COST_MUTEX = Mutex.new
33
+ private_constant :MIN_COST_MUTEX
34
+
22
35
  class << self
23
- attr_accessor :min_cost # :nodoc:
36
+ def included(base)
37
+ base.extend(ClassMethods) unless base.respond_to?(:has_secure_password)
38
+ end
39
+
40
+ # @!attribute [rw] min_cost
41
+ # Controls whether to use minimum cost for BCrypt hashing (for testing).
42
+ # @return [true, false]
43
+ def min_cost # :nodoc:
44
+ MIN_COST_MUTEX.synchronize { @min_cost.nil? ? false : @min_cost }
45
+ end
46
+
47
+ def min_cost=(value) # :nodoc:
48
+ MIN_COST_MUTEX.synchronize { @min_cost = value }
49
+ end
24
50
  end
25
- self.min_cost = false
26
51
 
52
+ # Class-level methods for secure password functionality.
27
53
  module ClassMethods
28
54
  # Adds methods to set and authenticate against a BCrypt password.
29
55
  # This mechanism requires you to have a +XXX_digest+ attribute.
@@ -82,6 +108,10 @@ module OmniAuth
82
108
  # user.authenticate_recovery_password('42password') # => user
83
109
  # User.find_by(name: 'david')&.authenticate('notright') # => false
84
110
  # User.find_by(name: 'david')&.authenticate('mUc3m00RsqyRe') # => user
111
+ #
112
+ # @param attribute [Symbol, String] the attribute name for the password (default: :password)
113
+ # @param validations [true, false] whether to add validations (default: true)
114
+ # @return [void]
85
115
  def has_secure_password(attribute = :password, validations: true)
86
116
  # Load bcrypt gem only when has_secure_password is used.
87
117
  # This is to avoid ActiveModel (and by extension the entire framework)
@@ -121,7 +151,12 @@ module OmniAuth
121
151
  end
122
152
  end
123
153
 
154
+ # A module that defines instance methods for password handling.
155
+ # Methods are defined dynamically based on the attribute name.
124
156
  class InstanceMethodsOnActivation < Module
157
+ # Initializes the module with the password attribute name.
158
+ #
159
+ # @param attribute [Symbol, String] the password attribute name
125
160
  def initialize(attribute)
126
161
  attr_reader(attribute)
127
162
 
@@ -1,9 +1,17 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module OmniAuth
3
+ module Omniauth
4
4
  module Identity
5
5
  module Version
6
- VERSION = "3.1.4"
6
+ VERSION = "3.2.0"
7
7
  end
8
+ VERSION = Version::VERSION # Traditional Constant Location
9
+ end
10
+ end
11
+
12
+ module OmniAuth
13
+ module Identity
14
+ Version = Omniauth::Identity::Version unless const_defined?(:Version, false)
15
+ VERSION = Version::VERSION unless const_defined?(:VERSION, false)
8
16
  end
9
17
  end
@@ -1,6 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # TODO[v4]: Remove this deprecation with v4 release.
4
+ require "version_gem"
5
+
4
6
  unless defined?(OmniAuth::Identity::Version::VERSION)
5
7
  # external gems
6
8
  require "version_gem"
@@ -18,20 +20,59 @@ end
18
20
 
19
21
  require "omniauth"
20
22
 
23
+ # The main OmniAuth module.
21
24
  module OmniAuth
25
+ # Container for OmniAuth strategy classes.
22
26
  module Strategies
27
+ # Autoload the Identity strategy.
23
28
  autoload :Identity, "omniauth/strategies/identity"
24
29
  end
25
30
 
31
+ # OmniAuth Identity provides a way to authenticate users using a username and password
32
+ # stored in your application's database. It supports multiple ORMs and provides
33
+ # secure password hashing.
34
+ #
35
+ # @example Basic Setup
36
+ # # In your Gemfile
37
+ # gem 'omniauth-identity'
38
+ #
39
+ # # In your OmniAuth configuration
40
+ # use OmniAuth::Strategies::Identity,
41
+ # fields: [:email],
42
+ # model: User
43
+ #
44
+ # @see OmniAuth::Strategies::Identity
45
+ # @see OmniAuth::Identity::Model
46
+ # @see OmniAuth::Identity::SecurePassword
26
47
  module Identity
48
+ # Autoload the Model module.
27
49
  autoload :Model, "omniauth/identity/model"
50
+ # Autoload the SecurePassword module.
28
51
  autoload :SecurePassword, "omniauth/identity/secure_password"
52
+
53
+ # Container for ORM-specific model adapters.
29
54
  module Models
55
+ # Autoload the ActiveRecord adapter.
30
56
  autoload :ActiveRecord, "omniauth/identity/models/active_record"
57
+ # Autoload the Mongoid adapter.
31
58
  autoload :Mongoid, "omniauth/identity/models/mongoid"
59
+ # Autoload the CouchPotato adapter.
32
60
  autoload :CouchPotatoModule, "omniauth/identity/models/couch_potato"
61
+ # Autoload the NoBrainer adapter.
33
62
  autoload :NoBrainer, "omniauth/identity/models/nobrainer"
63
+ # Autoload the ROM adapter.
64
+ autoload :Rom, "omniauth/identity/models/rom"
65
+ # Autoload the Sequel adapter.
34
66
  autoload :Sequel, "omniauth/identity/models/sequel"
35
67
  end
36
68
  end
37
69
  end
70
+ require_relative "identity/version"
71
+
72
+ OmniAuth::Identity::Version.class_eval do
73
+ extend VersionGem::Basic
74
+ end
75
+
76
+ Omniauth::Identity::Version.class_eval do
77
+ extend VersionGem::Basic
78
+ end