rodauth-rails 0.8.0 → 0.10.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 +46 -0
- data/README.md +445 -107
- data/lib/generators/rodauth/install_generator.rb +26 -15
- data/lib/generators/rodauth/migration/base.erb +2 -2
- data/lib/generators/rodauth/templates/app/lib/rodauth_app.rb +50 -49
- data/lib/generators/rodauth/templates/app/mailers/rodauth_mailer.rb +3 -3
- data/lib/rodauth/rails.rb +20 -0
- data/lib/rodauth/rails/app.rb +15 -25
- data/lib/rodauth/rails/app/flash.rb +5 -3
- data/lib/rodauth/rails/app/middleware.rb +20 -10
- data/lib/rodauth/rails/auth.rb +40 -0
- data/lib/rodauth/rails/controller_methods.rb +1 -5
- data/lib/rodauth/rails/feature.rb +32 -14
- data/lib/rodauth/rails/tasks.rake +1 -1
- data/lib/rodauth/rails/version.rb +1 -1
- data/rodauth-rails.gemspec +3 -1
- metadata +23 -9
- data/lib/generators/rodauth/mailer_generator.rb +0 -37
@@ -10,17 +10,20 @@ module Rodauth
|
|
10
10
|
include ::ActiveRecord::Generators::Migration
|
11
11
|
include MigrationHelpers
|
12
12
|
|
13
|
+
MAILER_VIEWS = %w[
|
14
|
+
email_auth
|
15
|
+
password_changed
|
16
|
+
reset_password
|
17
|
+
unlock_account
|
18
|
+
verify_account
|
19
|
+
verify_login_change
|
20
|
+
]
|
21
|
+
|
13
22
|
source_root "#{__dir__}/templates"
|
14
23
|
namespace "rodauth:install"
|
15
24
|
|
16
|
-
|
17
|
-
|
18
|
-
# value instead.
|
19
|
-
def self.default_value_for_option(name, options)
|
20
|
-
name == :api ? options[:default] : super
|
21
|
-
end
|
22
|
-
|
23
|
-
class_option :api, type: :boolean, desc: "Generate JSON-only configuration"
|
25
|
+
class_option :json, type: :boolean, desc: "Configure JSON support"
|
26
|
+
class_option :jwt, type: :boolean, desc: "Configure JWT support"
|
24
27
|
|
25
28
|
def create_rodauth_migration
|
26
29
|
return unless defined?(ActiveRecord::Base)
|
@@ -53,6 +56,14 @@ module Rodauth
|
|
53
56
|
template "app/models/account.rb"
|
54
57
|
end
|
55
58
|
|
59
|
+
def create_mailer
|
60
|
+
template "app/mailers/rodauth_mailer.rb"
|
61
|
+
|
62
|
+
MAILER_VIEWS.each do |view|
|
63
|
+
template "app/views/rodauth_mailer/#{view}.text.erb"
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
56
67
|
private
|
57
68
|
|
58
69
|
def sequel_uri_scheme
|
@@ -83,17 +94,17 @@ module Rodauth
|
|
83
94
|
end
|
84
95
|
end
|
85
96
|
|
86
|
-
def
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
97
|
+
def json?
|
98
|
+
options[:json]
|
99
|
+
end
|
100
|
+
|
101
|
+
def jwt?
|
102
|
+
options[:jwt] || Rodauth::Rails.api_only?
|
92
103
|
end
|
93
104
|
|
94
105
|
def migration_features
|
95
106
|
features = [:base, :reset_password, :verify_account, :verify_login_change]
|
96
|
-
features << :remember unless
|
107
|
+
features << :remember unless jwt?
|
97
108
|
features
|
98
109
|
end
|
99
110
|
end
|
@@ -5,11 +5,11 @@ enable_extension "citext"
|
|
5
5
|
create_table :accounts<%= primary_key_type %> do |t|
|
6
6
|
<% case activerecord_adapter -%>
|
7
7
|
<% when "postgresql" -%>
|
8
|
-
t.citext :email, null: false, index: { unique: true, where: "status IN ('
|
8
|
+
t.citext :email, null: false, index: { unique: true, where: "status IN ('unverified', 'verified')" }
|
9
9
|
<% else -%>
|
10
10
|
t.string :email, null: false, index: { unique: true }
|
11
11
|
<% end -%>
|
12
|
-
t.string :status, null: false, default: "
|
12
|
+
t.string :status, null: false, default: "unverified"
|
13
13
|
end
|
14
14
|
|
15
15
|
# Used if storing password hashes in a separate table (default)
|
@@ -1,8 +1,8 @@
|
|
1
1
|
class RodauthApp < Rodauth::Rails::App
|
2
|
-
configure
|
2
|
+
configure do
|
3
3
|
# List of authentication features that are loaded.
|
4
4
|
enable :create_account, :verify_account, :verify_account_grace_period,
|
5
|
-
:login, :logout,
|
5
|
+
:login, :logout<%= ", :remember" unless jwt? %><%= ", :json" if json? %><%= ", :jwt" if jwt? %>,
|
6
6
|
:reset_password, :change_password, :change_password_notify,
|
7
7
|
:change_login, :verify_login_change,
|
8
8
|
:close_account
|
@@ -14,6 +14,20 @@ class RodauthApp < Rodauth::Rails::App
|
|
14
14
|
# The secret key used for hashing public-facing tokens for various features.
|
15
15
|
# Defaults to Rails `secret_key_base`, but you can use your own secret key.
|
16
16
|
# hmac_secret "<%= SecureRandom.hex(64) %>"
|
17
|
+
<% if jwt? -%>
|
18
|
+
|
19
|
+
# Set JWT secret, which is used to cryptographically protect the token.
|
20
|
+
jwt_secret "<%= SecureRandom.hex(64) %>"
|
21
|
+
<% end -%>
|
22
|
+
<% if json? || jwt? -%>
|
23
|
+
|
24
|
+
# Accept only JSON requests.
|
25
|
+
only_json? true
|
26
|
+
|
27
|
+
# Handle login and password confirmation fields on the client side.
|
28
|
+
# require_password_confirmation? false
|
29
|
+
# require_login_confirmation? false
|
30
|
+
<% end -%>
|
17
31
|
|
18
32
|
# Specify the controller used for view rendering and CSRF verification.
|
19
33
|
rails_controller { RodauthController }
|
@@ -42,52 +56,34 @@ class RodauthApp < Rodauth::Rails::App
|
|
42
56
|
|
43
57
|
# Redirect to the app from login and registration pages if already logged in.
|
44
58
|
# already_logged_in { redirect login_redirect }
|
45
|
-
<% if api_only? -%>
|
46
|
-
|
47
|
-
# ==> JWT
|
48
|
-
# Set JWT secret, which is used to cryptographically protect the token.
|
49
|
-
jwt_secret "<%= SecureRandom.hex(64) %>"
|
50
|
-
|
51
|
-
# Don't require login confirmation param.
|
52
|
-
require_login_confirmation? false
|
53
|
-
|
54
|
-
# Don't require password confirmation param.
|
55
|
-
require_password_confirmation? false
|
56
|
-
<% end -%>
|
57
59
|
|
58
60
|
# ==> Emails
|
59
|
-
#
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
61
|
+
# Use a custom mailer for delivering authentication emails.
|
62
|
+
create_reset_password_email do
|
63
|
+
RodauthMailer.reset_password(email_to, reset_password_email_link)
|
64
|
+
end
|
65
|
+
create_verify_account_email do
|
66
|
+
RodauthMailer.verify_account(email_to, verify_account_email_link)
|
67
|
+
end
|
68
|
+
create_verify_login_change_email do |login|
|
69
|
+
RodauthMailer.verify_login_change(login, verify_login_change_old_login, verify_login_change_new_login, verify_login_change_email_link)
|
70
|
+
end
|
71
|
+
create_password_changed_email do
|
72
|
+
RodauthMailer.password_changed(email_to)
|
73
|
+
end
|
74
|
+
# create_email_auth_email do
|
75
|
+
# RodauthMailer.email_auth(email_to, email_auth_email_link)
|
68
76
|
# end
|
69
|
-
#
|
70
|
-
# RodauthMailer.
|
77
|
+
# create_unlock_account_email do
|
78
|
+
# RodauthMailer.unlock_account(email_to, unlock_account_email_link)
|
71
79
|
# end
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
# # RodauthMailer.unlock_account(email_to, unlock_account_email_link)
|
77
|
-
# # end
|
78
|
-
# send_email do |email|
|
79
|
-
# # queue email delivery on the mailer after the transaction commits
|
80
|
-
# db.after_commit { email.deliver_later }
|
81
|
-
# end
|
82
|
-
|
83
|
-
# In the meantime you can tweak settings for emails created by Rodauth
|
84
|
-
# email_subject_prefix "[MyApp] "
|
85
|
-
# email_from "noreply@myapp.com"
|
86
|
-
# send_email(&:deliver_later)
|
87
|
-
# reset_password_email_body { "Click here to reset your password: #{reset_password_email_link}" }
|
80
|
+
send_email do |email|
|
81
|
+
# queue email delivery on the mailer after the transaction commits
|
82
|
+
db.after_commit { email.deliver_later }
|
83
|
+
end
|
88
84
|
|
89
85
|
# ==> Flash
|
90
|
-
<% unless
|
86
|
+
<% unless json? || jwt? -%>
|
91
87
|
# Match flash keys with ones already used in the Rails app.
|
92
88
|
# flash_notice_key :success # default is :notice
|
93
89
|
# flash_error_key :error # default is :alert
|
@@ -107,7 +103,7 @@ class RodauthApp < Rodauth::Rails::App
|
|
107
103
|
|
108
104
|
# Change minimum number of password characters required when creating an account.
|
109
105
|
# password_minimum_length 8
|
110
|
-
<% unless
|
106
|
+
<% unless jwt? -%>
|
111
107
|
|
112
108
|
# ==> Remember Feature
|
113
109
|
# Remember all logged in users.
|
@@ -128,13 +124,14 @@ class RodauthApp < Rodauth::Rails::App
|
|
128
124
|
|
129
125
|
# Perform additional actions after the account is created.
|
130
126
|
# after_create_account do
|
131
|
-
# Profile.create!(account_id:
|
127
|
+
# Profile.create!(account_id: account_id, name: param("name"))
|
132
128
|
# end
|
133
129
|
|
134
130
|
# Do additional cleanup after the account is closed.
|
135
131
|
# after_close_account do
|
136
|
-
# Profile.find_by!(account_id:
|
132
|
+
# Profile.find_by!(account_id: account_id).destroy
|
137
133
|
# end
|
134
|
+
<% unless json? || jwt? -%>
|
138
135
|
|
139
136
|
# ==> Redirects
|
140
137
|
# Redirect to home page after logout.
|
@@ -145,25 +142,27 @@ class RodauthApp < Rodauth::Rails::App
|
|
145
142
|
|
146
143
|
# Redirect to login page after password reset.
|
147
144
|
reset_password_redirect { login_path }
|
145
|
+
<% end -%>
|
148
146
|
|
149
147
|
# ==> Deadlines
|
150
148
|
# Change default deadlines for some actions.
|
151
149
|
# verify_account_grace_period 3.days
|
152
150
|
# reset_password_deadline_interval Hash[hours: 6]
|
153
151
|
# verify_login_change_deadline_interval Hash[days: 2]
|
152
|
+
<% unless jwt? -%>
|
154
153
|
# remember_deadline_interval Hash[days: 30]
|
154
|
+
<% end -%>
|
155
155
|
end
|
156
156
|
|
157
157
|
# ==> Multiple configurations
|
158
158
|
# configure(:admin) do
|
159
|
-
# enable :http_basic_auth
|
160
|
-
#
|
159
|
+
# enable :http_basic_auth # enable different set of features
|
161
160
|
# prefix "/admin"
|
162
|
-
#
|
161
|
+
# session_key_prefix "admin_"
|
163
162
|
# end
|
164
163
|
|
165
164
|
route do |r|
|
166
|
-
<% unless
|
165
|
+
<% unless jwt? -%>
|
167
166
|
rodauth.load_memory # autologin remembered users
|
168
167
|
|
169
168
|
<% end -%>
|
@@ -188,6 +187,8 @@ class RodauthApp < Rodauth::Rails::App
|
|
188
187
|
# unless rodauth(:admin).logged_in?
|
189
188
|
# rodauth(:admin).require_http_basic_auth
|
190
189
|
# end
|
190
|
+
#
|
191
|
+
# r.pass # allow the Rails app to handle other "/admin/*" requests
|
191
192
|
# end
|
192
193
|
end
|
193
194
|
end
|
@@ -1,4 +1,4 @@
|
|
1
|
-
class
|
1
|
+
class RodauthMailer < ApplicationMailer
|
2
2
|
def verify_account(recipient, email_link)
|
3
3
|
@email_link = email_link
|
4
4
|
|
@@ -25,13 +25,13 @@ class <%= options[:name].camelize %>Mailer < ApplicationMailer
|
|
25
25
|
|
26
26
|
# def email_auth(recipient, email_link)
|
27
27
|
# @email_link = email_link
|
28
|
-
|
28
|
+
#
|
29
29
|
# mail to: recipient
|
30
30
|
# end
|
31
31
|
|
32
32
|
# def unlock_account(recipient, email_link)
|
33
33
|
# @email_link = email_link
|
34
|
-
|
34
|
+
#
|
35
35
|
# mail to: recipient
|
36
36
|
# end
|
37
37
|
end
|
data/lib/rodauth/rails.rb
CHANGED
@@ -8,6 +8,7 @@ module Rodauth
|
|
8
8
|
|
9
9
|
# This allows the developer to avoid loading Rodauth at boot time.
|
10
10
|
autoload :App, "rodauth/rails/app"
|
11
|
+
autoload :Auth, "rodauth/rails/auth"
|
11
12
|
|
12
13
|
@app = nil
|
13
14
|
@middleware = true
|
@@ -32,6 +33,15 @@ module Rodauth
|
|
32
33
|
scope.rodauth(name)
|
33
34
|
end
|
34
35
|
|
36
|
+
# routing constraint that requires authentication
|
37
|
+
def authenticated(name = nil, &condition)
|
38
|
+
lambda do |request|
|
39
|
+
rodauth = request.env.fetch ["rodauth", *name].join(".")
|
40
|
+
rodauth.require_authentication
|
41
|
+
rodauth.authenticated? && (condition.nil? || condition.call(rodauth))
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
35
45
|
if ::Rails.gem_version >= Gem::Version.new("5.2")
|
36
46
|
def secret_key_base
|
37
47
|
::Rails.application.secret_key_base
|
@@ -42,6 +52,16 @@ module Rodauth
|
|
42
52
|
end
|
43
53
|
end
|
44
54
|
|
55
|
+
if ::Rails.gem_version >= Gem::Version.new("5.0")
|
56
|
+
def api_only?
|
57
|
+
::Rails.application.config.api_only
|
58
|
+
end
|
59
|
+
else
|
60
|
+
def api_only?
|
61
|
+
false
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
45
65
|
def configure
|
46
66
|
yield self
|
47
67
|
end
|
data/lib/rodauth/rails/app.rb
CHANGED
@@ -1,6 +1,5 @@
|
|
1
1
|
require "roda"
|
2
|
-
require "rodauth"
|
3
|
-
require "rodauth/rails/feature"
|
2
|
+
require "rodauth/rails/auth"
|
4
3
|
|
5
4
|
module Rodauth
|
6
5
|
module Rails
|
@@ -11,38 +10,29 @@ module Rodauth
|
|
11
10
|
|
12
11
|
plugin :hooks
|
13
12
|
plugin :render, layout: false
|
13
|
+
plugin :pass
|
14
14
|
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
end
|
20
|
-
|
21
|
-
plugin :rodauth, name: name, csrf: false, flash: false, **options do
|
22
|
-
# load the Rails integration
|
23
|
-
enable :rails
|
15
|
+
unless Rodauth::Rails.api_only?
|
16
|
+
require "rodauth/rails/app/flash"
|
17
|
+
plugin Flash
|
18
|
+
end
|
24
19
|
|
25
|
-
|
26
|
-
|
20
|
+
def self.configure(*args, **options, &block)
|
21
|
+
auth_class = args.shift if args[0].is_a?(Class)
|
22
|
+
name = args.shift if args[0].is_a?(Symbol)
|
27
23
|
|
28
|
-
|
29
|
-
set_deadline_values? true
|
24
|
+
fail ArgumentError, "need to pass optional Rodauth::Auth subclass and optional configuration name" if args.any?
|
30
25
|
|
31
|
-
|
32
|
-
hmac_secret { Rodauth::Rails.secret_key_base }
|
26
|
+
auth_class ||= Class.new(Rodauth::Rails::Auth)
|
33
27
|
|
34
|
-
|
35
|
-
instance_exec(&block)
|
28
|
+
plugin :rodauth, auth_class: auth_class, name: name, csrf: false, flash: false, json: true, **options do
|
29
|
+
instance_exec(&block) if block
|
36
30
|
end
|
37
31
|
end
|
38
32
|
|
39
33
|
before do
|
40
|
-
|
41
|
-
|
42
|
-
env["rodauth.#{name}"] = rodauth(name)
|
43
|
-
else
|
44
|
-
env["rodauth"] = rodauth
|
45
|
-
end
|
34
|
+
opts[:rodauths]&.each_key do |name|
|
35
|
+
env[["rodauth", *name].join(".")] = rodauth(name)
|
46
36
|
end
|
47
37
|
end
|
48
38
|
end
|
@@ -30,10 +30,12 @@ module Rodauth
|
|
30
30
|
rails_request.flash
|
31
31
|
end
|
32
32
|
|
33
|
-
|
34
|
-
|
33
|
+
if ActionPack.version >= Gem::Version.new("5.0")
|
34
|
+
def commit_flash
|
35
35
|
rails_request.commit_flash
|
36
|
-
|
36
|
+
end
|
37
|
+
else
|
38
|
+
def commit_flash
|
37
39
|
# ActionPack 4.2 automatically commits flash
|
38
40
|
end
|
39
41
|
end
|
@@ -3,20 +3,30 @@ module Rodauth
|
|
3
3
|
class App
|
4
4
|
# Roda plugin that extends middleware plugin by propagating response headers.
|
5
5
|
module Middleware
|
6
|
-
def self.load_dependencies(app)
|
7
|
-
app.plugin :hooks
|
8
|
-
end
|
9
|
-
|
10
6
|
def self.configure(app)
|
11
|
-
|
12
|
-
if
|
13
|
-
|
7
|
+
handle_result = -> (env, res) do
|
8
|
+
if headers = env.delete("rodauth.rails.headers")
|
9
|
+
res[1] = headers.merge(res[1])
|
14
10
|
end
|
15
11
|
end
|
16
12
|
|
17
|
-
app.plugin :middleware, handle_result:
|
18
|
-
|
19
|
-
|
13
|
+
app.plugin :middleware, handle_result: handle_result do |middleware|
|
14
|
+
middleware.plugin :hooks
|
15
|
+
|
16
|
+
middleware.after do
|
17
|
+
if response.empty? && response.headers.any?
|
18
|
+
env["rodauth.rails.headers"] = response.headers
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
middleware.class_eval do
|
23
|
+
def self.inspect
|
24
|
+
"#{superclass}::Middleware"
|
25
|
+
end
|
26
|
+
|
27
|
+
def inspect
|
28
|
+
"#<#{self.class.inspect} request=#{request.inspect} response=#{response.inspect}>"
|
29
|
+
end
|
20
30
|
end
|
21
31
|
end
|
22
32
|
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require "rodauth"
|
2
|
+
require "rodauth/rails/feature"
|
3
|
+
|
4
|
+
module Rodauth
|
5
|
+
module Rails
|
6
|
+
# Base auth class that applies some default configuration and supports
|
7
|
+
# multi-level inheritance.
|
8
|
+
class Auth < Rodauth::Auth
|
9
|
+
class << self
|
10
|
+
attr_writer :features
|
11
|
+
attr_writer :routes
|
12
|
+
attr_accessor :configuration
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.inherited(auth_class)
|
16
|
+
super
|
17
|
+
auth_class.roda_class = Rodauth::Rails.app
|
18
|
+
auth_class.features = features.dup
|
19
|
+
auth_class.routes = routes.dup
|
20
|
+
auth_class.route_hash = route_hash.dup
|
21
|
+
auth_class.configuration = configuration.clone
|
22
|
+
auth_class.configuration.instance_variable_set(:@auth, auth_class)
|
23
|
+
end
|
24
|
+
|
25
|
+
# apply default configuration
|
26
|
+
configure do
|
27
|
+
enable :rails
|
28
|
+
|
29
|
+
# database functions are more complex to set up, so disable them by default
|
30
|
+
use_database_authentication_functions? false
|
31
|
+
|
32
|
+
# avoid having to set deadline values in column default values
|
33
|
+
set_deadline_values? true
|
34
|
+
|
35
|
+
# use HMACs for additional security
|
36
|
+
hmac_secret { Rodauth::Rails.secret_key_base }
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|