rodauth-rails 1.1.0 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8643f8a912963b78be7d03a815b813688304f4e4b2777a1fdade807f79a4c712
4
- data.tar.gz: 31aadb16115fc826750b7d160dcb572ae9c24255d99a7c640abef5fd157bd319
3
+ metadata.gz: f93b80457e9c6e9ea6083974e05514de8e605b0f9a4015fe765ffc1c4059c5ee
4
+ data.tar.gz: '06877735d144b893b1a7a08f125e34316196679e47038112c6df215e352268c6'
5
5
  SHA512:
6
- metadata.gz: dd27d717b3a01f0b5f43e346c0cd839e2703ee0db44f7503eb6ce80f7cffd4493f4ed9e208db474af56af536f675444170f16a6d47435e1f4ce2af38a7487916
7
- data.tar.gz: bbac5b7492751e76886a5bcd275af78aada1026d2cd95ddee732817e8d7a5a154f559e5c27ce08606d444a1ffd4640f8522744bd13939f6491288d57c986fea0
6
+ metadata.gz: 7f26bf373cb8a64e2e0d5156aff9ca94e468d9eb257dcd2e34f702d43a6506bacdc7a22bd85090ddefddc972ebb25b22f3012bca2f21b84aece970d138159166
7
+ data.tar.gz: b5811e6ae069e71f7cd8be35b9bd884c463aa709a1be4abc6fd792bf747ba7aabe0d7aa2f3f4f7c43466f153b163912d55957d0fd84d1314b37ac60ca4a5b221
data/CHANGELOG.md CHANGED
@@ -1,3 +1,13 @@
1
+ ## 1.2.0 (2022-02-11)
2
+
3
+ * Work around Active Record 4.2 not supporting procs for literal SQL column default (@janko)
4
+
5
+ * Avoid re-fetching the account in `#current_account` when it has already been fetched by Rodauth (@janko)
6
+
7
+ * Extract `#current_account` helper functionality into `#rails_account` Rodauth method (@janko)
8
+
9
+ * Use default account status values in generated configuration, with enum on `Account` model (@janko)
10
+
1
11
  ## 1.1.0 (2022-01-16)
2
12
 
3
13
  * Automatically route the path prefix in `r.rodauth` if one has been set (@janko)
data/README.md CHANGED
@@ -15,8 +15,8 @@ Useful links:
15
15
  Articles:
16
16
 
17
17
  * [Rodauth: A Refreshing Authentication Solution for Ruby](https://janko.io/rodauth-a-refreshing-authentication-solution-for-ruby/)
18
- * [Adding Authentication in Rails with Rodauth](https://janko.io/adding-authentication-in-rails-with-rodauth/)
19
- * [Adding Multifactor Authentication in Rails with Rodauth](https://janko.io/adding-multifactor-authentication-in-rails-with-rodauth/)
18
+ * [Rails Authentication with Rodauth](https://janko.io/adding-authentication-in-rails-with-rodauth/)
19
+ * [Multifactor Authentication in Rails with Rodauth](https://janko.io/adding-multifactor-authentication-in-rails-with-rodauth/)
20
20
  * [How to build an OIDC provider using rodauth-oauth on Rails](https://honeyryderchuck.gitlab.io/httpx/2021/03/15/oidc-provider-on-rails-using-rodauth-oauth.html)
21
21
 
22
22
  ## Why Rodauth?
@@ -140,16 +140,14 @@ authentication experience, and the forms use [Bootstrap] markup.
140
140
  ### Current account
141
141
 
142
142
  The `#current_account` method is defined in controllers and views, which
143
- returns the model instance of the currently logged in account.
143
+ returns the model instance of the currently logged in account. If the account
144
+ doesn't exist in the database, the session will be cleared.
144
145
 
145
146
  ```rb
146
147
  current_account #=> #<Account id=123 email="user@example.com">
147
148
  current_account.email #=> "user@example.com"
148
149
  ```
149
150
 
150
- If the account doesn't exist in the database, the session will be cleared and
151
- login required.
152
-
153
151
  Pass the configuration name to retrieve accounts belonging to other Rodauth
154
152
  configurations:
155
153
 
@@ -157,6 +155,8 @@ configurations:
157
155
  current_account(:admin)
158
156
  ```
159
157
 
158
+ This just delegates to the `#rails_account` method on the Rodauth object.
159
+
160
160
  #### Custom account model
161
161
 
162
162
  The `#current_account` method will try to infer the account model class from
@@ -248,6 +248,18 @@ Rails.application.routes.draw do
248
248
  end
249
249
  ```
250
250
 
251
+ The current account can be retrieved via the `#rails_account` method:
252
+
253
+ ```rb
254
+ # config/routes.rb
255
+ Rails.application.routes.draw do
256
+ # require user to be admin
257
+ constraints Rodauth::Rails.authenticated { |rodauth| rodauth.rails_account.admin? } do
258
+ # ...
259
+ end
260
+ end
261
+ ```
262
+
251
263
  You can specify the Rodauth configuration by passing the configuration name:
252
264
 
253
265
  ```rb
@@ -1097,16 +1109,15 @@ end
1097
1109
 
1098
1110
  The recommended [Rodauth migration] stores possible account status values in a
1099
1111
  separate table, and creates a foreign key on the accounts table, which ensures
1100
- only a valid status value will be persisted.
1112
+ only a valid status value will be persisted. Unfortunately, this doesn't work
1113
+ when the database is restored from the schema file, in which case the account
1114
+ statuses table will be empty. This happens in tests by default, but it's also
1115
+ not unusual to do it in development.
1101
1116
 
1102
- Unfortunately, this doesn't work when the database is restored from the schema
1103
- file, in which case the account statuses table will be empty. This happens in
1104
- tests by default, but it's also commonly done in development.
1105
-
1106
- To address this, rodauth-rails modifies the setup to store account status text
1107
- directly in the accounts table. If you're worried about invalid status values
1108
- creeping in, you may use enums instead. Alternatively, you can always go back
1109
- to the setup recommended by Rodauth.
1117
+ To address this, rodauth-rails uses a `status` column without a separate table.
1118
+ If you're worried about invalid status values creeping in, you may use enums
1119
+ instead. Alternatively, you can always go back to the setup recommended by
1120
+ Rodauth.
1110
1121
 
1111
1122
  ```rb
1112
1123
  # in the migration:
@@ -1122,14 +1133,13 @@ create_table :accounts do |t|
1122
1133
  end
1123
1134
  ```
1124
1135
  ```diff
1125
- configure do
1126
- # ...
1127
- - account_status_column :status
1128
- - account_unverified_status_value "unverified"
1129
- - account_open_status_value "verified"
1130
- - account_closed_status_value "closed"
1131
- # ...
1132
- end
1136
+ class RodauthMain < Rodauth::Rails::Auth
1137
+ configure do
1138
+ # ...
1139
+ - account_status_column :status
1140
+ # ...
1141
+ end
1142
+ end
1133
1143
  ```
1134
1144
 
1135
1145
  ### Deadline values
@@ -2,6 +2,6 @@
2
2
  create_table :account_active_session_keys, primary_key: [:account_id, :session_id] do |t|
3
3
  t.references :account, foreign_key: true<%= primary_key_type(:type) %>
4
4
  t.string :session_id
5
- t.datetime :created_at, null: false, default: -> { "CURRENT_TIMESTAMP" }
6
- t.datetime :last_use, null: false, default: -> { "CURRENT_TIMESTAMP" }
5
+ t.datetime :created_at, null: false, default: <%= current_timestamp %>
6
+ t.datetime :last_use, null: false, default: <%= current_timestamp %>
7
7
  end
@@ -1,7 +1,7 @@
1
1
  # Used by the audit logging feature
2
2
  create_table :account_authentication_audit_logs<%= primary_key_type %> do |t|
3
3
  t.references :account, foreign_key: true, null: false<%= primary_key_type(:type) %>
4
- t.datetime :at, null: false, default: -> { "CURRENT_TIMESTAMP" }
4
+ t.datetime :at, null: false, default: <%= current_timestamp %>
5
5
  t.text :message, null: false
6
6
  <% case activerecord_adapter -%>
7
7
  <% when "postgresql" -%>
@@ -9,10 +9,10 @@ create_table :accounts<%= primary_key_type %> do |t|
9
9
  <% else -%>
10
10
  t.string :email, null: false
11
11
  <% end -%>
12
- t.string :status, null: false, default: "unverified"
12
+ t.string :status, null: false, default: 1
13
13
  <% case activerecord_adapter -%>
14
14
  <% when "postgresql", "sqlite3" -%>
15
- t.index :email, unique: true, where: "status IN ('unverified', 'verified')"
15
+ t.index :email, unique: true, where: "status IN (1, 2)"
16
16
  <% else -%>
17
17
  t.index :email, unique: true
18
18
  <% end -%>
@@ -3,5 +3,5 @@ create_table :account_email_auth_keys<%= primary_key_type %> do |t|
3
3
  t.foreign_key :accounts, column: :id
4
4
  t.string :key, null: false
5
5
  t.datetime :deadline, null: false
6
- t.datetime :email_last_sent, null: false, default: -> { "CURRENT_TIMESTAMP" }
6
+ t.datetime :email_last_sent, null: false, default: <%= current_timestamp %>
7
7
  end
@@ -3,5 +3,5 @@ create_table :account_otp_keys<%= primary_key_type %> do |t|
3
3
  t.foreign_key :accounts, column: :id
4
4
  t.string :key, null: false
5
5
  t.integer :num_failures, null: false, default: 0
6
- t.datetime :last_use, null: false, default: -> { "CURRENT_TIMESTAMP" }
6
+ t.datetime :last_use, null: false, default: <%= current_timestamp %>
7
7
  end
@@ -1,5 +1,5 @@
1
1
  # Used by the password expiration feature
2
2
  create_table :account_password_change_times<%= primary_key_type %> do |t|
3
3
  t.foreign_key :accounts, column: :id
4
- t.datetime :changed_at, null: false, default: -> { "CURRENT_TIMESTAMP" }
4
+ t.datetime :changed_at, null: false, default: <%= current_timestamp %>
5
5
  end
@@ -3,5 +3,5 @@ create_table :account_password_reset_keys<%= primary_key_type %> do |t|
3
3
  t.foreign_key :accounts, column: :id
4
4
  t.string :key, null: false
5
5
  t.datetime :deadline, null: false
6
- t.datetime :email_last_sent, null: false, default: -> { "CURRENT_TIMESTAMP" }
6
+ t.datetime :email_last_sent, null: false, default: <%= current_timestamp %>
7
7
  end
@@ -4,5 +4,5 @@ create_table :account_sms_codes<%= primary_key_type %> do |t|
4
4
  t.string :phone_number, null: false
5
5
  t.integer :num_failures
6
6
  t.string :code
7
- t.datetime :code_issued_at, null: false, default: -> { "CURRENT_TIMESTAMP" }
7
+ t.datetime :code_issued_at, null: false, default: <%= current_timestamp %>
8
8
  end
@@ -2,6 +2,6 @@
2
2
  create_table :account_verification_keys<%= primary_key_type %> do |t|
3
3
  t.foreign_key :accounts, column: :id
4
4
  t.string :key, null: false
5
- t.datetime :requested_at, null: false, default: -> { "CURRENT_TIMESTAMP" }
6
- t.datetime :email_last_sent, null: false, default: -> { "CURRENT_TIMESTAMP" }
5
+ t.datetime :requested_at, null: false, default: <%= current_timestamp %>
6
+ t.datetime :email_last_sent, null: false, default: <%= current_timestamp %>
7
7
  end
@@ -8,5 +8,5 @@ create_table :account_webauthn_keys, primary_key: [:account_id, :webauthn_id] do
8
8
  t.string :webauthn_id
9
9
  t.string :public_key, null: false
10
10
  t.integer :sign_count, null: false
11
- t.datetime :last_use, null: false, default: -> { "CURRENT_TIMESTAMP" }
11
+ t.datetime :last_use, null: false, default: <%= current_timestamp %>
12
12
  end
@@ -63,6 +63,14 @@ module Rodauth
63
63
  ERB.new(content, 0, "-").result(binding)
64
64
  end
65
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
66
74
  end
67
75
  end
68
76
  end
@@ -31,11 +31,8 @@ class RodauthMain < Rodauth::Rails::Auth
31
31
  # Specify the controller used for view rendering and CSRF verification.
32
32
  rails_controller { RodauthController }
33
33
 
34
- # Store account status in a text column.
34
+ # Store account status in an integer column without foreign key constraint.
35
35
  account_status_column :status
36
- account_unverified_status_value "unverified"
37
- account_open_status_value "verified"
38
- account_closed_status_value "closed"
39
36
 
40
37
  # Store password hash in a column instead of a separate table.
41
38
  # account_password_hash_column :password_digest
@@ -1,3 +1,4 @@
1
1
  class Account < ApplicationRecord
2
2
  include Rodauth::Rails.model
3
+ enum :status, unverified: 1, verified: 2, closed: 3
3
4
  end
@@ -8,41 +8,16 @@ module Rodauth
8
8
  end
9
9
  end
10
10
 
11
- def rodauth(name = nil)
12
- request.env.fetch ["rodauth", *name].join(".")
13
- end
14
-
15
11
  def current_account(name = nil)
16
- model = rodauth(name).rails_account_model
17
- id = rodauth(name).session_value
12
+ rodauth(name).rails_account || rodauth(name).login_required
13
+ end
18
14
 
19
- @current_account ||= {}
20
- @current_account[name] ||= fetch_account(model, id) do
21
- rodauth(name).clear_session
22
- rodauth(name).login_required
23
- end
15
+ def rodauth(name = nil)
16
+ request.env.fetch ["rodauth", *name].join(".")
24
17
  end
25
18
 
26
19
  private
27
20
 
28
- def fetch_account(model, id, &not_found)
29
- if defined?(ActiveRecord::Base) && model < ActiveRecord::Base
30
- begin
31
- model.find(id)
32
- rescue ActiveRecord::RecordNotFound
33
- not_found.call
34
- end
35
- elsif defined?(Sequel::Model) && model < Sequel::Model
36
- begin
37
- model.with_pk!(id)
38
- rescue Sequel::NoMatchingRow
39
- not_found.call
40
- end
41
- else
42
- fail Error, "unsupported model type: #{model}"
43
- end
44
- end
45
-
46
21
  def rodauth_response
47
22
  res = catch(:halt) { return yield }
48
23
 
@@ -8,6 +8,17 @@ module Rodauth
8
8
  feature.auth_cached_method :rails_controller_instance
9
9
  end
10
10
 
11
+ def rails_account
12
+ account_from_session unless account
13
+
14
+ unless account
15
+ clear_session if logged_in?
16
+ return
17
+ end
18
+
19
+ @rails_account ||= instantiate_rails_account
20
+ end
21
+
11
22
  # Reset Rails session to protect from session fixation attacks.
12
23
  def clear_session
13
24
  rails_controller_instance.reset_session
@@ -43,6 +54,16 @@ module Rodauth
43
54
 
44
55
  private
45
56
 
57
+ def instantiate_rails_account
58
+ if defined?(ActiveRecord::Base) && rails_account_model < ActiveRecord::Base
59
+ rails_account_model.instantiate(account.stringify_keys)
60
+ elsif defined?(Sequel::Model) && rails_account_model < Sequel::Model
61
+ rails_account_model.load(account)
62
+ else
63
+ fail Error, "unsupported model type: #{rails_account_model}"
64
+ end
65
+ end
66
+
46
67
  # Instances of the configured controller with current request's env hash.
47
68
  def _rails_controller_instance
48
69
  controller = rails_controller.new
@@ -1,5 +1,5 @@
1
1
  module Rodauth
2
2
  module Rails
3
- VERSION = "1.1.0"
3
+ VERSION = "1.2.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.1.0
4
+ version: 1.2.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-01-16 00:00:00.000000000 Z
11
+ date: 2022-02-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: railties
@@ -268,7 +268,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
268
268
  - !ruby/object:Gem::Version
269
269
  version: '0'
270
270
  requirements: []
271
- rubygems_version: 3.2.15
271
+ rubygems_version: 3.3.3
272
272
  signing_key:
273
273
  specification_version: 4
274
274
  summary: Provides Rails integration for Rodauth.