rodauth 0.10.0 → 1.0.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.
Files changed (137) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +146 -0
  3. data/README.rdoc +644 -220
  4. data/Rakefile +99 -11
  5. data/doc/account_expiration.rdoc +55 -0
  6. data/doc/base.rdoc +104 -0
  7. data/doc/change_login.rdoc +29 -0
  8. data/doc/change_password.rdoc +26 -0
  9. data/doc/close_account.rdoc +31 -0
  10. data/doc/confirm_password.rdoc +22 -0
  11. data/doc/create_account.rdoc +34 -0
  12. data/doc/disallow_password_reuse.rdoc +37 -0
  13. data/doc/email_base.rdoc +19 -0
  14. data/doc/jwt.rdoc +35 -0
  15. data/doc/lockout.rdoc +83 -0
  16. data/doc/login.rdoc +27 -0
  17. data/doc/login_password_requirements_base.rdoc +50 -0
  18. data/doc/logout.rdoc +21 -0
  19. data/doc/otp.rdoc +100 -0
  20. data/doc/password_complexity.rdoc +50 -0
  21. data/doc/password_expiration.rdoc +52 -0
  22. data/doc/password_grace_period.rdoc +10 -0
  23. data/doc/recovery_codes.rdoc +60 -0
  24. data/doc/release_notes/1.0.0.txt +443 -0
  25. data/doc/remember.rdoc +82 -0
  26. data/doc/reset_password.rdoc +70 -0
  27. data/doc/session_expiration.rdoc +27 -0
  28. data/doc/single_session.rdoc +43 -0
  29. data/doc/sms_codes.rdoc +119 -0
  30. data/doc/two_factor_base.rdoc +27 -0
  31. data/doc/verify_account.rdoc +70 -0
  32. data/doc/verify_account_grace_period.rdoc +15 -0
  33. data/doc/verify_change_login.rdoc +9 -0
  34. data/lib/roda/plugins/rodauth.rb +3 -262
  35. data/lib/rodauth.rb +260 -0
  36. data/lib/rodauth/features/account_expiration.rb +108 -0
  37. data/lib/rodauth/features/base.rb +479 -0
  38. data/lib/rodauth/features/change_login.rb +77 -0
  39. data/lib/rodauth/features/change_password.rb +66 -0
  40. data/lib/rodauth/features/close_account.rb +82 -0
  41. data/lib/rodauth/features/confirm_password.rb +51 -0
  42. data/lib/rodauth/features/create_account.rb +128 -0
  43. data/lib/rodauth/features/disallow_password_reuse.rb +82 -0
  44. data/lib/rodauth/features/email_base.rb +63 -0
  45. data/lib/rodauth/features/jwt.rb +151 -0
  46. data/lib/rodauth/features/lockout.rb +262 -0
  47. data/lib/rodauth/features/login.rb +61 -0
  48. data/lib/rodauth/features/login_password_requirements_base.rb +123 -0
  49. data/lib/rodauth/features/logout.rb +37 -0
  50. data/lib/rodauth/features/otp.rb +338 -0
  51. data/lib/rodauth/features/password_complexity.rb +89 -0
  52. data/lib/rodauth/features/password_expiration.rb +111 -0
  53. data/lib/rodauth/features/password_grace_period.rb +46 -0
  54. data/lib/rodauth/features/recovery_codes.rb +240 -0
  55. data/lib/rodauth/features/remember.rb +200 -0
  56. data/lib/rodauth/features/reset_password.rb +207 -0
  57. data/lib/rodauth/features/session_expiration.rb +55 -0
  58. data/lib/rodauth/features/single_session.rb +87 -0
  59. data/lib/rodauth/features/sms_codes.rb +498 -0
  60. data/lib/rodauth/features/two_factor_base.rb +135 -0
  61. data/lib/rodauth/features/verify_account.rb +232 -0
  62. data/lib/rodauth/features/verify_account_grace_period.rb +76 -0
  63. data/lib/rodauth/features/verify_change_login.rb +20 -0
  64. data/lib/rodauth/migrations.rb +130 -0
  65. data/lib/rodauth/version.rb +9 -0
  66. data/spec/account_expiration_spec.rb +90 -0
  67. data/spec/all.rb +1 -0
  68. data/spec/change_login_spec.rb +149 -0
  69. data/spec/change_password_spec.rb +177 -0
  70. data/spec/close_account_spec.rb +162 -0
  71. data/spec/confirm_password_spec.rb +70 -0
  72. data/spec/create_account_spec.rb +127 -0
  73. data/spec/disallow_password_reuse_spec.rb +84 -0
  74. data/spec/lockout_spec.rb +228 -0
  75. data/spec/login_spec.rb +188 -0
  76. data/spec/migrate/001_tables.rb +103 -16
  77. data/spec/migrate/002_account_password_hash_column.rb +11 -0
  78. data/spec/migrate_password/001_tables.rb +60 -42
  79. data/spec/migrate_travis/001_tables.rb +116 -0
  80. data/spec/password_complexity_spec.rb +108 -0
  81. data/spec/password_expiration_spec.rb +243 -0
  82. data/spec/password_grace_period_spec.rb +93 -0
  83. data/spec/remember_spec.rb +424 -0
  84. data/spec/reset_password_spec.rb +185 -0
  85. data/spec/rodauth_spec.rb +57 -980
  86. data/spec/session_expiration_spec.rb +58 -0
  87. data/spec/single_session_spec.rb +107 -0
  88. data/spec/spec_helper.rb +202 -0
  89. data/spec/two_factor_spec.rb +1310 -0
  90. data/spec/verify_account_grace_period_spec.rb +135 -0
  91. data/spec/verify_account_spec.rb +142 -0
  92. data/spec/verify_change_login_spec.rb +46 -0
  93. data/spec/views/login.str +2 -2
  94. data/templates/add-recovery-codes.str +2 -0
  95. data/templates/button.str +5 -0
  96. data/templates/change-login.str +5 -18
  97. data/templates/change-password.str +6 -14
  98. data/templates/close-account.str +3 -6
  99. data/templates/confirm-password.str +4 -14
  100. data/templates/create-account.str +6 -30
  101. data/templates/login-confirm-field.str +6 -0
  102. data/templates/login-field.str +6 -0
  103. data/templates/login.str +5 -19
  104. data/templates/logout.str +2 -6
  105. data/templates/otp-auth-code-field.str +6 -0
  106. data/templates/otp-auth.str +8 -0
  107. data/templates/otp-disable.str +6 -0
  108. data/templates/otp-setup.str +21 -0
  109. data/templates/password-confirm-field.str +6 -0
  110. data/templates/password-field.str +6 -0
  111. data/templates/recovery-auth.str +12 -0
  112. data/templates/recovery-codes.str +6 -0
  113. data/templates/remember.str +8 -12
  114. data/templates/reset-password-request.str +2 -2
  115. data/templates/reset-password.str +4 -18
  116. data/templates/sms-auth.str +6 -0
  117. data/templates/sms-code-field.str +6 -0
  118. data/templates/sms-confirm.str +7 -0
  119. data/templates/sms-disable.str +7 -0
  120. data/templates/sms-request.str +5 -0
  121. data/templates/sms-setup.str +12 -0
  122. data/templates/unlock-account-request.str +3 -7
  123. data/templates/unlock-account.str +4 -7
  124. data/templates/verify-account-resend.str +2 -2
  125. data/templates/verify-account.str +2 -6
  126. metadata +191 -29
  127. data/lib/roda/plugins/rodauth/base.rb +0 -428
  128. data/lib/roda/plugins/rodauth/change_login.rb +0 -48
  129. data/lib/roda/plugins/rodauth/change_password.rb +0 -42
  130. data/lib/roda/plugins/rodauth/close_account.rb +0 -42
  131. data/lib/roda/plugins/rodauth/create_account.rb +0 -92
  132. data/lib/roda/plugins/rodauth/lockout.rb +0 -292
  133. data/lib/roda/plugins/rodauth/login.rb +0 -81
  134. data/lib/roda/plugins/rodauth/logout.rb +0 -36
  135. data/lib/roda/plugins/rodauth/remember.rb +0 -226
  136. data/lib/roda/plugins/rodauth/reset_password.rb +0 -205
  137. data/lib/roda/plugins/rodauth/verify_account.rb +0 -228
@@ -0,0 +1,9 @@
1
+ = Documentation for Verify Change Login Feature
2
+
3
+ The verify change login feature implements account reverification after
4
+ change login. Any time you use the verify account and change login
5
+ features together, you should probably use this, otherwise it is trivial
6
+ for users to work around account verification by creating an account with
7
+ an email address they control, and the changing the login to an email
8
+ address they don't control. Depends on the change login and verify
9
+ account grace period features.
@@ -1,264 +1,5 @@
1
- require 'tilt/string'
1
+ # frozen-string-literal: true
2
2
 
3
- class Roda
4
- module RodaPlugins
5
- module Rodauth
6
- def self.load_dependencies(app, opts={})
7
- app.plugin :render
8
- app.plugin :flash
9
- app.plugin :h
10
- end
11
-
12
- def self.configure(app, opts={}, &block)
13
- ((app.opts[:rodauths] ||= {})[opts[:name]] ||= Class.new(Auth)).configure(&block)
14
- end
15
-
16
- DSL_META_TYPES = [:auth, :auth_value].freeze
17
- FEATURES = {}
18
-
19
- class Feature < Module
20
- DSL_META_TYPES.each do |meth|
21
- name = :"#{meth}_methods"
22
- define_method(name) do |*v|
23
- iv = :"@#{name}"
24
- existing = instance_variable_get(iv) || []
25
- if v.empty?
26
- existing
27
- else
28
- instance_variable_set(iv, existing + v)
29
- end
30
- end
31
- end
32
-
33
- attr_accessor :feature_name
34
- attr_accessor :dependencies
35
-
36
- def self.define(name, &block)
37
- feature = new
38
- feature.dependencies = []
39
- feature.feature_name = name
40
- feature.module_eval(&block)
41
- FEATURES[name] = feature
42
- end
43
-
44
- DEFAULT_REDIRECT_BLOCK = proc{default_redirect}
45
- def redirect(&block)
46
- meth = :"#{feature_name}_redirect"
47
- block ||= DEFAULT_REDIRECT_BLOCK
48
- define_method(meth, &block)
49
- auth_value_methods meth
50
- end
51
-
52
- def view(page, title)
53
- meth = :"#{feature_name}_view"
54
- define_method(meth) do
55
- view(page, title)
56
- end
57
- auth_methods meth
58
- end
59
-
60
- def depends(*deps)
61
- dependencies.concat(deps)
62
- end
63
-
64
- def after
65
- meth = :"after_#{feature_name}"
66
- define_method(meth) do
67
- nil
68
- end
69
- auth_methods meth
70
- end
71
-
72
- def additional_form_tags
73
- meth = :"#{feature_name}_additional_form_tags"
74
- define_method(meth) do
75
- nil
76
- end
77
- auth_value_methods meth
78
- end
79
-
80
- def require_account
81
- @account_required = true
82
- end
83
-
84
- def account_required?
85
- @account_required
86
- end
87
-
88
- [:route, :notice_flash, :error_flash, :button].each do |meth|
89
- define_method(meth) do |v|
90
- inst_meth = :"#{feature_name}_#{meth}"
91
- define_method(inst_meth){v}
92
- auth_value_methods inst_meth
93
- end
94
- end
95
-
96
- [:get, :post, :route].each do |meth|
97
- define_method("#{meth}_block") do |&block|
98
- if block
99
- instance_variable_set("@#{meth}_block", block)
100
- else
101
- instance_variable_get("@#{meth}_block")
102
- end
103
- end
104
- end
105
- end
106
-
107
- class Auth
108
- class << self
109
- attr_reader :features
110
- attr_reader :route_block_methods
111
- end
112
-
113
- def self.inherited(subclass)
114
- super
115
- subclass.instance_exec do
116
- @features = []
117
- @route_block_methods = []
118
- end
119
- end
120
-
121
- def self.configure(&block)
122
- DSL.new(self, &block)
123
- end
124
-
125
- def self.freeze
126
- @features.freeze
127
- @route_block_methods.freeze
128
- super
129
- end
130
-
131
- def route_block_methods
132
- self.class.route_block_methods
133
- end
134
- end
135
-
136
- class DSL
137
- def def_auth_method(meth)
138
- define_sclass_method(meth) do |&block|
139
- _def_auth_method(meth, &block)
140
- end
141
- end
142
-
143
- def def_auth_value_method(meth)
144
- define_sclass_method(meth) do |*v, &block|
145
- v = v.first
146
- block ||= proc{v}
147
- _def_auth_method(meth, &block)
148
- end
149
- end
150
-
151
- def def_auth_block_method(meth)
152
- define_sclass_method(meth) do |&block|
153
- _def_auth_method(meth){block}
154
- end
155
- end
156
-
157
- def initialize(auth, &block)
158
- @auth = auth
159
- load_feature(:base)
160
- instance_exec(&block)
161
- end
162
-
163
- def enable(*features)
164
- new_features = features - @auth.features
165
- new_features.each{|f| load_feature(f)}
166
- @auth.features.concat(new_features)
167
- end
168
-
169
- private
170
-
171
- def _def_auth_method(meth, &block)
172
- @auth.send(:define_method, meth, &block)
173
- end
174
-
175
- def define_sclass_method(meth, &block)
176
- (class << self; self end).send(:define_method, meth, &block)
177
- end
178
-
179
- def load_feature(feature_name)
180
- require "roda/plugins/rodauth/#{feature_name}"
181
- feature = FEATURES[feature_name]
182
- enable(*feature.dependencies)
183
-
184
- DSL_META_TYPES.each do |type|
185
- feature.send(:"#{type}_methods").each{|m| send(:"def_#{type}_method", m)}
186
- end
187
-
188
- if get_block = feature.get_block
189
- def_auth_block_method :"#{feature_name}_get_block"
190
- _def_auth_method(:"#{feature_name}_get_block"){get_block}
191
- end
192
-
193
- if post_block = feature.post_block
194
- def_auth_block_method :"#{feature_name}_post_block"
195
- _def_auth_method(:"#{feature_name}_post_block"){post_block}
196
- end
197
-
198
- route_block = feature.route_block
199
- if route_block || (get_block && post_block)
200
- check_before_meth = :"check_before_#{feature_name}"
201
- before_meth = :"before_#{feature_name}"
202
- def_auth_block_method :"#{feature_name}_route_block"
203
- route_block ||= proc do |r, auth|
204
- r.is auth.send(:"#{feature_name}_route") do
205
- auth.check_before(feature)
206
- auth.send(before_meth)
207
-
208
- r.get do
209
- instance_exec(r, auth, &auth.send(:"#{feature_name}_get_block"))
210
- end
211
-
212
- r.post do
213
- instance_exec(r, auth, &auth.send(:"#{feature_name}_post_block"))
214
- end
215
- end
216
- end
217
- _def_auth_method(:"#{feature_name}_route_block"){route_block}
218
- _def_auth_method(before_meth){nil}
219
- def_auth_method(before_meth)
220
- @auth.route_block_methods << :"#{feature_name}_route_block"
221
- end
222
-
223
- @auth.send(:include, feature)
224
- end
225
- end
226
-
227
- module InstanceMethods
228
- def rodauth(name=nil)
229
- if name
230
- (@_rodauths ||= {})[name] ||= self.class.rodauth(name).new(self)
231
- else
232
- @_rodauth ||= self.class.rodauth.new(self)
233
- end
234
- end
235
- end
236
-
237
- module ClassMethods
238
- def rodauth(name=nil)
239
- opts[:rodauths][name]
240
- end
241
-
242
- def freeze
243
- if opts[:rodauths]
244
- opts[:rodauths].each_value(&:freeze)
245
- opts[:rodauths].freeze
246
- end
247
- super
248
- end
249
- end
250
-
251
- module RequestMethods
252
- def rodauth(name=nil)
253
- auth = scope.rodauth(name)
254
- auth.route_block_methods.each do |meth|
255
- scope.instance_exec(self, auth, &auth.send(meth))
256
- end
257
- end
258
- end
259
- end
260
-
261
- register_plugin(:rodauth, Rodauth)
262
- end
263
- end
3
+ require 'rodauth'
264
4
 
5
+ Roda::RodaPlugins.register_plugin(:rodauth, Rodauth)
@@ -0,0 +1,260 @@
1
+ # frozen-string-literal: true
2
+
3
+ require 'securerandom'
4
+
5
+ module Rodauth
6
+ def self.load_dependencies(app, opts={})
7
+ if opts[:json]
8
+ app.plugin :json
9
+ app.plugin :json_parser
10
+ end
11
+
12
+ unless opts[:json] == :only
13
+ require 'tilt/string'
14
+ app.plugin :render
15
+ app.plugin :csrf
16
+ app.plugin :flash
17
+ app.plugin :h
18
+ end
19
+ end
20
+
21
+ def self.configure(app, opts={}, &block)
22
+ ((app.opts[:rodauths] ||= {})[opts[:name]] ||= Class.new(Auth)).configure(&block)
23
+ end
24
+
25
+ FEATURES = {}
26
+
27
+ class FeatureConfiguration < Module
28
+ def def_configuration_methods(feature)
29
+ private_methods = feature.private_instance_methods.map(&:to_sym)
30
+ priv = proc{|m| private_methods.include?(m)}
31
+ feature.auth_methods.each{|m| def_auth_method(m, priv[m])}
32
+ feature.auth_value_methods.each{|m| def_auth_value_method(m, priv[m])}
33
+ feature.auth_private_methods.each{|m| def_auth_private_method(m)}
34
+ end
35
+
36
+ private
37
+
38
+ def def_auth_method(meth, priv)
39
+ define_method(meth) do |&block|
40
+ @auth.send(:define_method, meth, &block)
41
+ @auth.send(:private, meth) if priv
42
+ end
43
+ end
44
+
45
+ def def_auth_private_method(meth)
46
+ umeth = :"_#{meth}"
47
+ define_method(meth) do |&block|
48
+ @auth.send(:define_method, umeth, &block)
49
+ @auth.send(:private, umeth)
50
+ end
51
+ end
52
+
53
+ def def_auth_value_method(meth, priv)
54
+ define_method(meth) do |*v, &block|
55
+ v = v.first
56
+ block ||= proc{v}
57
+ @auth.send(:define_method, meth, &block)
58
+ @auth.send(:private, meth) if priv
59
+ end
60
+ end
61
+ end
62
+
63
+ class Feature < Module
64
+ [:auth, :auth_value, :auth_private].each do |meth|
65
+ name = :"#{meth}_methods"
66
+ define_method(name) do |*v|
67
+ iv = :"@#{name}"
68
+ existing = instance_variable_get(iv) || []
69
+ if v.empty?
70
+ existing
71
+ else
72
+ instance_variable_set(iv, existing + v)
73
+ end
74
+ end
75
+ end
76
+
77
+ attr_accessor :feature_name
78
+ attr_accessor :dependencies
79
+ attr_accessor :routes
80
+ attr_accessor :configuration
81
+
82
+ def route(name=feature_name, default=name.to_s.tr('_', '-'), &block)
83
+ auth_value_method "#{name}_route", default
84
+
85
+ handle_meth = "handle_#{name}"
86
+ route_meth = :"#{name}_route"
87
+ before route_meth
88
+
89
+ define_method(handle_meth) do
90
+ request.is send(route_meth) do
91
+ before_rodauth
92
+ instance_exec(request, &block)
93
+ end
94
+ end
95
+
96
+ routes << handle_meth
97
+ end
98
+
99
+ def self.define(name, &block)
100
+ feature = new
101
+ feature.dependencies = []
102
+ feature.routes = []
103
+ feature.feature_name = name
104
+ configuration = feature.configuration = FeatureConfiguration.new
105
+ feature.module_eval(&block)
106
+ configuration.def_configuration_methods(feature)
107
+ FEATURES[name] = feature
108
+ end
109
+
110
+ def configuration_module_eval(&block)
111
+ configuration.module_eval(&block)
112
+ end
113
+
114
+ DEFAULT_REDIRECT_BLOCK = proc{default_redirect}
115
+ def redirect(name=feature_name, &block)
116
+ meth = :"#{name}_redirect"
117
+ block ||= DEFAULT_REDIRECT_BLOCK
118
+ define_method(meth, &block)
119
+ auth_value_methods meth
120
+ end
121
+
122
+ def view(page, title, name=feature_name)
123
+ meth = :"#{name}_view"
124
+ define_method(meth) do
125
+ view(page, title)
126
+ end
127
+ auth_methods meth
128
+ end
129
+
130
+ def depends(*deps)
131
+ dependencies.concat(deps)
132
+ end
133
+
134
+ %w'after before'.each do |hook|
135
+ define_method(hook) do |*args|
136
+ name = args[0] || feature_name
137
+ meth = "#{hook}_#{name}"
138
+ class_eval("def #{meth}; super if defined?(super); _#{meth} end", __FILE__, __LINE__)
139
+ class_eval("def _#{meth}; nil end", __FILE__, __LINE__)
140
+ private meth, :"_#{meth}"
141
+ auth_private_methods(meth)
142
+ end
143
+ end
144
+
145
+ def additional_form_tags(name=feature_name)
146
+ auth_value_method(:"#{name}_additional_form_tags", nil)
147
+ end
148
+
149
+ def auth_value_method(meth, value)
150
+ define_method(meth){value}
151
+ auth_value_methods(meth)
152
+ end
153
+
154
+ def auth_cached_method(meth, iv=:"@#{meth}")
155
+ umeth = :"_#{meth}"
156
+ define_method(meth) do
157
+ if instance_variable_defined?(iv)
158
+ instance_variable_get(iv)
159
+ else
160
+ instance_variable_set(iv, send(umeth))
161
+ end
162
+ end
163
+ auth_private_methods(meth)
164
+ end
165
+
166
+ [:notice_flash, :error_flash, :button].each do |meth|
167
+ define_method(meth) do |v, *args|
168
+ name = args.shift || feature_name
169
+ auth_value_method(:"#{name}_#{meth}", v)
170
+ end
171
+ end
172
+ end
173
+
174
+ class Auth
175
+ class << self
176
+ attr_reader :features
177
+ attr_reader :routes
178
+ attr_accessor :route_hash
179
+ end
180
+
181
+ def self.inherited(subclass)
182
+ super
183
+ subclass.instance_exec do
184
+ @features = []
185
+ @routes = []
186
+ @route_hash = {}
187
+ end
188
+ end
189
+
190
+ def self.configure(&block)
191
+ Configuration.new(self, &block)
192
+ end
193
+
194
+ def self.freeze
195
+ @features.freeze
196
+ @routes.freeze
197
+ @route_hash.freeze
198
+ super
199
+ end
200
+ end
201
+
202
+ class Configuration
203
+ attr_reader :auth
204
+
205
+ def initialize(auth, &block)
206
+ @auth = auth
207
+ load_feature(:base)
208
+ instance_exec(&block)
209
+ auth.allocate.post_configure
210
+ end
211
+
212
+ def enable(*features)
213
+ new_features = features - @auth.features
214
+ new_features.each{|f| load_feature(f)}
215
+ @auth.features.concat(new_features)
216
+ end
217
+
218
+ private
219
+
220
+ def load_feature(feature_name)
221
+ require "rodauth/features/#{feature_name}"
222
+ feature = FEATURES[feature_name]
223
+ enable(*feature.dependencies)
224
+ extend feature.configuration
225
+
226
+ @auth.routes.concat(feature.routes)
227
+ @auth.send(:include, feature)
228
+ end
229
+ end
230
+
231
+ module InstanceMethods
232
+ def rodauth(name=nil)
233
+ if name
234
+ (@_rodauths ||= {})[name] ||= self.class.rodauth(name).new(self)
235
+ else
236
+ @_rodauth ||= self.class.rodauth.new(self)
237
+ end
238
+ end
239
+ end
240
+
241
+ module ClassMethods
242
+ def rodauth(name=nil)
243
+ opts[:rodauths][name]
244
+ end
245
+
246
+ def freeze
247
+ if opts[:rodauths]
248
+ opts[:rodauths].each_value(&:freeze)
249
+ opts[:rodauths].freeze
250
+ end
251
+ super
252
+ end
253
+ end
254
+
255
+ module RequestMethods
256
+ def rodauth(name=nil)
257
+ scope.rodauth(name).route!
258
+ end
259
+ end
260
+ end