rodauth 0.10.0 → 1.0.0

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