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