add_auth 0.3.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +27 -0
  3. data/README.md +61 -9
  4. data/ROADMAP.md +43 -31
  5. data/SECURITY.md +7 -1
  6. data/app/controllers/add_auth/accounts_controller.rb +10 -2
  7. data/app/controllers/add_auth/reauthentications_controller.rb +5 -0
  8. data/app/controllers/add_auth/sessions_controller.rb +1 -1
  9. data/app/views/add_auth/accounts/_register.html.erb +1 -1
  10. data/app/views/add_auth/accounts/_request.html.erb +3 -0
  11. data/lib/add_auth/configuration.rb +2 -2
  12. data/lib/add_auth/core/account_lifecycle.rb +51 -7
  13. data/lib/add_auth/core/account_policy.rb +21 -3
  14. data/lib/add_auth/core/session_cursor.rb +38 -0
  15. data/lib/add_auth/core/session_key.rb +32 -0
  16. data/lib/add_auth/core/sessions.rb +19 -8
  17. data/lib/add_auth/rails/authentication_pages.rb +1 -1
  18. data/lib/add_auth/rails/doctor.rb +10 -3
  19. data/lib/add_auth/rails/migration/key_type.rb +3 -3
  20. data/lib/add_auth/rails/runtime.rb +13 -4
  21. data/lib/add_auth/rails/stores/account_tokens.rb +10 -2
  22. data/lib/add_auth/rails/stores/sessions.rb +10 -2
  23. data/lib/add_auth/version.rb +1 -1
  24. data/lib/generators/add_auth/accounts/accounts_generator.rb +7 -1
  25. data/lib/generators/add_auth/accounts/templates/add_add_auth_accounts.rb.tt +2 -2
  26. data/lib/generators/add_auth/accounts/templates/add_add_auth_provisioning.rb.tt +9 -0
  27. data/lib/generators/add_auth/email_link/email_link_generator.rb +4 -1
  28. data/lib/generators/add_auth/email_tokens/email_tokens_generator.rb +2 -0
  29. data/lib/generators/add_auth/email_tokens/templates/create_add_auth_sign_in_tokens.rb.tt +1 -1
  30. data/lib/generators/add_auth/external_identities/external_identities_generator.rb +2 -0
  31. data/lib/generators/add_auth/external_identities/templates/create_add_auth_external_identities.rb.tt +6 -12
  32. data/lib/generators/add_auth/feature_configuration.rb +21 -0
  33. data/lib/generators/add_auth/install/templates/initializer.rb +154 -50
  34. data/lib/generators/add_auth/mobile_sessions/mobile_sessions_generator.rb +2 -0
  35. data/lib/generators/add_auth/mobile_sessions/templates/create_add_auth_mobile_handoffs.rb.tt +4 -4
  36. data/lib/generators/add_auth/notifications/notifications_generator.rb +5 -4
  37. data/lib/generators/add_auth/notifications/templates/create_add_auth_security_events.rb.tt +1 -1
  38. data/lib/generators/add_auth/passkeys/passkeys_generator.rb +5 -21
  39. data/lib/generators/add_auth/passkeys/templates/add_add_auth_passkeys.rb.tt +2 -2
  40. data/lib/generators/add_auth/primary_key.rb +19 -0
  41. data/lib/generators/add_auth/session_upgrade/session_upgrade_generator.rb +6 -1
  42. data/lib/generators/add_auth/session_upgrade/templates/add_add_auth_session_cursor_index.rb.tt +5 -0
  43. data/lib/generators/add_auth/step_up/step_up_generator.rb +5 -5
  44. metadata +21 -1
@@ -1,52 +1,156 @@
1
- # AddAuth configuration. Feature generators enable only their own block.
1
+ # frozen_string_literal: true
2
+
3
+ # AddAuth settings. Defaults are active; alternatives are commented out.
4
+ # Run the relevant generator and migrations before enabling a feature.
5
+ # Install alone enables no features. Existing initializers are never replaced.
2
6
  AddAuth.configure do |config|
3
- # Set false for email/passkey-only sign-in. Existing Rails password entry
4
- # routes remain guarded; the host owns password reset and account provisioning.
5
- # config.passwords_enabled = true
6
-
7
- # Ordinary Rails HTML navigation, including hosts without Hotwire:
8
- # config.turbo_enabled = false
9
- # Passkeys/captcha still use scoped gem JavaScript; no host JS build is needed.
10
-
11
- # BEGIN add_auth session
12
- # config.session.enabled = true
13
- # END add_auth session
14
- # config.session.lifetime = 12.hours
15
- # config.session.idle_timeout = 30.minutes
16
- # Legacy cookies are rejected by default. Opt into a finite bridge explicitly:
17
- # config.session.legacy_bridge_until = Time.iso8601("2026-09-13T00:00:00Z")
18
-
19
- # BEGIN add_auth email_link
20
- # config.email_link.enabled = true
21
- # END add_auth email_link
22
- # config.email_link.token_lifetime = 20.minutes
23
- # config.email_link.same_browser = false # true requires the requesting browser
24
- # REQUIRED for email delivery: fixed, trusted origin; never derive it from Host.
25
- # config.base_url = "https://your-app.example"
26
- # config.mail_from = "Your app <sign-in@your-app.example>"
27
- # Configure a durable Active Job adapter and schedule add_auth:deliver_pending.
28
- # Rails.cache is suitable only with shared atomic increments and expiry.
29
- # Solid Cache is not suitable; configure a separate counter store:
30
- # https://addauthgem.com/production/#rate-limits
31
- # Each maintenance pass handles at most this many rows per operation/model:
32
- # config.maintenance.batch_size = 100 # 1..1000
33
- # History is retained until the host chooses a retention period (seconds):
34
- # config.maintenance.session_retention = 7.days
35
- # config.maintenance.email_retention = 7.days
36
- # config.maintenance.notification_retention = 30.days
37
-
38
- # Apply the host's current confirmed/locked/disabled policy on every resume:
39
- # config.eligible = ->(user) { user.confirmed? && !user.locked? && !user.disabled? }
40
-
41
- # Styling: nil disables CSS; a local path uses your own compiled stylesheet.
42
- # config.stylesheet = "/add_auth.css"
43
- # config.css_classes = { input: "form-control", button: "btn btn-primary" }
44
- # Eject editable templates with bin/rails generate add_auth:views.
45
-
46
- # Enable optional features with add_auth:passkeys, add_auth:step_up,
47
- # add_auth:notifications or add_auth:challenge. Install alone enables none.
48
- # Passkey recovery needs a host-verified recovery address; strict accounts
49
- # use another passkey or the host's support process, never email recovery.
50
- # config.challenge = AddAuth::Core::Challenge::Null.new
51
- # config.challenge_on = []
7
+ # Sign-in and rendering. HTML forms work in all browser modes.
8
+ config.passwords_enabled = true
9
+ # config.passwords_enabled = false # Use only other explicitly enabled methods.
10
+ config.turbo_enabled = true
11
+ # config.turbo_enabled = false # Ordinary JS navigation, with Turbo absent.
12
+
13
+ # Host business eligibility is checked alongside Core account policy.
14
+ config.eligible = ->(_user) { true }
15
+ # config.eligible = ->(user) { user.access_state == "active" }
16
+
17
+ # Browser sessions: bin/rails generate add_auth:session_upgrade
18
+ config.session.enabled = false
19
+ config.session.lifetime = 43_200 # 12 hours, absolute expiry.
20
+ config.session.idle_timeout = 1800 # 30 minutes; no greater than lifetime.
21
+ config.session.legacy_bridge_until = nil # Reject old Rails session-ID cookies.
22
+ # config.session.legacy_bridge_until = Time.iso8601("2030-01-01T00:00:00Z")
23
+ # Choose a short, fixed cutover deadline for a reviewed legacy-cookie bridge.
24
+
25
+ # Account lifecycle: bin/rails generate add_auth:accounts
26
+ # Password-only host: bin/rails generate add_auth:accounts --no-email-link
27
+ config.lifecycle.enabled = false
28
+ # config.lifecycle.enabled = true # After policy review and migrations.
29
+ config.lifecycle.confirmation_required = true
30
+ # config.lifecycle.confirmation_required = false
31
+ # false: password signup provisions and signs in immediately, without mail.
32
+ # confirmed_at stays nil. Requires the add_auth_provisioned_at migration.
33
+ config.lifecycle.reset_unconfirmed = false
34
+ # config.lifecycle.reset_unconfirmed = true # Requires optional confirmation.
35
+ # true: the stored mailbox holder may reset an unconfirmed password account
36
+ # after consuming an exact-address, single-use reset proof. No automatic login
37
+ # or confirmation. This does not trust the address for any other recovery.
38
+ # false: confirm the address before password reset, or use host support.
39
+ # Email changes always need fresh reauthentication and proof of the new address.
40
+ config.lifecycle.password_policy = ->(password) { password.length >= 12 && password.bytesize <= 72 }
41
+ config.lifecycle.eligible = ->(_user) { true } # Additional lifecycle eligibility.
42
+ config.lifecycle.profile_attributes = ->(_profile) { {} } # Accept no profile fields.
43
+ # config.lifecycle.profile_attributes = ->(profile) { profile.slice(:name, :consent) }
44
+ # Also allowlist those fields in the host registration controller. Never roles.
45
+ config.lifecycle.provision = ->(_user) {} # Local writes in the account transaction.
46
+ # config.lifecycle.provision = ->(user) { user.create_personal_workspace! }
47
+ # Raise on failure. Use the same DB pool; remote work needs a host outbox.
48
+ config.lifecycle.maximum_attempts = 20 # Failed-password threshold: 1..100.
49
+ config.lifecycle.unlock_in = 3600 # Timed lock: 60..86400 seconds.
50
+ config.lifecycle.proof_lifetime = 3600 # Account links: 60..86400 seconds.
51
+ config.lifecycle.remember_lifetime = 1_209_600 # 14 days, absolute expiry.
52
+ config.lifecycle.remember_idle_timeout = 604_800 # 7 days; <= remember_lifetime.
53
+ config.lifecycle.deletion_allowed = ->(_user) { true } # Host authorization/retention.
54
+ # config.lifecycle.deletion_allowed = ->(user) { user.invoices.none? }
55
+ config.lifecycle.delete_account = ->(user) { user.destroy! } # Same-DB local deletion.
56
+
57
+ # Email-link sign-in: bin/rails generate add_auth:email_link
58
+ # Account confirmation/reset mail does not require email-link sign-in.
59
+ config.email_link.enabled = false
60
+ config.email_link.token_lifetime = 1200 # 20 minutes.
61
+ config.email_link.same_browser = false
62
+ # config.email_link.same_browser = true # Require the requesting browser.
63
+
64
+ # Fresh proof for sensitive actions: bin/rails generate add_auth:step_up
65
+ # Use --no-email-link for password-only reauthentication.
66
+ config.step_up.enabled = false
67
+ config.step_up.fresh_for = 600 # Ordinary proof: 10 minutes.
68
+ config.step_up.strong_for = 300 # Strong proof: 5 minutes.
69
+ config.step_up.purposes = {} # Built-in feature purposes are supplied by Core.
70
+ # config.step_up.purposes = {
71
+ # export_data: {methods: [:password], return_to: "/exports/new", label: "export your data"}
72
+ # }
73
+ # Recheck elevation and host authorization inside the actual mutation.
74
+
75
+ # Passkeys: bin/rails generate add_auth:passkeys
76
+ config.passkeys.enabled = false
77
+ config.passkeys.rp_id = nil # Required: stable deployment RP ID.
78
+ # config.passkeys.rp_id = "example.com"
79
+ config.passkeys.origins = [] # Required: exact trusted origins, never request Host.
80
+ # config.passkeys.origins = ["https://app.example.com"]
81
+ config.passkeys.name = "Your account"
82
+ config.passkeys.anonymous_limit = 1000 # Shared ceremony budget per five minutes.
83
+ config.trusted_recovery_address = ->(_user) {} # No trusted recovery by default.
84
+ # config.trusted_recovery_address = ->(user) { user.email_address if user.confirmed_at }
85
+ # Lifecycle additionally rejects unconfirmed addresses as trusted recovery.
86
+ config.support_url = nil # Required before strict-policy activation.
87
+ # config.support_url = "/support" # Document your host-owned recovery process.
88
+
89
+ # Security notices: bin/rails generate add_auth:notifications
90
+ config.notifications.enabled = false
91
+ # Mail/queue settings also serve account proofs and optional email sign-in.
92
+ config.base_url = nil # Required for links: fixed trusted HTTPS origin.
93
+ # config.base_url = "https://app.example.com"
94
+ config.mail_from = nil # Required for mail; configure SMTP in Rails as usual.
95
+ # config.mail_from = "Your app <sign-in@example.com>"
96
+ # Configure a durable Active Job adapter and run add_auth:deliver_pending each minute.
97
+ config.rate_limit_store = nil # Defaults to Rails.cache; shared atomic counters required.
98
+ # config.rate_limit_store = ActiveSupport::Cache::RedisCacheStore.new(url: ENV.fetch("AUTH_REDIS_URL"))
99
+ # Solid Cache can serve application caching, but cannot serve these counters.
100
+ config.maintenance.batch_size = 100 # Maximum rows per operation/model: 1..1000.
101
+ config.maintenance.session_retention = nil # Keep history until explicitly selected.
102
+ config.maintenance.email_retention = nil
103
+ config.maintenance.notification_retention = nil
104
+ config.maintenance.account_retention = nil
105
+ # config.maintenance.session_retention = 604_800 # 7 days, seconds.
106
+ # config.maintenance.email_retention = 604_800
107
+ # config.maintenance.notification_retention = 2_592_000 # 30 days.
108
+ # config.maintenance.account_retention = 604_800
109
+
110
+ # Captcha: bin/rails generate add_auth:challenge (provider-specific setup).
111
+ config.challenge = AddAuth::Core::Challenge::Null.new
112
+ config.challenge_on = [] # Available: sign_in, email_link, reauthenticate,
113
+ # passkey_enrollment, register, account_request, provider.
114
+ # config.challenge_on = [:sign_in, :register, :account_request]
115
+ config.challenge_when_unavailable = :closed
116
+ # config.challenge_when_unavailable = :open # Explicit outage bypass; emits an event.
117
+ # Replace challenge with a configured Turnstile or Recaptcha adapter. Keep
118
+ # secrets in Rails credentials/environment, and configure expected hostnames.
119
+
120
+ # Optional providers: bin/rails generate add_auth:external_identities
121
+ config.external_identities.enabled = false
122
+ # Register only reviewed Core provider configurations through
123
+ # config.external_identities.register(id:, label:, middleware_name:, configuration:,
124
+ # apple_form_post: false, reauthentication: false)
125
+ # or config.external_identities.register_native(configuration:).
126
+ # The generated add_auth_providers.rb explains provider-library middleware,
127
+ # verifier and credential setup. It remains commented out until reviewed.
128
+
129
+ # Native sessions: bin/rails generate add_auth:mobile_sessions
130
+ config.mobile.enabled = false
131
+ config.mobile.lifetime = nil # Required when enabled; choose finite seconds.
132
+ config.mobile.idle_timeout = nil # Required; <= lifetime.
133
+ config.mobile.clients = [] # Explicit first-party client identifiers.
134
+ config.mobile.callbacks = {} # Reviewed callbacks per client, when using handoffs.
135
+ config.mobile.apple_providers = {} # Native Apple provider IDs per client.
136
+ # config.mobile.lifetime = 2_592_000 # Example: 30 days.
137
+ # config.mobile.idle_timeout = 1_209_600 # Example: 14 days.
138
+ # config.mobile.clients = %w[android ios]
139
+
140
+ # Presentation: eject with bin/rails generate add_auth:views.
141
+ config.stylesheet = "/add_auth.css"
142
+ # config.stylesheet = nil # Supply your own styling.
143
+ config.css_classes = {}
144
+ # config.css_classes = {input: "form-control", button: "btn btn-primary"}
145
+
146
+ # Password migration and cryptography: retain Rails' verifier by default.
147
+ config.legacy_password_verifier = nil
148
+ config.current_password_support = AddAuth::Core::Passwords::BcryptSupport.new
149
+ # A custom verifier must also declare current_password_support.available?(digest:).
150
+ # Rails configures digest_secret from its key generator automatically. Preserve it.
151
+ # Outside Rails only:
152
+ # config.digest_secret = -> { ENV.fetch("ADD_AUTH_DIGEST_SECRET") }
153
+ # Optional adapter overrides must implement digest(token) and matches?(digest, token):
154
+ # config.session_token_digest = MySessionDigest.new
155
+ # config.sign_in_token_digest = MyProofDigest.new
52
156
  end
@@ -1,12 +1,14 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "rails/generators"
4
+ require "generators/add_auth/primary_key"
4
5
  require "rails/generators/active_record"
5
6
 
6
7
  module AddAuth
7
8
  module Generators
8
9
  class MobileSessionsGenerator < ::Rails::Generators::Base
9
10
  include ::Rails::Generators::Migration
11
+ include PrimaryKey
10
12
 
11
13
  source_root File.expand_path("templates", __dir__)
12
14
  def self.next_migration_number(dirname) = ::ActiveRecord::Generators::Base.next_migration_number(dirname)
@@ -1,9 +1,9 @@
1
+ require "add_auth/rails/migration/key_type"
2
+
1
3
  class CreateAddAuthMobileHandoffs < ActiveRecord::Migration[8.0]
2
4
  def change
3
- user_key = connection.columns(:users).find { |column| column.name == connection.primary_key(:users) }
4
- raise "Unsupported authentication primary key" unless user_key && [:integer, :uuid].include?(user_key.type)
5
- user_type = (user_key.type == :integer && user_key.limit == 8) ? :bigint : user_key.type
6
- create_table :add_auth_mobile_handoffs do |t|
5
+ user_type = AddAuth::Rails::Migration::KeyType.for(connection, :users)
6
+ create_table :add_auth_mobile_handoffs, id: <%= authentication_primary_key_type %> do |t|
7
7
  t.references :user, type: user_type, foreign_key: {on_delete: :cascade}
8
8
  t.string :external_digest, null: false
9
9
  t.string :digest
@@ -1,12 +1,16 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "rails/generators"
4
+ require "generators/add_auth/primary_key"
5
+ require "generators/add_auth/feature_configuration"
4
6
  require "rails/generators/active_record"
5
7
 
6
8
  module AddAuth
7
9
  module Generators
8
10
  class NotificationsGenerator < ::Rails::Generators::Base
9
11
  include ::Rails::Generators::Migration
12
+ include PrimaryKey
13
+ include FeatureConfiguration
10
14
 
11
15
  source_root File.expand_path("templates", __dir__)
12
16
  def self.next_migration_number(dirname) = ::ActiveRecord::Generators::Base.next_migration_number(dirname)
@@ -21,10 +25,7 @@ module AddAuth
21
25
  end
22
26
 
23
27
  def configuration
24
- path = "config/initializers/add_auth.rb"
25
- unless File.read(File.join(destination_root, path)).include?("config.notifications.enabled = true")
26
- append_to_file path, "\nAddAuth.configure do |config|\n config.notifications.enabled = true\nend\n"
27
- end
28
+ enable_feature(:notifications)
28
29
  say "Migrate, configure mail_from and a durable queue, and schedule add_auth:deliver_pending every minute."
29
30
  end
30
31
  end
@@ -2,7 +2,7 @@ require "add_auth/rails/migration/key_type"
2
2
 
3
3
  class CreateAddAuthSecurityEvents < ActiveRecord::Migration[8.0]
4
4
  def change
5
- create_table :add_auth_security_events do |t|
5
+ create_table :add_auth_security_events, id: <%= authentication_primary_key_type %> do |t|
6
6
  t.references :user, type: AddAuth::Rails::Migration::KeyType.for(connection, :users), foreign_key: {on_delete: :nullify}
7
7
  t.string :kind, null: false
8
8
  t.string :digest, null: false
@@ -1,12 +1,16 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "rails/generators"
4
+ require "generators/add_auth/primary_key"
5
+ require "generators/add_auth/feature_configuration"
4
6
  require "rails/generators/active_record"
5
7
 
6
8
  module AddAuth
7
9
  module Generators
8
10
  class PasskeysGenerator < ::Rails::Generators::Base
9
11
  include ::Rails::Generators::Migration
12
+ include PrimaryKey
13
+ include FeatureConfiguration
10
14
 
11
15
  source_root File.expand_path("templates", __dir__)
12
16
  def self.next_migration_number(dirname) = ::ActiveRecord::Generators::Base.next_migration_number(dirname)
@@ -47,27 +51,7 @@ module AddAuth
47
51
  post "recover/link", to: "add_auth/recoveries#confirm"
48
52
  ROUTES
49
53
  end
50
- path = "config/initializers/add_auth.rb"
51
- unless File.read(File.join(destination_root, path)).include?("config.passkeys.enabled = true")
52
- append_to_file path, <<~CONFIG
53
-
54
- AddAuth.configure do |config|
55
- config.passkeys.enabled = true
56
- # REQUIRED: stable RP ID and exact deployment origins, never request Host.
57
- # config.passkeys.rp_id = "example.com"
58
- # config.passkeys.origins = ["https://app.example.com"]
59
- # config.passkeys.name = "Your app"
60
- # Shared anonymous ceremony budget per five minutes; positive integer.
61
- # config.passkeys.anonymous_limit = 1000
62
- # Schedule add_auth:deliver_pending every minute. Production doctor
63
- # requires a completed cleanup within the last two minutes.
64
- # REQUIRED for email replacement: a host-verified recovery address.
65
- # config.trusted_recovery_address = ->(user) { user.email_address if user.confirmed? }
66
- # REQUIRED before strict activation: your documented support route.
67
- # config.support_url = "/support"
68
- end
69
- CONFIG
70
- end
54
+ enable_feature(:passkeys)
71
55
  say "Review and migrate before enabling traffic. Configure RP/origins, trusted recovery and notifications; run add_auth:doctor."
72
56
  end
73
57
  end
@@ -14,7 +14,7 @@ class AddAddAuthPasskeys < ActiveRecord::Migration[8.0]
14
14
  add_column :sessions, :authentication_policy_version, :integer, default: 0, null: false
15
15
  add_column :sessions, :authentication_credential_id, :string
16
16
  add_column :sessions, :authentication_uv, :boolean, default: false_default, null: false
17
- create_table :add_auth_credentials do |t|
17
+ create_table :add_auth_credentials, id: <%= authentication_primary_key_type %> do |t|
18
18
  t.references :user, type: user_key_type, null: false, foreign_key: {on_delete: :cascade}
19
19
  t.string :external_id, null: false
20
20
  t.text :public_key, null: false
@@ -29,7 +29,7 @@ class AddAddAuthPasskeys < ActiveRecord::Migration[8.0]
29
29
  t.timestamps
30
30
  end
31
31
  add_index :add_auth_credentials, :external_id, unique: true
32
- create_table :add_auth_ceremonies do |t|
32
+ create_table :add_auth_ceremonies, id: <%= authentication_primary_key_type %> do |t|
33
33
  t.string :digest, null: false
34
34
  t.string :challenge, null: false
35
35
  t.string :kind, null: false
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AddAuth
4
+ module Generators
5
+ module PrimaryKey
6
+ private
7
+
8
+ # Freeze Rails' generator preference into the migration source. Foreign
9
+ # keys independently follow the actual referenced table when it runs.
10
+ def authentication_primary_key_type
11
+ type = (::Rails.application.config.generators.options.dig(:active_record, :primary_key_type) || :bigint).to_s
12
+ unless %w[bigint integer uuid].include?(type)
13
+ raise Thor::Error, "AddAuth supports Rails primary_key_type :bigint, :integer or :uuid"
14
+ end
15
+ ":#{type}"
16
+ end
17
+ end
18
+ end
19
+ end
@@ -1,12 +1,14 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "rails/generators"
4
+ require "generators/add_auth/feature_configuration"
4
5
  require "rails/generators/active_record"
5
6
 
6
7
  module AddAuth
7
8
  module Generators
8
9
  class SessionUpgradeGenerator < ::Rails::Generators::Base
9
10
  include ::Rails::Generators::Migration
11
+ include FeatureConfiguration
10
12
 
11
13
  source_root File.expand_path("templates", __dir__)
12
14
 
@@ -23,6 +25,9 @@ module AddAuth
23
25
  end
24
26
 
25
27
  def migration
28
+ unless Dir[File.join(destination_root, "db/migrate/*_add_add_auth_session_cursor_index.rb")].any?
29
+ migration_template "add_add_auth_session_cursor_index.rb.tt", "db/migrate/add_add_auth_session_cursor_index.rb"
30
+ end
26
31
  unless Dir[File.join(destination_root, "db/migrate/*_extend_sessions_for_add_auth.rb")].any?
27
32
  migration_template "extend_sessions_for_add_auth.rb.tt", "db/migrate/extend_sessions_for_add_auth.rb"
28
33
  end
@@ -62,7 +67,7 @@ module AddAuth
62
67
  unless File.read(File.join(destination_root, path)).include?('require "add_auth/rails/authentication"')
63
68
  prepend_to_file path, %(require "add_auth/rails/authentication"\nrequire "add_auth/rails/user_lifecycle"\n)
64
69
  end
65
- gsub_file path, "# config.session.enabled = true", "config.session.enabled = true"
70
+ enable_feature(:session)
66
71
  routes = File.join(destination_root, "config/routes.rb")
67
72
  source = File.read(routes)
68
73
  unless source.include?("# AddAuth session management")
@@ -0,0 +1,5 @@
1
+ class AddAddAuthSessionCursorIndex < ActiveRecord::Migration[8.0]
2
+ def change
3
+ add_index :sessions, [:user_id, :created_at, :id], name: "index_add_auth_session_cursor"
4
+ end
5
+ end
@@ -1,18 +1,21 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "rails/generators"
4
+ require "generators/add_auth/feature_configuration"
4
5
  require "rails/generators/active_record"
5
6
 
6
7
  module AddAuth
7
8
  module Generators
8
9
  class StepUpGenerator < ::Rails::Generators::Base
9
10
  include ::Rails::Generators::Migration
11
+ include FeatureConfiguration
10
12
 
11
13
  source_root File.expand_path("templates", __dir__)
14
+ class_option :email_link, type: :boolean, default: true, desc: "Enable email sign-in alongside reauthentication"
12
15
  def self.next_migration_number(dirname) = ::ActiveRecord::Generators::Base.next_migration_number(dirname)
13
16
 
14
17
  def dependencies
15
- invoke "add_auth:email_link"
18
+ invoke "add_auth:email_link", [], enable: options[:email_link]
16
19
  end
17
20
 
18
21
  def persistence
@@ -33,10 +36,7 @@ module AddAuth
33
36
  post "reauthenticate/link", to: "add_auth/reauthentications#confirm"
34
37
  ROUTES
35
38
  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
39
+ enable_feature(:step_up)
40
40
  say "Migrate and declare config.step_up.purposes. Use with_elevated_session around sensitive database mutations."
41
41
  end
42
42
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: add_auth
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Taimoor Qureshi
@@ -23,6 +23,20 @@ dependencies:
23
23
  - - ">="
24
24
  - !ruby/object:Gem::Version
25
25
  version: '8.0'
26
+ - !ruby/object:Gem::Dependency
27
+ name: json
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - "<"
31
+ - !ruby/object:Gem::Version
32
+ version: '3.0'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "<"
38
+ - !ruby/object:Gem::Version
39
+ version: '3.0'
26
40
  - !ruby/object:Gem::Dependency
27
41
  name: webauthn
28
42
  requirement: !ruby/object:Gem::Requirement
@@ -315,6 +329,8 @@ files:
315
329
  - lib/add_auth/core/rate_limit.rb
316
330
  - lib/add_auth/core/remaining_factors.rb
317
331
  - lib/add_auth/core/security_events.rb
332
+ - lib/add_auth/core/session_cursor.rb
333
+ - lib/add_auth/core/session_key.rb
318
334
  - lib/add_auth/core/sessions.rb
319
335
  - lib/add_auth/core/step_up.rb
320
336
  - lib/add_auth/core/strategies/email_link.rb
@@ -364,6 +380,7 @@ files:
364
380
  - lib/add_auth/version.rb
365
381
  - lib/generators/add_auth/accounts/accounts_generator.rb
366
382
  - lib/generators/add_auth/accounts/templates/add_add_auth_accounts.rb.tt
383
+ - lib/generators/add_auth/accounts/templates/add_add_auth_provisioning.rb.tt
367
384
  - lib/generators/add_auth/accounts/templates/add_auth_account_token.rb
368
385
  - lib/generators/add_auth/accounts/templates/add_auth_address_claim.rb
369
386
  - lib/generators/add_auth/challenge/challenge_generator.rb
@@ -388,6 +405,7 @@ files:
388
405
  - lib/generators/add_auth/external_identities/templates/add_auth_external_transaction.rb
389
406
  - lib/generators/add_auth/external_identities/templates/create_add_auth_external_identities.rb.tt
390
407
  - lib/generators/add_auth/external_identities/templates/providers.rb
408
+ - lib/generators/add_auth/feature_configuration.rb
391
409
  - lib/generators/add_auth/install/install_generator.rb
392
410
  - lib/generators/add_auth/install/templates/initializer.rb
393
411
  - lib/generators/add_auth/javascript/javascript_generator.rb
@@ -407,8 +425,10 @@ files:
407
425
  - lib/generators/add_auth/passkeys/templates/add_add_auth_passkeys.rb.tt
408
426
  - lib/generators/add_auth/passkeys/templates/add_auth_ceremony.rb
409
427
  - lib/generators/add_auth/passkeys/templates/add_auth_credential.rb
428
+ - lib/generators/add_auth/primary_key.rb
410
429
  - lib/generators/add_auth/session_upgrade/session_upgrade_generator.rb
411
430
  - lib/generators/add_auth/session_upgrade/templates/add_add_auth_elevation.rb.tt
431
+ - lib/generators/add_auth/session_upgrade/templates/add_add_auth_session_cursor_index.rb.tt
412
432
  - lib/generators/add_auth/session_upgrade/templates/extend_sessions_for_add_auth.rb.tt
413
433
  - lib/generators/add_auth/step_up/step_up_generator.rb
414
434
  - lib/generators/add_auth/step_up/templates/add_add_auth_reauthentication.rb.tt