rodauth 2.25.0 → 2.26.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.
- 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: []
         |