rodauth-rails 1.3.1 → 1.4.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.
Files changed (52) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +8 -0
  3. data/README.md +27 -0
  4. data/lib/generators/rodauth/install_generator.rb +9 -10
  5. data/lib/generators/rodauth/migration/{account_expiration.erb → active_record/account_expiration.erb} +0 -0
  6. data/lib/generators/rodauth/migration/{active_sessions.erb → active_record/active_sessions.erb} +0 -0
  7. data/lib/generators/rodauth/migration/{audit_logging.erb → active_record/audit_logging.erb} +0 -0
  8. data/lib/generators/rodauth/migration/{base.erb → active_record/base.erb} +0 -0
  9. data/lib/generators/rodauth/migration/{disallow_password_reuse.erb → active_record/disallow_password_reuse.erb} +1 -1
  10. data/lib/generators/rodauth/migration/{email_auth.erb → active_record/email_auth.erb} +0 -0
  11. data/lib/generators/rodauth/migration/{jwt_refresh.erb → active_record/jwt_refresh.erb} +0 -0
  12. data/lib/generators/rodauth/migration/{lockout.erb → active_record/lockout.erb} +0 -0
  13. data/lib/generators/rodauth/migration/{otp.erb → active_record/otp.erb} +0 -0
  14. data/lib/generators/rodauth/migration/{password_expiration.erb → active_record/password_expiration.erb} +0 -0
  15. data/lib/generators/rodauth/migration/{recovery_codes.erb → active_record/recovery_codes.erb} +0 -0
  16. data/lib/generators/rodauth/migration/{remember.erb → active_record/remember.erb} +0 -0
  17. data/lib/generators/rodauth/migration/{reset_password.erb → active_record/reset_password.erb} +0 -0
  18. data/lib/generators/rodauth/migration/{single_session.erb → active_record/single_session.erb} +0 -0
  19. data/lib/generators/rodauth/migration/{sms_codes.erb → active_record/sms_codes.erb} +0 -0
  20. data/lib/generators/rodauth/migration/{verify_account.erb → active_record/verify_account.erb} +0 -0
  21. data/lib/generators/rodauth/migration/{verify_login_change.erb → active_record/verify_login_change.erb} +0 -0
  22. data/lib/generators/rodauth/migration/{webauthn.erb → active_record/webauthn.erb} +0 -0
  23. data/lib/generators/rodauth/migration/sequel/account_expiration.erb +7 -0
  24. data/lib/generators/rodauth/migration/sequel/active_sessions.erb +8 -0
  25. data/lib/generators/rodauth/migration/sequel/audit_logging.erb +17 -0
  26. data/lib/generators/rodauth/migration/sequel/base.erb +25 -0
  27. data/lib/generators/rodauth/migration/sequel/disallow_password_reuse.erb +6 -0
  28. data/lib/generators/rodauth/migration/sequel/email_auth.erb +7 -0
  29. data/lib/generators/rodauth/migration/sequel/jwt_refresh.erb +8 -0
  30. data/lib/generators/rodauth/migration/sequel/lockout.erb +11 -0
  31. data/lib/generators/rodauth/migration/sequel/otp.erb +7 -0
  32. data/lib/generators/rodauth/migration/sequel/password_expiration.erb +5 -0
  33. data/lib/generators/rodauth/migration/sequel/recovery_codes.erb +6 -0
  34. data/lib/generators/rodauth/migration/sequel/remember.erb +6 -0
  35. data/lib/generators/rodauth/migration/sequel/reset_password.erb +7 -0
  36. data/lib/generators/rodauth/migration/sequel/single_session.erb +5 -0
  37. data/lib/generators/rodauth/migration/sequel/sms_codes.erb +8 -0
  38. data/lib/generators/rodauth/migration/sequel/verify_account.erb +7 -0
  39. data/lib/generators/rodauth/migration/sequel/verify_login_change.erb +7 -0
  40. data/lib/generators/rodauth/migration/sequel/webauthn.erb +13 -0
  41. data/lib/generators/rodauth/migration_generator.rb +89 -9
  42. data/lib/generators/rodauth/templates/app/mailers/rodauth_mailer.rb +24 -0
  43. data/lib/generators/rodauth/templates/app/models/account.rb +7 -0
  44. data/lib/generators/rodauth/templates/db/migrate/create_rodauth.rb +8 -0
  45. data/lib/rodauth/rails/feature/associations.rb +54 -0
  46. data/lib/rodauth/rails/feature.rb +2 -0
  47. data/lib/rodauth/rails/middleware.rb +9 -0
  48. data/lib/rodauth/rails/model.rb +8 -8
  49. data/lib/rodauth/rails/version.rb +1 -1
  50. metadata +39 -22
  51. data/lib/generators/rodauth/migration_helpers.rb +0 -77
  52. data/lib/rodauth/rails/model/associations.rb +0 -195
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4e268594e5890725cbba25ee24ab158df204ada3789aeebe428b737307128d9e
4
- data.tar.gz: dca778863032dc428ac44b5feca48fe430ef55ea64f4e10050293d1c1a3d95c6
3
+ metadata.gz: 8b5fb22e3d8c84eafedffa3463b4e0ecdf481189b8b48adc2d31005f86251d87
4
+ data.tar.gz: 142e229b7c9a9b078f773f99885117268e759df2eb49e5252ce21754dcf61763
5
5
  SHA512:
6
- metadata.gz: 9008b2381959811820eca625f68c5df7ec2021319ceb2c7bdf6c75412f32ea51f01ffabd716da73f6565e263e0a74699c2c940a70296adf0b9c0b8576ef8e3de
7
- data.tar.gz: 4b0d70d43ff624b610bcded93a528b089d103f11975b7fdec9c8bd38b1f81b5c003de2fa1e4068f2c2006841f91783044280fa575ec5951b2ae319f4836a49b7
6
+ metadata.gz: b1c30c5b1714a80466ad3775720be50d2f02b8d06d016638e6d1ebfb7515cd6060463b975f9810977fe74eb7330ce12b9c6ff81839e41b2e4044615c606ca7bb
7
+ data.tar.gz: 4a532058b27cfc07d2ed234457b880a609c7b35186db976a547be2e83d1c18df7ac72402a9d52155819f2d97edb63b0e7742e908b6cb3ed1b40c2deae18e7e2e
data/CHANGELOG.md CHANGED
@@ -1,3 +1,11 @@
1
+ ## 1.4.0 (2022-05-04)
2
+
3
+ * Move association definitions to `#associations` Rodauth method, allowing external features to extend them (@janko)
4
+
5
+ * Add Sequel support for generating database migrations, model, and mailer (@janko)
6
+
7
+ * Skip calling Rodauth app on asset requests when using Sprockets or Propshaft (@janko)
8
+
1
9
  ## 1.3.1 (2022-04-22)
2
10
 
3
11
  * Ensure response status is logged when calling a halting rodauth method inside a controller (@janko)
data/README.md CHANGED
@@ -614,6 +614,33 @@ Rodauth::Rails.model(association_options: -> (name) {
614
614
  })
615
615
  ```
616
616
 
617
+ #### Extending Associations
618
+
619
+ External features can extend the list of associations with their own
620
+ definitions, which the model mixin will pick up and declare the new associations
621
+ on the model.
622
+
623
+ ```rb
624
+ # lib/rodauth/features/foo.rb
625
+ module Rodauth
626
+ Feature.define(:foo, :Foo) do
627
+ auth_value_method :foo_table, :account_foos
628
+ auth_value_method :foo_id_column, :id
629
+
630
+ def associations
631
+ list = super
632
+ list << {
633
+ name: :foo, # will define `Account::Foo` model
634
+ type: :one, # or :many
635
+ table: foo_table,
636
+ foreign_key: foo_id_column
637
+ }
638
+ list
639
+ end
640
+ end
641
+ end
642
+ ```
643
+
617
644
  ## Multiple configurations
618
645
 
619
646
  If you need to handle multiple types of accounts that require different
@@ -1,15 +1,10 @@
1
1
  require "rails/generators/base"
2
- require "rails/generators/active_record/migration"
3
- require "generators/rodauth/migration_helpers"
4
2
  require "securerandom"
5
3
 
6
4
  module Rodauth
7
5
  module Rails
8
6
  module Generators
9
7
  class InstallGenerator < ::Rails::Generators::Base
10
- include ::ActiveRecord::Generators::Migration
11
- include MigrationHelpers
12
-
13
8
  if RUBY_ENGINE == "jruby"
14
9
  SEQUEL_ADAPTERS = {
15
10
  "sqlite3" => "sqlite",
@@ -40,9 +35,7 @@ module Rodauth
40
35
  class_option :jwt, type: :boolean, desc: "Configure JWT support"
41
36
 
42
37
  def create_rodauth_migration
43
- return unless defined?(ActiveRecord::Railtie)
44
-
45
- migration_template "db/migrate/create_rodauth.rb"
38
+ invoke "rodauth:migration", migration_features, name: "create_rodauth"
46
39
  end
47
40
 
48
41
  def create_rodauth_initializer
@@ -66,8 +59,6 @@ module Rodauth
66
59
  end
67
60
 
68
61
  def create_account_model
69
- return unless defined?(ActiveRecord::Railtie)
70
-
71
62
  template "app/models/account.rb"
72
63
  end
73
64
 
@@ -112,6 +103,14 @@ module Rodauth
112
103
  scheme = "jdbc:#{scheme}" if RUBY_ENGINE == "jruby"
113
104
  scheme
114
105
  end
106
+
107
+ def activerecord_adapter
108
+ if ActiveRecord::Base.respond_to?(:connection_db_config)
109
+ ActiveRecord::Base.connection_db_config.adapter
110
+ else
111
+ ActiveRecord::Base.connection_config.fetch(:adapter)
112
+ end
113
+ end
115
114
  end
116
115
  end
117
116
  end
@@ -1,4 +1,4 @@
1
- # Used by the disallow_password_reuse feature
1
+ # Used by the disallow password reuse feature
2
2
  create_table :account_previous_password_hashes do |t|
3
3
  t.references :account, foreign_key: true<%= primary_key_type(:type) %>
4
4
  t.string :password_hash, null: false
@@ -0,0 +1,7 @@
1
+ # Used by the account expiration feature
2
+ create_table :account_activity_times do
3
+ foreign_key :id, :accounts, primary_key: true, type: :Bignum
4
+ DateTime :last_activity_at, null: false
5
+ DateTime :last_login_at, null: false
6
+ DateTime :expired_at
7
+ end
@@ -0,0 +1,8 @@
1
+ # Used by the active sessions feature
2
+ create_table :account_active_session_keys do
3
+ foreign_key :account_id, :accounts, type: :Bignum
4
+ String :session_id
5
+ Time :created_at, null: false, default: Sequel::CURRENT_TIMESTAMP
6
+ Time :last_use, null: false, default: Sequel::CURRENT_TIMESTAMP
7
+ primary_key [:account_id, :session_id]
8
+ end
@@ -0,0 +1,17 @@
1
+ # Used by the audit logging feature
2
+ create_table :account_authentication_audit_logs do
3
+ primary_key :id, type: :Bignum
4
+ foreign_key :account_id, :accounts, null: false, type: :Bignum
5
+ DateTime :at, null: false, default: Sequel::CURRENT_TIMESTAMP
6
+ String :message, null: false
7
+ <% case db.database_type -%>
8
+ <% when :postgres -%>
9
+ jsonb :metadata
10
+ <% when :sqlite, :mysql -%>
11
+ json :metadata
12
+ <% else -%>
13
+ String :metadata
14
+ <% end -%>
15
+ index [:account_id, :at], name: :audit_account_at_idx
16
+ index :at, name: :audit_at_idx
17
+ end
@@ -0,0 +1,25 @@
1
+ <% if db.database_type == :postgres -%>
2
+ enable_extension "citext"
3
+
4
+ <% end -%>
5
+ create_table :accounts do
6
+ primary_key :id, type: :Bignum
7
+ <% if db.database_type == :postgres -%>
8
+ citext :email, null: false
9
+ constraint :valid_email, email: /^[^,;@ \r\n]+@[^,@; \r\n]+\.[^,@; \r\n]+$/
10
+ <% else -%>
11
+ String :email, null: false
12
+ <% end -%>
13
+ String :status, null: false, default: "unverified"
14
+ <% if db.supports_partial_indexes? -%>
15
+ index :email, unique: true, where: { status: ["unverified", "verified"] }
16
+ <% else -%>
17
+ index :email, unique: true
18
+ <% end -%>
19
+ end
20
+
21
+ # Used if storing password hashes in a separate table (default)
22
+ create_table :account_password_hashes do
23
+ foreign_key :id, :accounts, primary_key: true, type: :Bignum
24
+ String :password_hash, null: false
25
+ end
@@ -0,0 +1,6 @@
1
+ # Used by the disallow password reuse feature
2
+ create_table :account_previous_password_hashes do
3
+ primary_key :id, type: :Bignum
4
+ foreign_key :account_id, :accounts, type: :Bignum
5
+ String :password_hash, null: false
6
+ end
@@ -0,0 +1,7 @@
1
+ # Used by the email auth feature
2
+ create_table :account_email_auth_keys do
3
+ foreign_key :id, :accounts, primary_key: true, type: :Bignum
4
+ String :key, null: false
5
+ DateTime :deadline, null: false
6
+ DateTime :email_last_sent, null: false, default: Sequel::CURRENT_TIMESTAMP
7
+ end
@@ -0,0 +1,8 @@
1
+ # Used by the jwt refresh feature
2
+ create_table :account_jwt_refresh_keys do
3
+ primary_key :id, type: :Bignum
4
+ foreign_key :account_id, :accounts, null: false, type: :Bignum
5
+ String :key, null: false
6
+ DateTime :deadline, null: false
7
+ index :account_id, name: :account_jwt_rk_account_id_idx
8
+ end
@@ -0,0 +1,11 @@
1
+ # Used by the lockout feature
2
+ create_table :account_login_failures do
3
+ foreign_key :id, :accounts, primary_key: true, type: :Bignum
4
+ Integer :number, null: false, default: 1
5
+ end
6
+ create_table :account_lockouts do
7
+ foreign_key :id, :accounts, primary_key: true, type: :Bignum
8
+ String :key, null: false
9
+ DateTime :deadline, null: false
10
+ DateTime :email_last_sent
11
+ end
@@ -0,0 +1,7 @@
1
+ # Used by the otp feature
2
+ create_table :account_otp_keys do
3
+ foreign_key :id, :accounts, primary_key: true, type: :Bignum
4
+ String :key, null: false
5
+ Integer :num_failures, null: false, default: 0
6
+ Time :last_use, null: false, default: Sequel::CURRENT_TIMESTAMP
7
+ end
@@ -0,0 +1,5 @@
1
+ # Used by the password expiration feature
2
+ create_table :account_password_change_times do
3
+ foreign_key :id, :accounts, primary_key: true, type: :Bignum
4
+ DateTime :changed_at, null: false, default: Sequel::CURRENT_TIMESTAMP
5
+ end
@@ -0,0 +1,6 @@
1
+ # Used by the recovery codes feature
2
+ create_table :account_recovery_codes do
3
+ foreign_key :id, :accounts, type: :Bignum
4
+ String :code
5
+ primary_key [:id, :code]
6
+ end
@@ -0,0 +1,6 @@
1
+ # Used by the remember me feature
2
+ create_table :account_remember_keys do
3
+ foreign_key :id, :accounts, primary_key: true, type: :Bignum
4
+ String :key, null: false
5
+ DateTime :deadline, null: false
6
+ end
@@ -0,0 +1,7 @@
1
+ # Used by the password reset feature
2
+ create_table :account_password_reset_keys do
3
+ foreign_key :id, :accounts, primary_key: true, type: :Bignum
4
+ String :key, null: false
5
+ DateTime :deadline, null: false
6
+ DateTime :email_last_sent, null: false, default: Sequel::CURRENT_TIMESTAMP
7
+ end
@@ -0,0 +1,5 @@
1
+ # Used by the single session feature
2
+ create_table :account_session_keys do
3
+ foreign_key :id, :accounts, primary_key: true, type: :Bignum
4
+ String :key, null: false
5
+ end
@@ -0,0 +1,8 @@
1
+ # Used by the sms codes feature
2
+ create_table :account_sms_codes do
3
+ foreign_key :id, :accounts, primary_key: true, type: :Bignum
4
+ String :phone_number, null: false
5
+ Integer :num_failures
6
+ String :code
7
+ DateTime :code_issued_at, null: false, default: Sequel::CURRENT_TIMESTAMP
8
+ end
@@ -0,0 +1,7 @@
1
+ # Used by the account verification feature
2
+ create_table :account_verification_keys do
3
+ foreign_key :id, :accounts, primary_key: true, type: :Bignum
4
+ String :key, null: false
5
+ DateTime :requested_at, null: false, default: Sequel::CURRENT_TIMESTAMP
6
+ DateTime :email_last_sent, null: false, default: Sequel::CURRENT_TIMESTAMP
7
+ end
@@ -0,0 +1,7 @@
1
+ # Used by the verify login change feature
2
+ create_table :account_login_change_keys do
3
+ foreign_key :id, :accounts, primary_key: true, type: :Bignum
4
+ String :key, null: false
5
+ String :login, null: false
6
+ DateTime :deadline, null: false
7
+ end
@@ -0,0 +1,13 @@
1
+ # Used by the webauthn feature
2
+ create_table :account_webauthn_user_ids do
3
+ foreign_key :id, :accounts, primary_key: true, type: :Bignum
4
+ String :webauthn_id, null: false
5
+ end
6
+ create_table :account_webauthn_keys do
7
+ foreign_key :account_id, :accounts, type: :Bignum
8
+ String :webauthn_id
9
+ String :public_key, null: false
10
+ Integer :sign_count, null: false
11
+ Time :last_use, null: false, default: Sequel::CURRENT_TIMESTAMP
12
+ primary_key [:account_id, :webauthn_id]
13
+ end
@@ -1,14 +1,11 @@
1
1
  require "rails/generators/base"
2
2
  require "rails/generators/active_record/migration"
3
- require "generators/rodauth/migration_helpers"
3
+ require "erb"
4
4
 
5
5
  module Rodauth
6
6
  module Rails
7
7
  module Generators
8
8
  class MigrationGenerator < ::Rails::Generators::Base
9
- include ::ActiveRecord::Generators::Migration
10
- include MigrationHelpers
11
-
12
9
  source_root "#{__dir__}/templates"
13
10
  namespace "rodauth:migration"
14
11
 
@@ -20,19 +17,102 @@ module Rodauth
20
17
  desc: "Name of the generated migration file"
21
18
 
22
19
  def create_rodauth_migration
23
- return unless defined?(ActiveRecord::Railtie)
24
20
  return if features.empty?
25
21
 
26
- migration_template "db/migrate/create_rodauth.rb", "#{migration_name}.rb"
22
+ migration_template "db/migrate/create_rodauth.rb", File.join(db_migrate_path, "#{migration_name}.rb")
27
23
  end
28
24
 
29
- def migration_features
30
- features
31
- end
25
+ private
32
26
 
33
27
  def migration_name
34
28
  options[:name] || "create_rodauth_#{features.join("_")}"
35
29
  end
30
+
31
+ def migration_content
32
+ features
33
+ .select { |feature| File.exist?(migration_chunk(feature)) }
34
+ .map { |feature| File.read(migration_chunk(feature)) }
35
+ .map { |content| erb_eval(content) }
36
+ .join("\n")
37
+ .indent(4)
38
+ end
39
+
40
+ def erb_eval(content)
41
+ if ERB.version[/\d+\.\d+\.\d+/].to_s >= "2.2.0"
42
+ ERB.new(content, trim_mode: "-").result(binding)
43
+ else
44
+ ERB.new(content, 0, "-").result(binding)
45
+ end
46
+ end
47
+
48
+ if defined?(::ActiveRecord::Railtie) # Active Record
49
+ include ::ActiveRecord::Generators::Migration
50
+
51
+ def db_migrate_path
52
+ return "db/migrate" unless ActiveRecord.version >= Gem::Version.new("5.0")
53
+
54
+ super
55
+ end
56
+
57
+ def migration_chunk(feature)
58
+ "#{__dir__}/migration/active_record/#{feature}.erb"
59
+ end
60
+
61
+ def migration_version
62
+ return unless ActiveRecord.version >= Gem::Version.new("5.0")
63
+
64
+ "[#{ActiveRecord::Migration.current_version}]"
65
+ end
66
+
67
+ def activerecord_adapter
68
+ if ActiveRecord::Base.respond_to?(:connection_db_config)
69
+ ActiveRecord::Base.connection_db_config.adapter
70
+ else
71
+ ActiveRecord::Base.connection_config.fetch(:adapter)
72
+ end
73
+ end
74
+
75
+ def primary_key_type(key = :id)
76
+ generators = ::Rails.application.config.generators
77
+ column_type = generators.options[:active_record][:primary_key_type]
78
+
79
+ return unless column_type
80
+
81
+ if key
82
+ ", #{key}: :#{column_type}"
83
+ else
84
+ column_type
85
+ end
86
+ end
87
+
88
+ def current_timestamp
89
+ if ActiveRecord.version >= Gem::Version.new("5.0")
90
+ %(-> { "CURRENT_TIMESTAMP" })
91
+ else
92
+ %(OpenStruct.new(quoted_id: "CURRENT_TIMESTAMP"))
93
+ end
94
+ end
95
+ else # Sequel
96
+ include ::Rails::Generators::Migration
97
+
98
+ def self.next_migration_number(dirname)
99
+ next_migration_number = current_migration_number(dirname) + 1
100
+ [Time.now.utc.strftime('%Y%m%d%H%M%S'), format('%.14d', next_migration_number)].max
101
+ end
102
+
103
+ def db_migrate_path
104
+ "db/migrate"
105
+ end
106
+
107
+ def migration_chunk(feature)
108
+ "#{__dir__}/migration/sequel/#{feature}.erb"
109
+ end
110
+
111
+ def db
112
+ db = ::Sequel::DATABASES.first if defined?(::Sequel)
113
+ db or fail Rodauth::Rails::Error, "missing Sequel database connection"
114
+ end
115
+ end
36
116
  end
37
117
  end
38
118
  end
@@ -1,14 +1,22 @@
1
1
  class RodauthMailer < ApplicationMailer
2
2
  def verify_account(account_id, key)
3
3
  @email_link = rodauth.verify_account_url(key: email_token(account_id, key))
4
+ <% if defined?(ActiveRecord::Railtie) -%>
4
5
  @account = Account.find(account_id)
6
+ <% else -%>
7
+ @account = Account.with_pk!(account_id)
8
+ <% end -%>
5
9
 
6
10
  mail to: @account.email, subject: rodauth.verify_account_email_subject
7
11
  end
8
12
 
9
13
  def reset_password(account_id, key)
10
14
  @email_link = rodauth.reset_password_url(key: email_token(account_id, key))
15
+ <% if defined?(ActiveRecord::Railtie) -%>
11
16
  @account = Account.find(account_id)
17
+ <% else -%>
18
+ @account = Account.with_pk!(account_id)
19
+ <% end -%>
12
20
 
13
21
  mail to: @account.email, subject: rodauth.reset_password_email_subject
14
22
  end
@@ -17,27 +25,43 @@ class RodauthMailer < ApplicationMailer
17
25
  @old_login = old_login
18
26
  @new_login = new_login
19
27
  @email_link = rodauth.verify_login_change_url(key: email_token(account_id, key))
28
+ <% if defined?(ActiveRecord::Railtie) -%>
20
29
  @account = Account.find(account_id)
30
+ <% else -%>
31
+ @account = Account.with_pk!(account_id)
32
+ <% end -%>
21
33
 
22
34
  mail to: new_login, subject: rodauth.verify_login_change_email_subject
23
35
  end
24
36
 
25
37
  def password_changed(account_id)
38
+ <% if defined?(ActiveRecord::Railtie) -%>
26
39
  @account = Account.find(account_id)
40
+ <% else -%>
41
+ @account = Account.with_pk!(account_id)
42
+ <% end -%>
27
43
 
28
44
  mail to: @account.email, subject: rodauth.password_changed_email_subject
29
45
  end
30
46
 
31
47
  # def email_auth(account_id, key)
32
48
  # @email_link = rodauth.email_auth_url(key: email_token(account_id, key))
49
+ <% if defined?(ActiveRecord::Railtie) -%>
33
50
  # @account = Account.find(account_id)
51
+ <% else -%>
52
+ # @account = Account.with_pk!(account_id)
53
+ <% end -%>
34
54
 
35
55
  # mail to: @account.email, subject: rodauth.email_auth_email_subject
36
56
  # end
37
57
 
38
58
  # def unlock_account(account_id, key)
39
59
  # @email_link = rodauth.unlock_account_url(key: email_token(account_id, key))
60
+ <% if defined?(ActiveRecord::Railtie) -%>
40
61
  # @account = Account.find(account_id)
62
+ <% else -%>
63
+ # @account = Account.with_pk!(account_id)
64
+ <% end -%>
41
65
 
42
66
  # mail to: @account.email, subject: rodauth.unlock_account_email_subject
43
67
  # end
@@ -1,3 +1,4 @@
1
+ <% if defined?(ActiveRecord::Railtie) -%>
1
2
  class Account < ApplicationRecord
2
3
  include Rodauth::Rails.model
3
4
  <% if ActiveRecord.version >= Gem::Version.new("7.0") -%>
@@ -6,3 +7,9 @@ class Account < ApplicationRecord
6
7
  enum status: { unverified: 1, verified: 2, closed: 3 }
7
8
  <% end -%>
8
9
  end
10
+ <% else -%>
11
+ class Account < Sequel::Model
12
+ plugin :enum
13
+ enum :status, unverified: 1, verified: 2, closed: 3
14
+ end
15
+ <% end -%>
@@ -1,5 +1,13 @@
1
+ <% if defined?(::ActiveRecord::Railtie) -%>
1
2
  class <%= migration_class_name %> < ActiveRecord::Migration<%= migration_version %>
2
3
  def change
3
4
  <%= migration_content -%>
4
5
  end
5
6
  end
7
+ <% else -%>
8
+ Sequel.migration do
9
+ change do
10
+ <%= migration_content -%>
11
+ end
12
+ end
13
+ <% end -%>
@@ -0,0 +1,54 @@
1
+ module Rodauth
2
+ module Rails
3
+ module Feature
4
+ module Associations
5
+ def associations
6
+ list = []
7
+
8
+ features.each do |feature|
9
+ case feature
10
+ when :remember
11
+ list << { name: :remember_key, type: :one, table: remember_table, foreign_key: remember_id_column }
12
+ when :verify_account
13
+ list << { name: :verification_key, type: :one, table: verify_account_table, foreign_key: verify_account_id_column }
14
+ when :reset_password
15
+ list << { name: :password_reset_key, type: :one, table: reset_password_table, foreign_key: reset_password_id_column }
16
+ when :verify_login_change
17
+ list << { name: :login_change_key, type: :one, table: verify_login_change_table, foreign_key: verify_login_change_id_column }
18
+ when :lockout
19
+ list << { name: :lockout, type: :one, table: account_lockouts_table, foreign_key: account_lockouts_id_column }
20
+ list << { name: :login_failure, type: :one, table: account_login_failures_table, foreign_key: account_login_failures_id_column }
21
+ when :email_auth
22
+ list << { name: :email_auth_key, type: :one, table: email_auth_table, foreign_key: email_auth_id_column }
23
+ when :account_expiration
24
+ list << { name: :activity_time, type: :one, table: account_activity_table, foreign_key: account_activity_id_column }
25
+ when :active_sessions
26
+ list << { name: :active_session_keys, type: :many, table: active_sessions_table, foreign_key: active_sessions_account_id_column }
27
+ when :audit_logging
28
+ list << { name: :authentication_audit_logs, type: :many, table: audit_logging_table, foreign_key: audit_logging_account_id_column }
29
+ when :disallow_password_reuse
30
+ list << { name: :previous_password_hashes, type: :many, table: previous_password_hash_table, foreign_key: previous_password_account_id_column }
31
+ when :jwt_refresh
32
+ list << { name: :jwt_refresh_keys, type: :many, table: jwt_refresh_token_table, foreign_key: jwt_refresh_token_account_id_column }
33
+ when :password_expiration
34
+ list << { name: :password_change_time, type: :one, table: password_expiration_table, foreign_key: password_expiration_id_column }
35
+ when :single_session
36
+ list << { name: :session_key, type: :one, table: single_session_table, foreign_key: single_session_id_column }
37
+ when :otp
38
+ list << { name: :otp_key, type: :one, table: otp_keys_table, foreign_key: otp_keys_id_column }
39
+ when :sms_codes
40
+ list << { name: :sms_code, type: :one, table: sms_codes_table, foreign_key: sms_id_column }
41
+ when :recovery_codes
42
+ list << { name: :recovery_codes, type: :many, table: recovery_codes_table, foreign_key: recovery_codes_id_column }
43
+ when :webauthn
44
+ list << { name: :webauthn_user_id, type: :one, table: webauthn_user_ids_table, foreign_key: webauthn_user_ids_account_id_column }
45
+ list << { name: :webauthn_keys, type: :many, table: webauthn_keys_table, foreign_key: webauthn_keys_account_id_column }
46
+ end
47
+ end
48
+
49
+ list
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
@@ -11,6 +11,7 @@ module Rodauth
11
11
  require "rodauth/rails/feature/email"
12
12
  require "rodauth/rails/feature/instrumentation"
13
13
  require "rodauth/rails/feature/internal_request"
14
+ require "rodauth/rails/feature/associations"
14
15
 
15
16
  include Rodauth::Rails::Feature::Base
16
17
  include Rodauth::Rails::Feature::Callbacks
@@ -19,5 +20,6 @@ module Rodauth
19
20
  include Rodauth::Rails::Feature::Email
20
21
  include Rodauth::Rails::Feature::Instrumentation
21
22
  include Rodauth::Rails::Feature::InternalRequest
23
+ include Rodauth::Rails::Feature::Associations
22
24
  end
23
25
  end
@@ -9,6 +9,8 @@ module Rodauth
9
9
  end
10
10
 
11
11
  def call(env)
12
+ return @app.call(env) if asset_request?(env)
13
+
12
14
  app = Rodauth::Rails.app.new(@app)
13
15
 
14
16
  # allow the Rails app to call Rodauth methods that throw :halt
@@ -16,6 +18,13 @@ module Rodauth
16
18
  app.call(env)
17
19
  end
18
20
  end
21
+
22
+ # Check whether it's a request to an asset managed by Sprockets or Propshaft.
23
+ def asset_request?(env)
24
+ return false unless ::Rails.application.config.respond_to?(:assets)
25
+
26
+ env["PATH_INFO"] =~ %r(\A/{0,2}#{::Rails.application.config.assets.prefix})
27
+ end
19
28
  end
20
29
  end
21
30
  end
@@ -1,7 +1,7 @@
1
1
  module Rodauth
2
2
  module Rails
3
3
  class Model < Module
4
- require "rodauth/rails/model/associations"
4
+ ASSOCIATION_TYPES = { one: :has_one, many: :has_many }
5
5
 
6
6
  def initialize(auth_class, association_options: {})
7
7
  @auth_class = auth_class
@@ -46,8 +46,8 @@ module Rodauth
46
46
  def define_associations(model)
47
47
  define_password_hash_association(model) unless rodauth.account_password_hash_column
48
48
 
49
- feature_associations.each do |association|
50
- define_association(model, **association)
49
+ rodauth.associations.each do |association|
50
+ define_association(model, **association, type: ASSOCIATION_TYPES.fetch(association[:type]))
51
51
  end
52
52
  end
53
53
 
@@ -74,19 +74,19 @@ module Rodauth
74
74
 
75
75
  model.const_set(name.to_s.singularize.camelize, associated_model)
76
76
 
77
+ unless name == :authentication_audit_logs
78
+ dependent = type == :has_many ? :delete_all : :delete
79
+ end
80
+
77
81
  model.public_send type, name, scope,
78
82
  class_name: associated_model.name,
79
83
  foreign_key: foreign_key,
80
- dependent: type == :has_many ? :delete_all : :delete,
84
+ dependent: dependent,
81
85
  inverse_of: :account,
82
86
  **options,
83
87
  **association_options(name)
84
88
  end
85
89
 
86
- def feature_associations
87
- Rodauth::Rails::Model::Associations.call(rodauth)
88
- end
89
-
90
90
  def association_options(name)
91
91
  options = @association_options
92
92
  options = options.call(name) if options.respond_to?(:call)
@@ -1,5 +1,5 @@
1
1
  module Rodauth
2
2
  module Rails
3
- VERSION = "1.3.1"
3
+ VERSION = "1.4.0"
4
4
  end
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rodauth-rails
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.3.1
4
+ version: 1.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Janko Marohnić
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-04-22 00:00:00.000000000 Z
11
+ date: 2022-05-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: railties
@@ -167,26 +167,43 @@ files:
167
167
  - LICENSE.txt
168
168
  - README.md
169
169
  - lib/generators/rodauth/install_generator.rb
170
- - lib/generators/rodauth/migration/account_expiration.erb
171
- - lib/generators/rodauth/migration/active_sessions.erb
172
- - lib/generators/rodauth/migration/audit_logging.erb
173
- - lib/generators/rodauth/migration/base.erb
174
- - lib/generators/rodauth/migration/disallow_password_reuse.erb
175
- - lib/generators/rodauth/migration/email_auth.erb
176
- - lib/generators/rodauth/migration/jwt_refresh.erb
177
- - lib/generators/rodauth/migration/lockout.erb
178
- - lib/generators/rodauth/migration/otp.erb
179
- - lib/generators/rodauth/migration/password_expiration.erb
180
- - lib/generators/rodauth/migration/recovery_codes.erb
181
- - lib/generators/rodauth/migration/remember.erb
182
- - lib/generators/rodauth/migration/reset_password.erb
183
- - lib/generators/rodauth/migration/single_session.erb
184
- - lib/generators/rodauth/migration/sms_codes.erb
185
- - lib/generators/rodauth/migration/verify_account.erb
186
- - lib/generators/rodauth/migration/verify_login_change.erb
187
- - lib/generators/rodauth/migration/webauthn.erb
170
+ - lib/generators/rodauth/migration/active_record/account_expiration.erb
171
+ - lib/generators/rodauth/migration/active_record/active_sessions.erb
172
+ - lib/generators/rodauth/migration/active_record/audit_logging.erb
173
+ - lib/generators/rodauth/migration/active_record/base.erb
174
+ - lib/generators/rodauth/migration/active_record/disallow_password_reuse.erb
175
+ - lib/generators/rodauth/migration/active_record/email_auth.erb
176
+ - lib/generators/rodauth/migration/active_record/jwt_refresh.erb
177
+ - lib/generators/rodauth/migration/active_record/lockout.erb
178
+ - lib/generators/rodauth/migration/active_record/otp.erb
179
+ - lib/generators/rodauth/migration/active_record/password_expiration.erb
180
+ - lib/generators/rodauth/migration/active_record/recovery_codes.erb
181
+ - lib/generators/rodauth/migration/active_record/remember.erb
182
+ - lib/generators/rodauth/migration/active_record/reset_password.erb
183
+ - lib/generators/rodauth/migration/active_record/single_session.erb
184
+ - lib/generators/rodauth/migration/active_record/sms_codes.erb
185
+ - lib/generators/rodauth/migration/active_record/verify_account.erb
186
+ - lib/generators/rodauth/migration/active_record/verify_login_change.erb
187
+ - lib/generators/rodauth/migration/active_record/webauthn.erb
188
+ - lib/generators/rodauth/migration/sequel/account_expiration.erb
189
+ - lib/generators/rodauth/migration/sequel/active_sessions.erb
190
+ - lib/generators/rodauth/migration/sequel/audit_logging.erb
191
+ - lib/generators/rodauth/migration/sequel/base.erb
192
+ - lib/generators/rodauth/migration/sequel/disallow_password_reuse.erb
193
+ - lib/generators/rodauth/migration/sequel/email_auth.erb
194
+ - lib/generators/rodauth/migration/sequel/jwt_refresh.erb
195
+ - lib/generators/rodauth/migration/sequel/lockout.erb
196
+ - lib/generators/rodauth/migration/sequel/otp.erb
197
+ - lib/generators/rodauth/migration/sequel/password_expiration.erb
198
+ - lib/generators/rodauth/migration/sequel/recovery_codes.erb
199
+ - lib/generators/rodauth/migration/sequel/remember.erb
200
+ - lib/generators/rodauth/migration/sequel/reset_password.erb
201
+ - lib/generators/rodauth/migration/sequel/single_session.erb
202
+ - lib/generators/rodauth/migration/sequel/sms_codes.erb
203
+ - lib/generators/rodauth/migration/sequel/verify_account.erb
204
+ - lib/generators/rodauth/migration/sequel/verify_login_change.erb
205
+ - lib/generators/rodauth/migration/sequel/webauthn.erb
188
206
  - lib/generators/rodauth/migration_generator.rb
189
- - lib/generators/rodauth/migration_helpers.rb
190
207
  - lib/generators/rodauth/templates/INSTRUCTIONS
191
208
  - lib/generators/rodauth/templates/app/controllers/rodauth_controller.rb
192
209
  - lib/generators/rodauth/templates/app/mailers/rodauth_mailer.rb
@@ -247,6 +264,7 @@ files:
247
264
  - lib/rodauth/rails/auth.rb
248
265
  - lib/rodauth/rails/controller_methods.rb
249
266
  - lib/rodauth/rails/feature.rb
267
+ - lib/rodauth/rails/feature/associations.rb
250
268
  - lib/rodauth/rails/feature/base.rb
251
269
  - lib/rodauth/rails/feature/callbacks.rb
252
270
  - lib/rodauth/rails/feature/csrf.rb
@@ -256,7 +274,6 @@ files:
256
274
  - lib/rodauth/rails/feature/render.rb
257
275
  - lib/rodauth/rails/middleware.rb
258
276
  - lib/rodauth/rails/model.rb
259
- - lib/rodauth/rails/model/associations.rb
260
277
  - lib/rodauth/rails/railtie.rb
261
278
  - lib/rodauth/rails/tasks.rake
262
279
  - lib/rodauth/rails/test.rb
@@ -1,77 +0,0 @@
1
- require "erb"
2
-
3
- module Rodauth
4
- module Rails
5
- module Generators
6
- module MigrationHelpers
7
- attr_reader :migration_class_name
8
-
9
- def migration_template(source, destination = File.basename(source))
10
- @migration_class_name = destination.chomp(".rb").camelize
11
-
12
- super source, File.join(db_migrate_path, destination)
13
- end
14
-
15
- private
16
-
17
- def migration_content
18
- migration_features
19
- .select { |feature| File.exist?("#{__dir__}/migration/#{feature}.erb") }
20
- .map { |feature| File.read("#{__dir__}/migration/#{feature}.erb") }
21
- .map { |content| erb_eval(content) }
22
- .join("\n")
23
- .indent(4)
24
- end
25
-
26
- def activerecord_adapter
27
- if ActiveRecord::Base.respond_to?(:connection_db_config)
28
- ActiveRecord::Base.connection_db_config.adapter
29
- else
30
- ActiveRecord::Base.connection_config.fetch(:adapter)
31
- end
32
- end
33
-
34
- def migration_version
35
- return unless ActiveRecord.version >= Gem::Version.new("5.0")
36
-
37
- "[#{ActiveRecord::VERSION::MAJOR}.#{ActiveRecord::VERSION::MINOR}]"
38
- end
39
-
40
- def db_migrate_path
41
- return "db/migrate" unless ActiveRecord.version >= Gem::Version.new("5.0")
42
-
43
- super
44
- end
45
-
46
- def primary_key_type(key = :id)
47
- generators = ::Rails.application.config.generators
48
- column_type = generators.options[:active_record][:primary_key_type]
49
-
50
- return unless column_type
51
-
52
- if key
53
- ", #{key}: :#{column_type}"
54
- else
55
- column_type
56
- end
57
- end
58
-
59
- def erb_eval(content)
60
- if ERB.version[/\d+\.\d+\.\d+/].to_s >= "2.2.0"
61
- ERB.new(content, trim_mode: "-").result(binding)
62
- else
63
- ERB.new(content, 0, "-").result(binding)
64
- end
65
- end
66
-
67
- def current_timestamp
68
- if ActiveRecord.version >= Gem::Version.new("5.0")
69
- %(-> { "CURRENT_TIMESTAMP" })
70
- else
71
- %(OpenStruct.new(quoted_id: "CURRENT_TIMESTAMP"))
72
- end
73
- end
74
- end
75
- end
76
- end
77
- end
@@ -1,195 +0,0 @@
1
- module Rodauth
2
- module Rails
3
- class Model
4
- class Associations
5
- attr_reader :rodauth
6
-
7
- def self.call(rodauth)
8
- new(rodauth).call
9
- end
10
-
11
- def initialize(rodauth)
12
- @rodauth = rodauth
13
- end
14
-
15
- def call
16
- rodauth.features
17
- .select { |feature| respond_to?(feature, true) }
18
- .flat_map { |feature| send(feature) }
19
- end
20
-
21
- private
22
-
23
- def remember
24
- {
25
- name: :remember_key,
26
- type: :has_one,
27
- table: rodauth.remember_table,
28
- foreign_key: rodauth.remember_id_column,
29
- }
30
- end
31
-
32
- def verify_account
33
- {
34
- name: :verification_key,
35
- type: :has_one,
36
- table: rodauth.verify_account_table,
37
- foreign_key: rodauth.verify_account_id_column,
38
- }
39
- end
40
-
41
- def reset_password
42
- {
43
- name: :password_reset_key,
44
- type: :has_one,
45
- table: rodauth.reset_password_table,
46
- foreign_key: rodauth.reset_password_id_column,
47
- }
48
- end
49
-
50
- def verify_login_change
51
- {
52
- name: :login_change_key,
53
- type: :has_one,
54
- table: rodauth.verify_login_change_table,
55
- foreign_key: rodauth.verify_login_change_id_column,
56
- }
57
- end
58
-
59
- def lockout
60
- [
61
- {
62
- name: :lockout,
63
- type: :has_one,
64
- table: rodauth.account_lockouts_table,
65
- foreign_key: rodauth.account_lockouts_id_column,
66
- },
67
- {
68
- name: :login_failure,
69
- type: :has_one,
70
- table: rodauth.account_login_failures_table,
71
- foreign_key: rodauth.account_login_failures_id_column,
72
- }
73
- ]
74
- end
75
-
76
- def email_auth
77
- {
78
- name: :email_auth_key,
79
- type: :has_one,
80
- table: rodauth.email_auth_table,
81
- foreign_key: rodauth.email_auth_id_column,
82
- }
83
- end
84
-
85
- def account_expiration
86
- {
87
- name: :activity_time,
88
- type: :has_one,
89
- table: rodauth.account_activity_table,
90
- foreign_key: rodauth.account_activity_id_column,
91
- }
92
- end
93
-
94
- def active_sessions
95
- {
96
- name: :active_session_keys,
97
- type: :has_many,
98
- table: rodauth.active_sessions_table,
99
- foreign_key: rodauth.active_sessions_account_id_column,
100
- }
101
- end
102
-
103
- def audit_logging
104
- {
105
- name: :authentication_audit_logs,
106
- type: :has_many,
107
- table: rodauth.audit_logging_table,
108
- foreign_key: rodauth.audit_logging_account_id_column,
109
- dependent: nil,
110
- }
111
- end
112
-
113
- def disallow_password_reuse
114
- {
115
- name: :previous_password_hashes,
116
- type: :has_many,
117
- table: rodauth.previous_password_hash_table,
118
- foreign_key: rodauth.previous_password_account_id_column,
119
- }
120
- end
121
-
122
- def jwt_refresh
123
- {
124
- name: :jwt_refresh_keys,
125
- type: :has_many,
126
- table: rodauth.jwt_refresh_token_table,
127
- foreign_key: rodauth.jwt_refresh_token_account_id_column,
128
- }
129
- end
130
-
131
- def password_expiration
132
- {
133
- name: :password_change_time,
134
- type: :has_one,
135
- table: rodauth.password_expiration_table,
136
- foreign_key: rodauth.password_expiration_id_column,
137
- }
138
- end
139
-
140
- def single_session
141
- {
142
- name: :session_key,
143
- type: :has_one,
144
- table: rodauth.single_session_table,
145
- foreign_key: rodauth.single_session_id_column,
146
- }
147
- end
148
-
149
- def otp
150
- {
151
- name: :otp_key,
152
- type: :has_one,
153
- table: rodauth.otp_keys_table,
154
- foreign_key: rodauth.otp_keys_id_column,
155
- }
156
- end
157
-
158
- def sms_codes
159
- {
160
- name: :sms_code,
161
- type: :has_one,
162
- table: rodauth.sms_codes_table,
163
- foreign_key: rodauth.sms_id_column,
164
- }
165
- end
166
-
167
- def recovery_codes
168
- {
169
- name: :recovery_codes,
170
- type: :has_many,
171
- table: rodauth.recovery_codes_table,
172
- foreign_key: rodauth.recovery_codes_id_column,
173
- }
174
- end
175
-
176
- def webauthn
177
- [
178
- {
179
- name: :webauthn_user_id,
180
- type: :has_one,
181
- table: rodauth.webauthn_user_ids_table,
182
- foreign_key: rodauth.webauthn_user_ids_account_id_column,
183
- },
184
- {
185
- name: :webauthn_keys,
186
- type: :has_many,
187
- table: rodauth.webauthn_keys_table,
188
- foreign_key: rodauth.webauthn_keys_account_id_column,
189
- }
190
- ]
191
- end
192
- end
193
- end
194
- end
195
- end