rodauth-rails 0.8.0 → 0.10.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 +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
|