rodauth 0.9.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/CHANGELOG +3 -0
- data/MIT-LICENSE +18 -0
- data/README.rdoc +484 -0
- data/Rakefile +91 -0
- data/lib/roda/plugins/rodauth.rb +265 -0
- data/lib/roda/plugins/rodauth/base.rb +428 -0
- data/lib/roda/plugins/rodauth/change_login.rb +48 -0
- data/lib/roda/plugins/rodauth/change_password.rb +42 -0
- data/lib/roda/plugins/rodauth/close_account.rb +42 -0
- data/lib/roda/plugins/rodauth/create_account.rb +92 -0
- data/lib/roda/plugins/rodauth/lockout.rb +292 -0
- data/lib/roda/plugins/rodauth/login.rb +77 -0
- data/lib/roda/plugins/rodauth/logout.rb +36 -0
- data/lib/roda/plugins/rodauth/remember.rb +226 -0
- data/lib/roda/plugins/rodauth/reset_password.rb +205 -0
- data/lib/roda/plugins/rodauth/verify_account.rb +228 -0
- data/spec/migrate/001_tables.rb +64 -0
- data/spec/migrate_password/001_tables.rb +38 -0
- data/spec/rodauth_spec.rb +1114 -0
- data/spec/views/layout.str +11 -0
- data/spec/views/login.str +21 -0
- data/templates/change-login.str +22 -0
- data/templates/change-password.str +21 -0
- data/templates/close-account.str +9 -0
- data/templates/confirm-password.str +16 -0
- data/templates/create-account.str +33 -0
- data/templates/login.str +25 -0
- data/templates/logout.str +9 -0
- data/templates/remember.str +28 -0
- data/templates/reset-password-email.str +5 -0
- data/templates/reset-password-request.str +7 -0
- data/templates/reset-password.str +23 -0
- data/templates/unlock-account-email.str +5 -0
- data/templates/unlock-account-request.str +11 -0
- data/templates/unlock-account.str +11 -0
- data/templates/verify-account-email.str +4 -0
- data/templates/verify-account-resend.str +7 -0
- data/templates/verify-account.str +11 -0
- metadata +227 -0
data/Rakefile
ADDED
@@ -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
|