rodauth-model 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 7ea8768862eb1ba1bf2bdf1bb93872c0167230bf8c139ca49db5b17927288797
4
+ data.tar.gz: fe85b970b503ecd33a9c074d1b8763616e65fac5e88fd0fbbd49d2b986ae29ea
5
+ SHA512:
6
+ metadata.gz: 4b3f777be5c15eadacb69e074a4bf7b9aacad161e3b6c98f4c2f7ac0a3712af07259e82e62b339e18539522d98694d5d38d95318a11b10cac43c3cf042e87f0e
7
+ data.tar.gz: 7788e39bd9de5138773a90c5cac2babdef39da6b9a2a557a37af902c4b1bc851ec26e7e09dd6c6a62d799c3b39198750c39a24560025c2acb7e9fb0223bb4379
data/CHANGELOG.md ADDED
@@ -0,0 +1,5 @@
1
+ ## [Unreleased]
2
+
3
+ ## [0.1.0] - 2022-01-04
4
+
5
+ - Initial release
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2022 Janko Marohnić
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,202 @@
1
+ # rodauth-model
2
+
3
+ Extension for [Rodauth] providing a mixin for the account model that defines password attribute and associations based on enabled authentication features. At the moment only Active Record is supported.
4
+
5
+ ## Installation
6
+
7
+ ```sh
8
+ $ bundle add rodauth-model
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ Assuming with have a `RodauthApp` Roda subclass with Rodauth configured, we can build the mixin from a Rodauth class, and include it into the account model:
14
+
15
+ ```rb
16
+ require "rodauth/model" # require before enabling any authentication features
17
+
18
+ class RodauthApp < Roda
19
+ plugin :rodauth do
20
+ # ...
21
+ end
22
+ end
23
+ ```
24
+ ```rb
25
+ class Account < ActiveRecord::Base
26
+ include Rodauth::Model(RodauthApp.rodauth)
27
+ end
28
+ ```
29
+
30
+ If you have multiple Rodauth configurations, pass the one for which you want associations to be defined.
31
+
32
+ ```rb
33
+ class Account < ActiveRecord::Base
34
+ include Rodauth::Model(RodauthApp.rodauth(:admin))
35
+ end
36
+ ```
37
+
38
+ ### Password attribute
39
+
40
+ Regardless of whether you're storing the password hash in a column in the accounts table, or in a separate table, the `#password` attribute can be used to set or clear the password hash.
41
+
42
+ ```rb
43
+ account = Account.create!(email: "user@example.com", password: "secret")
44
+
45
+ # when password hash is stored in a column on the accounts table
46
+ account.password_hash #=> "$2a$12$k/Ub1I2iomi84RacqY89Hu4.M0vK7klRnRtzorDyvOkVI.hKhkNw."
47
+
48
+ # when password hash is stored in a separate table
49
+ account.password_hash #=> #<Account::PasswordHash...> (record from `account_password_hashes` table)
50
+ account.password_hash.password_hash #=> "$2a$12$k/Ub1..." (inaccessible when using database authentication functions)
51
+
52
+ account.password = nil # clears password hash
53
+ account.password_hash #=> nil
54
+ ```
55
+
56
+ Note that the password attribute doesn't come with validations, making it unsuitable for forms. It was primarily intended to allow easily creating accounts in development console and in tests.
57
+
58
+ ### Associations
59
+
60
+ The mixin defines associations for Rodauth tables associated to the accounts table:
61
+
62
+ ```rb
63
+ account.remember_key #=> #<Account::RememberKey> (record from `account_remember_keys` table)
64
+ account.active_session_keys #=> [#<Account::ActiveSessionKey>,...] (records from `account_active_session_keys` table)
65
+ ```
66
+
67
+ You can also reference the associated models directly:
68
+
69
+ ```rb
70
+ # model referencing the `account_authentication_audit_logs` table
71
+ Account::AuthenticationAuditLog.where(message: "login").group(:account_id)
72
+ ```
73
+
74
+ The associated models define the inverse `belongs_to :account` association:
75
+
76
+ ```rb
77
+ Account::ActiveSessionKey.includes(:account).map(&:account)
78
+ ```
79
+
80
+ ### Association options
81
+
82
+ By default, all associations except for audit logs have `dependent: :delete` set, to allow for easy deletion of account records in the console. You can use `:association_options` to modify global or per-association options:
83
+
84
+ ```rb
85
+ # don't auto-delete associations when account model is deleted
86
+ Rodauth::Model(RodauthApp.rodauth, association_options: { dependent: nil })
87
+
88
+ # require authentication audit logs to be eager loaded before retrieval
89
+ Rodauth::Model(RodauthApp.rodauth, association_options: -> (name) {
90
+ { strict_loading: true } if name == :authentication_audit_logs
91
+ })
92
+ ```
93
+
94
+ ## Association reference
95
+
96
+ Below is a list of all associations defined depending on the features loaded:
97
+
98
+ | Feature | Association | Type | Model | Table (default) |
99
+ | :------ | :---------- | :--- | :---- | :---- |
100
+ | account_expiration | `:activity_time` | `has_one` | `ActivityTime` | `account_activity_times` |
101
+ | active_sessions | `:active_session_keys` | `has_many` | `ActiveSessionKey` | `account_active_session_keys` |
102
+ | audit_logging | `:authentication_audit_logs` | `has_many` | `AuthenticationAuditLog` | `account_authentication_audit_logs` |
103
+ | disallow_password_reuse | `:previous_password_hashes` | `has_many` | `PreviousPasswordHash` | `account_previous_password_hashes` |
104
+ | email_auth | `:email_auth_key` | `has_one` | `EmailAuthKey` | `account_email_auth_keys` |
105
+ | jwt_refresh | `:jwt_refresh_keys` | `has_many` | `JwtRefreshKey` | `account_jwt_refresh_keys` |
106
+ | lockout | `:lockout` | `has_one` | `Lockout` | `account_lockouts` |
107
+ | lockout | `:login_failure` | `has_one` | `LoginFailure` | `account_login_failures` |
108
+ | otp | `:otp_key` | `has_one` | `OtpKey` | `account_otp_keys` |
109
+ | password_expiration | `:password_change_time` | `has_one` | `PasswordChangeTime` | `account_password_change_times` |
110
+ | recovery_codes | `:recovery_codes` | `has_many` | `RecoveryCode` | `account_recovery_codes` |
111
+ | remember | `:remember_key` | `has_one` | `RememberKey` | `account_remember_keys` |
112
+ | reset_password | `:password_reset_key` | `has_one` | `PasswordResetKey` | `account_password_reset_keys` |
113
+ | single_session | `:session_key` | `has_one` | `SessionKey` | `account_session_keys` |
114
+ | sms_codes | `:sms_code` | `has_one` | `SmsCode` | `account_sms_codes` |
115
+ | verify_account | `:verification_key` | `has_one` | `VerificationKey` | `account_verification_keys` |
116
+ | verify_login_change | `:login_change_key` | `has_one` | `LoginChangeKey` | `account_login_change_keys` |
117
+ | webauthn | `:webauthn_keys` | `has_many` | `WebauthnKey` | `account_webauthn_keys` |
118
+ | webauthn | `:webauthn_user_id` | `has_one` | `WebauthnUserId` | `account_webauthn_user_ids` |
119
+
120
+ Note that some Rodauth tables use composite primary keys, which Active Record doesn't support out of the box. For associations to work properly, you might need to add the [composite_primary_keys] gem to your Gemfile.
121
+
122
+ ## Extending associations
123
+
124
+ It's possible to register custom associations for an external feature, which the model mixin would pick up and automatically define the association on the model if the feature is enabled.
125
+
126
+ ```rb
127
+ # lib/rodauth/features/foo.rb
128
+ module Rodauth
129
+ Feature.define(:foo, :Foo) do
130
+ auth_value_method :foo_table, :account_foos
131
+ auth_value_method :foo_id_column, :id
132
+ # ...
133
+ end
134
+
135
+ if defined?(Model)
136
+ Model.register_association(:foo) do
137
+ { name: :foo, type: :one, table: foo_table, key: foo_id_column }
138
+ end
139
+ end
140
+ end
141
+ ```
142
+
143
+ The `Rodauth::Model.register_association` method receives the feature name and a block, which is evaluted in the context of a Rodauth instance and should return the association definition with the following items:
144
+
145
+ * `:name` – association name
146
+ * `:type` – relationship type (`:one` for one-to-one, `:many` for one-to-many)
147
+ * `:table` – associated table name
148
+ * `:key` – foreign key on the associated table
149
+
150
+ It's possible to register multiple associations for the same Rodauth feature.
151
+
152
+ ## Examples
153
+
154
+ ### Checking whether account has multifactor authentication enabled
155
+
156
+ ```rb
157
+ class Account < ActiveRecord::Base
158
+ include Rodauth::Model(RodauthApp.rodauth)
159
+
160
+ def mfa_enabled?
161
+ otp_key || (sms_code && sms_code.num_failures.nil?) || recovery_codes.any?
162
+ end
163
+ end
164
+ ```
165
+
166
+ ### Retrieving all accounts with multifactor authentication enabled
167
+
168
+ ```rb
169
+ class Account < ActiveRecord::Base
170
+ include Rodauth::Model(RodauthApp.rodauth)
171
+
172
+ scope :otp_setup, -> { where(otp_key: OtpKey.all) }
173
+ scope :sms_codes_setup, -> { where(sms_code: SmsCode.where(num_failures: nil)) }
174
+ scope :recovery_codes_setup, -> { where(recovery_codes: RecoveryCode.all) }
175
+ scope :mfa_enabled, -> { merge(otp_setup.or(sms_codes_setup).or(recovery_codes_setup)) }
176
+ end
177
+ ```
178
+
179
+ ## Future plans
180
+
181
+ ### Sequel support
182
+
183
+ Currently on Active Record models are supported, but I would like support Sequel models in the near future as well.
184
+
185
+ ### Joined associations
186
+
187
+ It's possible to have multiple Rodauth configurations that operate on the same tables, but it's currently possible to define associations just for a single configuration. I would like to support grabbing associations from multiple associations.
188
+
189
+ ## Contributing
190
+
191
+ Bug reports and pull requests are welcome on GitHub at https://github.com/janko/rodauth-model. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/janko/rodauth-model/blob/main/CODE_OF_CONDUCT.md).
192
+
193
+ ## License
194
+
195
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
196
+
197
+ ## Code of Conduct
198
+
199
+ Everyone interacting in the Rodauth::Model project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/janko/rodauth-model/blob/main/CODE_OF_CONDUCT.md).
200
+
201
+ [Rodauth]: https://rodauth.jeremyevans.net
202
+ [composite_primary_keys]: https://github.com/composite-primary-keys/composite_primary_keys
@@ -0,0 +1,91 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rodauth/model/associations"
4
+
5
+ module Rodauth
6
+ class Model
7
+ module ActiveRecord
8
+ ASSOCIATION_TYPES = { one: :has_one, many: :has_many }
9
+
10
+ private
11
+
12
+ def define_methods(model)
13
+ rodauth = @auth_class.allocate.freeze
14
+
15
+ attr_reader :password
16
+
17
+ define_method(:password=) do |password|
18
+ @password = password
19
+ password_hash = rodauth.send(:password_hash, password) if password
20
+ set_password_hash(password_hash)
21
+ end
22
+
23
+ define_method(:set_password_hash) do |password_hash|
24
+ if rodauth.account_password_hash_column
25
+ public_send(:"#{rodauth.account_password_hash_column}=", password_hash)
26
+ else
27
+ if password_hash
28
+ record = self.password_hash || build_password_hash
29
+ record.public_send(:"#{rodauth.password_hash_column}=", password_hash)
30
+ else
31
+ self.password_hash&.mark_for_destruction
32
+ end
33
+ end
34
+ end
35
+ end
36
+
37
+ def define_associations(model)
38
+ define_password_hash_association(model) unless rodauth.account_password_hash_column
39
+
40
+ feature_associations.each do |association|
41
+ association[:type] = ASSOCIATION_TYPES.fetch(association[:type])
42
+ association[:foreign_key] = association.delete(:key)
43
+
44
+ define_association(model, **association)
45
+ end
46
+ end
47
+
48
+ def define_password_hash_association(model)
49
+ password_hash_id_column = rodauth.password_hash_id_column
50
+ scope = -> { select(password_hash_id_column) } if rodauth.send(:use_database_authentication_functions?)
51
+
52
+ define_association model,
53
+ type: :has_one,
54
+ name: :password_hash,
55
+ table: rodauth.password_hash_table,
56
+ foreign_key: password_hash_id_column,
57
+ scope: scope,
58
+ autosave: true
59
+ end
60
+
61
+ def define_association(model, type:, name:, table:, foreign_key:, scope: nil, **options)
62
+ associated_model = Class.new(model.superclass)
63
+ associated_model.table_name = table
64
+ associated_model.belongs_to :account,
65
+ class_name: model.name,
66
+ foreign_key: foreign_key,
67
+ inverse_of: name
68
+
69
+ model.const_set(name.to_s.singularize.camelize, associated_model)
70
+
71
+ unless name == :authentication_audit_logs
72
+ dependent = type == :has_many ? :delete_all : :delete
73
+ end
74
+
75
+ model.public_send type, name, scope,
76
+ class_name: associated_model.name,
77
+ foreign_key: foreign_key,
78
+ dependent: dependent,
79
+ inverse_of: :account,
80
+ **options,
81
+ **association_options(name)
82
+ end
83
+
84
+ def association_options(name)
85
+ options = @association_options
86
+ options = options.call(name) if options.respond_to?(:call)
87
+ options || {}
88
+ end
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rodauth
4
+ Model.register_association(:remember) do
5
+ { name: :remember_key, type: :one, table: remember_table, key: remember_id_column }
6
+ end
7
+ Model.register_association(:verify_account) do
8
+ { name: :verification_key, type: :one, table: verify_account_table, key: verify_account_id_column }
9
+ end
10
+ Model.register_association(:reset_password) do
11
+ { name: :password_reset_key, type: :one, table: reset_password_table, key: reset_password_id_column }
12
+ end
13
+ Model.register_association(:verify_login_change) do
14
+ { name: :login_change_key, type: :one, table: verify_login_change_table, key: verify_login_change_id_column }
15
+ end
16
+ Model.register_association(:lockout) do
17
+ { name: :lockout, type: :one, table: account_lockouts_table, key: account_lockouts_id_column }
18
+ end
19
+ Model.register_association(:lockout) do
20
+ { name: :login_failure, type: :one, table: account_login_failures_table, key: account_login_failures_id_column }
21
+ end
22
+ Model.register_association(:email_auth) do
23
+ { name: :email_auth_key, type: :one, table: email_auth_table, key: email_auth_id_column }
24
+ end
25
+ Model.register_association(:account_expiration) do
26
+ { name: :activity_time, type: :one, table: account_activity_table, key: account_activity_id_column }
27
+ end
28
+ Model.register_association(:active_sessions) do
29
+ { name: :active_session_keys, type: :many, table: active_sessions_table, key: active_sessions_account_id_column }
30
+ end
31
+ Model.register_association(:audit_logging) do
32
+ { name: :authentication_audit_logs, type: :many, table: audit_logging_table, key: audit_logging_account_id_column }
33
+ end
34
+ Model.register_association(:disallow_password_reuse) do
35
+ { name: :previous_password_hashes, type: :many, table: previous_password_hash_table, key: previous_password_account_id_column }
36
+ end
37
+ Model.register_association(:jwt_refresh) do
38
+ { name: :jwt_refresh_keys, type: :many, table: jwt_refresh_token_table, key: jwt_refresh_token_account_id_column }
39
+ end
40
+ Model.register_association(:password_expiration) do
41
+ { name: :password_change_time, type: :one, table: password_expiration_table, key: password_expiration_id_column }
42
+ end
43
+ Model.register_association(:single_session) do
44
+ { name: :session_key, type: :one, table: single_session_table, key: single_session_id_column }
45
+ end
46
+ Model.register_association(:otp) do
47
+ { name: :otp_key, type: :one, table: otp_keys_table, key: otp_keys_id_column }
48
+ end
49
+ Model.register_association(:sms_codes) do
50
+ { name: :sms_code, type: :one, table: sms_codes_table, key: sms_id_column }
51
+ end
52
+ Model.register_association(:recovery_codes) do
53
+ { name: :recovery_codes, type: :many, table: recovery_codes_table, key: recovery_codes_id_column }
54
+ end
55
+ Model.register_association(:webauthn) do
56
+ { name: :webauthn_user_id, type: :one, table: webauthn_user_ids_table, key: webauthn_user_ids_account_id_column }
57
+ end
58
+ Model.register_association(:webauthn) do
59
+ { name: :webauthn_keys, type: :many, table: webauthn_keys_table, key: webauthn_keys_account_id_column }
60
+ end
61
+ end
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rodauth
4
+ def self.Model(*args, **options)
5
+ Rodauth::Model.new(*args, **options)
6
+ end
7
+
8
+ class Model < Module
9
+ Error = Class.new(StandardError)
10
+
11
+ autoload :ActiveRecord, "rodauth/model/active_record"
12
+
13
+ def self.associations
14
+ @associations ||= {}
15
+ end
16
+
17
+ def self.register_association(feature, &block)
18
+ associations[feature] ||= []
19
+ associations[feature] << block
20
+ end
21
+
22
+ def initialize(auth_class, association_options: {})
23
+ @auth_class = auth_class
24
+ @association_options = association_options
25
+ end
26
+
27
+ def included(model)
28
+ if defined?(::ActiveRecord::Base) && model < ::ActiveRecord::Base
29
+ extend Rodauth::Model::ActiveRecord
30
+ elsif defined?(::Sequel::Model) && model < ::Sequel::Model
31
+ raise Error, "Sequel models are not yet supported"
32
+ else
33
+ raise Error, "must be an Active Record model"
34
+ end
35
+
36
+ define_associations(model)
37
+ define_methods(model)
38
+ end
39
+
40
+ def inspect
41
+ "#<#{self.class}(#{@auth_class.inspect})>"
42
+ end
43
+
44
+ private
45
+
46
+ def feature_associations
47
+ self.class.associations
48
+ .values_at(*rodauth.features)
49
+ .compact
50
+ .flatten
51
+ .map { |block| rodauth.instance_exec(&block) }
52
+ end
53
+
54
+ def rodauth
55
+ @auth_class.allocate
56
+ end
57
+ end
58
+ end
59
+
60
+ require "rodauth/model/associations"
@@ -0,0 +1,3 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rodauth/model"
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ Gem::Specification.new do |spec|
4
+ spec.name = "rodauth-model"
5
+ spec.version = "0.1.0"
6
+ spec.authors = ["Janko Marohnić"]
7
+ spec.email = ["janko@hey.com"]
8
+
9
+ spec.description = "Provides model mixin for Active Record and Sequel that defines password attribute and associations based on Rodauth configuration."
10
+ spec.summary = spec.description
11
+ spec.homepage = "https://github.com/janko/rodauth-model"
12
+ spec.license = "MIT"
13
+ spec.required_ruby_version = ">= 2.3"
14
+
15
+ spec.metadata["source_code_uri"] = "https://github.com/janko/rodauth-model"
16
+
17
+ spec.files = Dir["README.md", "LICENSE.txt", "CHANGELOG.md", "lib/**/*", "*.gemspec"]
18
+ spec.require_paths = ["lib"]
19
+
20
+ spec.add_dependency "rodauth", "~> 2.0"
21
+
22
+ spec.add_development_dependency "minitest"
23
+ spec.add_development_dependency "minitest-hooks"
24
+ spec.add_development_dependency "bcrypt"
25
+ spec.add_development_dependency "jwt"
26
+ spec.add_development_dependency "rotp"
27
+ spec.add_development_dependency "rqrcode"
28
+ spec.add_development_dependency "webauthn" unless RUBY_ENGINE == "jruby"
29
+ end
metadata ADDED
@@ -0,0 +1,166 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rodauth-model
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Janko Marohnić
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2022-05-07 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rodauth
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '2.0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '2.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: minitest
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: minitest-hooks
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: bcrypt
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: jwt
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rotp
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: rqrcode
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: webauthn
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
125
+ description: Provides model mixin for Active Record and Sequel that defines password
126
+ attribute and associations based on Rodauth configuration.
127
+ email:
128
+ - janko@hey.com
129
+ executables: []
130
+ extensions: []
131
+ extra_rdoc_files: []
132
+ files:
133
+ - CHANGELOG.md
134
+ - LICENSE.txt
135
+ - README.md
136
+ - lib/rodauth-model.rb
137
+ - lib/rodauth/model.rb
138
+ - lib/rodauth/model/active_record.rb
139
+ - lib/rodauth/model/associations.rb
140
+ - rodauth-model.gemspec
141
+ homepage: https://github.com/janko/rodauth-model
142
+ licenses:
143
+ - MIT
144
+ metadata:
145
+ source_code_uri: https://github.com/janko/rodauth-model
146
+ post_install_message:
147
+ rdoc_options: []
148
+ require_paths:
149
+ - lib
150
+ required_ruby_version: !ruby/object:Gem::Requirement
151
+ requirements:
152
+ - - ">="
153
+ - !ruby/object:Gem::Version
154
+ version: '2.3'
155
+ required_rubygems_version: !ruby/object:Gem::Requirement
156
+ requirements:
157
+ - - ">="
158
+ - !ruby/object:Gem::Version
159
+ version: '0'
160
+ requirements: []
161
+ rubygems_version: 3.3.3
162
+ signing_key:
163
+ specification_version: 4
164
+ summary: Provides model mixin for Active Record and Sequel that defines password attribute
165
+ and associations based on Rodauth configuration.
166
+ test_files: []