rodauth-rails 1.3.1 → 1.4.0

Sign up to get free protection for your applications and to get access to all the features.
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