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,108 @@
1
+ # frozen-string-literal: true
2
+
3
+ module Rodauth
4
+ AccountExpiration = Feature.define(:account_expiration) do
5
+ error_flash "You cannot log into this account as it has expired"
6
+ redirect
7
+ after
8
+
9
+ auth_value_method :account_activity_expired_column, :expired_at
10
+ auth_value_method :account_activity_id_column, :id
11
+ auth_value_method :account_activity_last_activity_column, :last_activity_at
12
+ auth_value_method :account_activity_last_login_column, :last_login_at
13
+ auth_value_method :account_activity_table, :account_activity_times
14
+ auth_value_method :expire_account_after, 180*86400
15
+ auth_value_method :expire_account_on_last_activity?, false
16
+
17
+ auth_methods(
18
+ :account_expired?,
19
+ :account_expired_at,
20
+ :last_account_activity_at,
21
+ :last_account_login_at,
22
+ :set_expired,
23
+ :update_last_activity,
24
+ :update_last_login
25
+ )
26
+
27
+ def last_account_activity_at
28
+ get_activity_timestamp(session_value, account_activity_last_activity_column)
29
+ end
30
+
31
+ def last_account_login_at
32
+ get_activity_timestamp(session_value, account_activity_last_login_column)
33
+ end
34
+
35
+ def account_expired_at
36
+ get_activity_timestamp(account_id, account_activity_expired_column)
37
+ end
38
+
39
+ def update_last_login
40
+ update_activity(account_id, account_activity_last_login_column, account_activity_last_activity_column)
41
+ end
42
+
43
+ def update_last_activity
44
+ if session_value
45
+ update_activity(session_value, account_activity_last_activity_column)
46
+ end
47
+ end
48
+
49
+ def set_expired
50
+ update_activity(account_id, account_activity_expired_column)
51
+ after_account_expiration
52
+ end
53
+
54
+ def account_expired?
55
+ columns = [account_activity_last_activity_column, account_activity_last_login_column, account_activity_expired_column]
56
+ last_activity, last_login, expired = account_activity_ds(account_id).get(columns)
57
+ return true if expired
58
+ timestamp = convert_timestamp(expire_account_on_last_activity? ? last_activity : last_login)
59
+ return false unless timestamp
60
+ timestamp < Time.now - expire_account_after
61
+ end
62
+
63
+ def check_account_expiration
64
+ if account_expired?
65
+ set_expired unless account_expired_at
66
+ set_redirect_error_flash account_expiration_error_flash
67
+ redirect account_expiration_redirect
68
+ end
69
+ update_last_login
70
+ end
71
+
72
+ private
73
+
74
+ def after_close_account
75
+ super if defined?(super)
76
+ account_activity_ds(account_id).delete
77
+ end
78
+
79
+ def update_session
80
+ check_account_expiration
81
+ super
82
+ end
83
+
84
+ def account_activity_ds(account_id)
85
+ db[account_activity_table].
86
+ where(account_activity_id_column=>account_id)
87
+ end
88
+
89
+ def get_activity_timestamp(account_id, column)
90
+ convert_timestamp(account_activity_ds(account_id).get(column))
91
+ end
92
+
93
+ def update_activity(account_id, *columns)
94
+ ds = account_activity_ds(account_id)
95
+ hash = {}
96
+ columns.each do |c|
97
+ hash[c] = Sequel::CURRENT_TIMESTAMP
98
+ end
99
+ if ds.update(hash) == 0
100
+ hash[account_activity_id_column] = account_id
101
+ hash[account_activity_last_activity_column] ||= Sequel::CURRENT_TIMESTAMP
102
+ hash[account_activity_last_login_column] ||= Sequel::CURRENT_TIMESTAMP
103
+ # It is safe to ignore uniqueness violations here, as a concurrent insert would also use current timestamps.
104
+ ignore_uniqueness_violation{ds.insert(hash)}
105
+ end
106
+ end
107
+ end
108
+ end
@@ -0,0 +1,479 @@
1
+ # frozen-string-literal: true
2
+
3
+ module Rodauth
4
+ Base = Feature.define(:base) do
5
+ before 'rodauth'
6
+
7
+ error_flash "Please login to continue", 'require_login'
8
+
9
+ auth_value_method :account_id_column, :id
10
+ auth_value_method :account_open_status_value, 2
11
+ auth_value_method :account_password_hash_column, nil
12
+ auth_value_method :account_select, nil
13
+ auth_value_method :account_status_column, :status_id
14
+ auth_value_method :account_unverified_status_value, 1
15
+ auth_value_method :accounts_table, :accounts
16
+ auth_value_method :default_redirect, '/'
17
+ auth_value_method :invalid_password_message, "invalid password"
18
+ auth_value_method :login_column, :email
19
+ auth_value_method :password_hash_id_column, :id
20
+ auth_value_method :password_hash_column, :password_hash
21
+ auth_value_method :password_hash_table, :account_password_hashes
22
+ auth_value_method :no_matching_login_message, "no matching login"
23
+ auth_value_method :login_param, 'login'
24
+ auth_value_method :login_label, 'Login'
25
+ auth_value_method :password_label, 'Password'
26
+ auth_value_method :password_param, 'password'
27
+ auth_value_method :modifications_require_password?, true
28
+ auth_value_method :session_key, :account_id
29
+ auth_value_method :prefix, ''
30
+ auth_value_method :require_bcrypt?, true
31
+ auth_value_method :skip_status_checks?, true
32
+ auth_value_method :title_instance_variable, nil
33
+ auth_value_method :unverified_account_message, "unverified account, please verify account before logging in"
34
+
35
+ redirect(:require_login){"#{prefix}/login"}
36
+
37
+ auth_value_methods(
38
+ :db,
39
+ :require_login_redirect,
40
+ :set_deadline_values?,
41
+ :use_date_arithmetic?,
42
+ :use_database_authentication_functions?
43
+ )
44
+
45
+ auth_methods(
46
+ :account_id,
47
+ :account_session_value,
48
+ :already_logged_in,
49
+ :authenticated?,
50
+ :clear_session,
51
+ :csrf_tag,
52
+ :function_name,
53
+ :logged_in?,
54
+ :login_required,
55
+ :open_account?,
56
+ :password_match?,
57
+ :random_key,
58
+ :redirect,
59
+ :session_value,
60
+ :set_error_flash,
61
+ :set_notice_flash,
62
+ :set_notice_now_flash,
63
+ :set_redirect_error_flash,
64
+ :set_title,
65
+ :unverified_account_message,
66
+ :update_session
67
+ )
68
+
69
+ auth_private_methods(
70
+ :account_from_login,
71
+ :account_from_session
72
+ )
73
+
74
+ configuration_module_eval do
75
+ def auth_class_eval(&block)
76
+ auth.class_eval(&block)
77
+ end
78
+
79
+ def account_model(model)
80
+ warn "account_model is deprecated, use db and accounts_table settings"
81
+ db model.db
82
+ accounts_table model.table_name
83
+ account_select model.dataset.opts[:select]
84
+ end
85
+ end
86
+
87
+ attr_reader :scope
88
+ attr_reader :account
89
+
90
+ def initialize(scope)
91
+ @scope = scope
92
+ end
93
+
94
+ def features
95
+ self.class.features
96
+ end
97
+
98
+ def request
99
+ scope.request
100
+ end
101
+
102
+ def response
103
+ scope.response
104
+ end
105
+
106
+ def session
107
+ scope.session
108
+ end
109
+
110
+ def flash
111
+ scope.flash
112
+ end
113
+
114
+ def route!
115
+ if meth = self.class.route_hash[request.remaining_path]
116
+ send(meth)
117
+ end
118
+
119
+ nil
120
+ end
121
+
122
+ def set_field_error(field, error)
123
+ (@field_errors ||= {})[field] = error
124
+ end
125
+
126
+ def field_error(field)
127
+ return nil unless @field_errors
128
+ @field_errors[field]
129
+ end
130
+
131
+ def account_id
132
+ account[account_id_column]
133
+ end
134
+ alias account_session_value account_id
135
+
136
+ def session_value
137
+ session[session_key]
138
+ end
139
+ alias logged_in? session_value
140
+
141
+ def account_from_login(login)
142
+ @account = _account_from_login(login)
143
+ end
144
+
145
+ def open_account?
146
+ skip_status_checks? || account[account_status_column] == account_open_status_value
147
+ end
148
+
149
+ def db
150
+ Sequel::DATABASES.first
151
+ end
152
+
153
+ # If the account_password_hash_column is set, the password hash is verified in
154
+ # ruby, it will not use a database function to do so, it will check the password
155
+ # hash using bcrypt.
156
+ def account_password_hash_column
157
+ nil
158
+ end
159
+
160
+ def check_already_logged_in
161
+ already_logged_in if logged_in?
162
+ end
163
+
164
+ def already_logged_in
165
+ nil
166
+ end
167
+
168
+ def clear_session
169
+ session.clear
170
+ end
171
+
172
+ def login_required
173
+ set_redirect_error_flash require_login_error_flash
174
+ redirect require_login_redirect
175
+ end
176
+
177
+ def set_title(title)
178
+ if title_instance_variable
179
+ scope.instance_variable_set(title_instance_variable, title)
180
+ end
181
+ end
182
+
183
+ def set_error_flash(message)
184
+ flash.now[:error] = message
185
+ end
186
+
187
+ def set_redirect_error_flash(message)
188
+ flash[:error] = message
189
+ end
190
+
191
+ def set_notice_flash(message)
192
+ flash[:notice] = message
193
+ end
194
+
195
+ def set_notice_now_flash(message)
196
+ flash.now[:notice] = message
197
+ end
198
+
199
+ def require_login
200
+ login_required unless logged_in?
201
+ end
202
+
203
+ def authenticated?
204
+ logged_in?
205
+ end
206
+
207
+ def require_authentication
208
+ require_login
209
+ end
210
+
211
+ def account_initial_status_value
212
+ account_open_status_value
213
+ end
214
+
215
+ def account_from_session
216
+ @account = _account_from_session
217
+ end
218
+
219
+ def csrf_tag
220
+ scope.csrf_tag if scope.respond_to?(:csrf_tag)
221
+ end
222
+
223
+ def button(value, opts={})
224
+ opts = {:locals=>{:value=>value, :opts=>opts}}
225
+ opts[:path] = template_path('button')
226
+ scope.render(opts)
227
+ end
228
+
229
+ def view(page, title)
230
+ set_title(title)
231
+ _view(:view, page)
232
+ end
233
+
234
+ def render(page)
235
+ _view(:render, page)
236
+ end
237
+
238
+ def post_configure
239
+ require 'bcrypt' if require_bcrypt?
240
+ db.extension :date_arithmetic if use_date_arithmetic?
241
+ route_hash= {}
242
+ self.class.routes.each do |meth|
243
+ route_hash["/#{send("#{meth.to_s.sub(/\Ahandle_/, '')}_route")}"] = meth
244
+ end
245
+ self.class.route_hash = route_hash.freeze
246
+ end
247
+
248
+ def password_match?(password)
249
+ if account_password_hash_column
250
+ BCrypt::Password.new(account[account_password_hash_column]) == password
251
+ elsif use_database_authentication_functions?
252
+ id = account_id
253
+ if salt = db.get(Sequel.function(function_name(:rodauth_get_salt), id))
254
+ hash = BCrypt::Engine.hash_secret(password, salt)
255
+ db.get(Sequel.function(function_name(:rodauth_valid_password_hash), id, hash))
256
+ end
257
+ else
258
+ # :nocov:
259
+ if hash = password_hash_ds.get(password_hash_column)
260
+ BCrypt::Password.new(hash) == password
261
+ end
262
+ # :nocov:
263
+ end
264
+ end
265
+
266
+ private
267
+
268
+ def update_session
269
+ clear_session
270
+ session[session_key] = account_session_value
271
+ end
272
+
273
+ # Return a string for the parameter name. This will be an empty
274
+ # string if the parameter doesn't exist.
275
+ def param(key)
276
+ param_or_nil(key).to_s
277
+ end
278
+
279
+ # Return a string for the parameter name, or nil if there is no
280
+ # parameter with that name.
281
+ def param_or_nil(key)
282
+ value = request.params[key]
283
+ value.to_s unless value.nil?
284
+ end
285
+
286
+ def redirect(path)
287
+ request.redirect(path)
288
+ end
289
+
290
+ def transaction(opts={}, &block)
291
+ db.transaction(opts, &block)
292
+ end
293
+
294
+ if RUBY_VERSION >= '1.9'
295
+ def random_key
296
+ SecureRandom.urlsafe_base64(32)
297
+ end
298
+ else
299
+ # :nocov:
300
+ def random_key
301
+ SecureRandom.hex(32)
302
+ end
303
+ # :nocov:
304
+ end
305
+
306
+ def timing_safe_eql?(provided, actual)
307
+ provided = provided.to_s
308
+ Rack::Utils.secure_compare(provided.ljust(actual.length), actual) && provided.length == actual.length
309
+ end
310
+
311
+ def require_account
312
+ require_authentication
313
+ require_account_session
314
+ end
315
+
316
+ def require_account_session
317
+ unless account_from_session
318
+ clear_session
319
+ login_required
320
+ end
321
+ end
322
+
323
+ def catch_error(&block)
324
+ catch(:rodauth_error, &block)
325
+ end
326
+
327
+ def throw_error(field, error)
328
+ set_field_error(field, error)
329
+ throw :rodauth_error
330
+ end
331
+
332
+ def use_date_arithmetic?
333
+ set_deadline_values?
334
+ end
335
+
336
+ def set_deadline_values?
337
+ db.database_type == :mysql
338
+ end
339
+
340
+ def use_database_authentication_functions?
341
+ case db.database_type
342
+ when :postgres, :mysql, :mssql
343
+ true
344
+ else
345
+ # :nocov:
346
+ false
347
+ # :nocov:
348
+ end
349
+ end
350
+
351
+ def function_name(name)
352
+ if db.database_type == :mssql
353
+ # :nocov:
354
+ "dbo.#{name}"
355
+ # :nocov:
356
+ else
357
+ name
358
+ end
359
+ end
360
+
361
+ def _account_from_login(login)
362
+ ds = db[accounts_table].where(login_column=>login)
363
+ ds = ds.select(*account_select) if account_select
364
+ ds = ds.where(account_status_column=>[account_unverified_status_value, account_open_status_value]) unless skip_status_checks?
365
+ ds.first
366
+ end
367
+
368
+ def _account_from_session
369
+ ds = account_ds(session_value)
370
+ ds = ds.where(account_session_status_filter) unless skip_status_checks?
371
+ ds.first
372
+ end
373
+
374
+ def account_session_status_filter
375
+ {account_status_column=>account_open_status_value}
376
+ end
377
+
378
+ def template_path(page)
379
+ File.join(File.dirname(__FILE__), '../../../templates', "#{page}.str")
380
+ end
381
+
382
+ def account_ds(id=account_id)
383
+ raise ArgumentError, "invalid account id passed to account_ds" unless id
384
+ ds = db[accounts_table].where(account_id_column=>id)
385
+ ds = ds.select(*account_select) if account_select
386
+ ds
387
+ end
388
+
389
+ def password_hash_ds
390
+ db[password_hash_table].where(password_hash_id_column=>account_id)
391
+ end
392
+
393
+ # This is needed for jdbc/sqlite, which returns timestamp columns as strings
394
+ def convert_timestamp(timestamp)
395
+ timestamp = db.to_application_timestamp(timestamp) if timestamp.is_a?(String)
396
+ timestamp
397
+ end
398
+
399
+ # This is used to avoid race conditions when using the pattern of inserting when
400
+ # an update affects no rows. In such cases, if a row is inserted between the
401
+ # update and the insert, the insert will fail with a uniqueness error, but
402
+ # retrying will work. It is possible for it to fail again, but only if the row
403
+ # is deleted before the update and readded before the insert, which is very
404
+ # unlikely to happen. In such cases, raising an exception is acceptable.
405
+ def retry_on_uniqueness_violation(&block)
406
+ if raises_uniqueness_violation?(&block)
407
+ yield
408
+ end
409
+ end
410
+
411
+ # In cases where retrying on uniqueness violations cannot work, this will detect
412
+ # whether a uniqueness violation is raised by the block and return the exception if so.
413
+ # This method should be used if you don't care about the exception itself.
414
+ def raises_uniqueness_violation?(&block)
415
+ transaction(:savepoint=>:only, &block)
416
+ false
417
+ rescue unique_constraint_violation_class => e
418
+ e
419
+ end
420
+
421
+ # Work around jdbc/sqlite issue where it only raises ConstraintViolation and not
422
+ # UniqueConstraintViolation.
423
+ def unique_constraint_violation_class
424
+ if db.adapter_scheme == :jdbc && db.database_type == :sqlite
425
+ # :nocov:
426
+ Sequel::ConstraintViolation
427
+ # :nocov:
428
+ else
429
+ Sequel::UniqueConstraintViolation
430
+ end
431
+ end
432
+
433
+ # If you would like to operate/reraise the exception, this alias makes more sense.
434
+ alias raised_uniqueness_violation raises_uniqueness_violation?
435
+
436
+ # If you just want to ignore uniqueness violations, this alias makes more sense.
437
+ alias ignore_uniqueness_violation raises_uniqueness_violation?
438
+
439
+ # This is needed on MySQL, which doesn't support non constant defaults other than
440
+ # CURRENT_TIMESTAMP.
441
+ def set_deadline_value(hash, column, interval)
442
+ if set_deadline_values?
443
+ # :nocov:
444
+ hash[column] = Sequel.date_add(Sequel::CURRENT_TIMESTAMP, interval)
445
+ # :nocov:
446
+ end
447
+ end
448
+
449
+ def set_session_value(key, value)
450
+ session[key] = value
451
+ end
452
+
453
+ def update_hash_ds(hash, ds, values)
454
+ num = ds.update(values)
455
+ if num == 1
456
+ values.each do |k, v|
457
+ account[k] = v == Sequel::CURRENT_TIMESTAMP ? Time.now : v
458
+ end
459
+ end
460
+ num
461
+ end
462
+
463
+ def update_account(values, ds=account_ds)
464
+ update_hash_ds(account, ds, values)
465
+ end
466
+
467
+ def _view(meth, page)
468
+ auth = self
469
+ auth_template_path = template_path(page)
470
+ scope.instance_exec do
471
+ template_opts = find_template(parse_template_opts(page, :locals=>{:rodauth=>auth}))
472
+ unless File.file?(template_path(template_opts))
473
+ template_opts[:path] = auth_template_path
474
+ end
475
+ send(meth, template_opts)
476
+ end
477
+ end
478
+ end
479
+ end