rodauth-model 0.1.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.
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: []