rodauth-rails 0.4.0 → 0.6.1
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 +177 -77
- data/lib/generators/rodauth/install_generator.rb +28 -18
- 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/app/controllers/rodauth_controller.rb +2 -1
- data/lib/generators/rodauth/templates/app/lib/rodauth_app.rb +18 -18
- data/lib/generators/rodauth/templates/config/initializers/sequel.rb +1 -5
- data/lib/generators/rodauth/templates/db/migrate/create_rodauth.rb +2 -176
- data/lib/rodauth/rails.rb +23 -4
- data/lib/rodauth/rails/app.rb +3 -1
- data/lib/rodauth/rails/app/flash.rb +1 -1
- data/lib/rodauth/rails/app/middleware.rb +26 -0
- data/lib/rodauth/rails/feature.rb +92 -25
- data/lib/rodauth/rails/railtie.rb +11 -0
- data/lib/rodauth/rails/tasks.rake +28 -0
- data/lib/rodauth/rails/version.rb +1 -1
- data/rodauth-rails.gemspec +3 -3
- metadata +29 -7
@@ -1,6 +1,6 @@
|
|
1
1
|
require "rails/generators/base"
|
2
2
|
require "rails/generators/active_record/migration"
|
3
|
-
|
3
|
+
require "generators/rodauth/migration_helpers"
|
4
4
|
require "securerandom"
|
5
5
|
|
6
6
|
module Rodauth
|
@@ -8,6 +8,7 @@ module Rodauth
|
|
8
8
|
module Generators
|
9
9
|
class InstallGenerator < ::Rails::Generators::Base
|
10
10
|
include ::ActiveRecord::Generators::Migration
|
11
|
+
include MigrationHelpers
|
11
12
|
|
12
13
|
source_root "#{__dir__}/templates"
|
13
14
|
namespace "rodauth:install"
|
@@ -15,7 +16,7 @@ module Rodauth
|
|
15
16
|
def create_rodauth_migration
|
16
17
|
return unless defined?(ActiveRecord::Base)
|
17
18
|
|
18
|
-
migration_template "db/migrate/create_rodauth.rb"
|
19
|
+
migration_template "db/migrate/create_rodauth.rb"
|
19
20
|
end
|
20
21
|
|
21
22
|
def create_rodauth_initializer
|
@@ -24,7 +25,6 @@ module Rodauth
|
|
24
25
|
|
25
26
|
def create_sequel_initializer
|
26
27
|
return unless defined?(ActiveRecord::Base)
|
27
|
-
return unless %w[postgresql mysql2 sqlite3].include?(activerecord_adapter)
|
28
28
|
return if defined?(Sequel) && !Sequel::DATABASES.empty?
|
29
29
|
|
30
30
|
template "config/initializers/sequel.rb"
|
@@ -46,35 +46,45 @@ module Rodauth
|
|
46
46
|
|
47
47
|
private
|
48
48
|
|
49
|
-
def
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
def migration_version
|
56
|
-
if ActiveRecord.version >= Gem::Version.new("5.0")
|
57
|
-
"[#{ActiveRecord::VERSION::MAJOR}.#{ActiveRecord::VERSION::MINOR}]"
|
49
|
+
def sequel_uri_scheme
|
50
|
+
if RUBY_ENGINE == "jruby"
|
51
|
+
"jdbc:#{sequel_jdbc_subadapter}"
|
52
|
+
else
|
53
|
+
sequel_adapter
|
58
54
|
end
|
59
55
|
end
|
60
56
|
|
61
57
|
def sequel_adapter
|
62
58
|
case activerecord_adapter
|
63
|
-
when "
|
64
|
-
when "
|
65
|
-
when "
|
59
|
+
when "sqlite3" then "sqlite"
|
60
|
+
when "oracle_enhanced" then "oracle" # https://github.com/rsim/oracle-enhanced
|
61
|
+
when "sqlserver" then "tinytds" # https://github.com/rails-sqlserver/activerecord-sqlserver-adapter
|
62
|
+
else
|
63
|
+
activerecord_adapter
|
66
64
|
end
|
67
65
|
end
|
68
66
|
|
69
|
-
def
|
70
|
-
|
67
|
+
def sequel_jdbc_subadapter
|
68
|
+
case activerecord_adapter
|
69
|
+
when "sqlite3" then "sqlite"
|
70
|
+
when "oracle_enhanced" then "oracle" # https://github.com/rsim/oracle-enhanced
|
71
|
+
when "sqlserver" then "mssql"
|
72
|
+
else
|
73
|
+
activerecord_adapter
|
74
|
+
end
|
71
75
|
end
|
72
76
|
|
73
77
|
def api_only?
|
74
|
-
return
|
78
|
+
return unless ::Rails.gem_version >= Gem::Version.new("5.0")
|
75
79
|
|
76
80
|
::Rails.application.config.api_only
|
77
81
|
end
|
82
|
+
|
83
|
+
def migration_features
|
84
|
+
features = [:base, :reset_password, :verify_account, :verify_login_change]
|
85
|
+
features << :remember unless api_only?
|
86
|
+
features
|
87
|
+
end
|
78
88
|
end
|
79
89
|
end
|
80
90
|
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
|
@@ -11,6 +11,10 @@ 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
|
+
|
14
18
|
# Specify the controller used for view rendering and CSRF verification.
|
15
19
|
rails_controller { RodauthController }
|
16
20
|
|
@@ -53,31 +57,27 @@ class RodauthApp < Rodauth::Rails::App
|
|
53
57
|
|
54
58
|
# ==> Emails
|
55
59
|
# Uncomment the lines below once you've imported mailer views.
|
56
|
-
#
|
57
|
-
#
|
60
|
+
# create_reset_password_email do
|
61
|
+
# RodauthMailer.reset_password(email_to, reset_password_email_link)
|
58
62
|
# end
|
59
|
-
#
|
60
|
-
#
|
63
|
+
# create_verify_account_email do
|
64
|
+
# RodauthMailer.verify_account(email_to, verify_account_email_link)
|
61
65
|
# end
|
62
|
-
#
|
63
|
-
#
|
66
|
+
# create_verify_login_change_email do |login|
|
67
|
+
# RodauthMailer.verify_login_change(login, verify_login_change_old_login, verify_login_change_new_login, verify_login_change_email_link)
|
64
68
|
# end
|
65
|
-
#
|
66
|
-
#
|
69
|
+
# create_password_changed_email do
|
70
|
+
# RodauthMailer.password_changed(email_to)
|
67
71
|
# end
|
68
|
-
# #
|
69
|
-
# #
|
72
|
+
# # create_email_auth_email do
|
73
|
+
# # RodauthMailer.email_auth(email_to, email_auth_email_link)
|
70
74
|
# # end
|
71
|
-
# #
|
72
|
-
# #
|
75
|
+
# # create_unlock_account_email do
|
76
|
+
# # RodauthMailer.unlock_account(email_to, unlock_account_email_link)
|
73
77
|
# # end
|
74
|
-
#
|
78
|
+
# send_email do |email|
|
75
79
|
# # queue email delivery on the mailer after the transaction commits
|
76
|
-
#
|
77
|
-
# db.after_commit do
|
78
|
-
# RodauthMailer.public_send(type, *args).deliver_later
|
79
|
-
# end
|
80
|
-
# end
|
80
|
+
# db.after_commit { email.deliver_later }
|
81
81
|
# end
|
82
82
|
|
83
83
|
# In the meantime you can tweak settings for emails created by Rodauth
|
@@ -1,8 +1,4 @@
|
|
1
1
|
require "sequel/core"
|
2
2
|
|
3
3
|
# initialize Sequel and have it reuse Active Record's database connection
|
4
|
-
|
5
|
-
DB = Sequel.connect("jdbc:<%= sequel_adapter %>://", extensions: :activerecord_connection)
|
6
|
-
<% else -%>
|
7
|
-
DB = Sequel.<%= sequel_adapter %>(extensions: :activerecord_connection)
|
8
|
-
<% end -%>
|
4
|
+
DB = Sequel.connect("<%= sequel_uri_scheme %>://", extensions: :activerecord_connection)
|