add_auth 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (122) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +125 -0
  3. data/CODE_OF_CONDUCT.md +4 -0
  4. data/LICENSE.txt +21 -0
  5. data/README.md +545 -0
  6. data/ROADMAP.md +244 -0
  7. data/SECURITY.md +75 -0
  8. data/app/controllers/add_auth/assets_controller.rb +54 -0
  9. data/app/controllers/add_auth/passkeys_controller.rb +144 -0
  10. data/app/controllers/add_auth/reauthentications_controller.rb +81 -0
  11. data/app/controllers/add_auth/recoveries_controller.rb +50 -0
  12. data/app/controllers/add_auth/sessions_controller.rb +62 -0
  13. data/app/controllers/add_auth/sign_ins_controller.rb +65 -0
  14. data/app/helpers/add_auth/sign_ins_helper.rb +42 -0
  15. data/app/javascript/controllers/.keep +0 -0
  16. data/app/jobs/add_auth/delivery_job.rb +31 -0
  17. data/app/jobs/add_auth/email_delivery_job.rb +15 -0
  18. data/app/jobs/add_auth/email_request_job.rb +21 -0
  19. data/app/jobs/add_auth/security_notification_job.rb +13 -0
  20. data/app/mailers/add_auth/security_mailer.rb +16 -0
  21. data/app/mailers/add_auth/sign_in_mailer.rb +36 -0
  22. data/app/models/concerns/.keep +0 -0
  23. data/app/views/.keep +0 -0
  24. data/app/views/add_auth/passkeys/_controls.html.erb +17 -0
  25. data/app/views/add_auth/passkeys/_list.html.erb +32 -0
  26. data/app/views/add_auth/reauthentications/_check_email.html.erb +3 -0
  27. data/app/views/add_auth/reauthentications/_confirmation.html.erb +7 -0
  28. data/app/views/add_auth/reauthentications/_different_browser.html.erb +3 -0
  29. data/app/views/add_auth/reauthentications/_form.html.erb +27 -0
  30. data/app/views/add_auth/reauthentications/_invalid_link.html.erb +3 -0
  31. data/app/views/add_auth/recoveries/_check_email.html.erb +3 -0
  32. data/app/views/add_auth/recoveries/_confirmation.html.erb +9 -0
  33. data/app/views/add_auth/recoveries/_form.html.erb +11 -0
  34. data/app/views/add_auth/recoveries/_invalid_link.html.erb +3 -0
  35. data/app/views/add_auth/security_mailer/notice.text.erb +3 -0
  36. data/app/views/add_auth/sessions/_list.html.erb +25 -0
  37. data/app/views/add_auth/sessions/_revoke_all.html.erb +18 -0
  38. data/app/views/add_auth/sessions/index.html.erb +12 -0
  39. data/app/views/add_auth/sign_in_mailer/link.text.erb +5 -0
  40. data/app/views/add_auth/sign_ins/_check_email.html.erb +5 -0
  41. data/app/views/add_auth/sign_ins/_confirmation.html.erb +9 -0
  42. data/app/views/add_auth/sign_ins/_different_browser.html.erb +3 -0
  43. data/app/views/add_auth/sign_ins/_form.html.erb +34 -0
  44. data/app/views/add_auth/sign_ins/_invalid_link.html.erb +3 -0
  45. data/app/views/add_auth/sign_ins/show.html.erb +1 -0
  46. data/app/views/layouts/add_auth/authentication.html.erb +19 -0
  47. data/lib/add_auth/configuration.rb +61 -0
  48. data/lib/add_auth/core/access_policy.rb +55 -0
  49. data/lib/add_auth/core/browser_binding.rb +28 -0
  50. data/lib/add_auth/core/challenge/base.rb +100 -0
  51. data/lib/add_auth/core/challenge/http.rb +130 -0
  52. data/lib/add_auth/core/challenge/null.rb +21 -0
  53. data/lib/add_auth/core/challenge/recaptcha.rb +76 -0
  54. data/lib/add_auth/core/challenge/test.rb +32 -0
  55. data/lib/add_auth/core/challenge/turnstile.rb +46 -0
  56. data/lib/add_auth/core/delivery.rb +55 -0
  57. data/lib/add_auth/core/digest/base.rb +34 -0
  58. data/lib/add_auth/core/digest/hmac.rb +41 -0
  59. data/lib/add_auth/core/intake.rb +59 -0
  60. data/lib/add_auth/core/maintenance.rb +45 -0
  61. data/lib/add_auth/core/rate_limit.rb +30 -0
  62. data/lib/add_auth/core/security_events.rb +48 -0
  63. data/lib/add_auth/core/sessions.rb +287 -0
  64. data/lib/add_auth/core/step_up.rb +129 -0
  65. data/lib/add_auth/core/strategies/email_link.rb +201 -0
  66. data/lib/add_auth/core/strategies/passkey.rb +286 -0
  67. data/lib/add_auth/rails/authentication.rb +67 -0
  68. data/lib/add_auth/rails/authentication_pages.rb +66 -0
  69. data/lib/add_auth/rails/delivery_cipher.rb +33 -0
  70. data/lib/add_auth/rails/doctor.rb +154 -0
  71. data/lib/add_auth/rails/ejection.rb +138 -0
  72. data/lib/add_auth/rails/elevation.rb +35 -0
  73. data/lib/add_auth/rails/engine.rb +34 -0
  74. data/lib/add_auth/rails/password_entry.rb +34 -0
  75. data/lib/add_auth/rails/runtime.rb +216 -0
  76. data/lib/add_auth/rails/stores/account_lock.rb +40 -0
  77. data/lib/add_auth/rails/stores/delivery_state.rb +21 -0
  78. data/lib/add_auth/rails/stores/email_tokens.rb +92 -0
  79. data/lib/add_auth/rails/stores/maintenance.rb +46 -0
  80. data/lib/add_auth/rails/stores/passkeys.rb +63 -0
  81. data/lib/add_auth/rails/stores/security_events.rb +40 -0
  82. data/lib/add_auth/rails/stores/sessions.rb +62 -0
  83. data/lib/add_auth/rails/user_lifecycle.rb +35 -0
  84. data/lib/add_auth/result.rb +55 -0
  85. data/lib/add_auth/testing.rb +30 -0
  86. data/lib/add_auth/version.rb +5 -0
  87. data/lib/add_auth.rb +52 -0
  88. data/lib/generators/add_auth/challenge/challenge_generator.rb +48 -0
  89. data/lib/generators/add_auth/challenge/templates/recaptcha.rb.tt +28 -0
  90. data/lib/generators/add_auth/challenge/templates/turnstile.rb.tt +24 -0
  91. data/lib/generators/add_auth/controllers/controllers_generator.rb +14 -0
  92. data/lib/generators/add_auth/ejection.rb +18 -0
  93. data/lib/generators/add_auth/email_link/email_link_generator.rb +56 -0
  94. data/lib/generators/add_auth/email_link/templates/add_add_auth_email_binding.rb.tt +5 -0
  95. data/lib/generators/add_auth/email_link/templates/add_add_auth_email_delivery.rb.tt +10 -0
  96. data/lib/generators/add_auth/email_link/templates/add_auth.css +28 -0
  97. data/lib/generators/add_auth/email_link/templates/add_auth_challenge.js +129 -0
  98. data/lib/generators/add_auth/email_tokens/email_tokens_generator.rb +35 -0
  99. data/lib/generators/add_auth/email_tokens/templates/add_auth_sign_in_token.rb +4 -0
  100. data/lib/generators/add_auth/email_tokens/templates/create_add_auth_sign_in_tokens.rb.tt +18 -0
  101. data/lib/generators/add_auth/install/install_generator.rb +27 -0
  102. data/lib/generators/add_auth/install/templates/initializer.rb +46 -0
  103. data/lib/generators/add_auth/javascript/javascript_generator.rb +14 -0
  104. data/lib/generators/add_auth/javascript/templates/application.js +2 -0
  105. data/lib/generators/add_auth/javascript/templates/codec.js +28 -0
  106. data/lib/generators/add_auth/javascript/templates/passkey.js +81 -0
  107. data/lib/generators/add_auth/mailer_views/mailer_views_generator.rb +14 -0
  108. data/lib/generators/add_auth/notifications/notifications_generator.rb +32 -0
  109. data/lib/generators/add_auth/notifications/templates/add_auth_security_event.rb +4 -0
  110. data/lib/generators/add_auth/notifications/templates/create_add_auth_security_events.rb.tt +18 -0
  111. data/lib/generators/add_auth/passkeys/passkeys_generator.rb +75 -0
  112. data/lib/generators/add_auth/passkeys/templates/add_add_auth_passkeys.rb.tt +45 -0
  113. data/lib/generators/add_auth/passkeys/templates/add_auth_ceremony.rb +4 -0
  114. data/lib/generators/add_auth/passkeys/templates/add_auth_credential.rb +4 -0
  115. data/lib/generators/add_auth/session_upgrade/session_upgrade_generator.rb +79 -0
  116. data/lib/generators/add_auth/session_upgrade/templates/add_add_auth_elevation.rb.tt +12 -0
  117. data/lib/generators/add_auth/session_upgrade/templates/extend_sessions_for_add_auth.rb.tt +14 -0
  118. data/lib/generators/add_auth/step_up/step_up_generator.rb +44 -0
  119. data/lib/generators/add_auth/step_up/templates/add_add_auth_reauthentication.rb.tt +9 -0
  120. data/lib/generators/add_auth/views/views_generator.rb +16 -0
  121. data/lib/tasks/add_auth.rake +38 -0
  122. metadata +361 -0
@@ -0,0 +1,45 @@
1
+ class AddAddAuthPasskeys < ActiveRecord::Migration[8.0]
2
+ def change
3
+ # Rails 8.0 cannot reflect SQLite's FALSE literal emitted by Rails 8.1.
4
+ # Keep the additive schema readable by both supported Rails lines.
5
+ false_default = (connection.adapter_name == "SQLite") ? -> { "0" } : false
6
+ add_column :users, :webauthn_id, :string
7
+ add_index :users, :webauthn_id, unique: true
8
+ add_column :users, :add_auth_strict, :boolean, default: false_default, null: false
9
+ add_column :users, :add_auth_policy_version, :integer, default: 0, null: false
10
+ add_column :sessions, :authentication_policy_version, :integer, default: 0, null: false
11
+ add_column :sessions, :authentication_credential_id, :string
12
+ add_column :sessions, :authentication_uv, :boolean, default: false_default, null: false
13
+ create_table :add_auth_credentials do |t|
14
+ t.references :user, null: false, foreign_key: {on_delete: :cascade}
15
+ t.string :external_id, null: false
16
+ t.text :public_key, null: false
17
+ t.bigint :sign_count, default: 0, null: false
18
+ t.string :nickname, default: "Passkey", null: false, limit: 60
19
+ t.string :aaguid
20
+ t.json :transports
21
+ t.boolean :backup_eligible, default: false_default, null: false
22
+ t.boolean :backup_state, default: false_default, null: false
23
+ t.datetime :last_used_at
24
+ t.datetime :revoked_at
25
+ t.timestamps
26
+ end
27
+ add_index :add_auth_credentials, :external_id, unique: true
28
+ create_table :add_auth_ceremonies do |t|
29
+ t.string :digest, null: false
30
+ t.string :challenge, null: false
31
+ t.string :kind, null: false
32
+ t.string :browser_digest, null: false
33
+ t.string :configuration_digest, null: false
34
+ t.references :user, foreign_key: {on_delete: :cascade}
35
+ t.references :session, foreign_key: {on_delete: :cascade}
36
+ t.string :session_digest
37
+ t.string :authentication_purpose
38
+ t.datetime :expires_at, null: false
39
+ t.datetime :consumed_at
40
+ t.timestamps
41
+ end
42
+ add_index :add_auth_ceremonies, :digest, unique: true
43
+ add_index :add_auth_ceremonies, :expires_at
44
+ end
45
+ end
@@ -0,0 +1,4 @@
1
+ class AddAuthCeremony < ApplicationRecord
2
+ belongs_to :user, optional: true
3
+ self.filter_attributes += [:digest, :challenge, :browser_digest, :session_digest, :public_key, :external_id]
4
+ end
@@ -0,0 +1,4 @@
1
+ class AddAuthCredential < ApplicationRecord
2
+ belongs_to :user
3
+ self.filter_attributes += [:digest, :challenge, :browser_digest, :session_digest, :public_key, :external_id]
4
+ end
@@ -0,0 +1,79 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails/generators"
4
+ require "rails/generators/active_record"
5
+
6
+ module AddAuth
7
+ module Generators
8
+ class SessionUpgradeGenerator < ::Rails::Generators::Base
9
+ include ::Rails::Generators::Migration
10
+
11
+ source_root File.expand_path("templates", __dir__)
12
+
13
+ def shared_browser_runtime
14
+ unless File.read(File.join(destination_root, "config/routes.rb")).include?("add_auth/application.js")
15
+ route 'get "add_auth/application.js", to: "add_auth/assets#application"'
16
+ end
17
+ end
18
+
19
+ def self.next_migration_number(dirname) = ::ActiveRecord::Generators::Base.next_migration_number(dirname)
20
+
21
+ def install_configuration
22
+ invoke "add_auth:install"
23
+ end
24
+
25
+ def migration
26
+ unless Dir[File.join(destination_root, "db/migrate/*_extend_sessions_for_add_auth.rb")].any?
27
+ migration_template "extend_sessions_for_add_auth.rb.tt", "db/migrate/extend_sessions_for_add_auth.rb"
28
+ end
29
+ unless Dir[File.join(destination_root, "db/migrate/*_add_add_auth_elevation.rb")].any?
30
+ migration_template "add_add_auth_elevation.rb.tt", "db/migrate/add_add_auth_elevation.rb"
31
+ end
32
+ end
33
+
34
+ def assets_and_sign_in
35
+ source = File.read(File.join(destination_root, "config/routes.rb"))
36
+ {"add_auth.css" => "assets#stylesheet", "add_auth/turbo.js" => "assets#turbo",
37
+ "add_auth/boot.js" => "assets#boot", "add_auth/stimulus.js" => "assets#stimulus", "add_auth/challenge.js" => "assets#challenge",
38
+ "sign-in" => "sign_ins#new"}.each do |path, action|
39
+ route %(get "#{path}", to: "add_auth/#{action}") unless source.include?(%("#{path}"))
40
+ end
41
+ route 'post "sign-in/password", to: "add_auth/sign_ins#password"' unless source.include?('"sign-in/password"')
42
+ end
43
+
44
+ def hooks
45
+ path = "app/controllers/application_controller.rb"
46
+ source = File.read(File.join(destination_root, path))
47
+ unless source.include?("include Authentication")
48
+ raise Thor::Error, "Expected ApplicationController to include Authentication; integrate AddAuth::Rails::Authentication after it manually."
49
+ end
50
+ unless source.include?("include AddAuth::Rails::Authentication")
51
+ inject_into_file path, "\n include AddAuth::Rails::Authentication", after: "include Authentication"
52
+ end
53
+ path = "app/models/user.rb"
54
+ unless File.read(File.join(destination_root, path)).include?("include AddAuth::Rails::UserLifecycle")
55
+ inject_into_file path, " include AddAuth::Rails::UserLifecycle\n", after: /class User < [^\n]+\n/
56
+ end
57
+ path = "app/models/session.rb"
58
+ unless File.read(File.join(destination_root, path)).include?("self.filter_attributes")
59
+ inject_into_file path, " self.filter_attributes += [:token_digest, :elevation_credential_id]\n", after: /class Session < [^\n]+\n/
60
+ end
61
+ path = "config/initializers/add_auth.rb"
62
+ unless File.read(File.join(destination_root, path)).include?('require "add_auth/rails/authentication"')
63
+ prepend_to_file path, %(require "add_auth/rails/authentication"\nrequire "add_auth/rails/user_lifecycle"\n)
64
+ end
65
+ gsub_file path, "# config.session.enabled = true", "config.session.enabled = true"
66
+ routes = File.join(destination_root, "config/routes.rb")
67
+ source = File.read(routes)
68
+ unless source.include?("# AddAuth session management")
69
+ route <<~ROUTES
70
+ # AddAuth session management
71
+ get "sessions/revoke-all", to: "add_auth/sessions#new_revoke_all"
72
+ post "sessions/revoke-all", to: "add_auth/sessions#revoke_all"
73
+ resources :security_sessions, path: "sessions", only: [:index, :destroy], controller: "add_auth/sessions"
74
+ ROUTES
75
+ end
76
+ end
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,12 @@
1
+ class AddAddAuthElevation < ActiveRecord::Migration[8.0]
2
+ def change
3
+ # Rails 8.0 cannot reflect SQLite's FALSE literal emitted by Rails 8.1.
4
+ # Keep the additive schema readable by both supported Rails lines.
5
+ false_default = (connection.adapter_name == "SQLite") ? -> { "0" } : false
6
+ add_column :sessions, :elevated_at, :datetime
7
+ add_column :sessions, :elevated_with, :string
8
+ add_column :sessions, :elevation_purpose, :string
9
+ add_column :sessions, :elevation_credential_id, :string
10
+ add_column :sessions, :elevation_uv, :boolean, default: false_default, null: false
11
+ end
12
+ end
@@ -0,0 +1,14 @@
1
+ class ExtendSessionsForAddAuth < ActiveRecord::Migration[8.0]
2
+ def change
3
+ add_column :sessions, :token_digest, :string, limit: 64
4
+ add_index :sessions, :token_digest, unique: true
5
+ add_column :sessions, :authenticated_with, :string
6
+ add_column :sessions, :authenticated_at, :datetime
7
+ add_column :sessions, :expires_at, :datetime
8
+ add_column :sessions, :last_seen_at, :datetime
9
+ add_column :sessions, :revoked_at, :datetime
10
+ add_index :sessions, [:user_id, :id], name: "index_add_auth_session_pages"
11
+ add_index :sessions, :expires_at
12
+ add_index :sessions, :revoked_at
13
+ end
14
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails/generators"
4
+ require "rails/generators/active_record"
5
+
6
+ module AddAuth
7
+ module Generators
8
+ class StepUpGenerator < ::Rails::Generators::Base
9
+ include ::Rails::Generators::Migration
10
+
11
+ source_root File.expand_path("templates", __dir__)
12
+ def self.next_migration_number(dirname) = ::ActiveRecord::Generators::Base.next_migration_number(dirname)
13
+
14
+ def dependencies
15
+ invoke "add_auth:email_link"
16
+ end
17
+
18
+ def persistence
19
+ unless Dir[File.join(destination_root, "db/migrate/*_add_add_auth_reauthentication.rb")].any?
20
+ migration_template "add_add_auth_reauthentication.rb.tt", "db/migrate/add_add_auth_reauthentication.rb"
21
+ end
22
+ end
23
+
24
+ def wiring
25
+ unless File.read(File.join(destination_root, "config/routes.rb")).include?("# AddAuth reauthentication")
26
+ route <<~ROUTES
27
+ # AddAuth reauthentication
28
+ get "reauthenticate", to: "add_auth/reauthentications#new"
29
+ post "reauthenticate/password", to: "add_auth/reauthentications#password"
30
+ post "reauthenticate/email", to: "add_auth/reauthentications#request_link"
31
+ get "reauthenticate/check-email", to: "add_auth/reauthentications#check_email"
32
+ get "reauthenticate/link", to: "add_auth/reauthentications#link"
33
+ post "reauthenticate/link", to: "add_auth/reauthentications#confirm"
34
+ ROUTES
35
+ end
36
+ initializer = "config/initializers/add_auth.rb"
37
+ unless File.read(File.join(destination_root, initializer)).include?("config.step_up.enabled = true")
38
+ append_to_file initializer, "\nAddAuth.configure do |config|\n config.step_up.enabled = true\n # Declare purpose methods and a fixed safe GET return_to before use.\nend\n"
39
+ end
40
+ say "Migrate and declare config.step_up.purposes. Use with_elevated_session around sensitive database mutations."
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,9 @@
1
+ class AddAddAuthReauthentication < ActiveRecord::Migration[8.0]
2
+ def change
3
+ add_column :sessions, :elevation_version, :string
4
+ add_column :sessions, :elevation_expires_at, :datetime
5
+ add_reference :add_auth_sign_in_tokens, :session, foreign_key: {on_delete: :cascade}
6
+ add_column :add_auth_sign_in_tokens, :session_digest, :string
7
+ add_column :add_auth_sign_in_tokens, :authentication_purpose, :string
8
+ end
9
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails/generators"
4
+ require "generators/add_auth/ejection"
5
+
6
+ module AddAuth
7
+ module Generators
8
+ class ViewsGenerator < ::Rails::Generators::Base
9
+ include Ejection
10
+
11
+ class_option :only, type: :string, default: "email_link"
12
+
13
+ def generate = eject(:views, only: options[:only])
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ namespace :add_auth do
4
+ desc "Check installed AddAuth authentication, delivery and maintenance wiring"
5
+ task doctor: :environment do
6
+ require "add_auth/rails/doctor"
7
+ doctor = AddAuth::Rails::Doctor.new
8
+ problems = doctor.call
9
+ Array(doctor.ejections).each do |entry|
10
+ puts "Customized: #{entry[:path]}" if entry[:customized]
11
+ puts entry[:diff] if entry[:diff]
12
+ end
13
+ puts problems.empty? ? "AddAuth configuration checks passed." : problems.join("\n")
14
+ abort "AddAuth configuration needs attention" if problems.any?
15
+ end
16
+
17
+ desc "Run bounded delivery recovery and retention; schedule at least every minute"
18
+ task deliver_pending: :environment do
19
+ require "add_auth/rails/stores/maintenance"
20
+ models = {}
21
+ models[:session] = ::Session if AddAuth.configuration.session.enabled && defined?(::Session)
22
+ models[:ceremony] = ::AddAuthCeremony if defined?(::AddAuthCeremony)
23
+ models[:email] = ::AddAuthSignInToken if defined?(::AddAuthSignInToken)
24
+ models[:notification] = ::AddAuthSecurityEvent if defined?(::AddAuthSecurityEvent)
25
+ stores = models.filter_map do |kind, model|
26
+ [kind, AddAuth::Rails::Stores::Maintenance.new(model: model, kind: kind)] if model.table_exists?
27
+ end.to_h
28
+ jobs = {email: AddAuth::EmailDeliveryJob, notification: AddAuth::SecurityNotificationJob}
29
+ enqueue = lambda do |kind, id|
30
+ raise AddAuth::Error, "maintenance enqueue failed" unless jobs.fetch(kind).perform_later(id)
31
+ end
32
+ ActiveSupport::Notifications.instrument("maintenance.add_auth") do |payload|
33
+ payload.merge!(AddAuth::Core::Maintenance.new(stores: stores,
34
+ options: AddAuth.configuration.maintenance, enqueue: enqueue).call)
35
+ AddAuth::Rails::Runtime.record_maintenance
36
+ end
37
+ end
38
+ end
metadata ADDED
@@ -0,0 +1,361 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: add_auth
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.2.1
5
+ platform: ruby
6
+ authors:
7
+ - Taimoor Qureshi
8
+ bindir: exe
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: rails
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - ">="
17
+ - !ruby/object:Gem::Version
18
+ version: '8.0'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - ">="
24
+ - !ruby/object:Gem::Version
25
+ version: '8.0'
26
+ - !ruby/object:Gem::Dependency
27
+ name: webauthn
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - "~>"
31
+ - !ruby/object:Gem::Version
32
+ version: '3.4'
33
+ - - ">="
34
+ - !ruby/object:Gem::Version
35
+ version: 3.4.3
36
+ type: :runtime
37
+ prerelease: false
38
+ version_requirements: !ruby/object:Gem::Requirement
39
+ requirements:
40
+ - - "~>"
41
+ - !ruby/object:Gem::Version
42
+ version: '3.4'
43
+ - - ">="
44
+ - !ruby/object:Gem::Version
45
+ version: 3.4.3
46
+ - !ruby/object:Gem::Dependency
47
+ name: public_suffix
48
+ requirement: !ruby/object:Gem::Requirement
49
+ requirements:
50
+ - - "~>"
51
+ - !ruby/object:Gem::Version
52
+ version: '7.0'
53
+ type: :runtime
54
+ prerelease: false
55
+ version_requirements: !ruby/object:Gem::Requirement
56
+ requirements:
57
+ - - "~>"
58
+ - !ruby/object:Gem::Version
59
+ version: '7.0'
60
+ - !ruby/object:Gem::Dependency
61
+ name: turbo-rails
62
+ requirement: !ruby/object:Gem::Requirement
63
+ requirements:
64
+ - - "~>"
65
+ - !ruby/object:Gem::Version
66
+ version: '2.0'
67
+ type: :runtime
68
+ prerelease: false
69
+ version_requirements: !ruby/object:Gem::Requirement
70
+ requirements:
71
+ - - "~>"
72
+ - !ruby/object:Gem::Version
73
+ version: '2.0'
74
+ - !ruby/object:Gem::Dependency
75
+ name: stimulus-rails
76
+ requirement: !ruby/object:Gem::Requirement
77
+ requirements:
78
+ - - "~>"
79
+ - !ruby/object:Gem::Version
80
+ version: '1.3'
81
+ type: :runtime
82
+ prerelease: false
83
+ version_requirements: !ruby/object:Gem::Requirement
84
+ requirements:
85
+ - - "~>"
86
+ - !ruby/object:Gem::Version
87
+ version: '1.3'
88
+ - !ruby/object:Gem::Dependency
89
+ name: bcrypt
90
+ requirement: !ruby/object:Gem::Requirement
91
+ requirements:
92
+ - - "~>"
93
+ - !ruby/object:Gem::Version
94
+ version: 3.1.7
95
+ type: :runtime
96
+ prerelease: false
97
+ version_requirements: !ruby/object:Gem::Requirement
98
+ requirements:
99
+ - - "~>"
100
+ - !ruby/object:Gem::Version
101
+ version: 3.1.7
102
+ - !ruby/object:Gem::Dependency
103
+ name: rspec-rails
104
+ requirement: !ruby/object:Gem::Requirement
105
+ requirements:
106
+ - - "~>"
107
+ - !ruby/object:Gem::Version
108
+ version: '8.0'
109
+ type: :development
110
+ prerelease: false
111
+ version_requirements: !ruby/object:Gem::Requirement
112
+ requirements:
113
+ - - "~>"
114
+ - !ruby/object:Gem::Version
115
+ version: '8.0'
116
+ - !ruby/object:Gem::Dependency
117
+ name: sqlite3
118
+ requirement: !ruby/object:Gem::Requirement
119
+ requirements:
120
+ - - ">="
121
+ - !ruby/object:Gem::Version
122
+ version: '2.1'
123
+ type: :development
124
+ prerelease: false
125
+ version_requirements: !ruby/object:Gem::Requirement
126
+ requirements:
127
+ - - ">="
128
+ - !ruby/object:Gem::Version
129
+ version: '2.1'
130
+ - !ruby/object:Gem::Dependency
131
+ name: pg
132
+ requirement: !ruby/object:Gem::Requirement
133
+ requirements:
134
+ - - "~>"
135
+ - !ruby/object:Gem::Version
136
+ version: '1.6'
137
+ type: :development
138
+ prerelease: false
139
+ version_requirements: !ruby/object:Gem::Requirement
140
+ requirements:
141
+ - - "~>"
142
+ - !ruby/object:Gem::Version
143
+ version: '1.6'
144
+ - !ruby/object:Gem::Dependency
145
+ name: standard
146
+ requirement: !ruby/object:Gem::Requirement
147
+ requirements:
148
+ - - "~>"
149
+ - !ruby/object:Gem::Version
150
+ version: '1.3'
151
+ type: :development
152
+ prerelease: false
153
+ version_requirements: !ruby/object:Gem::Requirement
154
+ requirements:
155
+ - - "~>"
156
+ - !ruby/object:Gem::Version
157
+ version: '1.3'
158
+ - !ruby/object:Gem::Dependency
159
+ name: capybara
160
+ requirement: !ruby/object:Gem::Requirement
161
+ requirements:
162
+ - - "~>"
163
+ - !ruby/object:Gem::Version
164
+ version: '3.40'
165
+ type: :development
166
+ prerelease: false
167
+ version_requirements: !ruby/object:Gem::Requirement
168
+ requirements:
169
+ - - "~>"
170
+ - !ruby/object:Gem::Version
171
+ version: '3.40'
172
+ - !ruby/object:Gem::Dependency
173
+ name: selenium-webdriver
174
+ requirement: !ruby/object:Gem::Requirement
175
+ requirements:
176
+ - - "~>"
177
+ - !ruby/object:Gem::Version
178
+ version: '4.48'
179
+ type: :development
180
+ prerelease: false
181
+ version_requirements: !ruby/object:Gem::Requirement
182
+ requirements:
183
+ - - "~>"
184
+ - !ruby/object:Gem::Version
185
+ version: '4.48'
186
+ - !ruby/object:Gem::Dependency
187
+ name: webmock
188
+ requirement: !ruby/object:Gem::Requirement
189
+ requirements:
190
+ - - "~>"
191
+ - !ruby/object:Gem::Version
192
+ version: '3.26'
193
+ type: :development
194
+ prerelease: false
195
+ version_requirements: !ruby/object:Gem::Requirement
196
+ requirements:
197
+ - - "~>"
198
+ - !ruby/object:Gem::Version
199
+ version: '3.26'
200
+ description: |
201
+ AddAuth extends the output of `bin/rails generate authentication` with the
202
+ credentials it does not cover: passkeys, email-link sign-in, purpose-bound
203
+ reauthentication and recovery. It adds hardened sessions, security mail and
204
+ challenge providers, with shared Turbo/HTML pages and permitted no-JavaScript
205
+ alternatives. It upgrades the generated User and Session models in place.
206
+ Documentation: https://addauthgem.com.
207
+ email:
208
+ - taimoorq@gmail.com
209
+ executables: []
210
+ extensions: []
211
+ extra_rdoc_files: []
212
+ files:
213
+ - CHANGELOG.md
214
+ - CODE_OF_CONDUCT.md
215
+ - LICENSE.txt
216
+ - README.md
217
+ - ROADMAP.md
218
+ - SECURITY.md
219
+ - app/controllers/add_auth/assets_controller.rb
220
+ - app/controllers/add_auth/passkeys_controller.rb
221
+ - app/controllers/add_auth/reauthentications_controller.rb
222
+ - app/controllers/add_auth/recoveries_controller.rb
223
+ - app/controllers/add_auth/sessions_controller.rb
224
+ - app/controllers/add_auth/sign_ins_controller.rb
225
+ - app/helpers/add_auth/sign_ins_helper.rb
226
+ - app/javascript/controllers/.keep
227
+ - app/jobs/add_auth/delivery_job.rb
228
+ - app/jobs/add_auth/email_delivery_job.rb
229
+ - app/jobs/add_auth/email_request_job.rb
230
+ - app/jobs/add_auth/security_notification_job.rb
231
+ - app/mailers/add_auth/security_mailer.rb
232
+ - app/mailers/add_auth/sign_in_mailer.rb
233
+ - app/models/concerns/.keep
234
+ - app/views/.keep
235
+ - app/views/add_auth/passkeys/_controls.html.erb
236
+ - app/views/add_auth/passkeys/_list.html.erb
237
+ - app/views/add_auth/reauthentications/_check_email.html.erb
238
+ - app/views/add_auth/reauthentications/_confirmation.html.erb
239
+ - app/views/add_auth/reauthentications/_different_browser.html.erb
240
+ - app/views/add_auth/reauthentications/_form.html.erb
241
+ - app/views/add_auth/reauthentications/_invalid_link.html.erb
242
+ - app/views/add_auth/recoveries/_check_email.html.erb
243
+ - app/views/add_auth/recoveries/_confirmation.html.erb
244
+ - app/views/add_auth/recoveries/_form.html.erb
245
+ - app/views/add_auth/recoveries/_invalid_link.html.erb
246
+ - app/views/add_auth/security_mailer/notice.text.erb
247
+ - app/views/add_auth/sessions/_list.html.erb
248
+ - app/views/add_auth/sessions/_revoke_all.html.erb
249
+ - app/views/add_auth/sessions/index.html.erb
250
+ - app/views/add_auth/sign_in_mailer/link.text.erb
251
+ - app/views/add_auth/sign_ins/_check_email.html.erb
252
+ - app/views/add_auth/sign_ins/_confirmation.html.erb
253
+ - app/views/add_auth/sign_ins/_different_browser.html.erb
254
+ - app/views/add_auth/sign_ins/_form.html.erb
255
+ - app/views/add_auth/sign_ins/_invalid_link.html.erb
256
+ - app/views/add_auth/sign_ins/show.html.erb
257
+ - app/views/layouts/add_auth/authentication.html.erb
258
+ - lib/add_auth.rb
259
+ - lib/add_auth/configuration.rb
260
+ - lib/add_auth/core/access_policy.rb
261
+ - lib/add_auth/core/browser_binding.rb
262
+ - lib/add_auth/core/challenge/base.rb
263
+ - lib/add_auth/core/challenge/http.rb
264
+ - lib/add_auth/core/challenge/null.rb
265
+ - lib/add_auth/core/challenge/recaptcha.rb
266
+ - lib/add_auth/core/challenge/test.rb
267
+ - lib/add_auth/core/challenge/turnstile.rb
268
+ - lib/add_auth/core/delivery.rb
269
+ - lib/add_auth/core/digest/base.rb
270
+ - lib/add_auth/core/digest/hmac.rb
271
+ - lib/add_auth/core/intake.rb
272
+ - lib/add_auth/core/maintenance.rb
273
+ - lib/add_auth/core/rate_limit.rb
274
+ - lib/add_auth/core/security_events.rb
275
+ - lib/add_auth/core/sessions.rb
276
+ - lib/add_auth/core/step_up.rb
277
+ - lib/add_auth/core/strategies/email_link.rb
278
+ - lib/add_auth/core/strategies/passkey.rb
279
+ - lib/add_auth/rails/authentication.rb
280
+ - lib/add_auth/rails/authentication_pages.rb
281
+ - lib/add_auth/rails/delivery_cipher.rb
282
+ - lib/add_auth/rails/doctor.rb
283
+ - lib/add_auth/rails/ejection.rb
284
+ - lib/add_auth/rails/elevation.rb
285
+ - lib/add_auth/rails/engine.rb
286
+ - lib/add_auth/rails/password_entry.rb
287
+ - lib/add_auth/rails/runtime.rb
288
+ - lib/add_auth/rails/stores/account_lock.rb
289
+ - lib/add_auth/rails/stores/delivery_state.rb
290
+ - lib/add_auth/rails/stores/email_tokens.rb
291
+ - lib/add_auth/rails/stores/maintenance.rb
292
+ - lib/add_auth/rails/stores/passkeys.rb
293
+ - lib/add_auth/rails/stores/security_events.rb
294
+ - lib/add_auth/rails/stores/sessions.rb
295
+ - lib/add_auth/rails/user_lifecycle.rb
296
+ - lib/add_auth/result.rb
297
+ - lib/add_auth/testing.rb
298
+ - lib/add_auth/version.rb
299
+ - lib/generators/add_auth/challenge/challenge_generator.rb
300
+ - lib/generators/add_auth/challenge/templates/recaptcha.rb.tt
301
+ - lib/generators/add_auth/challenge/templates/turnstile.rb.tt
302
+ - lib/generators/add_auth/controllers/controllers_generator.rb
303
+ - lib/generators/add_auth/ejection.rb
304
+ - lib/generators/add_auth/email_link/email_link_generator.rb
305
+ - lib/generators/add_auth/email_link/templates/add_add_auth_email_binding.rb.tt
306
+ - lib/generators/add_auth/email_link/templates/add_add_auth_email_delivery.rb.tt
307
+ - lib/generators/add_auth/email_link/templates/add_auth.css
308
+ - lib/generators/add_auth/email_link/templates/add_auth_challenge.js
309
+ - lib/generators/add_auth/email_tokens/email_tokens_generator.rb
310
+ - lib/generators/add_auth/email_tokens/templates/add_auth_sign_in_token.rb
311
+ - lib/generators/add_auth/email_tokens/templates/create_add_auth_sign_in_tokens.rb.tt
312
+ - lib/generators/add_auth/install/install_generator.rb
313
+ - lib/generators/add_auth/install/templates/initializer.rb
314
+ - lib/generators/add_auth/javascript/javascript_generator.rb
315
+ - lib/generators/add_auth/javascript/templates/application.js
316
+ - lib/generators/add_auth/javascript/templates/codec.js
317
+ - lib/generators/add_auth/javascript/templates/passkey.js
318
+ - lib/generators/add_auth/mailer_views/mailer_views_generator.rb
319
+ - lib/generators/add_auth/notifications/notifications_generator.rb
320
+ - lib/generators/add_auth/notifications/templates/add_auth_security_event.rb
321
+ - lib/generators/add_auth/notifications/templates/create_add_auth_security_events.rb.tt
322
+ - lib/generators/add_auth/passkeys/passkeys_generator.rb
323
+ - lib/generators/add_auth/passkeys/templates/add_add_auth_passkeys.rb.tt
324
+ - lib/generators/add_auth/passkeys/templates/add_auth_ceremony.rb
325
+ - lib/generators/add_auth/passkeys/templates/add_auth_credential.rb
326
+ - lib/generators/add_auth/session_upgrade/session_upgrade_generator.rb
327
+ - lib/generators/add_auth/session_upgrade/templates/add_add_auth_elevation.rb.tt
328
+ - lib/generators/add_auth/session_upgrade/templates/extend_sessions_for_add_auth.rb.tt
329
+ - lib/generators/add_auth/step_up/step_up_generator.rb
330
+ - lib/generators/add_auth/step_up/templates/add_add_auth_reauthentication.rb.tt
331
+ - lib/generators/add_auth/views/views_generator.rb
332
+ - lib/tasks/add_auth.rake
333
+ homepage: https://addauthgem.com
334
+ licenses:
335
+ - MIT
336
+ metadata:
337
+ homepage_uri: https://addauthgem.com
338
+ source_code_uri: https://github.com/taimoorq/add_auth/tree/master
339
+ changelog_uri: https://github.com/taimoorq/add_auth/blob/master/CHANGELOG.md
340
+ bug_tracker_uri: https://github.com/taimoorq/add_auth/issues
341
+ documentation_uri: https://addauthgem.com
342
+ rubygems_mfa_required: 'true'
343
+ allowed_push_host: https://rubygems.org
344
+ rdoc_options: []
345
+ require_paths:
346
+ - lib
347
+ required_ruby_version: !ruby/object:Gem::Requirement
348
+ requirements:
349
+ - - ">="
350
+ - !ruby/object:Gem::Version
351
+ version: 3.3.0
352
+ required_rubygems_version: !ruby/object:Gem::Requirement
353
+ requirements:
354
+ - - ">="
355
+ - !ruby/object:Gem::Version
356
+ version: '0'
357
+ requirements: []
358
+ rubygems_version: 3.6.9
359
+ specification_version: 4
360
+ summary: Passkeys, email sign-in and session hardening for Rails 8 authentication.
361
+ test_files: []