rodauth 2.25.0 → 2.26.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG +16 -0
- data/README.rdoc +86 -86
- data/doc/argon2.rdoc +1 -0
- data/doc/base.rdoc +2 -0
- data/doc/password_pepper.rdoc +8 -0
- data/doc/release_notes/2.26.0.txt +45 -0
- data/lib/rodauth/features/argon2.rb +11 -2
- data/lib/rodauth/features/base.rb +30 -6
- data/lib/rodauth/features/internal_request.rb +1 -0
- data/lib/rodauth/features/login.rb +6 -1
- data/lib/rodauth/features/otp.rb +1 -1
- data/lib/rodauth/features/two_factor_base.rb +18 -4
- data/lib/rodauth/version.rb +1 -1
- data/lib/rodauth.rb +7 -2
- data/templates/login-form-footer.str +1 -1
- data/templates/two-factor-auth.str +1 -1
- data/templates/two-factor-manage.str +2 -2
- metadata +7 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4a071d4186e38bc942b2eeff4eb684dc5600f9b56e182b77bbdb1db1ecb0b26e
|
4
|
+
data.tar.gz: 828ee9405e19f0f368101ce7fa00c5db997dfe32137a5da36f842ac1ac350d03
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8813675142dc50ec6f2383a76e5f10a9fdc8071ff3be1f328cd44d51e910f2908546b3ad7db1b93f0f54e24318ab1b29c18fc8db5b978357212b93a11624cab6
|
7
|
+
data.tar.gz: b502557f7fffd20431601ecf85dde35451823eac85e8bd70334f6fc619cf03ea725439dffc8e362fc28a530a12520e74b1b681f4640477a0a95304396cc04422
|
data/CHANGELOG
CHANGED
@@ -1,3 +1,19 @@
|
|
1
|
+
=== 2.26.0 (2022-10-21)
|
2
|
+
|
3
|
+
* Raise a more informative error when using a feature requiring hmac_secret but not setting hmac_secret (janko) (#271)
|
4
|
+
|
5
|
+
* Limit parameter bytesize to 1024 by default, override with max_param_bytesize configuration method (jeremyevans)
|
6
|
+
|
7
|
+
* Skip displaying links for disabled routes (janko) (#269)
|
8
|
+
|
9
|
+
* Do not prefix flash keys with the session key prefix (jeremyevans) (#266)
|
10
|
+
|
11
|
+
* Set configuration_name correctly for internal request classes (janko) (#265)
|
12
|
+
|
13
|
+
* Add argon2_secret configuration method to the argon2 feature to specify the secret/pepper used for argon2 password hashes (janko) (#264)
|
14
|
+
|
15
|
+
* Use white background instead of transparent background for QR code in otp feature (jeremyevans) (#256)
|
16
|
+
|
1
17
|
=== 2.25.0 (2022-06-22)
|
2
18
|
|
3
19
|
* Support disabling routes by passing nil/false to *_route methods (janko) (#245)
|
data/README.rdoc
CHANGED
@@ -80,7 +80,7 @@ features in use. These are development dependencies instead of
|
|
80
80
|
runtime dependencies in the gem as it is possible to run without them:
|
81
81
|
|
82
82
|
tilt :: Used by all features unless in JSON API only mode.
|
83
|
-
rack_csrf :: Used for CSRF support if the :
|
83
|
+
rack_csrf :: Used for CSRF support if the <tt>csrf: :rack_csrf</tt> plugin
|
84
84
|
option is given (the default is to use Roda's route_csrf
|
85
85
|
plugin, as that allows for more secure request-specific
|
86
86
|
tokens).
|
@@ -336,18 +336,18 @@ When running the migration for the +ph+ user you'll need to modify a couple
|
|
336
336
|
things for the schema changes:
|
337
337
|
|
338
338
|
create_table(:account_password_hashes) do
|
339
|
-
foreign_key :id, Sequel[:${DATABASE_NAME}][:accounts], :
|
340
|
-
String :password_hash, :
|
339
|
+
foreign_key :id, Sequel[:${DATABASE_NAME}][:accounts], primary_key: true, type: :Bignum
|
340
|
+
String :password_hash, null: false
|
341
341
|
end
|
342
|
-
Rodauth.create_database_authentication_functions(self, :
|
342
|
+
Rodauth.create_database_authentication_functions(self, table_name: Sequel[:${DATABASE_NAME}_password][:account_password_hashes])
|
343
343
|
|
344
344
|
# if using the disallow_password_reuse feature:
|
345
345
|
create_table(:account_previous_password_hashes) do
|
346
|
-
primary_key :id, :
|
347
|
-
foreign_key :account_id, Sequel[:${DATABASE_NAME}][:accounts], :
|
348
|
-
String :password_hash, :
|
346
|
+
primary_key :id, type: :Bignum
|
347
|
+
foreign_key :account_id, Sequel[:${DATABASE_NAME}][:accounts], type: :Bignum
|
348
|
+
String :password_hash, null: false
|
349
349
|
end
|
350
|
-
Rodauth.create_database_previous_password_check_functions(self, :
|
350
|
+
Rodauth.create_database_previous_password_check_functions(self, table_name: Sequel[:${DATABASE_NAME}_password][:account_previous_password_hashes])
|
351
351
|
|
352
352
|
You'll also need to use the following Rodauth configuration methods so that the
|
353
353
|
app account calls functions in a separate schema:
|
@@ -412,33 +412,33 @@ Note that these migrations require Sequel 4.35.0+.
|
|
412
412
|
|
413
413
|
# Used by the account verification and close account features
|
414
414
|
create_table(:account_statuses) do
|
415
|
-
Integer :id, :
|
416
|
-
String :name, :
|
415
|
+
Integer :id, primary_key: true
|
416
|
+
String :name, null: false, unique: true
|
417
417
|
end
|
418
418
|
from(:account_statuses).import([:id, :name], [[1, 'Unverified'], [2, 'Verified'], [3, 'Closed']])
|
419
419
|
|
420
420
|
db = self
|
421
421
|
create_table(:accounts) do
|
422
|
-
primary_key :id, :
|
423
|
-
foreign_key :status_id, :account_statuses, :
|
422
|
+
primary_key :id, type: :Bignum
|
423
|
+
foreign_key :status_id, :account_statuses, null: false, default: 1
|
424
424
|
if db.database_type == :postgres
|
425
|
-
citext :email, :
|
426
|
-
constraint :valid_email, :
|
425
|
+
citext :email, null: false
|
426
|
+
constraint :valid_email, email: /^[^,;@ \r\n]+@[^,@; \r\n]+\.[^,@; \r\n]+$/
|
427
427
|
else
|
428
|
-
String :email, :
|
428
|
+
String :email, null: false
|
429
429
|
end
|
430
430
|
if db.supports_partial_indexes?
|
431
|
-
index :email, :
|
431
|
+
index :email, unique: true, where: {status_id: [1, 2]}
|
432
432
|
else
|
433
|
-
index :email, :
|
433
|
+
index :email, unique: true
|
434
434
|
end
|
435
435
|
end
|
436
436
|
|
437
437
|
deadline_opts = proc do |days|
|
438
438
|
if database_type == :mysql
|
439
|
-
{:
|
439
|
+
{null: false}
|
440
440
|
else
|
441
|
-
{:
|
441
|
+
{null: false, default: Sequel.date_add(Sequel::CURRENT_TIMESTAMP, days: days)}
|
442
442
|
end
|
443
443
|
end
|
444
444
|
|
@@ -452,140 +452,140 @@ Note that these migrations require Sequel 4.35.0+.
|
|
452
452
|
String
|
453
453
|
end
|
454
454
|
create_table(:account_authentication_audit_logs) do
|
455
|
-
primary_key :id, :
|
456
|
-
foreign_key :account_id, :accounts, :
|
457
|
-
DateTime :at, :
|
458
|
-
String :message, :
|
455
|
+
primary_key :id, type: :Bignum
|
456
|
+
foreign_key :account_id, :accounts, null: false, type: :Bignum
|
457
|
+
DateTime :at, null: false, default: Sequel::CURRENT_TIMESTAMP
|
458
|
+
String :message, null: false
|
459
459
|
column :metadata, json_type
|
460
|
-
index [:account_id, :at], :
|
461
|
-
index :at, :
|
460
|
+
index [:account_id, :at], name: :audit_account_at_idx
|
461
|
+
index :at, name: :audit_at_idx
|
462
462
|
end
|
463
463
|
|
464
464
|
# Used by the password reset feature
|
465
465
|
create_table(:account_password_reset_keys) do
|
466
|
-
foreign_key :id, :accounts, :
|
467
|
-
String :key, :
|
466
|
+
foreign_key :id, :accounts, primary_key: true, type: :Bignum
|
467
|
+
String :key, null: false
|
468
468
|
DateTime :deadline, deadline_opts[1]
|
469
|
-
DateTime :email_last_sent, :
|
469
|
+
DateTime :email_last_sent, null: false, default: Sequel::CURRENT_TIMESTAMP
|
470
470
|
end
|
471
471
|
|
472
472
|
# Used by the jwt refresh feature
|
473
473
|
create_table(:account_jwt_refresh_keys) do
|
474
|
-
primary_key :id, :
|
475
|
-
foreign_key :account_id, :accounts, :
|
476
|
-
String :key, :
|
474
|
+
primary_key :id, type: :Bignum
|
475
|
+
foreign_key :account_id, :accounts, null: false, type: :Bignum
|
476
|
+
String :key, null: false
|
477
477
|
DateTime :deadline, deadline_opts[1]
|
478
|
-
index :account_id, :
|
478
|
+
index :account_id, name: :account_jwt_rk_account_id_idx
|
479
479
|
end
|
480
480
|
|
481
481
|
# Used by the account verification feature
|
482
482
|
create_table(:account_verification_keys) do
|
483
|
-
foreign_key :id, :accounts, :
|
484
|
-
String :key, :
|
485
|
-
DateTime :requested_at, :
|
486
|
-
DateTime :email_last_sent, :
|
483
|
+
foreign_key :id, :accounts, primary_key: true, type: :Bignum
|
484
|
+
String :key, null: false
|
485
|
+
DateTime :requested_at, null: false, default: Sequel::CURRENT_TIMESTAMP
|
486
|
+
DateTime :email_last_sent, null: false, default: Sequel::CURRENT_TIMESTAMP
|
487
487
|
end
|
488
488
|
|
489
489
|
# Used by the verify login change feature
|
490
490
|
create_table(:account_login_change_keys) do
|
491
|
-
foreign_key :id, :accounts, :
|
492
|
-
String :key, :
|
493
|
-
String :login, :
|
491
|
+
foreign_key :id, :accounts, primary_key: true, type: :Bignum
|
492
|
+
String :key, null: false
|
493
|
+
String :login, null: false
|
494
494
|
DateTime :deadline, deadline_opts[1]
|
495
495
|
end
|
496
496
|
|
497
497
|
# Used by the remember me feature
|
498
498
|
create_table(:account_remember_keys) do
|
499
|
-
foreign_key :id, :accounts, :
|
500
|
-
String :key, :
|
499
|
+
foreign_key :id, :accounts, primary_key: true, type: :Bignum
|
500
|
+
String :key, null: false
|
501
501
|
DateTime :deadline, deadline_opts[14]
|
502
502
|
end
|
503
503
|
|
504
504
|
# Used by the lockout feature
|
505
505
|
create_table(:account_login_failures) do
|
506
|
-
foreign_key :id, :accounts, :
|
507
|
-
Integer :number, :
|
506
|
+
foreign_key :id, :accounts, primary_key: true, type: :Bignum
|
507
|
+
Integer :number, null: false, default: 1
|
508
508
|
end
|
509
509
|
create_table(:account_lockouts) do
|
510
|
-
foreign_key :id, :accounts, :
|
511
|
-
String :key, :
|
510
|
+
foreign_key :id, :accounts, primary_key: true, type: :Bignum
|
511
|
+
String :key, null: false
|
512
512
|
DateTime :deadline, deadline_opts[1]
|
513
513
|
DateTime :email_last_sent
|
514
514
|
end
|
515
515
|
|
516
516
|
# Used by the email auth feature
|
517
517
|
create_table(:account_email_auth_keys) do
|
518
|
-
foreign_key :id, :accounts, :
|
519
|
-
String :key, :
|
518
|
+
foreign_key :id, :accounts, primary_key: true, type: :Bignum
|
519
|
+
String :key, null: false
|
520
520
|
DateTime :deadline, deadline_opts[1]
|
521
|
-
DateTime :email_last_sent, :
|
521
|
+
DateTime :email_last_sent, null: false, default: Sequel::CURRENT_TIMESTAMP
|
522
522
|
end
|
523
523
|
|
524
524
|
# Used by the password expiration feature
|
525
525
|
create_table(:account_password_change_times) do
|
526
|
-
foreign_key :id, :accounts, :
|
527
|
-
DateTime :changed_at, :
|
526
|
+
foreign_key :id, :accounts, primary_key: true, type: :Bignum
|
527
|
+
DateTime :changed_at, null: false, default: Sequel::CURRENT_TIMESTAMP
|
528
528
|
end
|
529
529
|
|
530
530
|
# Used by the account expiration feature
|
531
531
|
create_table(:account_activity_times) do
|
532
|
-
foreign_key :id, :accounts, :
|
533
|
-
DateTime :last_activity_at, :
|
534
|
-
DateTime :last_login_at, :
|
532
|
+
foreign_key :id, :accounts, primary_key: true, type: :Bignum
|
533
|
+
DateTime :last_activity_at, null: false
|
534
|
+
DateTime :last_login_at, null: false
|
535
535
|
DateTime :expired_at
|
536
536
|
end
|
537
537
|
|
538
538
|
# Used by the single session feature
|
539
539
|
create_table(:account_session_keys) do
|
540
|
-
foreign_key :id, :accounts, :
|
541
|
-
String :key, :
|
540
|
+
foreign_key :id, :accounts, primary_key: true, type: :Bignum
|
541
|
+
String :key, null: false
|
542
542
|
end
|
543
543
|
|
544
544
|
# Used by the active sessions feature
|
545
545
|
create_table(:account_active_session_keys) do
|
546
|
-
foreign_key :account_id, :accounts, :
|
546
|
+
foreign_key :account_id, :accounts, type: :Bignum
|
547
547
|
String :session_id
|
548
|
-
Time :created_at, :
|
549
|
-
Time :last_use, :
|
548
|
+
Time :created_at, null: false, default: Sequel::CURRENT_TIMESTAMP
|
549
|
+
Time :last_use, null: false, default: Sequel::CURRENT_TIMESTAMP
|
550
550
|
primary_key [:account_id, :session_id]
|
551
551
|
end
|
552
552
|
|
553
553
|
# Used by the webauthn feature
|
554
554
|
create_table(:account_webauthn_user_ids) do
|
555
|
-
foreign_key :id, :accounts, :
|
556
|
-
String :webauthn_id, :
|
555
|
+
foreign_key :id, :accounts, primary_key: true, type: :Bignum
|
556
|
+
String :webauthn_id, null: false
|
557
557
|
end
|
558
558
|
create_table(:account_webauthn_keys) do
|
559
|
-
foreign_key :account_id, :accounts, :
|
559
|
+
foreign_key :account_id, :accounts, type: :Bignum
|
560
560
|
String :webauthn_id
|
561
|
-
String :public_key, :
|
562
|
-
Integer :sign_count, :
|
563
|
-
Time :last_use, :
|
561
|
+
String :public_key, null: false
|
562
|
+
Integer :sign_count, null: false
|
563
|
+
Time :last_use, null: false, default: Sequel::CURRENT_TIMESTAMP
|
564
564
|
primary_key [:account_id, :webauthn_id]
|
565
565
|
end
|
566
566
|
|
567
567
|
# Used by the otp feature
|
568
568
|
create_table(:account_otp_keys) do
|
569
|
-
foreign_key :id, :accounts, :
|
570
|
-
String :key, :
|
571
|
-
Integer :num_failures, :
|
572
|
-
Time :last_use, :
|
569
|
+
foreign_key :id, :accounts, primary_key: true, type: :Bignum
|
570
|
+
String :key, null: false
|
571
|
+
Integer :num_failures, null: false, default: 0
|
572
|
+
Time :last_use, null: false, default: Sequel::CURRENT_TIMESTAMP
|
573
573
|
end
|
574
574
|
|
575
575
|
# Used by the recovery codes feature
|
576
576
|
create_table(:account_recovery_codes) do
|
577
|
-
foreign_key :id, :accounts, :
|
577
|
+
foreign_key :id, :accounts, type: :Bignum
|
578
578
|
String :code
|
579
579
|
primary_key [:id, :code]
|
580
580
|
end
|
581
581
|
|
582
582
|
# Used by the sms codes feature
|
583
583
|
create_table(:account_sms_codes) do
|
584
|
-
foreign_key :id, :accounts, :
|
585
|
-
String :phone_number, :
|
584
|
+
foreign_key :id, :accounts, primary_key: true, type: :Bignum
|
585
|
+
String :phone_number, null: false
|
586
586
|
Integer :num_failures
|
587
587
|
String :code
|
588
|
-
DateTime :code_issued_at, :
|
588
|
+
DateTime :code_issued_at, null: false, default: Sequel::CURRENT_TIMESTAMP
|
589
589
|
end
|
590
590
|
|
591
591
|
case database_type
|
@@ -652,8 +652,8 @@ Second migration, run using the +ph+ account:
|
|
652
652
|
Sequel.migration do
|
653
653
|
up do
|
654
654
|
create_table(:account_password_hashes) do
|
655
|
-
foreign_key :id, :accounts, :
|
656
|
-
String :password_hash, :
|
655
|
+
foreign_key :id, :accounts, primary_key: true, type: :Bignum
|
656
|
+
String :password_hash, null: false
|
657
657
|
end
|
658
658
|
Rodauth.create_database_authentication_functions(self)
|
659
659
|
case database_type
|
@@ -682,9 +682,9 @@ Second migration, run using the +ph+ account:
|
|
682
682
|
|
683
683
|
# Used by the disallow_password_reuse feature
|
684
684
|
create_table(:account_previous_password_hashes) do
|
685
|
-
primary_key :id, :
|
686
|
-
foreign_key :account_id, :accounts, :
|
687
|
-
String :password_hash, :
|
685
|
+
primary_key :id, type: :Bignum
|
686
|
+
foreign_key :account_id, :accounts, type: :Bignum
|
687
|
+
String :password_hash, null: false
|
688
688
|
end
|
689
689
|
Rodauth.create_database_previous_password_check_functions(self)
|
690
690
|
|
@@ -725,8 +725,8 @@ To support multiple separate migration users, you can run the migration
|
|
725
725
|
for the password user using Sequel's migration API:
|
726
726
|
|
727
727
|
Sequel.extension :migration
|
728
|
-
Sequel.postgres('DATABASE_NAME', :
|
729
|
-
Sequel::Migrator.run(db, 'path/to/password_user/migrations', :
|
728
|
+
Sequel.postgres('DATABASE_NAME', user: 'PASSWORD_USER_NAME') do |db|
|
729
|
+
Sequel::Migrator.run(db, 'path/to/password_user/migrations', table: 'schema_info_password')
|
730
730
|
end
|
731
731
|
|
732
732
|
If the database is not PostgreSQL, MySQL, or Microsoft SQL Server, or you
|
@@ -1112,7 +1112,7 @@ providing a name for any alternate configuration:
|
|
1112
1112
|
|
1113
1113
|
plugin :rodauth do
|
1114
1114
|
end
|
1115
|
-
plugin :rodauth, :
|
1115
|
+
plugin :rodauth, name: :secondary do
|
1116
1116
|
end
|
1117
1117
|
|
1118
1118
|
Then in your routing code, any time you call rodauth, you can provide
|
@@ -1133,7 +1133,7 @@ alternate configurations. If you are using the remember feature in both
|
|
1133
1133
|
configurations, you may also want to set a different remember key in the
|
1134
1134
|
alternate configuration:
|
1135
1135
|
|
1136
|
-
plugin :rodauth, :
|
1136
|
+
plugin :rodauth, name: :secondary do
|
1137
1137
|
session_key_prefix "secondary_"
|
1138
1138
|
remember_cookie_key "_secondary_remember"
|
1139
1139
|
end
|
@@ -1235,7 +1235,7 @@ Facebook OAuth access token.
|
|
1235
1235
|
|
1236
1236
|
account_from_login do |access_token|
|
1237
1237
|
fb = Koala::Facebook::API.new(access_token)
|
1238
|
-
if me = fb.get_object('me', :
|
1238
|
+
if me = fb.get_object('me', fields: [:email])
|
1239
1239
|
me['email']
|
1240
1240
|
end
|
1241
1241
|
end
|
@@ -1369,7 +1369,7 @@ To add support for handling JSON responses, you can pass the +:json+
|
|
1369
1369
|
option to the plugin, and enable the JWT feature in addition to
|
1370
1370
|
other features you plan to use:
|
1371
1371
|
|
1372
|
-
plugin :rodauth, :
|
1372
|
+
plugin :rodauth, json: true do
|
1373
1373
|
enable :login, :logout, :jwt
|
1374
1374
|
end
|
1375
1375
|
|
@@ -1377,12 +1377,12 @@ If you do not want to load the HTML plugins that Rodauth usually loads
|
|
1377
1377
|
(render, csrf, flash, h), because you are building a JSON-only API,
|
1378
1378
|
pass <tt>:json => :only</tt>
|
1379
1379
|
|
1380
|
-
plugin :rodauth, :
|
1380
|
+
plugin :rodauth, json: :only do
|
1381
1381
|
enable :login, :logout, :jwt
|
1382
1382
|
end
|
1383
1383
|
|
1384
1384
|
Note that by default, the features that send email depend on the
|
1385
|
-
render plugin, so if using the <tt
|
1385
|
+
render plugin, so if using the <tt>json: :only</tt> option, you
|
1386
1386
|
either need to load the render plugin manually or you need to
|
1387
1387
|
use the necessary *_email_body configuration options to specify
|
1388
1388
|
the body of the emails.
|
@@ -1391,7 +1391,7 @@ The JWT feature enables JSON API support for all of the other features
|
|
1391
1391
|
that Rodauth ships with. If you would like JSON API access that still uses
|
1392
1392
|
rack session for storing session data, enable the JSON feature instead:
|
1393
1393
|
|
1394
|
-
plugin :rodauth, :
|
1394
|
+
plugin :rodauth, json: true do
|
1395
1395
|
enable :login, :logout, :json
|
1396
1396
|
only_json? true # if you want to only handle JSON requests
|
1397
1397
|
end
|
data/doc/argon2.rdoc
CHANGED
@@ -46,4 +46,5 @@ memory.
|
|
46
46
|
|
47
47
|
== Auth Value Methods
|
48
48
|
|
49
|
+
argon2_secret :: A secret key used as input at hashing time, folded into the value of the hash.
|
49
50
|
use_argon2? :: Whether to use the argon2 password hash algorithm for new passwords (true by default). The only reason to set this to false is if you have existing passwords using argon2 that you want to support, but want to use bcrypt for new passwords.
|
data/doc/base.rdoc
CHANGED
@@ -60,6 +60,7 @@ login_required_error_status :: The response status to return when a login is req
|
|
60
60
|
login_uses_email? :: Whether the login field uses email, used to set the type of the login field as well as the autocomplete setting.
|
61
61
|
mark_input_fields_with_autocomplete? :: Whether input fields should be marked with autocomplete attribute appropriate for the field, true by default.
|
62
62
|
mark_input_fields_with_inputmode? :: Whether input fields should be marked with inputmode attribute appropriate for the field, true by default.
|
63
|
+
max_param_bytesize :: The maximum bytesize allowed for submitted parameters, 1024 by default. Use nil for no limit.
|
63
64
|
modifications_require_password? :: Whether making changes to an account requires the user reinputing their password. True by default if the account has a password.
|
64
65
|
no_matching_login_error_status :: The response status to use when the login is not in the database, 401 by default.
|
65
66
|
no_matching_login_message :: The error message to display when the login used is not in the database.
|
@@ -101,6 +102,7 @@ logged_in? :: Whether the current session is logged in.
|
|
101
102
|
login_required :: Action to take when a login is required to access the page and the user is not logged in.
|
102
103
|
null_byte_parameter_value(key, value) :: The value to use for the parameter if the parameter includes an ASCII NUL byte ("\0"), nil by default to ignore the parameter.
|
103
104
|
open_account? :: Whether the current account is an open account (not closed or unverified).
|
105
|
+
over_max_bytesize_param_value(key, value) :: The value to use for the parameter if the parameter is over the maximum allowed bytesize, nil by default to ignore the parameter.
|
104
106
|
password_match?(password) :: Check whether the given password matches the stored password hash.
|
105
107
|
random_key :: A randomly generated string, used for creating tokens.
|
106
108
|
redirect(path) :: Redirect the request to the given path.
|
data/doc/password_pepper.rdoc
CHANGED
@@ -15,6 +15,14 @@ If your database already contains password hashes that were created without a
|
|
15
15
|
password pepper, these will get automatically updated with a password pepper
|
16
16
|
next time the user successfully enters their password.
|
17
17
|
|
18
|
+
If you're using bcrypt (default), you should set +password_maximum_bytes+ so
|
19
|
+
that password + pepper don't exceed 72 bytes. This is because bcrypt truncates
|
20
|
+
passwords longer than 72 bytes, enabling an attacker to crack the pepper if the
|
21
|
+
password bytesize is unlimited. If you're using argon2, you should probably set
|
22
|
+
+argon2_secret+ instead of using this feature.
|
23
|
+
|
24
|
+
== Pepper Rotation
|
25
|
+
|
18
26
|
You can rotate the password pepper as well, just make sure to add the previous
|
19
27
|
pepper to the +previous_password_peppers+ array. Password hashes using the old
|
20
28
|
pepper will get automatically updated on the next successful password match.
|
@@ -0,0 +1,45 @@
|
|
1
|
+
= New Features
|
2
|
+
|
3
|
+
* An argon2_secret configuration method has been added to the argon2
|
4
|
+
feature, supporting argon2's built-in password peppering.
|
5
|
+
|
6
|
+
= Other Improvements
|
7
|
+
|
8
|
+
* Links are no longer automatically displayed for routes that are
|
9
|
+
disabled by calling the *_route method with nil.
|
10
|
+
|
11
|
+
* The QR code used by the otp feature now uses a white background
|
12
|
+
instead of a transparent background, fixing issues when the
|
13
|
+
underlying background is dark.
|
14
|
+
|
15
|
+
* Input parameter bytesize is now limited to 1024 bytes by default.
|
16
|
+
Parameters larger than that will be ignored, as if they weren't
|
17
|
+
submitted.
|
18
|
+
|
19
|
+
* The Rodauth::Auth class for internal request classes now uses the
|
20
|
+
same configuration name as the class it is based on.
|
21
|
+
|
22
|
+
* The session_key_prefix configuration method no longer also prefixes
|
23
|
+
the keys used in the flash hash.
|
24
|
+
|
25
|
+
* The *_path and *_url methods now return nil when the related *_route
|
26
|
+
method returns nil, indicating the route is disabled.
|
27
|
+
|
28
|
+
* A more explicit error message is raised when using a feature that
|
29
|
+
requires the hmac_secret being set and not setting hmac_secret.
|
30
|
+
|
31
|
+
= Backwards Compatibility
|
32
|
+
|
33
|
+
* If you are using session_key_prefix and flash messages, you will
|
34
|
+
probably need to adjust your code to remove the prefix from the
|
35
|
+
expected flash keys, or manually prefix the flash keys by using
|
36
|
+
the flash_error_key and flash_notice_key configuration methods.
|
37
|
+
|
38
|
+
* The limiting of input parameter bytesizes by default could potentially
|
39
|
+
break applications that use Rodauth's parameter parsing method to
|
40
|
+
handle parameters that Rodauth itself doesn't handle. You can use
|
41
|
+
the max_param_bytesize configuration method to set a larger bytesize,
|
42
|
+
or use a value of nil with the method for the previous behavior of
|
43
|
+
no limit. Additionally, to customize the behavior if a parameter
|
44
|
+
is over the allowed bytesize, you can use the
|
45
|
+
over_max_bytesize_param_value configuration method.
|
@@ -12,6 +12,7 @@ module Rodauth
|
|
12
12
|
Feature.define(:argon2, :Argon2) do
|
13
13
|
depends :login_password_requirements_base
|
14
14
|
|
15
|
+
auth_value_method :argon2_secret, nil
|
15
16
|
auth_value_method :use_argon2?, true
|
16
17
|
|
17
18
|
private
|
@@ -35,7 +36,14 @@ module Rodauth
|
|
35
36
|
|
36
37
|
def password_hash(password)
|
37
38
|
return super unless use_argon2?
|
38
|
-
|
39
|
+
|
40
|
+
if secret = argon2_secret
|
41
|
+
argon2_params = Hash[password_hash_cost]
|
42
|
+
argon2_params[:secret] = secret
|
43
|
+
else
|
44
|
+
argon2_params = password_hash_cost
|
45
|
+
end
|
46
|
+
::Argon2::Password.new(argon2_params).create(password)
|
39
47
|
end
|
40
48
|
|
41
49
|
def password_hash_match?(hash, password)
|
@@ -48,6 +56,7 @@ module Rodauth
|
|
48
56
|
|
49
57
|
argon2_params = Hash[extract_password_hash_cost(salt)]
|
50
58
|
argon2_params[argon2_salt_option] = Base64.decode64(salt.split('$').last)
|
59
|
+
argon2_params[:secret] = argon2_secret
|
51
60
|
::Argon2::Password.new(argon2_params).create(password)
|
52
61
|
end
|
53
62
|
|
@@ -75,7 +84,7 @@ module Rodauth
|
|
75
84
|
end
|
76
85
|
|
77
86
|
def argon2_password_hash_match?(hash, password)
|
78
|
-
::Argon2::Password.verify_password(password, hash)
|
87
|
+
::Argon2::Password.verify_password(password, hash, argon2_secret)
|
79
88
|
end
|
80
89
|
end
|
81
90
|
end
|
@@ -24,8 +24,8 @@ module Rodauth
|
|
24
24
|
auth_value_method :check_csrf_block, nil
|
25
25
|
auth_value_method :check_csrf_opts, {}.freeze
|
26
26
|
auth_value_method :default_redirect, '/'
|
27
|
-
|
28
|
-
|
27
|
+
flash_key :flash_error_key, :error
|
28
|
+
flash_key :flash_notice_key, :notice
|
29
29
|
auth_value_method :hmac_secret, nil
|
30
30
|
translatable_method :input_field_label_suffix, ''
|
31
31
|
auth_value_method :input_field_error_class, 'error is-invalid'
|
@@ -37,6 +37,7 @@ module Rodauth
|
|
37
37
|
auth_value_method :login_column, :email
|
38
38
|
auth_value_method :login_required_error_status, 401
|
39
39
|
auth_value_method :lockout_error_status, 403
|
40
|
+
auth_value_method :max_param_bytesize, 1024
|
40
41
|
auth_value_method :password_hash_id_column, :id
|
41
42
|
auth_value_method :password_hash_column, :password_hash
|
42
43
|
auth_value_method :password_hash_table, :account_password_hashes
|
@@ -96,6 +97,7 @@ module Rodauth
|
|
96
97
|
:login_required,
|
97
98
|
:null_byte_parameter_value,
|
98
99
|
:open_account?,
|
100
|
+
:over_max_bytesize_param_value,
|
99
101
|
:password_match?,
|
100
102
|
:random_key,
|
101
103
|
:redirect,
|
@@ -455,11 +457,17 @@ module Rodauth
|
|
455
457
|
value = raw_param(key)
|
456
458
|
unless value.nil?
|
457
459
|
value = value.to_s
|
458
|
-
value =
|
460
|
+
value = over_max_bytesize_param_value(key, value) if max_param_bytesize && value.bytesize > max_param_bytesize
|
461
|
+
value = null_byte_parameter_value(key, value) if value && value.include?("\0")
|
459
462
|
end
|
460
463
|
value
|
461
464
|
end
|
462
465
|
|
466
|
+
# Return nil by default for values over maximum bytesize.
|
467
|
+
def over_max_bytesize_param_value(key, value)
|
468
|
+
nil
|
469
|
+
end
|
470
|
+
|
463
471
|
# Return nil by default for values with null bytes
|
464
472
|
def null_byte_parameter_value(key, value)
|
465
473
|
nil
|
@@ -541,7 +549,11 @@ module Rodauth
|
|
541
549
|
end
|
542
550
|
|
543
551
|
def convert_session_key(key)
|
544
|
-
key = "#{session_key_prefix}#{key}"
|
552
|
+
key = :"#{session_key_prefix}#{key}" if session_key_prefix
|
553
|
+
normalize_session_or_flash_key(key)
|
554
|
+
end
|
555
|
+
|
556
|
+
def normalize_session_or_flash_key(key)
|
545
557
|
scope.opts[:sessions_convert_symbols] ? key.to_s : key
|
546
558
|
end
|
547
559
|
|
@@ -654,7 +666,7 @@ module Rodauth
|
|
654
666
|
end
|
655
667
|
|
656
668
|
def _account_from_login(login)
|
657
|
-
ds =
|
669
|
+
ds = account_table_ds.where(login_column=>login)
|
658
670
|
ds = ds.select(*account_select) if account_select
|
659
671
|
ds = ds.where(account_status_column=>[account_unverified_status_value, account_open_status_value]) unless skip_status_checks?
|
660
672
|
ds.first
|
@@ -667,6 +679,8 @@ module Rodauth
|
|
667
679
|
end
|
668
680
|
|
669
681
|
def compute_raw_hmac(data)
|
682
|
+
raise ArgumentError, "hmac_secret not set" unless hmac_secret
|
683
|
+
|
670
684
|
OpenSSL::HMAC.digest(OpenSSL::Digest::SHA256.new, hmac_secret, data)
|
671
685
|
end
|
672
686
|
|
@@ -692,11 +706,15 @@ module Rodauth
|
|
692
706
|
|
693
707
|
def account_ds(id=account_id)
|
694
708
|
raise ArgumentError, "invalid account id passed to account_ds" unless id
|
695
|
-
ds =
|
709
|
+
ds = account_table_ds.where(account_id_column=>id)
|
696
710
|
ds = ds.select(*account_select) if account_select
|
697
711
|
ds
|
698
712
|
end
|
699
713
|
|
714
|
+
def account_table_ds
|
715
|
+
db[accounts_table]
|
716
|
+
end
|
717
|
+
|
700
718
|
def password_hash_ds
|
701
719
|
db[password_hash_table].where(password_hash_id_column=>account ? account_id : session_value)
|
702
720
|
end
|
@@ -761,6 +779,12 @@ module Rodauth
|
|
761
779
|
end
|
762
780
|
end
|
763
781
|
|
782
|
+
def _filter_links(links)
|
783
|
+
links.select!{|_, link| link}
|
784
|
+
links.sort!
|
785
|
+
links
|
786
|
+
end
|
787
|
+
|
764
788
|
def internal_request?
|
765
789
|
false
|
766
790
|
end
|
@@ -340,6 +340,7 @@ module Rodauth
|
|
340
340
|
|
341
341
|
klass = self.class
|
342
342
|
internal_class = Class.new(klass)
|
343
|
+
internal_class.instance_variable_set(:@configuration_name, klass.configuration_name)
|
343
344
|
|
344
345
|
if blocks = klass.instance_variable_get(:@internal_request_configuration_blocks)
|
345
346
|
configuration = internal_class.configuration
|
@@ -20,11 +20,12 @@ module Rodauth
|
|
20
20
|
session_key :login_redirect_session_key, :login_redirect
|
21
21
|
|
22
22
|
auth_cached_method :multi_phase_login_forms
|
23
|
-
auth_cached_method :login_form_footer_links
|
24
23
|
auth_cached_method :login_form_footer
|
25
24
|
|
26
25
|
auth_value_methods :login_return_to_requested_location_path
|
27
26
|
|
27
|
+
auth_private_methods :login_form_footer_links
|
28
|
+
|
28
29
|
internal_request_method
|
29
30
|
internal_request_method :valid_login_and_password?
|
30
31
|
|
@@ -125,6 +126,10 @@ module Rodauth
|
|
125
126
|
"<input type='hidden' name=\"#{login_param}\" value=\"#{scope.h param(login_param)}\" />"
|
126
127
|
end
|
127
128
|
|
129
|
+
def login_form_footer_links
|
130
|
+
@login_form_footer_links ||= _filter_links(_login_form_footer_links)
|
131
|
+
end
|
132
|
+
|
128
133
|
def render_multi_phase_login_forms
|
129
134
|
multi_phase_login_forms.sort.map{|_, form, _| form}.join("\n")
|
130
135
|
end
|
data/lib/rodauth/features/otp.rb
CHANGED
@@ -308,7 +308,7 @@ module Rodauth
|
|
308
308
|
end
|
309
309
|
|
310
310
|
def otp_qr_code
|
311
|
-
svg = RQRCode::QRCode.new(otp_provisioning_uri).as_svg(:module_size=>8, :viewbox=>true, :use_path=>true)
|
311
|
+
svg = RQRCode::QRCode.new(otp_provisioning_uri).as_svg(:module_size=>8, :viewbox=>true, :use_path=>true, :fill=>"#fff")
|
312
312
|
svg.sub(/\A<\?xml version="1\.0" standalone="yes"\?>/, '')
|
313
313
|
end
|
314
314
|
|
@@ -43,10 +43,6 @@ module Rodauth
|
|
43
43
|
translatable_method :two_factor_disable_link_text, "Remove All Multifactor Authentication Methods"
|
44
44
|
auth_value_method :two_factor_auth_return_to_requested_location?, false
|
45
45
|
|
46
|
-
auth_cached_method :two_factor_auth_links
|
47
|
-
auth_cached_method :two_factor_setup_links
|
48
|
-
auth_cached_method :two_factor_remove_links
|
49
|
-
|
50
46
|
auth_value_methods :two_factor_modifications_require_password?
|
51
47
|
|
52
48
|
auth_methods(
|
@@ -57,6 +53,12 @@ module Rodauth
|
|
57
53
|
:two_factor_update_session
|
58
54
|
)
|
59
55
|
|
56
|
+
auth_private_methods(
|
57
|
+
:two_factor_auth_links,
|
58
|
+
:two_factor_setup_links,
|
59
|
+
:two_factor_remove_links
|
60
|
+
)
|
61
|
+
|
60
62
|
internal_request_method :two_factor_disable
|
61
63
|
|
62
64
|
route(:two_factor_manage, 'multifactor-manage') do |r|
|
@@ -206,6 +208,18 @@ module Rodauth
|
|
206
208
|
nil
|
207
209
|
end
|
208
210
|
|
211
|
+
def two_factor_auth_links
|
212
|
+
@two_factor_auth_links ||= _filter_links(_two_factor_auth_links)
|
213
|
+
end
|
214
|
+
|
215
|
+
def two_factor_setup_links
|
216
|
+
@two_factor_setup_links ||= _filter_links(_two_factor_setup_links)
|
217
|
+
end
|
218
|
+
|
219
|
+
def two_factor_remove_links
|
220
|
+
@two_factor_remove_links ||= _filter_links(_two_factor_remove_links)
|
221
|
+
end
|
222
|
+
|
209
223
|
private
|
210
224
|
|
211
225
|
def _two_factor_auth_links
|
data/lib/rodauth/version.rb
CHANGED
data/lib/rodauth.rb
CHANGED
@@ -126,8 +126,8 @@ module Rodauth
|
|
126
126
|
route_meth = :"#{name}_route"
|
127
127
|
auth_value_method route_meth, default
|
128
128
|
|
129
|
-
define_method(:"#{name}_path"){|opts={}| route_path(send(route_meth), opts)}
|
130
|
-
define_method(:"#{name}_url"){|opts={}| route_url(send(route_meth), opts)}
|
129
|
+
define_method(:"#{name}_path"){|opts={}| route_path(send(route_meth), opts) if send(route_meth)}
|
130
|
+
define_method(:"#{name}_url"){|opts={}| route_url(send(route_meth), opts) if send(route_meth)}
|
131
131
|
|
132
132
|
handle_meth = :"handle_#{name}"
|
133
133
|
internal_handle_meth = :"_#{handle_meth}"
|
@@ -269,6 +269,11 @@ module Rodauth
|
|
269
269
|
auth_value_methods(meth)
|
270
270
|
end
|
271
271
|
|
272
|
+
def flash_key(meth, value)
|
273
|
+
define_method(meth){normalize_session_or_flash_key(value)}
|
274
|
+
auth_value_methods(meth)
|
275
|
+
end
|
276
|
+
|
272
277
|
def auth_value_method(meth, value)
|
273
278
|
define_method(meth){value}
|
274
279
|
auth_value_methods(meth)
|
@@ -1,6 +1,6 @@
|
|
1
1
|
#{rodauth.login_form_footer_links_heading}
|
2
2
|
<ul class="rodauth-links rodauth-login-footer-links">
|
3
|
-
#{rodauth.login_form_footer_links.
|
3
|
+
#{rodauth.login_form_footer_links.map do |_, link, text|
|
4
4
|
"<li><a href=\"#{h link}\">#{h text}</a></li>"
|
5
5
|
end.join("\n")}
|
6
6
|
</ul>
|
@@ -1,7 +1,7 @@
|
|
1
1
|
#{rodauth.two_factor_setup_heading unless rodauth.two_factor_setup_links.empty?}
|
2
2
|
|
3
3
|
<ul class="rodauth-links rodauth-multifactor-setup-links">
|
4
|
-
#{rodauth.two_factor_setup_links.
|
4
|
+
#{rodauth.two_factor_setup_links.map do |_, link, text|
|
5
5
|
"<li><a href=\"#{h link}\">#{h text}</a></li>"
|
6
6
|
end.join("\n")}
|
7
7
|
</ul>
|
@@ -9,7 +9,7 @@ end.join("\n")}
|
|
9
9
|
#{rodauth.two_factor_remove_heading unless rodauth.two_factor_remove_links.empty?}
|
10
10
|
|
11
11
|
<ul class="rodauth-links rodauth-multifactor-remove-links">
|
12
|
-
#{rodauth.two_factor_remove_links.
|
12
|
+
#{rodauth.two_factor_remove_links.map do |_, link, text|
|
13
13
|
"<li><a href=\"#{h link}\">#{h text}</a></li>"
|
14
14
|
end.join("\n")}
|
15
15
|
#{"<li><a href=\"#{h rodauth.two_factor_disable_path}\">#{rodauth.two_factor_disable_link_text}</a></li>" if rodauth.two_factor_remove_links.length > 1}
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rodauth
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.
|
4
|
+
version: 2.26.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jeremy Evans
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-
|
11
|
+
date: 2022-10-21 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: sequel
|
@@ -341,6 +341,7 @@ extra_rdoc_files:
|
|
341
341
|
- doc/release_notes/2.23.0.txt
|
342
342
|
- doc/release_notes/2.24.0.txt
|
343
343
|
- doc/release_notes/2.25.0.txt
|
344
|
+
- doc/release_notes/2.26.0.txt
|
344
345
|
- doc/release_notes/2.3.0.txt
|
345
346
|
- doc/release_notes/2.4.0.txt
|
346
347
|
- doc/release_notes/2.5.0.txt
|
@@ -453,6 +454,7 @@ files:
|
|
453
454
|
- doc/release_notes/2.23.0.txt
|
454
455
|
- doc/release_notes/2.24.0.txt
|
455
456
|
- doc/release_notes/2.25.0.txt
|
457
|
+
- doc/release_notes/2.26.0.txt
|
456
458
|
- doc/release_notes/2.3.0.txt
|
457
459
|
- doc/release_notes/2.4.0.txt
|
458
460
|
- doc/release_notes/2.5.0.txt
|
@@ -588,7 +590,7 @@ metadata:
|
|
588
590
|
documentation_uri: https://rodauth.jeremyevans.net/documentation.html
|
589
591
|
mailing_list_uri: https://github.com/jeremyevans/rodauth/discussions
|
590
592
|
source_code_uri: https://github.com/jeremyevans/rodauth
|
591
|
-
post_install_message:
|
593
|
+
post_install_message:
|
592
594
|
rdoc_options:
|
593
595
|
- "--quiet"
|
594
596
|
- "--line-numbers"
|
@@ -611,7 +613,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
611
613
|
version: '0'
|
612
614
|
requirements: []
|
613
615
|
rubygems_version: 3.3.7
|
614
|
-
signing_key:
|
616
|
+
signing_key:
|
615
617
|
specification_version: 4
|
616
618
|
summary: Authentication and Account Management Framework for Rack Applications
|
617
619
|
test_files: []
|