omniauth-identity 3.1.5 → 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.
data/SECURITY.md CHANGED
@@ -2,13 +2,9 @@
2
2
 
3
3
  ## Supported Versions
4
4
 
5
- | Version | Supported |
6
- |---------|-----------|
7
- | 3.1.x | ✅ |
8
- | 3.0.x | ❌ |
9
- | 2.x | ❌ |
10
- | 1.x | ❌ |
11
- | 0.x | ❌ |
5
+ | Version | Supported |
6
+ |----------|-----------|
7
+ | 3.1.latest | ✅ |
12
8
 
13
9
  ## Security contact information
14
10
 
@@ -23,11 +19,3 @@ please consider sponsoring the project / maintainer @ https://liberapay.com/pbol
23
19
  or find other sponsorship links in the [README].
24
20
 
25
21
  [README]: README.md
26
-
27
- ## Enterprise Support
28
-
29
- Available as part of the Tidelift Subscription.
30
-
31
- The maintainers of this library and thousands of other packages are working with Tidelift to deliver commercial support and maintenance for the open source packages you use to build your applications. Save time, reduce risk, and improve code health, while paying the maintainers for the exact packages you use. [Learn more.][tidelift-ref]
32
-
33
- [tidelift-ref]: https://tidelift.com/subscription/pkg/rubygems-omniauth-identity?utm_source=rubygems-omniauth-identity&utm_medium=referral&utm_campaign=enterprise&utm_term=repo
data/certs/pboling.pem ADDED
@@ -0,0 +1,27 @@
1
+ -----BEGIN CERTIFICATE-----
2
+ MIIEgDCCAuigAwIBAgIBATANBgkqhkiG9w0BAQsFADBDMRUwEwYDVQQDDAxwZXRl
3
+ ci5ib2xpbmcxFTATBgoJkiaJk/IsZAEZFgVnbWFpbDETMBEGCgmSJomT8ixkARkW
4
+ A2NvbTAeFw0yNTA1MDQxNTMzMDlaFw00NTA0MjkxNTMzMDlaMEMxFTATBgNVBAMM
5
+ DHBldGVyLmJvbGluZzEVMBMGCgmSJomT8ixkARkWBWdtYWlsMRMwEQYKCZImiZPy
6
+ LGQBGRYDY29tMIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEAruUoo0WA
7
+ uoNuq6puKWYeRYiZekz/nsDeK5x/0IEirzcCEvaHr3Bmz7rjo1I6On3gGKmiZs61
8
+ LRmQ3oxy77ydmkGTXBjruJB+pQEn7UfLSgQ0xa1/X3kdBZt6RmabFlBxnHkoaGY5
9
+ mZuZ5+Z7walmv6sFD9ajhzj+oIgwWfnEHkXYTR8I6VLN7MRRKGMPoZ/yvOmxb2DN
10
+ coEEHWKO9CvgYpW7asIihl/9GMpKiRkcYPm9dGQzZc6uTwom1COfW0+ZOFrDVBuV
11
+ FMQRPswZcY4Wlq0uEBLPU7hxnCL9nKK6Y9IhdDcz1mY6HZ91WImNslOSI0S8hRpj
12
+ yGOWxQIhBT3fqCBlRIqFQBudrnD9jSNpSGsFvbEijd5ns7Z9ZMehXkXDycpGAUj1
13
+ to/5cuTWWw1JqUWrKJYoifnVhtE1o1DZ+LkPtWxHtz5kjDG/zR3MG0Ula0UOavlD
14
+ qbnbcXPBnwXtTFeZ3C+yrWpE4pGnl3yGkZj9SMTlo9qnTMiPmuWKQDatAgMBAAGj
15
+ fzB9MAkGA1UdEwQCMAAwCwYDVR0PBAQDAgSwMB0GA1UdDgQWBBQE8uWvNbPVNRXZ
16
+ HlgPbc2PCzC4bjAhBgNVHREEGjAYgRZwZXRlci5ib2xpbmdAZ21haWwuY29tMCEG
17
+ A1UdEgQaMBiBFnBldGVyLmJvbGluZ0BnbWFpbC5jb20wDQYJKoZIhvcNAQELBQAD
18
+ ggGBAJbnUwfJQFPkBgH9cL7hoBfRtmWiCvdqdjeTmi04u8zVNCUox0A4gT982DE9
19
+ wmuN12LpdajxZONqbXuzZvc+nb0StFwmFYZG6iDwaf4BPywm2e/Vmq0YG45vZXGR
20
+ L8yMDSK1cQXjmA+ZBKOHKWavxP6Vp7lWvjAhz8RFwqF9GuNIdhv9NpnCAWcMZtpm
21
+ GUPyIWw/Cw/2wZp74QzZj6Npx+LdXoLTF1HMSJXZ7/pkxLCsB8m4EFVdb/IrW/0k
22
+ kNSfjtAfBHO8nLGuqQZVH9IBD1i9K6aSs7pT6TW8itXUIlkIUI2tg5YzW6OFfPzq
23
+ QekSkX3lZfY+HTSp/o+YvKkqWLUV7PQ7xh1ZYDtocpaHwgxe/j3bBqHE+CUPH2vA
24
+ 0V/FwdTRWcwsjVoOJTrYcff8pBZ8r2MvtAc54xfnnhGFzeRHfcltobgFxkAXdE6p
25
+ DVjBtqT23eugOqQ73umLcYDZkc36vnqGxUBSsXrzY9pzV5gGr2I8YUxMqf6ATrZt
26
+ L9nRqA==
27
+ -----END CERTIFICATE-----
@@ -1,7 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "auth_sanitizer/loader"
4
+
3
5
  module OmniAuth
4
6
  module Identity
7
+ AUTH_SANITIZER = AuthSanitizer::Loader.load_isolated unless const_defined?(:AUTH_SANITIZER, false)
8
+
5
9
  # This module provides an include-able interface for implementing the
6
10
  # necessary API for OmniAuth Identity to properly locate identities
7
11
  # and provide all necessary information.
@@ -30,6 +34,7 @@ module OmniAuth
30
34
  # Standard OmniAuth schema attributes that may be stored in the model.
31
35
  # @return [Array<String>] List of attribute names.
32
36
  SCHEMA_ATTRIBUTES = %w[name email nickname first_name last_name location description image phone].freeze
37
+ FILTERED_INSPECT_ATTRIBUTES = %i[password password_confirmation password_digest].freeze
33
38
 
34
39
  class << self
35
40
  # Called when this module is included in a model class.
@@ -40,6 +45,9 @@ module OmniAuth
40
45
  # @param base [Class] the model class including this module
41
46
  # @return [void]
42
47
  def included(base)
48
+ base.include(OmniAuth::Identity::AUTH_SANITIZER::FilteredAttributes)
49
+ base.prepend(OmniAuth::Identity::AUTH_SANITIZER::FilteredAttributes)
50
+ base.filtered_attributes(*FILTERED_INSPECT_ATTRIBUTES)
43
51
  base.extend(ClassMethods)
44
52
  base.extend(ClassCreateApi) unless base.respond_to?(:create)
45
53
  i_methods = base.instance_methods
@@ -50,6 +58,9 @@ module OmniAuth
50
58
 
51
59
  # Class-level methods for OmniAuth Identity models.
52
60
  module ClassMethods
61
+ AUTH_KEY_MUTEX = Mutex.new
62
+ private_constant :AUTH_KEY_MUTEX
63
+
53
64
  # Authenticate a user with the given key and password.
54
65
  #
55
66
  # @param [String] conditions The unique login key provided for a given identity.
@@ -62,16 +73,23 @@ module OmniAuth
62
73
  instance.authenticate(password)
63
74
  end
64
75
 
76
+ def inherited(subclass)
77
+ super if defined?(super)
78
+ subclass.filtered_attributes(*filtered_attribute_names) if subclass.respond_to?(:filtered_attributes)
79
+ end
80
+
65
81
  # Used to set or retrieve the method that will be used to get
66
82
  # and set the user-supplied authentication key.
67
83
  #
68
84
  # @param method [String, Symbol, false] The method name to set, or false to retrieve.
69
85
  # @return [String] The method name.
70
86
  def auth_key(method = false)
71
- @auth_key = method.to_s unless method == false
72
- @auth_key = nil if !defined?(@auth_key) || @auth_key == ""
87
+ AUTH_KEY_MUTEX.synchronize do
88
+ @auth_key = method.to_s unless method == false
89
+ @auth_key = nil if !defined?(@auth_key) || @auth_key == ""
73
90
 
74
- @auth_key || "email"
91
+ @auth_key || "email"
92
+ end
75
93
  end
76
94
 
77
95
  # Locate an identity given its unique login key.
@@ -174,7 +192,7 @@ module OmniAuth
174
192
  # @param [String] value The value to which the auth key should be
175
193
  # set.
176
194
  def auth_key=(value)
177
- auth_key_setter = "#{self.class.auth_key}=".to_sym
195
+ auth_key_setter = :"#{self.class.auth_key}="
178
196
  if respond_to?(auth_key_setter)
179
197
  send(auth_key_setter, value)
180
198
  else
@@ -51,23 +51,25 @@ module OmniAuth
51
51
  # class User < OmniAuth::Identity::Models::ActiveRecord
52
52
  # self.auth_key = :email
53
53
  # end
54
- def self.auth_key=(key)
55
- super
56
- validates_uniqueness_of(key, case_sensitive: false)
57
- end
54
+ class << self
55
+ def auth_key=(key)
56
+ super
57
+ validates_uniqueness_of(key, case_sensitive: false)
58
+ end
58
59
 
59
- # @!method self.locate(search_hash)
60
- # Finds a record by the given search criteria.
61
- #
62
- # If the model has a 'provider' column, it defaults to 'identity'.
63
- #
64
- # @param search_hash [Hash] the attributes to search for
65
- # @return [ActiveRecord::Base, nil] the first matching record or nil
66
- # @example
67
- # User.locate(email: 'user@example.com')
68
- def self.locate(search_hash)
69
- search_hash = search_hash.reverse_merge!("provider" => "identity") if column_names.include?("provider")
70
- where(search_hash).first
60
+ # @!method self.locate(search_hash)
61
+ # Finds a record by the given search criteria.
62
+ #
63
+ # If the model has a 'provider' column, it defaults to 'identity'.
64
+ #
65
+ # @param search_hash [Hash] the attributes to search for
66
+ # @return [ActiveRecord::Base, nil] the first matching record or nil
67
+ # @example
68
+ # User.locate(email: 'user@example.com')
69
+ def locate(search_hash)
70
+ search_hash = search_hash.reverse_merge!("provider" => "identity") if column_names.include?("provider")
71
+ where(search_hash).first
72
+ end
71
73
  end
72
74
  end
73
75
  end
@@ -32,56 +32,60 @@ module OmniAuth
32
32
  # @note CouchPotato::Persistence must be included before OmniAuth::Identity::Models::CouchPotatoModule.
33
33
  # @note Includes "Module" in the name for invalid legacy reasons. Rename only with a major version bump.
34
34
  module CouchPotatoModule
35
- # Called when this module is included in a model class.
36
- #
37
- # This method extends the base class with OmniAuth Identity functionality,
38
- # including secure password support and authentication key validation.
39
- #
40
- # @param base [Class] the model class including this module
41
- # @return [void]
42
- def self.included(base)
43
- base.class_eval do
44
- include(::OmniAuth::Identity::Model)
45
- include(::OmniAuth::Identity::SecurePassword)
35
+ class << self
36
+ # Called when this module is included in a model class.
37
+ #
38
+ # This method extends the base class with OmniAuth Identity functionality,
39
+ # including secure password support and authentication key validation.
40
+ #
41
+ # @param base [Class] the model class including this module
42
+ # @return [void]
43
+ def included(base)
44
+ base.class_eval do
45
+ include(::OmniAuth::Identity::Model)
46
+ include(::OmniAuth::Identity::SecurePassword)
46
47
 
47
- # validations: true (default) incurs a dependency on ActiveModel, but CouchPotato is ActiveModel based.
48
- has_secure_password
48
+ # validations: true (default) incurs a dependency on ActiveModel, but CouchPotato is ActiveModel based.
49
+ has_secure_password
49
50
 
50
- # @!method self.auth_key=(key)
51
- # Sets the authentication key for the model and adds uniqueness validation.
52
- #
53
- # @param key [Symbol, String] the attribute to use as the authentication key
54
- # @return [void]
55
- # @example
56
- # class User
57
- # include OmniAuth::Identity::Models::CouchPotatoModule
58
- # self.auth_key = :email
59
- # end
60
- def self.auth_key=(key)
61
- super
62
- validates_uniqueness_of(key, case_sensitive: false)
63
- end
51
+ class << self
52
+ # @!method self.auth_key=(key)
53
+ # Sets the authentication key for the model and adds uniqueness validation.
54
+ #
55
+ # @param key [Symbol, String] the attribute to use as the authentication key
56
+ # @return [void]
57
+ # @example
58
+ # class User
59
+ # include OmniAuth::Identity::Models::CouchPotatoModule
60
+ # self.auth_key = :email
61
+ # end
62
+ def auth_key=(key)
63
+ super
64
+ validates_uniqueness_of(key, case_sensitive: false)
65
+ end
64
66
 
65
- # @!method self.locate(search_hash)
66
- # Finds a record by the given search criteria.
67
- #
68
- # @param search_hash [Hash] the attributes to search for
69
- # @return [Object, nil] the first matching record or nil
70
- # @example
71
- # User.locate(email: 'user@example.com')
72
- def self.locate(search_hash)
73
- where(search_hash).first
74
- end
67
+ # @!method self.locate(search_hash)
68
+ # Finds a record by the given search criteria.
69
+ #
70
+ # @param search_hash [Hash] the attributes to search for
71
+ # @return [Object, nil] the first matching record or nil
72
+ # @example
73
+ # User.locate(email: 'user@example.com')
74
+ def locate(search_hash)
75
+ where(search_hash).first
76
+ end
77
+ end
75
78
 
76
- # @!method save
77
- # Saves the document to the CouchDB database.
78
- #
79
- # @return [Boolean] true if saved successfully, false otherwise
80
- # @example
81
- # user = User.new(email: 'user@example.com', password: 'password')
82
- # user.save # => true
83
- def save
84
- CouchPotato.database.save_document(self)
79
+ # @!method save
80
+ # Saves the document to the CouchDB database.
81
+ #
82
+ # @return [Boolean] true if saved successfully, false otherwise
83
+ # @example
84
+ # user = User.new(email: 'user@example.com', password: 'password')
85
+ # user.save # => true
86
+ def save
87
+ CouchPotato.database.save_document(self)
88
+ end
85
89
  end
86
90
  end
87
91
  end
@@ -30,45 +30,49 @@ module OmniAuth
30
30
  #
31
31
  # @note Mongoid is based on ActiveModel, so validations are enabled by default.
32
32
  module Mongoid
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 self.included(base)
41
- base.class_eval do
42
- include(::OmniAuth::Identity::Model)
43
- include(::OmniAuth::Identity::SecurePassword)
33
+ class << self
34
+ # Called when this module is included in a model class.
35
+ #
36
+ # This method extends the base class with OmniAuth Identity functionality,
37
+ # including secure password support and authentication key validation.
38
+ #
39
+ # @param base [Class] the model class including this module
40
+ # @return [void]
41
+ def included(base)
42
+ base.class_eval do
43
+ include(::OmniAuth::Identity::Model)
44
+ include(::OmniAuth::Identity::SecurePassword)
44
45
 
45
- # validations: true (default) incurs a dependency on ActiveModel, but Mongoid is ActiveModel based.
46
- has_secure_password
46
+ # validations: true (default) incurs a dependency on ActiveModel, but Mongoid is ActiveModel based.
47
+ has_secure_password
47
48
 
48
- # @!method self.auth_key=(key)
49
- # Sets the authentication key for the model and adds uniqueness validation.
50
- #
51
- # @param key [Symbol, String] the attribute to use as the authentication key
52
- # @return [void]
53
- # @example
54
- # class User
55
- # include OmniAuth::Identity::Models::Mongoid
56
- # self.auth_key = :email
57
- # end
58
- def self.auth_key=(key)
59
- super
60
- validates_uniqueness_of(key, case_sensitive: false)
61
- end
49
+ class << self
50
+ # @!method self.auth_key=(key)
51
+ # Sets the authentication key for the model and adds uniqueness validation.
52
+ #
53
+ # @param key [Symbol, String] the attribute to use as the authentication key
54
+ # @return [void]
55
+ # @example
56
+ # class User
57
+ # include OmniAuth::Identity::Models::Mongoid
58
+ # self.auth_key = :email
59
+ # end
60
+ def auth_key=(key)
61
+ super
62
+ validates_uniqueness_of(key, case_sensitive: false)
63
+ end
62
64
 
63
- # @!method self.locate(search_hash)
64
- # Finds a record by the given search criteria.
65
- #
66
- # @param search_hash [Hash] the attributes to search for
67
- # @return [Mongoid::Document, nil] the first matching record or nil
68
- # @example
69
- # User.locate(email: 'user@example.com')
70
- def self.locate(search_hash)
71
- where(search_hash).first
65
+ # @!method self.locate(search_hash)
66
+ # Finds a record by the given search criteria.
67
+ #
68
+ # @param search_hash [Hash] the attributes to search for
69
+ # @return [Mongoid::Document, nil] the first matching record or nil
70
+ # @example
71
+ # User.locate(email: 'user@example.com')
72
+ def locate(search_hash)
73
+ where(search_hash).first
74
+ end
75
+ end
72
76
  end
73
77
  end
74
78
  end
@@ -29,45 +29,49 @@ module OmniAuth
29
29
  #
30
30
  # @note NoBrainer is based on ActiveModel, so validations are enabled by default.
31
31
  module NoBrainer
32
- # Called when this module is included in a model class.
33
- #
34
- # This method extends the base class with OmniAuth Identity functionality,
35
- # including secure password support and authentication key validation.
36
- #
37
- # @param base [Class] the model class including this module
38
- # @return [void]
39
- def self.included(base)
40
- base.class_eval do
41
- include(::OmniAuth::Identity::Model)
42
- 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)
43
44
 
44
- # validations: true (default) incurs a dependency on ActiveModel, but NoBrainer is ActiveModel based.
45
- has_secure_password
45
+ # validations: true (default) incurs a dependency on ActiveModel, but NoBrainer is ActiveModel based.
46
+ has_secure_password
46
47
 
47
- # @!method self.auth_key=(key)
48
- # Sets the authentication key for the model and adds uniqueness validation.
49
- #
50
- # @param key [Symbol, String] the attribute to use as the authentication key
51
- # @return [void]
52
- # @example
53
- # class User
54
- # include OmniAuth::Identity::Models::NoBrainer
55
- # self.auth_key = :email
56
- # end
57
- def self.auth_key=(key)
58
- super
59
- validates_uniqueness_of(key, case_sensitive: false)
60
- 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
61
63
 
62
- # @!method self.locate(search_hash)
63
- # Finds a record by the given search criteria.
64
- #
65
- # @param search_hash [Hash] the attributes to search for
66
- # @return [Object, nil] the first matching record or nil
67
- # @example
68
- # User.locate(email: 'user@example.com')
69
- def self.locate(search_hash)
70
- 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
71
75
  end
72
76
  end
73
77
  end
@@ -25,24 +25,26 @@ module OmniAuth
25
25
  # password_field :password_digest
26
26
  # end
27
27
  module Rom
28
- def self.included(base)
29
- # Align with other adapters: rely on OmniAuth::Identity::Model for API
30
- base.include(::OmniAuth::Identity::Model)
31
- base.extend(ClassMethods)
32
-
33
- base.class_eval do
34
- # OmniAuth::Identity required instance API
35
- # Authenticates the instance with the provided password
36
- # Returns self on success, false otherwise (to match Model.authenticate contract)
37
- def authenticate(password)
38
- digest_key = self.class.password_field
39
- password_digest = @identity_data[digest_key]
40
- return false unless password_digest
41
-
42
- begin
43
- BCrypt::Password.new(password_digest) == password && self
44
- rescue BCrypt::Errors::InvalidHash
45
- false
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
46
48
  end
47
49
  end
48
50
  end
@@ -52,29 +54,40 @@ module OmniAuth
52
54
  # Default ROM relation name when none is configured
53
55
  DEFAULT_RELATION_NAME = :identities
54
56
 
57
+ ROM_CONFIG_MUTEX = Mutex.new
58
+ private_constant :ROM_CONFIG_MUTEX
59
+
55
60
  # Configuration DSL
56
61
  # These methods act like the DSL on `OmniAuth::Identity::Model` (e.g. `auth_key`) —
57
62
  # when called with an argument they set the configuration, and when called
58
63
  # without an argument they return the current value (with sensible defaults).
59
64
  def rom_container(value = false)
60
- @rom_container = value unless value == false
61
- container = @rom_container
62
- container.respond_to?(:call) ? container.call : container
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
63
70
  end
64
71
 
65
72
  def rom_relation_name(value = false)
66
- @rom_relation_name = value unless value == false
67
- @rom_relation_name || DEFAULT_RELATION_NAME
73
+ ROM_CONFIG_MUTEX.synchronize do
74
+ @rom_relation_name = value unless value == false
75
+ @rom_relation_name || DEFAULT_RELATION_NAME
76
+ end
68
77
  end
69
78
 
70
79
  def owner_relation_name(value = false)
71
- @owner_relation_name = value unless value == false
72
- @owner_relation_name
80
+ ROM_CONFIG_MUTEX.synchronize do
81
+ @owner_relation_name = value unless value == false
82
+ @owner_relation_name
83
+ end
73
84
  end
74
85
 
75
86
  def password_field(value = false)
76
- @password_field = value unless value == false
77
- (@password_field || :password_digest).to_sym
87
+ ROM_CONFIG_MUTEX.synchronize do
88
+ @password_field = value unless value == false
89
+ (@password_field || :password_digest).to_sym
90
+ end
78
91
  end
79
92
 
80
93
  # Align with other adapters: use Model.auth_key (getter/setter) for the login attribute