rodauth-rails 0.3.1 → 0.6.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 +4 -4
- data/CHANGELOG.md +42 -0
- data/README.md +169 -69
- data/lib/generators/rodauth/install_generator.rb +34 -17
- data/lib/generators/rodauth/migration/account_expiration.erb +7 -0
- data/lib/generators/rodauth/migration/active_sessions.erb +7 -0
- data/lib/generators/rodauth/migration/audit_logging.erb +16 -0
- data/lib/generators/rodauth/migration/base.erb +19 -0
- data/lib/generators/rodauth/migration/disallow_password_reuse.erb +5 -0
- data/lib/generators/rodauth/migration/email_auth.erb +7 -0
- data/lib/generators/rodauth/migration/jwt_refresh.erb +7 -0
- data/lib/generators/rodauth/migration/lockout.erb +11 -0
- data/lib/generators/rodauth/migration/otp.erb +7 -0
- data/lib/generators/rodauth/migration/password_expiration.erb +5 -0
- data/lib/generators/rodauth/migration/recovery_codes.erb +6 -0
- data/lib/generators/rodauth/migration/remember.erb +6 -0
- data/lib/generators/rodauth/migration/reset_password.erb +7 -0
- data/lib/generators/rodauth/migration/single_session.erb +5 -0
- data/lib/generators/rodauth/migration/sms_codes.erb +8 -0
- data/lib/generators/rodauth/migration/verify_account.erb +7 -0
- data/lib/generators/rodauth/migration/verify_login_change.erb +7 -0
- data/lib/generators/rodauth/migration/webauthn.erb +12 -0
- data/lib/generators/rodauth/migration_generator.rb +32 -0
- data/lib/generators/rodauth/migration_helpers.rb +69 -0
- data/lib/generators/rodauth/templates/{lib → app/lib}/rodauth_app.rb +26 -2
- data/lib/generators/rodauth/templates/config/initializers/sequel.rb +1 -5
- data/lib/generators/rodauth/templates/db/migrate/create_rodauth.rb +2 -167
- data/lib/rodauth/rails.rb +24 -5
- data/lib/rodauth/rails/app.rb +5 -4
- data/lib/rodauth/rails/feature.rb +69 -13
- data/lib/rodauth/rails/flash.rb +48 -0
- data/lib/rodauth/rails/railtie.rb +11 -0
- data/lib/rodauth/rails/tasks.rake +28 -0
- data/lib/rodauth/rails/version.rb +5 -0
- data/rodauth-rails.gemspec +6 -4
- metadata +31 -9
- data/lib/rodauth/rails/app/flash.rb +0 -50
@@ -1,11 +1,14 @@
|
|
1
1
|
require "rails/generators/base"
|
2
2
|
require "rails/generators/active_record/migration"
|
3
|
+
require "generators/rodauth/migration_helpers"
|
4
|
+
require "securerandom"
|
3
5
|
|
4
6
|
module Rodauth
|
5
7
|
module Rails
|
6
8
|
module Generators
|
7
9
|
class InstallGenerator < ::Rails::Generators::Base
|
8
10
|
include ::ActiveRecord::Generators::Migration
|
11
|
+
include MigrationHelpers
|
9
12
|
|
10
13
|
source_root "#{__dir__}/templates"
|
11
14
|
namespace "rodauth:install"
|
@@ -13,7 +16,7 @@ module Rodauth
|
|
13
16
|
def create_rodauth_migration
|
14
17
|
return unless defined?(ActiveRecord::Base)
|
15
18
|
|
16
|
-
migration_template "db/migrate/create_rodauth.rb"
|
19
|
+
migration_template "db/migrate/create_rodauth.rb"
|
17
20
|
end
|
18
21
|
|
19
22
|
def create_rodauth_initializer
|
@@ -22,17 +25,18 @@ module Rodauth
|
|
22
25
|
|
23
26
|
def create_sequel_initializer
|
24
27
|
return unless defined?(ActiveRecord::Base)
|
25
|
-
return unless %w[postgresql mysql2 sqlite3].include?(activerecord_adapter)
|
26
28
|
return if defined?(Sequel) && !Sequel::DATABASES.empty?
|
27
29
|
|
28
30
|
template "config/initializers/sequel.rb"
|
29
31
|
end
|
30
32
|
|
31
33
|
def create_rodauth_app
|
32
|
-
template "lib/rodauth_app.rb"
|
34
|
+
template "app/lib/rodauth_app.rb"
|
33
35
|
end
|
34
36
|
|
35
37
|
def create_rodauth_controller
|
38
|
+
return if api_only?
|
39
|
+
|
36
40
|
template "app/controllers/rodauth_controller.rb"
|
37
41
|
end
|
38
42
|
|
@@ -44,31 +48,44 @@ module Rodauth
|
|
44
48
|
|
45
49
|
private
|
46
50
|
|
47
|
-
def
|
48
|
-
|
49
|
-
|
51
|
+
def sequel_uri_scheme
|
52
|
+
if RUBY_ENGINE == "jruby"
|
53
|
+
"jdbc:#{sequel_jdbc_subadapter}"
|
54
|
+
else
|
55
|
+
sequel_adapter
|
56
|
+
end
|
50
57
|
end
|
51
58
|
|
52
|
-
def
|
53
|
-
|
54
|
-
|
59
|
+
def sequel_adapter
|
60
|
+
case activerecord_adapter
|
61
|
+
when "sqlite3" then "sqlite"
|
62
|
+
when "oracle_enhanced" then "oracle" # https://github.com/rsim/oracle-enhanced
|
63
|
+
when "sqlserver" then "tinytds" # https://github.com/rails-sqlserver/activerecord-sqlserver-adapter
|
64
|
+
else
|
65
|
+
activerecord_adapter
|
55
66
|
end
|
56
67
|
end
|
57
68
|
|
58
|
-
def
|
69
|
+
def sequel_jdbc_subadapter
|
59
70
|
case activerecord_adapter
|
60
|
-
when "
|
61
|
-
when "
|
62
|
-
when "
|
71
|
+
when "sqlite3" then "sqlite"
|
72
|
+
when "oracle_enhanced" then "oracle" # https://github.com/rsim/oracle-enhanced
|
73
|
+
when "sqlserver" then "mssql"
|
74
|
+
else
|
75
|
+
activerecord_adapter
|
63
76
|
end
|
64
77
|
end
|
65
78
|
|
66
|
-
def
|
67
|
-
|
79
|
+
def api_only?
|
80
|
+
return unless ::Rails.gem_version >= Gem::Version.new("5.0")
|
81
|
+
|
82
|
+
::Rails.application.config.api_only
|
68
83
|
end
|
69
84
|
|
70
|
-
def
|
71
|
-
|
85
|
+
def migration_features
|
86
|
+
features = [:base, :reset_password, :verify_account, :verify_login_change]
|
87
|
+
features << :remember unless api_only?
|
88
|
+
features
|
72
89
|
end
|
73
90
|
end
|
74
91
|
end
|
@@ -0,0 +1,7 @@
|
|
1
|
+
# Used by the active sessions feature
|
2
|
+
create_table :account_active_session_keys, primary_key: [:account_id, :session_id] do |t|
|
3
|
+
t.references :account, foreign_key: true<%= primary_key_type(:type) %>
|
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" }
|
7
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# Used by the audit logging feature
|
2
|
+
create_table :account_authentication_audit_logs<%= primary_key_type %> do |t|
|
3
|
+
t.references :account, foreign_key: true, null: false<%= primary_key_type(:type) %>
|
4
|
+
t.datetime :at, null: false, default: -> { "CURRENT_TIMESTAMP" }
|
5
|
+
t.text :message, null: false
|
6
|
+
<% case activerecord_adapter -%>
|
7
|
+
<% when "postgresql" -%>
|
8
|
+
t.jsonb :metadata
|
9
|
+
<% when "sqlite3", "mysql2" -%>
|
10
|
+
t.json :metadata
|
11
|
+
<% else -%>
|
12
|
+
t.string :metadata
|
13
|
+
<% end -%>
|
14
|
+
t.index [:account_id, :at], name: "audit_account_at_idx"
|
15
|
+
t.index :at, name: "audit_at_idx"
|
16
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
<% if activerecord_adapter == "postgresql" -%>
|
2
|
+
enable_extension "citext"
|
3
|
+
|
4
|
+
<% end -%>
|
5
|
+
create_table :accounts<%= primary_key_type %> do |t|
|
6
|
+
<% case activerecord_adapter -%>
|
7
|
+
<% when "postgresql" -%>
|
8
|
+
t.citext :email, null: false, index: { unique: true, where: "status IN ('verified', 'unverified')" }
|
9
|
+
<% else -%>
|
10
|
+
t.string :email, null: false, index: { unique: true }
|
11
|
+
<% end -%>
|
12
|
+
t.string :status, null: false, default: "verified"
|
13
|
+
end
|
14
|
+
|
15
|
+
# Used if storing password hashes in a separate table (default)
|
16
|
+
create_table :account_password_hashes<%= primary_key_type %> do |t|
|
17
|
+
t.foreign_key :accounts, column: :id
|
18
|
+
t.string :password_hash, null: false
|
19
|
+
end
|
@@ -0,0 +1,7 @@
|
|
1
|
+
# Used by the email auth feature
|
2
|
+
create_table :account_email_auth_keys<%= primary_key_type %> do |t|
|
3
|
+
t.foreign_key :accounts, column: :id
|
4
|
+
t.string :key, null: false
|
5
|
+
t.datetime :deadline, null: false
|
6
|
+
t.datetime :email_last_sent, null: false, default: -> { "CURRENT_TIMESTAMP" }
|
7
|
+
end
|
@@ -0,0 +1,7 @@
|
|
1
|
+
# Used by the jwt refresh feature
|
2
|
+
create_table :account_jwt_refresh_keys<%= primary_key_type %> do |t|
|
3
|
+
t.references :account, foreign_key: true, null: false<%= primary_key_type(:type) %>
|
4
|
+
t.string :key, null: false
|
5
|
+
t.datetime :deadline, null: false
|
6
|
+
t.index :account_id, name: "account_jwt_rk_account_id_idx"
|
7
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
# Used by the lockout feature
|
2
|
+
create_table :account_login_failures<%= primary_key_type %> do |t|
|
3
|
+
t.foreign_key :accounts, column: :id
|
4
|
+
t.integer :number, null: false, default: 1
|
5
|
+
end
|
6
|
+
create_table :account_lockouts<%= primary_key_type %> do |t|
|
7
|
+
t.foreign_key :accounts, column: :id
|
8
|
+
t.string :key, null: false
|
9
|
+
t.datetime :deadline, null: false
|
10
|
+
t.datetime :email_last_sent
|
11
|
+
end
|
@@ -0,0 +1,7 @@
|
|
1
|
+
# Used by the otp feature
|
2
|
+
create_table :account_otp_keys<%= primary_key_type %> do |t|
|
3
|
+
t.foreign_key :accounts, column: :id
|
4
|
+
t.string :key, null: false
|
5
|
+
t.integer :num_failures, null: false, default: 0
|
6
|
+
t.datetime :last_use, null: false, default: -> { "CURRENT_TIMESTAMP" }
|
7
|
+
end
|
@@ -0,0 +1,7 @@
|
|
1
|
+
# Used by the password reset feature
|
2
|
+
create_table :account_password_reset_keys<%= primary_key_type %> do |t|
|
3
|
+
t.foreign_key :accounts, column: :id
|
4
|
+
t.string :key, null: false
|
5
|
+
t.datetime :deadline, null: false
|
6
|
+
t.datetime :email_last_sent, null: false, default: -> { "CURRENT_TIMESTAMP" }
|
7
|
+
end
|
@@ -0,0 +1,8 @@
|
|
1
|
+
# Used by the sms codes feature
|
2
|
+
create_table :account_sms_codes<%= primary_key_type %> do |t|
|
3
|
+
t.foreign_key :accounts, column: :id
|
4
|
+
t.string :phone_number, null: false
|
5
|
+
t.integer :num_failures
|
6
|
+
t.string :code
|
7
|
+
t.datetime :code_issued_at, null: false, default: -> { "CURRENT_TIMESTAMP" }
|
8
|
+
end
|
@@ -0,0 +1,7 @@
|
|
1
|
+
# Used by the account verification feature
|
2
|
+
create_table :account_verification_keys<%= primary_key_type %> do |t|
|
3
|
+
t.foreign_key :accounts, column: :id
|
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" }
|
7
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
# Used by the webauthn feature
|
2
|
+
create_table :account_webauthn_user_ids<%= primary_key_type %> do |t|
|
3
|
+
t.foreign_key :accounts, column: :id
|
4
|
+
t.string :webauthn_id, null: false
|
5
|
+
end
|
6
|
+
create_table :account_webauthn_keys, primary_key: [:account_id, :webauthn_id] do |t|
|
7
|
+
t.references :account, foreign_key: true<%= primary_key_type(:type) %>
|
8
|
+
t.string :webauthn_id
|
9
|
+
t.string :public_key, null: false
|
10
|
+
t.integer :sign_count, null: false
|
11
|
+
t.datetime :last_use, null: false, default: -> { "CURRENT_TIMESTAMP" }
|
12
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require "rails/generators/base"
|
2
|
+
require "rails/generators/active_record/migration"
|
3
|
+
require "generators/rodauth/migration_helpers"
|
4
|
+
|
5
|
+
module Rodauth
|
6
|
+
module Rails
|
7
|
+
module Generators
|
8
|
+
class MigrationGenerator < ::Rails::Generators::Base
|
9
|
+
include ::ActiveRecord::Generators::Migration
|
10
|
+
include MigrationHelpers
|
11
|
+
|
12
|
+
source_root "#{__dir__}/templates"
|
13
|
+
namespace "rodauth:migration"
|
14
|
+
|
15
|
+
argument :features, optional: true, type: :array,
|
16
|
+
desc: "Rodauth features to create tables for (otp, sms_codes, single_session, account_expiration etc.)",
|
17
|
+
default: %w[]
|
18
|
+
|
19
|
+
def create_rodauth_migration
|
20
|
+
return unless defined?(ActiveRecord::Base)
|
21
|
+
return if features.empty?
|
22
|
+
|
23
|
+
migration_template "db/migrate/create_rodauth.rb", "create_rodauth_#{features.join("_")}.rb"
|
24
|
+
end
|
25
|
+
|
26
|
+
def migration_features
|
27
|
+
features
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,69 @@
|
|
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
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -1,8 +1,8 @@
|
|
1
1
|
class RodauthApp < Rodauth::Rails::App
|
2
|
-
configure do
|
2
|
+
configure<%= " json: :only" if api_only? %> do
|
3
3
|
# List of authentication features that are loaded.
|
4
4
|
enable :create_account, :verify_account, :verify_account_grace_period,
|
5
|
-
:login, :
|
5
|
+
:login, :logout, <%= api_only? ? ":jwt" : ":remember" %>,
|
6
6
|
:reset_password, :change_password, :change_password_notify,
|
7
7
|
:change_login, :verify_login_change,
|
8
8
|
:close_account
|
@@ -11,9 +11,15 @@ class RodauthApp < Rodauth::Rails::App
|
|
11
11
|
# http://rodauth.jeremyevans.net/documentation.html
|
12
12
|
|
13
13
|
# ==> General
|
14
|
+
# The secret key used for hashing public-facing tokens for various features.
|
15
|
+
# Defaults to Rails `secret_key_base`, but you can use your own secret key.
|
16
|
+
# hmac_secret "<%= SecureRandom.hex(64) %>"
|
17
|
+
|
18
|
+
<% unless api_only? -%>
|
14
19
|
# Specify the controller used for view rendering and CSRF verification.
|
15
20
|
rails_controller { RodauthController }
|
16
21
|
|
22
|
+
<% end -%>
|
17
23
|
# Store account status in a text column.
|
18
24
|
account_status_column :status
|
19
25
|
account_unverified_status_value "unverified"
|
@@ -38,6 +44,18 @@ class RodauthApp < Rodauth::Rails::App
|
|
38
44
|
|
39
45
|
# Redirect to the app from login and registration pages if already logged in.
|
40
46
|
# already_logged_in { redirect login_redirect }
|
47
|
+
<% if api_only? -%>
|
48
|
+
|
49
|
+
# ==> JWT
|
50
|
+
# Set JWT secret, which is used to cryptographically protect the token.
|
51
|
+
jwt_secret "<%= SecureRandom.hex(64) %>"
|
52
|
+
|
53
|
+
# Don't require login confirmation param.
|
54
|
+
require_login_confirmation? false
|
55
|
+
|
56
|
+
# Don't require password confirmation param.
|
57
|
+
require_password_confirmation? false
|
58
|
+
<% end -%>
|
41
59
|
|
42
60
|
# ==> Emails
|
43
61
|
# Uncomment the lines below once you've imported mailer views.
|
@@ -75,10 +93,12 @@ class RodauthApp < Rodauth::Rails::App
|
|
75
93
|
# reset_password_email_body { "Click here to reset your password: #{reset_password_email_link}" }
|
76
94
|
|
77
95
|
# ==> Flash
|
96
|
+
<% unless api_only? -%>
|
78
97
|
# Match flash keys with ones already used in the Rails app.
|
79
98
|
# flash_notice_key :success # default is :notice
|
80
99
|
# flash_error_key :error # default is :alert
|
81
100
|
|
101
|
+
<% end -%>
|
82
102
|
# Override default flash messages.
|
83
103
|
# create_account_notice_flash "Your account has been created. Please verify your account by visiting the confirmation link sent to your email address."
|
84
104
|
# require_login_error_flash "Login is required for accessing this page"
|
@@ -93,6 +113,7 @@ class RodauthApp < Rodauth::Rails::App
|
|
93
113
|
|
94
114
|
# Change minimum number of password characters required when creating an account.
|
95
115
|
# password_minimum_length 8
|
116
|
+
<% unless api_only? -%>
|
96
117
|
|
97
118
|
# ==> Remember Feature
|
98
119
|
# Remember all logged in users.
|
@@ -103,6 +124,7 @@ class RodauthApp < Rodauth::Rails::App
|
|
103
124
|
|
104
125
|
# Extend user's remember period when remembered via a cookie
|
105
126
|
extend_remember_deadline? true
|
127
|
+
<% end -%>
|
106
128
|
|
107
129
|
# ==> Hooks
|
108
130
|
# Validate custom fields in the create account form.
|
@@ -147,8 +169,10 @@ class RodauthApp < Rodauth::Rails::App
|
|
147
169
|
# end
|
148
170
|
|
149
171
|
route do |r|
|
172
|
+
<% unless api_only? -%>
|
150
173
|
rodauth.load_memory # autologin remembered users
|
151
174
|
|
175
|
+
<% end -%>
|
152
176
|
r.rodauth # route rodauth requests
|
153
177
|
|
154
178
|
# ==> Authenticating Requests
|