rodauth 0.9.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 (40) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG +3 -0
  3. data/MIT-LICENSE +18 -0
  4. data/README.rdoc +484 -0
  5. data/Rakefile +91 -0
  6. data/lib/roda/plugins/rodauth.rb +265 -0
  7. data/lib/roda/plugins/rodauth/base.rb +428 -0
  8. data/lib/roda/plugins/rodauth/change_login.rb +48 -0
  9. data/lib/roda/plugins/rodauth/change_password.rb +42 -0
  10. data/lib/roda/plugins/rodauth/close_account.rb +42 -0
  11. data/lib/roda/plugins/rodauth/create_account.rb +92 -0
  12. data/lib/roda/plugins/rodauth/lockout.rb +292 -0
  13. data/lib/roda/plugins/rodauth/login.rb +77 -0
  14. data/lib/roda/plugins/rodauth/logout.rb +36 -0
  15. data/lib/roda/plugins/rodauth/remember.rb +226 -0
  16. data/lib/roda/plugins/rodauth/reset_password.rb +205 -0
  17. data/lib/roda/plugins/rodauth/verify_account.rb +228 -0
  18. data/spec/migrate/001_tables.rb +64 -0
  19. data/spec/migrate_password/001_tables.rb +38 -0
  20. data/spec/rodauth_spec.rb +1114 -0
  21. data/spec/views/layout.str +11 -0
  22. data/spec/views/login.str +21 -0
  23. data/templates/change-login.str +22 -0
  24. data/templates/change-password.str +21 -0
  25. data/templates/close-account.str +9 -0
  26. data/templates/confirm-password.str +16 -0
  27. data/templates/create-account.str +33 -0
  28. data/templates/login.str +25 -0
  29. data/templates/logout.str +9 -0
  30. data/templates/remember.str +28 -0
  31. data/templates/reset-password-email.str +5 -0
  32. data/templates/reset-password-request.str +7 -0
  33. data/templates/reset-password.str +23 -0
  34. data/templates/unlock-account-email.str +5 -0
  35. data/templates/unlock-account-request.str +11 -0
  36. data/templates/unlock-account.str +11 -0
  37. data/templates/verify-account-email.str +4 -0
  38. data/templates/verify-account-resend.str +7 -0
  39. data/templates/verify-account.str +11 -0
  40. metadata +227 -0
@@ -0,0 +1,91 @@
1
+ require "rake"
2
+ require "rake/clean"
3
+
4
+ CLEAN.include ["rodauth-*.gem", "rdoc", "coverage"]
5
+
6
+ # Packaging
7
+
8
+ desc "Build rodauth gem"
9
+ task :package=>[:clean] do |p|
10
+ sh %{#{FileUtils::RUBY} -S gem build rodauth.gemspec}
11
+ end
12
+
13
+ ### RDoc
14
+
15
+ RDOC_DEFAULT_OPTS = ["--line-numbers", "--inline-source", '--title', 'Rodauth: Authentication framework using Roda, Sequel, and PostgreSQL']
16
+
17
+ begin
18
+ gem 'hanna-nouveau'
19
+ RDOC_DEFAULT_OPTS.concat(['-f', 'hanna'])
20
+ rescue Gem::LoadError
21
+ end
22
+
23
+ rdoc_task_class = begin
24
+ require "rdoc/task"
25
+ RDoc::Task
26
+ rescue LoadError
27
+ require "rake/rdoctask"
28
+ Rake::RDocTask
29
+ end
30
+
31
+ RDOC_OPTS = RDOC_DEFAULT_OPTS + ['--main', 'README.rdoc']
32
+ RDOC_FILES = %w"README.rdoc CHANGELOG MIT-LICENSE lib/**/*.rb" + Dir["doc/*.rdoc"] + Dir['doc/release_notes/*.txt']
33
+
34
+ rdoc_task_class.new do |rdoc|
35
+ rdoc.rdoc_dir = "rdoc"
36
+ rdoc.options += RDOC_OPTS
37
+ rdoc.rdoc_files.add RDOC_FILES
38
+ end
39
+
40
+ # Specs
41
+
42
+ desc "Run specs"
43
+ task :default=>:spec
44
+
45
+ spec = proc do |env|
46
+ env.each{|k,v| ENV[k] = v}
47
+ sh "#{FileUtils::RUBY} spec/rodauth_spec.rb"
48
+ env.each{|k,v| ENV.delete(k)}
49
+ end
50
+
51
+ desc "Run specs"
52
+ task "spec" do
53
+ spec.call({})
54
+ end
55
+
56
+ desc "Run specs with coverage"
57
+ task "spec_cov" do
58
+ ENV['COVERAGE'] = '1'
59
+ spec.call('COVERAGE'=>'1')
60
+ end
61
+
62
+ desc "Run specs with -w, some warnings filtered"
63
+ task "spec_w" do
64
+ ENV['RUBYOPT'] ? (ENV['RUBYOPT'] += " -w") : (ENV['RUBYOPT'] = '-w')
65
+ rake = ENV['RAKE'] || "#{FileUtils::RUBY} -S rake"
66
+ sh %{#{rake} 2>&1 | egrep -v \": warning: instance variable @.* not initialized|: warning: method redefined; discarding old|: warning: previous definition of|: warning: statement not reached"}
67
+ end
68
+
69
+ desc "Setup database used for testing"
70
+ task :db_setup do
71
+ sh 'echo "CREATE USER rodauth_test PASSWORD \'rodauth_test\'" | psql -U postgres'
72
+ sh 'echo "CREATE USER rodauth_test_password PASSWORD \'rodauth_test\'" | psql -U postgres'
73
+ sh 'createdb -U postgres -O rodauth_test rodauth_test'
74
+ sh 'echo "CREATE EXTENSION pgcrypto" | psql -U postgres rodauth_test'
75
+ sh 'echo "CREATE EXTENSION citext" | psql -U postgres rodauth_test'
76
+ require 'sequel'
77
+ Sequel.extension :migration
78
+ Sequel.postgres(:user=>'rodauth_test', :password=>'rodauth_test') do |db|
79
+ Sequel::Migrator.run(db, 'spec/migrate')
80
+ end
81
+ Sequel.postgres('rodauth_test', :user=>'rodauth_test_password', :password=>'rodauth_test') do |db|
82
+ Sequel::Migrator.run(db, 'spec/migrate_password', :table=>'schema_info_password')
83
+ end
84
+ end
85
+
86
+ desc "Teardown database used for testing"
87
+ task :db_teardown do
88
+ sh 'dropdb -U postgres rodauth_test'
89
+ sh 'dropuser -U postgres rodauth_test_password'
90
+ sh 'dropuser -U postgres rodauth_test'
91
+ end
@@ -0,0 +1,265 @@
1
+ require 'tilt/string'
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
+ app.plugin :csrf
11
+ end
12
+
13
+ def self.configure(app, opts={}, &block)
14
+ ((app.opts[:rodauths] ||= {})[opts[:name]] ||= Class.new(Auth)).configure(&block)
15
+ end
16
+
17
+ DSL_META_TYPES = [:auth, :auth_value].freeze
18
+ FEATURES = {}
19
+
20
+ class Feature < Module
21
+ DSL_META_TYPES.each do |meth|
22
+ name = :"#{meth}_methods"
23
+ define_method(name) do |*v|
24
+ iv = :"@#{name}"
25
+ existing = instance_variable_get(iv) || []
26
+ if v.empty?
27
+ existing
28
+ else
29
+ instance_variable_set(iv, existing + v)
30
+ end
31
+ end
32
+ end
33
+
34
+ attr_accessor :feature_name
35
+ attr_accessor :dependencies
36
+
37
+ def self.define(name, &block)
38
+ feature = new
39
+ feature.dependencies = []
40
+ feature.feature_name = name
41
+ feature.module_eval(&block)
42
+ FEATURES[name] = feature
43
+ end
44
+
45
+ DEFAULT_REDIRECT_BLOCK = proc{default_redirect}
46
+ def redirect(&block)
47
+ meth = :"#{feature_name}_redirect"
48
+ block ||= DEFAULT_REDIRECT_BLOCK
49
+ define_method(meth, &block)
50
+ auth_value_methods meth
51
+ end
52
+
53
+ def view(page, title)
54
+ meth = :"#{feature_name}_view"
55
+ define_method(meth) do
56
+ view(page, title)
57
+ end
58
+ auth_methods meth
59
+ end
60
+
61
+ def depends(*deps)
62
+ dependencies.concat(deps)
63
+ end
64
+
65
+ def after
66
+ meth = :"after_#{feature_name}"
67
+ define_method(meth) do
68
+ nil
69
+ end
70
+ auth_methods meth
71
+ end
72
+
73
+ def additional_form_tags
74
+ meth = :"#{feature_name}_additional_form_tags"
75
+ define_method(meth) do
76
+ nil
77
+ end
78
+ auth_value_methods meth
79
+ end
80
+
81
+ def require_account
82
+ @account_required = true
83
+ end
84
+
85
+ def account_required?
86
+ @account_required
87
+ end
88
+
89
+ [:route, :notice_flash, :error_flash, :button].each do |meth|
90
+ define_method(meth) do |v|
91
+ inst_meth = :"#{feature_name}_#{meth}"
92
+ define_method(inst_meth){v}
93
+ auth_value_methods inst_meth
94
+ end
95
+ end
96
+
97
+ [:get, :post, :route].each do |meth|
98
+ define_method("#{meth}_block") do |&block|
99
+ if block
100
+ instance_variable_set("@#{meth}_block", block)
101
+ else
102
+ instance_variable_get("@#{meth}_block")
103
+ end
104
+ end
105
+ end
106
+ end
107
+
108
+ class Auth
109
+ class << self
110
+ attr_reader :features
111
+ attr_reader :route_block_methods
112
+ end
113
+
114
+ def self.inherited(subclass)
115
+ super
116
+ subclass.instance_exec do
117
+ @features = []
118
+ @route_block_methods = []
119
+ end
120
+ end
121
+
122
+ def self.configure(&block)
123
+ DSL.new(self, &block)
124
+ end
125
+
126
+ def self.freeze
127
+ @features.freeze
128
+ @route_block_methods.freeze
129
+ super
130
+ end
131
+
132
+ def route_block_methods
133
+ self.class.route_block_methods
134
+ end
135
+ end
136
+
137
+ class DSL
138
+ def def_auth_method(meth)
139
+ define_sclass_method(meth) do |&block|
140
+ _def_auth_method(meth, &block)
141
+ end
142
+ end
143
+
144
+ def def_auth_value_method(meth)
145
+ define_sclass_method(meth) do |*v, &block|
146
+ v = v.first
147
+ block ||= proc{v}
148
+ _def_auth_method(meth, &block)
149
+ end
150
+ end
151
+
152
+ def def_auth_block_method(meth)
153
+ define_sclass_method(meth) do |&block|
154
+ _def_auth_method(meth){block}
155
+ end
156
+ end
157
+
158
+ def initialize(auth, &block)
159
+ @auth = auth
160
+ load_feature(:base)
161
+ instance_exec(&block)
162
+ end
163
+
164
+ def enable(*features)
165
+ new_features = features - @auth.features
166
+ new_features.each{|f| load_feature(f)}
167
+ @auth.features.concat(new_features)
168
+ end
169
+
170
+ private
171
+
172
+ def _def_auth_method(meth, &block)
173
+ @auth.send(:define_method, meth, &block)
174
+ end
175
+
176
+ def define_sclass_method(meth, &block)
177
+ (class << self; self end).send(:define_method, meth, &block)
178
+ end
179
+
180
+ def load_feature(feature_name)
181
+ require "roda/plugins/rodauth/#{feature_name}"
182
+ feature = FEATURES[feature_name]
183
+ enable(*feature.dependencies)
184
+
185
+ DSL_META_TYPES.each do |type|
186
+ feature.send(:"#{type}_methods").each{|m| send(:"def_#{type}_method", m)}
187
+ end
188
+
189
+ if get_block = feature.get_block
190
+ def_auth_block_method :"#{feature_name}_get_block"
191
+ _def_auth_method(:"#{feature_name}_get_block"){get_block}
192
+ end
193
+
194
+ if post_block = feature.post_block
195
+ def_auth_block_method :"#{feature_name}_post_block"
196
+ _def_auth_method(:"#{feature_name}_post_block"){post_block}
197
+ end
198
+
199
+ route_block = feature.route_block
200
+ if route_block || (get_block && post_block)
201
+ check_before_meth = :"check_before_#{feature_name}"
202
+ before_meth = :"before_#{feature_name}"
203
+ def_auth_block_method :"#{feature_name}_route_block"
204
+ route_block ||= proc do |r, auth|
205
+ r.is auth.send(:"#{feature_name}_route") do
206
+ auth.check_before(feature)
207
+ auth.send(before_meth)
208
+
209
+ r.get do
210
+ instance_exec(r, auth, &auth.send(:"#{feature_name}_get_block"))
211
+ end
212
+
213
+ r.post do
214
+ instance_exec(r, auth, &auth.send(:"#{feature_name}_post_block"))
215
+ end
216
+ end
217
+ end
218
+ _def_auth_method(:"#{feature_name}_route_block"){route_block}
219
+ _def_auth_method(before_meth){nil}
220
+ def_auth_method(before_meth)
221
+ @auth.route_block_methods << :"#{feature_name}_route_block"
222
+ end
223
+
224
+ @auth.send(:include, feature)
225
+ end
226
+ end
227
+
228
+ module InstanceMethods
229
+ def rodauth(name=nil)
230
+ if name
231
+ (@_rodauths ||= {})[name] ||= self.class.rodauth(name).new(self)
232
+ else
233
+ @_rodauth ||= self.class.rodauth.new(self)
234
+ end
235
+ end
236
+ end
237
+
238
+ module ClassMethods
239
+ def rodauth(name=nil)
240
+ opts[:rodauths][name]
241
+ end
242
+
243
+ def freeze
244
+ if opts[:rodauths]
245
+ opts[:rodauths].each_value(&:freeze)
246
+ opts[:rodauths].freeze
247
+ end
248
+ super
249
+ end
250
+ end
251
+
252
+ module RequestMethods
253
+ def rodauth(name=nil)
254
+ auth = scope.rodauth(name)
255
+ auth.route_block_methods.each do |meth|
256
+ scope.instance_exec(self, auth, &auth.send(meth))
257
+ end
258
+ end
259
+ end
260
+ end
261
+
262
+ register_plugin(:rodauth, Rodauth)
263
+ end
264
+ end
265
+
@@ -0,0 +1,428 @@
1
+ class Roda
2
+ module RodaPlugins
3
+ module Rodauth
4
+ Base = Feature.define(:base) do
5
+ auth_value_methods(
6
+ :account_id,
7
+ :account_model,
8
+ :account_open_status_value,
9
+ :account_password_hash_column,
10
+ :account_status_id,
11
+ :account_unverified_status_value,
12
+ :default_redirect,
13
+ :email_from,
14
+ :email_subject_prefix,
15
+ :login_column,
16
+ :login_confirm_label,
17
+ :login_confirm_param,
18
+ :login_label,
19
+ :login_param,
20
+ :logins_do_not_match_message,
21
+ :no_matching_login_message,
22
+ :password_confirm_label,
23
+ :password_confirm_param,
24
+ :password_does_not_meet_requirements_message,
25
+ :password_hash_column,
26
+ :password_hash_cost,
27
+ :password_hash_table,
28
+ :password_label,
29
+ :password_minimum_length,
30
+ :password_param,
31
+ :passwords_do_not_match_message,
32
+ :prefix,
33
+ :require_login_notice_message,
34
+ :require_login_redirect,
35
+ :session_key,
36
+ :skip_status_checks?,
37
+ :title_instance_variable
38
+ )
39
+
40
+ auth_methods(
41
+ :account_from_login,
42
+ :account_from_session,
43
+ :account_id_value,
44
+ :account_session_value,
45
+ :after_close_account,
46
+ :already_logged_in,
47
+ :clear_session,
48
+ :create_email,
49
+ :email_to,
50
+ :logged_in?,
51
+ :login_errors_message,
52
+ :login_required,
53
+ :open_account?,
54
+ :password_hash,
55
+ :password_meets_requirements?,
56
+ :random_key,
57
+ :session_value,
58
+ :set_error_flash,
59
+ :set_notice_flash,
60
+ :set_password,
61
+ :set_redirect_error_flash,
62
+ :set_title,
63
+ :unverified_account_message,
64
+ :update_session
65
+ )
66
+
67
+ attr_reader :scope
68
+ attr_reader :account
69
+
70
+ def initialize(scope)
71
+ @scope = scope
72
+ end
73
+
74
+ def features
75
+ self.class.features
76
+ end
77
+
78
+ def request
79
+ scope.request
80
+ end
81
+
82
+ def response
83
+ scope.response
84
+ end
85
+
86
+ def session
87
+ scope.session
88
+ end
89
+
90
+ def flash
91
+ scope.flash
92
+ end
93
+
94
+ # Overridable methods
95
+
96
+ def account_id_value
97
+ account.send(account_id)
98
+ end
99
+ alias account_session_value account_id_value
100
+
101
+ def session_value
102
+ session[session_key]
103
+ end
104
+
105
+ def account_status_id_value
106
+ account.send(account_status_id)
107
+ end
108
+
109
+ def _account_from_login(login)
110
+ @account = account_from_login(login)
111
+ end
112
+
113
+ def account_from_login(login)
114
+ ds = account_model.where(login_column=>login)
115
+ ds = ds.where(account_status_id=>[account_unverified_status_value, account_open_status_value]) unless skip_status_checks?
116
+ ds.first
117
+ end
118
+
119
+ def open_account?
120
+ skip_status_checks? || account_status_id_value == account_open_status_value
121
+ end
122
+
123
+ def unverified_account_message
124
+ "unverified account, please verify account before logging in"
125
+ end
126
+
127
+ def update_session
128
+ clear_session
129
+ session[session_key] = account_session_value
130
+ end
131
+
132
+ def check_before(feature)
133
+ meth = :"check_before_#{feature.feature_name}"
134
+ if respond_to?(meth)
135
+ send(meth)
136
+ elsif feature.account_required?
137
+ require_account
138
+ elsif logged_in?
139
+ already_logged_in
140
+ end
141
+ end
142
+
143
+ def account_model
144
+ ::Account
145
+ end
146
+
147
+ def db
148
+ account_model.db
149
+ end
150
+
151
+ # If the account_password_hash_column is set, the password hash is verified in
152
+ # ruby, it will not use a database function to do so, it will check the password
153
+ # hash using bcrypt.
154
+ def account_password_hash_column
155
+ nil
156
+ end
157
+
158
+ def already_logged_in
159
+ nil
160
+ end
161
+
162
+ def clear_session
163
+ session.clear
164
+ end
165
+
166
+ def default_redirect
167
+ '/'
168
+ end
169
+
170
+ def require_login_redirect
171
+ "#{prefix}/login"
172
+ end
173
+
174
+ def require_login_notice_message
175
+ "Please login to continue"
176
+ end
177
+
178
+ def prefix
179
+ ''
180
+ end
181
+
182
+ def login_required
183
+ set_notice_flash require_login_notice_message
184
+ request.redirect require_login_redirect
185
+ end
186
+
187
+ def random_key
188
+ require 'securerandom'
189
+ if RUBY_VERSION >= '1.9'
190
+ SecureRandom.urlsafe_base64(32)
191
+ else
192
+ # :nocov:
193
+ SecureRandom.hex(32)
194
+ # :nocov:
195
+ end
196
+ end
197
+
198
+ def title_instance_variable
199
+ nil
200
+ end
201
+
202
+ def set_title(title)
203
+ if title_instance_variable
204
+ scope.instance_variable_set(title_instance_variable, title)
205
+ end
206
+ end
207
+
208
+ def set_error_flash(message)
209
+ flash.now[:error] = message
210
+ end
211
+
212
+ def set_redirect_error_flash(message)
213
+ flash[:error] = message
214
+ end
215
+
216
+ def set_notice_flash(message)
217
+ flash[:notice] = message
218
+ end
219
+
220
+ def login_column
221
+ :email
222
+ end
223
+
224
+ def password_hash_column
225
+ :password_hash
226
+ end
227
+
228
+ def password_hash_table
229
+ :account_password_hashes
230
+ end
231
+
232
+ def no_matching_login_message
233
+ "no matching login"
234
+ end
235
+
236
+ def logged_in?
237
+ session[session_key]
238
+ end
239
+
240
+ def require_login
241
+ login_required unless logged_in?
242
+ end
243
+
244
+ def require_account
245
+ require_login
246
+ unless _account_from_session
247
+ clear_session
248
+ login_required
249
+ end
250
+ end
251
+
252
+ def login_param
253
+ 'login'
254
+ end
255
+
256
+ def login_confirm_param
257
+ 'login-confirm'
258
+ end
259
+
260
+ def login_label
261
+ 'Login'
262
+ end
263
+
264
+ def login_confirm_label
265
+ "Confirm #{login_label}"
266
+ end
267
+
268
+ def password_label
269
+ 'Password'
270
+ end
271
+
272
+ def password_confirm_label
273
+ "Confirm #{password_label}"
274
+ end
275
+
276
+ def login_errors_message
277
+ if errors = account.errors.on(login_column)
278
+ errors.join(', ')
279
+ end
280
+ end
281
+
282
+ def logins_do_not_match_message
283
+ 'logins do not match'
284
+ end
285
+
286
+ def password_param
287
+ 'password'
288
+ end
289
+
290
+ def password_confirm_param
291
+ 'password-confirm'
292
+ end
293
+
294
+ def session_key
295
+ :account_id
296
+ end
297
+
298
+ def account_id
299
+ :id
300
+ end
301
+
302
+ def account_status_id
303
+ :status_id
304
+ end
305
+
306
+ def passwords_do_not_match_message
307
+ 'passwords do not match'
308
+ end
309
+
310
+ def password_does_not_meet_requirements_message
311
+ "invalid password, does not meet requirements (minimum #{password_minimum_length} characters)"
312
+ end
313
+
314
+ def password_minimum_length
315
+ 6
316
+ end
317
+
318
+ def password_meets_requirements?(password)
319
+ password_minimum_length <= password.length
320
+ end
321
+
322
+ def account_unverified_status_value
323
+ 1
324
+ end
325
+
326
+ def account_open_status_value
327
+ 2
328
+ end
329
+
330
+ def account_initial_status_value
331
+ account_open_status_value
332
+ end
333
+
334
+ def _account_from_session
335
+ @account = account_from_session
336
+ end
337
+
338
+ def account_from_session
339
+ ds = account_model.where(account_id=>scope.session[session_key])
340
+ ds = ds.where(account_status_id=>account_open_status_value) unless skip_status_checks?
341
+ ds.first
342
+ end
343
+
344
+ def password_hash_cost
345
+ require 'bcrypt'
346
+ if ENV['RACK_ENV'] == 'test'
347
+ BCrypt::Engine::MIN_COST
348
+ else
349
+ # :nocov:
350
+ BCrypt::Engine::DEFAULT_COST
351
+ # :nocov:
352
+ end
353
+ end
354
+
355
+ def password_hash(password)
356
+ require 'bcrypt'
357
+ BCrypt::Password.create(password, :cost=>password_hash_cost)
358
+ end
359
+
360
+ def set_password(password)
361
+ hash = password_hash(password)
362
+ if account_password_hash_column
363
+ account.set(account_password_hash_column=>hash).save_changes(:raise_on_save_failure=>true)
364
+ else
365
+ if db[password_hash_table].where(account_id=>account_id_value).update(password_hash_column=>hash) == 0
366
+ db[password_hash_table].insert(account_id=>account_id_value, password_hash_column=>hash)
367
+ end
368
+ end
369
+ end
370
+
371
+ def transaction(&block)
372
+ db.transaction(&block)
373
+ end
374
+
375
+ def email_from
376
+ "webmaster@#{request.host}"
377
+ end
378
+
379
+ def email_to
380
+ account.email
381
+ end
382
+
383
+ def create_email(subject, body)
384
+ require 'mail'
385
+ m = Mail.new
386
+ m.from = email_from
387
+ m.to = email_to
388
+ m.subject = "#{email_subject_prefix}#{subject}"
389
+ m.body = body
390
+ m
391
+ end
392
+
393
+ def email_subject_prefix
394
+ nil
395
+ end
396
+
397
+ def view(page, title)
398
+ set_title(title)
399
+ _view(:view, page)
400
+ end
401
+
402
+ def render(page)
403
+ _view(:render, page)
404
+ end
405
+
406
+ def skip_status_checks?
407
+ false
408
+ end
409
+
410
+ def after_close_account
411
+ end
412
+
413
+ private
414
+
415
+ def _view(meth, page)
416
+ auth = self
417
+ scope.instance_exec do
418
+ template_opts = find_template(parse_template_opts(page, :locals=>{:rodauth=>auth}))
419
+ unless File.file?(template_path(template_opts))
420
+ template_opts[:path] = File.join(File.dirname(__FILE__), '../../../../templates', "#{page}.str")
421
+ end
422
+ send(meth, template_opts)
423
+ end
424
+ end
425
+ end
426
+ end
427
+ end
428
+ end