add_auth 0.2.2 → 0.3.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 (122) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +22 -0
  3. data/README.md +38 -14
  4. data/ROADMAP.md +87 -38
  5. data/SECURITY.md +6 -0
  6. data/app/controllers/add_auth/accounts_controller.rb +138 -0
  7. data/app/controllers/add_auth/assets_controller.rb +2 -1
  8. data/app/controllers/add_auth/external_identities_controller.rb +56 -0
  9. data/app/controllers/add_auth/mobile_sessions_controller.rb +120 -0
  10. data/app/controllers/add_auth/provider_sign_ins_controller.rb +248 -0
  11. data/app/helpers/add_auth/sign_ins_helper.rb +2 -1
  12. data/app/jobs/add_auth/account_delivery_job.rb +14 -0
  13. data/app/jobs/add_auth/account_request_job.rb +17 -0
  14. data/app/mailers/add_auth/account_mailer.rb +12 -0
  15. data/app/views/add_auth/account_mailer/link.text.erb +7 -0
  16. data/app/views/add_auth/accounts/_check_email.html.erb +5 -0
  17. data/app/views/add_auth/accounts/_delete.html.erb +6 -0
  18. data/app/views/add_auth/accounts/_email.html.erb +12 -0
  19. data/app/views/add_auth/accounts/_invalid_proof.html.erb +5 -0
  20. data/app/views/add_auth/accounts/_password.html.erb +12 -0
  21. data/app/views/add_auth/accounts/_proof.html.erb +16 -0
  22. data/app/views/add_auth/accounts/_register.html.erb +17 -0
  23. data/app/views/add_auth/accounts/_request.html.erb +14 -0
  24. data/app/views/add_auth/accounts/_unavailable.html.erb +3 -0
  25. data/app/views/add_auth/external_identities/_index.html.erb +22 -0
  26. data/app/views/add_auth/external_identities/_unavailable.html.erb +3 -0
  27. data/app/views/add_auth/passkeys/_controls.html.erb +3 -1
  28. data/app/views/add_auth/provider_sign_ins/_enrollment.html.erb +12 -0
  29. data/app/views/add_auth/provider_sign_ins/_failure.html.erb +3 -0
  30. data/app/views/add_auth/provider_sign_ins/_prepare.html.erb +7 -0
  31. data/app/views/add_auth/reauthentications/_form.html.erb +9 -0
  32. data/app/views/add_auth/sessions/_list.html.erb +1 -1
  33. data/app/views/add_auth/sessions/index.html.erb +7 -0
  34. data/app/views/add_auth/sign_ins/_confirmation.html.erb +1 -0
  35. data/app/views/add_auth/sign_ins/_form.html.erb +17 -0
  36. data/app/views/add_auth/sign_ins/_remember.html.erb +6 -0
  37. data/app/views/layouts/add_auth/authentication.html.erb +4 -3
  38. data/exe/add_auth-preflight +26 -0
  39. data/lib/add_auth/configuration.rb +78 -4
  40. data/lib/add_auth/core/access_policy.rb +11 -1
  41. data/lib/add_auth/core/account_lifecycle.rb +251 -0
  42. data/lib/add_auth/core/account_policy.rb +50 -0
  43. data/lib/add_auth/core/external_identities/evidence.rb +98 -0
  44. data/lib/add_auth/core/external_identities.rb +278 -0
  45. data/lib/add_auth/core/intake.rb +6 -1
  46. data/lib/add_auth/core/maintenance.rb +5 -3
  47. data/lib/add_auth/core/migration/account_adoption.rb +79 -0
  48. data/lib/add_auth/core/migration/readiness.rb +41 -0
  49. data/lib/add_auth/core/mobile_authentication.rb +35 -0
  50. data/lib/add_auth/core/mobile_handoffs.rb +83 -0
  51. data/lib/add_auth/core/mobile_profile.rb +46 -0
  52. data/lib/add_auth/core/mobile_response.rb +12 -0
  53. data/lib/add_auth/core/native_authentication.rb +77 -0
  54. data/lib/add_auth/core/passwords/bcrypt_support.rb +28 -0
  55. data/lib/add_auth/core/passwords/credential.rb +49 -0
  56. data/lib/add_auth/core/passwords/legacy_bcrypt.rb +43 -0
  57. data/lib/add_auth/core/passwords/lifecycle.rb +60 -0
  58. data/lib/add_auth/core/remaining_factors.rb +24 -0
  59. data/lib/add_auth/core/sessions.rb +102 -17
  60. data/lib/add_auth/core/step_up.rb +13 -1
  61. data/lib/add_auth/migration/static_inventory.rb +116 -0
  62. data/lib/add_auth/rails/account_password_entry.rb +24 -0
  63. data/lib/add_auth/rails/authentication.rb +1 -1
  64. data/lib/add_auth/rails/authentication_pages.rb +5 -0
  65. data/lib/add_auth/rails/doctor.rb +70 -3
  66. data/lib/add_auth/rails/ejection.rb +1 -1
  67. data/lib/add_auth/rails/engine.rb +6 -1
  68. data/lib/add_auth/rails/migration/account_store.rb +41 -0
  69. data/lib/add_auth/rails/migration/effective_inventory.rb +162 -0
  70. data/lib/add_auth/rails/migration/key_type.rb +23 -0
  71. data/lib/add_auth/rails/migration/source_bridge.rb +53 -0
  72. data/lib/add_auth/rails/password_adoption.rb +37 -0
  73. data/lib/add_auth/rails/provider_enrollment.rb +61 -0
  74. data/lib/add_auth/rails/provider_libraries/apple_form_post_correlation.rb +127 -0
  75. data/lib/add_auth/rails/provider_libraries/apple_jwks.rb +68 -0
  76. data/lib/add_auth/rails/provider_libraries/apple_native.rb +55 -0
  77. data/lib/add_auth/rails/provider_libraries/callback_route.rb +16 -0
  78. data/lib/add_auth/rails/provider_libraries/omniauth.rb +136 -0
  79. data/lib/add_auth/rails/provider_libraries/omniauth_correlation.rb +107 -0
  80. data/lib/add_auth/rails/provider_libraries/openid_connect.rb +62 -0
  81. data/lib/add_auth/rails/provider_libraries/request_protection.rb +43 -0
  82. data/lib/add_auth/rails/runtime.rb +170 -8
  83. data/lib/add_auth/rails/stores/account_credentials.rb +44 -0
  84. data/lib/add_auth/rails/stores/account_lock.rb +16 -7
  85. data/lib/add_auth/rails/stores/account_tokens.rb +96 -0
  86. data/lib/add_auth/rails/stores/authority.rb +28 -0
  87. data/lib/add_auth/rails/stores/commit_dispatch.rb +32 -0
  88. data/lib/add_auth/rails/stores/external_identities.rb +91 -0
  89. data/lib/add_auth/rails/stores/maintenance.rb +1 -1
  90. data/lib/add_auth/rails/stores/mobile_handoffs.rb +53 -0
  91. data/lib/add_auth/rails/stores/security_events.rb +3 -6
  92. data/lib/add_auth/rails/user_lifecycle.rb +8 -10
  93. data/lib/add_auth/result.rb +1 -0
  94. data/lib/add_auth/version.rb +1 -1
  95. data/lib/add_auth.rb +4 -0
  96. data/lib/generators/add_auth/accounts/accounts_generator.rb +52 -0
  97. data/lib/generators/add_auth/accounts/templates/add_add_auth_accounts.rb.tt +49 -0
  98. data/lib/generators/add_auth/accounts/templates/add_auth_account_token.rb +6 -0
  99. data/lib/generators/add_auth/accounts/templates/add_auth_address_claim.rb +6 -0
  100. data/lib/generators/add_auth/devise_accounts/devise_accounts_generator.rb +23 -0
  101. data/lib/generators/add_auth/devise_accounts/templates/prepare_add_auth_devise_accounts.rb.tt +17 -0
  102. data/lib/generators/add_auth/email_link/templates/add_auth_challenge.js +1 -0
  103. data/lib/generators/add_auth/email_tokens/templates/create_add_auth_sign_in_tokens.rb.tt +3 -1
  104. data/lib/generators/add_auth/external_identities/external_identities_generator.rb +52 -0
  105. data/lib/generators/add_auth/external_identities/templates/add_add_auth_external_enrollment.rb.tt +5 -0
  106. data/lib/generators/add_auth/external_identities/templates/add_add_auth_external_identity_validity.rb.tt +5 -0
  107. data/lib/generators/add_auth/external_identities/templates/add_auth_external_identity.rb +4 -0
  108. data/lib/generators/add_auth/external_identities/templates/add_auth_external_transaction.rb +6 -0
  109. data/lib/generators/add_auth/external_identities/templates/create_add_auth_external_identities.rb.tt +50 -0
  110. data/lib/generators/add_auth/external_identities/templates/providers.rb +51 -0
  111. data/lib/generators/add_auth/install/templates/initializer.rb +4 -0
  112. data/lib/generators/add_auth/javascript/templates/passkey.js +8 -5
  113. data/lib/generators/add_auth/mobile_sessions/mobile_sessions_generator.rb +48 -0
  114. data/lib/generators/add_auth/mobile_sessions/templates/add_add_auth_mobile_sessions.rb.tt +9 -0
  115. data/lib/generators/add_auth/mobile_sessions/templates/add_auth_mobile_handoff.rb +5 -0
  116. data/lib/generators/add_auth/mobile_sessions/templates/create_add_auth_mobile_handoffs.rb.tt +27 -0
  117. data/lib/generators/add_auth/mobile_sessions/templates/mobile.rb +8 -0
  118. data/lib/generators/add_auth/notifications/templates/create_add_auth_security_events.rb.tt +3 -1
  119. data/lib/generators/add_auth/passkeys/templates/add_add_auth_passkeys.rb.tt +7 -3
  120. data/lib/generators/add_auth/step_up/templates/add_add_auth_reauthentication.rb.tt +3 -1
  121. data/lib/tasks/add_auth.rake +18 -1
  122. metadata +85 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: fb4c1fc11f2a73f54a42c1bc5379f328d63e4e3cf4124f83ceaa87187ee04ed8
4
- data.tar.gz: bb0c1cf6c79e629cf3257a161c867dc0ea722596ccebe6a9b4dd609881e2be6a
3
+ metadata.gz: 5856e424011a6e4b49dabd271324aadd29862aad6e0dc5a8b5e06c6e1ea27186
4
+ data.tar.gz: 5169c854a4428c4daee558abb68208eae29a1f5ae98b91478c9e803ccb3383d4
5
5
  SHA512:
6
- metadata.gz: b9cf550a17e5736ac23be86f151571b6434949c137295c9e12489cead2ba5393d26bb2652c50469eb40775dcb85e8b937bbf1a0fbe5d2359b9d07172efa94fa3
7
- data.tar.gz: 76d3b435c7f3f6994f639a9d397cba861d1e25071a6143a121d596549c85e9736c5d4a0a54caff1bf7f05e77a8b168915e909002aef847aaaf41d406547de308
6
+ metadata.gz: ee5a6a0df85d2719372eba860f0afb1030c3c1ccaf5644fd0ece45395e0f648bd689e0cb8b8b48a04ffa3fb5d066aab5aa8d4925ba502d0db26e8922c48302a9
7
+ data.tar.gz: 5b9bcc71dd3f72271dd140eb9b335cc93feb6222ec4a95e012afc2eaaa6f96945cd3ff7193e2fbfd816dc4e66bcaea293f9075ae9948931892952a4ba9b25b74
data/CHANGELOG.md CHANGED
@@ -1,5 +1,27 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.3.0 — 2026-09-09
4
+
5
+ Optional account lifecycle, bounded Devise migration and native authentication.
6
+ The standard Rails-generator installation remains supported; enable only the
7
+ additional capabilities your host needs. The latest-0.x support policy continues.
8
+
9
+ - Add read-only Devise inventory, additive account preparation, explicit legacy
10
+ password adoption and retired-source write fences. Preserve account IDs and
11
+ reject ambiguous identifier/credential conversions.
12
+ - Add optional registration, confirmation/reconfirmation, reset, lock/unlock,
13
+ account changes and remembered sessions with shared Core policy and delivery.
14
+ - Integrate maintained provider libraries without owning their registrations,
15
+ protocol exchange, unrelated routes or provider API credentials. Preserve
16
+ identity ownership and require independent enrollment confirmation.
17
+ - Add opt-in finite native sessions, account-scoped revocation, S256-bound
18
+ single-use browser handoffs and optional nonce-bound Apple token verification.
19
+ Mobile duration settings are separate from browser settings; refresh-token
20
+ families are not part of this finite profile.
21
+ - Verify generated/ejected pages with Turbo, ordinary JavaScript without Turbo
22
+ and permitted no-JS. Add populated migration, checkpoint, compatibility
23
+ rollback, provider and native integration fixtures.
24
+
3
25
  ## 0.2.2 — 2026-09-08
4
26
 
5
27
  - Reject Solid Cache as an authentication rate-limit store: simultaneous first
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # AddAuth
2
2
 
3
- **0.2.1:** password, email-link and passkey sign-in extend
3
+ **0.3.0:** password, email-link and passkey sign-in extend
4
4
  Rails' generated authentication. AddAuth adds hardened sessions, verification
5
5
  for sensitive actions, passkey management and email recovery with an optional
6
6
  strict policy. Turnstile and reCAPTCHA integrations are available. See
@@ -9,6 +9,11 @@ strict policy. Turnstile and reCAPTCHA integrations are available. See
9
9
  AddAuth builds on top of Rails 8's built-in login system instead of
10
10
  replacing it -- it keeps using your existing `User` and `Session` models.
11
11
 
12
+ Version 0.3.0 adds optional account lifecycle, bounded Devise migration,
13
+ provider-library integration and finite Android/iOS sessions. See the
14
+ [migration guide](https://addauthgem.com/migrating-from-devise/) before changing
15
+ an existing Devise app; do not run the Rails generator over its custom files.
16
+
12
17
  Read the [documentation](https://addauthgem.com) for setup guides, configuration
13
18
  reference and troubleshooting.
14
19
 
@@ -18,18 +23,18 @@ Install the `add_auth` gem from RubyGems through your Rails app’s Gemfile,
18
23
  then enable password and email-link sign-in. Use Ruby 3.3+ and Rails 8.0+
19
24
  with Active Record, and run the commands below from your Rails app’s root.
20
25
 
21
- **Release availability:** these commands require the published `0.2.1` package.
26
+ **Release availability:** these commands require the published `0.3.0` package.
22
27
  If it is not yet listed on [RubyGems](https://rubygems.org/gems/add_auth/versions),
23
28
  wait for publication; see [release status](https://addauthgem.com/release-status/).
24
29
  Start in your app’s development environment; use the deployment settings
25
30
  below before enabling sign-in for users.
26
31
 
27
32
  1. Keep `source "https://rubygems.org"` in your app’s Gemfile and add the
28
- 0.2 release line:
33
+ 0.3 release line:
29
34
 
30
35
  ```ruby
31
36
  # Gemfile
32
- gem "add_auth", "~> 0.2.1"
37
+ gem "add_auth", "~> 0.3.0"
33
38
  ```
34
39
 
35
40
  Bundler downloads the package from RubyGems and records the resolved
@@ -39,8 +44,8 @@ below before enabling sign-in for users.
39
44
  bundle install
40
45
  ```
41
46
 
42
- 2. If your app doesn't already have Rails' built-in login system, add it
43
- first:
47
+ 2. For a fresh app without an existing authentication system, generate
48
+ Rails' built-in login system:
44
49
 
45
50
  ```sh
46
51
  bin/rails generate authentication
@@ -533,14 +538,33 @@ or recovery procedures work in production.
533
538
 
534
539
  ## Scope
535
540
 
536
- AddAuth 0.2 implements password/email/passkey sign-in, session
537
- adoption and management, purpose-bound verification, passkey recovery and
538
- strict account policy, durable security mail, and challenge adapters. Account
539
- provisioning, address confirmation and password reset remain host responsibilities.
540
- Release acceptance uses local real-database, generated-host, browser and
541
- SMTP/queue/cache tests. Hosts verify their own live providers and supported
542
- physical devices before deployment. Recovery codes and AddAuth-owned password
543
- policy remain deferred.
541
+ AddAuth 0.3 implements password/email/passkey sign-in, session adoption and
542
+ management, purpose-bound verification, passkey recovery and strict account
543
+ policy, durable security mail and challenge adapters. Optional account lifecycle
544
+ adds registration, confirmation/reconfirmation, password reset, lock/unlock and
545
+ account changes. Hosts retain account eligibility, resource authorization and
546
+ provider registrations.
547
+
548
+ ## Optional integrations in 0.3.0
549
+
550
+ Enable bounded [Devise migration](https://addauthgem.com/migrating-from-devise/),
551
+ [account lifecycle](https://addauthgem.com/account-lifecycle/),
552
+ [provider sign-in](https://addauthgem.com/omniauth/) and
553
+ [Android/iOS sessions](https://addauthgem.com/mobile-authentication/) independently.
554
+ Provider protocol handling stays with maintained libraries. Native sessions use
555
+ finite expiry; the verified profile is 30 days absolute and 14 days idle, with
556
+ fresh sign-in after expiry. Refresh-token families are not included.
557
+
558
+ Migration evidence covers the documented Devise 5.0.4 source profiles, including
559
+ integer and UUID accounts; it does not establish universal Devise-module parity.
560
+ Preserve current credentials and revocations during cutover and compatible
561
+ rollback. Follow the migration guide before removing Devise or retiring columns.
562
+
563
+ Release acceptance uses local real-database, generated-host, browser,
564
+ SMTP/queue/cache and native-consumer tests. Hosts verify their own live providers
565
+ and supported physical devices before deployment. Recovery codes and multiple
566
+ realms remain deferred. See [ROADMAP.md](ROADMAP.md) for scope and progress, and
567
+ [CONTRIBUTING.md](CONTRIBUTING.md) for synthetic acceptance commands.
544
568
 
545
569
  ## Roadmap
546
570
 
data/ROADMAP.md CHANGED
@@ -4,15 +4,16 @@ AddAuth extends Rails 8's authentication generator with email-link sign-in,
4
4
  passkeys, purpose-bound reauthentication, hardened sessions and pluggable captcha.
5
5
  It reuses the host's accounts and Session model.
6
6
 
7
- **Reviewed 2026-09-07: 0.2.1 is published, and the planned v1 strategy features
8
- have shipped.** The next work makes adoption, upgrades and the future 1.0 support
9
- commitment clearer. Milestones below describe priorities, not promised dates or
7
+ **Reviewed 2026-09-09: 0.2.2 is published, and the planned v1 strategy features
8
+ have shipped.** The bounded Devise migration and native additions below are locally verified
9
+ and selected for 0.3.0. The future 1.0 support commitment remains separate. Milestones below describe priorities, not promised dates or
10
10
  versions. See [release status](https://addauthgem.com/release-status/),
11
11
  [the changelog](CHANGELOG.md) and [the manual](https://addauthgem.com/).
12
12
 
13
- **0.2.2 is prepared locally, not published.** It contains the counter-store
14
- correction and upgrade/operations coverage described below. The owner selected
15
- this patch before accepting the proposed 1.0 commitment.
13
+ **[0.2.2 is published and verified](https://github.com/taimoorq/add_auth/releases/tag/v0.2.2).**
14
+ It contains the counter-store correction and upgrade/operations coverage below.
15
+ The registry package passes fresh-host installation and the matching manual is
16
+ live. The owner selected this patch before accepting the proposed 1.0 commitment.
16
17
 
17
18
  An unchecked item is remaining work; deferred candidates need a scope decision
18
19
  before implementation. Checked features have passing relevant acceptance specs.
@@ -23,11 +24,10 @@ this public checklist is derived from its section 14 and stands on its own.
23
24
 
24
25
  ## Now — maintain 0.2 and learn from adoption
25
26
 
26
- - [ ] **R1 · Resolve the outstanding dependency update.** Review
27
- [actions/checkout PR #1](https://github.com/taimoorq/add_auth/pull/1) against
28
- current master. Its old failed Ruby checks need current evidence; retain
29
- immutable action pins and all merge/release protections. Record a merge,
30
- replacement or reasoned deferral.
27
+ - [x] **R1 · Resolve the outstanding dependency update.**
28
+ [PR #9](https://github.com/taimoorq/add_auth/pull/9) updates the immutable
29
+ checkout pin to 7.0.1 with current required checks passing. Old Dependabot
30
+ PR #1 is closed as superseded. All merge/release protections remain in force.
31
31
  - [x] **R1 · Complete the 2026-09-07 currency review.** Review Rails/Ruby support,
32
32
  authentication APIs, WebAuthn, advisories and the competitive landscape
33
33
  before the next version bump; repeat the standing review at least quarterly.
@@ -45,9 +45,9 @@ this public checklist is derived from its section 14 and stands on its own.
45
45
  quickstart, doctor and troubleshooting guidance with exact prerequisites,
46
46
  observable success and recovery steps. Keep the public manual tied to the
47
47
  published package. Upgrade, compatibility, cache and ejection guidance is
48
- prepared locally; build and browser checks pass. Publication is pending.
48
+ published for 0.2.2; build, browser and live deployment checks pass.
49
49
 
50
- The current master CI and CodeQL runs pass. The post-publication
50
+ The exact 0.2.2 release commit passed the full CI matrix and CodeQL. The post-publication
51
51
  passwordless-fixture isolation failure is fixed in
52
52
  [PR #8](https://github.com/taimoorq/add_auth/pull/8); it is not an open runtime
53
53
  defect or a reason by itself to republish 0.2.1.
@@ -67,8 +67,8 @@ defect or a reason by itself to republish 0.2.1.
67
67
  whether Solid Cache meets atomic rate-limit increment/TTL requirements;
68
68
  document a tested separate store if needed. Publish only verified adapter
69
69
  support. Solid Queue 1.7.0 passes with a separate queue database. Solid
70
- Cache 1.0.10 loses concurrent first increments on PostgreSQL; the pending
71
- fix rejects it for abuse counters and documents a separate Redis store.
70
+ Cache 1.0.10 loses concurrent first increments on PostgreSQL; 0.2.2
71
+ rejects it for abuse counters and documents a separate Redis store.
72
72
  - [x] **R4 · Measure bounded maintenance and recovery.** Record workload,
73
73
  query counts, backlog drain and latency for session pages, outbox retries
74
74
  and cleanup. Use those measurements to explain batch sizing, retention,
@@ -79,46 +79,95 @@ defect or a reason by itself to republish 0.2.1.
79
79
  Core/host hooks and Results, routes, generators/ejection metadata, testing
80
80
  helpers and redacted events. Distinguish internal APIs and document
81
81
  compatibility, deprecation and migration rules. The current integration
82
- reference and proposed contract are prepared; owner acceptance of the
83
- 1.0 commitment remains the separate item below.
82
+ reference is published; the proposed 1.0 contract remains for owner
83
+ review under the separate item below.
84
84
  - [ ] **R5 · Set the 1.0 support policy.** Specify supported runtime/database
85
85
  combinations and security-supported release lines. Publish an upgrade
86
86
  guide and evidence-backed troubleshooting updates before the candidate
87
87
  freezes. The current latest-0.x policy remains in
88
88
  [SECURITY.md](SECURITY.md).
89
- - [ ] **R8 · Define Devise migration readiness.** Inventory source versions,
89
+ - [x] **R8 · Define Devise migration readiness.** Inventory source versions,
90
90
  modules, extensions and customizations; provide a read-only preflight with
91
91
  supported mappings, prerequisites and actionable blockers. Running Rails 8
92
92
  alone does not establish the authentication contracts AddAuth needs.
93
- - [ ] **R8 · Provide a path for Rails-aligned Devise apps.** Reuse verified
93
+ - [x] **R8 · Provide a path for Rails-aligned Devise apps.** Reuse verified
94
94
  account/session contracts and preserve host customizations while replacing
95
95
  remaining Devise authentication wiring. Prove password compatibility,
96
96
  account restrictions, session/token revocation and safe cutover/rollback.
97
- - [ ] **R8 · Provide a path for standard or customized Devise apps.** Guide an
97
+ - [x] **R8 · Provide a path for standard or customized Devise apps.** Guide an
98
98
  additive conversion from existing models, identifiers, password storage
99
99
  and controllers to the supported Rails contracts. Preserve account IDs and
100
100
  associations; resolve incompatible hashes, identifier collisions and
101
101
  unsupported module policies before cutover. Include older-runtime
102
102
  prerequisites and host-owned lifecycle replacements where needed.
103
- - [ ] **R8 · Rehearse and document both migration paths.** Test populated Devise
103
+ - [x] **R8 · Add optional account lifecycle support.** Registration,
104
+ confirmation/reconfirmation, password reset, lock/unlock and account
105
+ changes must preserve ownership, account policy and session revocation.
106
+ - [x] **R8 · Preserve provider sign-in through maintained libraries.** Add
107
+ optional OmniAuth integration with verified identity binding, explicit
108
+ linking/unlinking and usable recovery for provider-only accounts.
109
+ Preserve host-owned OAuth configuration, routes and other provider flows;
110
+ delegate protocol work to the selected libraries.
111
+ - [x] **R8 · Rehearse both migration paths and draft the guides.** Test populated Devise
104
112
  hosts before and after migration, including custom schema, peppered
105
113
  passwords, denied accounts, interrupted conversion and rollback. Verify
106
- supported database/runtime and Turbo/no-JS journeys, publish step-by-step
107
- guides and prove the destination works with Devise removed.
114
+ supported database/runtime, ordinary HTML with JavaScript and Turbo absent,
115
+ Turbo enhancement and permitted no-JS journeys. Draft step-by-step guides
116
+ and prove the destination works with Devise removed.
117
+ - [ ] **R8 · Publish migration and native guides with the feature release.**
118
+ Verify the released package, installation commands, HTTPS and deep links;
119
+ keep development guides explicitly unpublished until then.
120
+
121
+ Local development has verified preflight, optional account lifecycle and
122
+ provider-library integration across the supported Ruby/Rails matrix, including
123
+ ordinary HTML, Turbo and permitted no-JS journeys. These additions are
124
+ unpublished. Migration/checkpoint/compatible-rollback tests and native client
125
+ acceptance are complete locally. The full adopter suite passes 1,330 examples
126
+ with zero failures and three existing empty scaffold examples pending. Six
127
+ development guides pass build, browser, keyboard and no-JS checks. Publication
128
+ and the proposed 1.0 commitment remain separate gates.
129
+
130
+ ## Locally verified — Android and iOS login
131
+
132
+ Mobile support is now part of the active migration implementation. It is not available in the published gem. These capabilities extend the
133
+ same account and session policies. The verified finite profile uses explicit
134
+ 30-day absolute and 14-day idle limits, with fresh sign-in after expiry. Renewable
135
+ refresh-token families remain a distinct optional capability, currently disabled.
136
+ The owner selected 0.3.0; publication and package verification remain pending.
137
+
138
+ - [x] **R8 · Password and provider login for native clients.** Return credentials
139
+ over authenticated HTTPS responses; preserve supported client contracts.
140
+ - [x] **R8 · Secure browser-to-app sign-in handoffs.** Short-lived, single-use
141
+ codes bound to state and a verifier, exact callback validation, and native
142
+ Apple nonce verification; no reusable bearer tokens in callback URLs.
143
+ - [x] **R8 · Device sessions and revocation.** Expiring, digested API credentials,
144
+ cookie/bearer separation, device listing, current-device and all-device
145
+ logout, and invalidation on password/account changes. Evaluate optional
146
+ renewal with rotation and replay detection as a distinct profile.
147
+ - [x] **R8 · Android/iOS migration acceptance.** Test secure client storage,
148
+ cancellation/restarts, account switching, expiry and concurrent handoffs,
149
+ plus supported old/new client combinations. Android/iOS consumers also
150
+ pass against an installed gem in an isolated Rails adopter over local HTTPS.
108
151
 
109
152
  Each change owns its tests and keeps intermediate releases usable. R3/R4 findings
110
153
  feed R5. A patch may address compatible corrections; another 0.x minor is possible
111
154
  if integration contracts change. A 0.3 release is not a prerequisite for 1.0.
112
- R8 is planned after the prepared 0.2.2 correction; its supported source profiles
113
- and delivery version will be decided during discovery, with public API impact
114
- resolved before R5 freezes. Devise migration support is not available yet.
155
+ R8 follows the published 0.2.2 correction; its supported source profiles
156
+ are bounded to the verified Devise 5.0.4 fixtures. The selected delivery version is 0.3.0. Its pre-1.0 integration contracts
157
+ remain subject to review before R5 freezes. Devise migration support is not available yet.
158
+
159
+ ## Delivery — 0.3.0 feature release, then the proposed 1.0 gate
115
160
 
116
- ## Delivery 0.2.2 first, then the proposed 1.0 gate
161
+ - [x] Select 0.3.0 for the locally verified migration, lifecycle, provider and native scope.
162
+ - [ ] Pass required PR and exact default-branch CI/CodeQL for 0.3.0.
163
+ - [ ] Publish 0.3.0 through protected OIDC and verify the downloaded package and manual.
117
164
 
118
- - [ ] **R6 · Deliver the prepared 0.2.2 correction.** Commit and review the
119
- tested changes, pass current required GitHub checks, publish through
120
- protected OIDC, verify the registry package in a fresh host and publish
121
- the matching manual. Preserve the existing latest-0.x support policy.
165
+ - [x] **R6 · Deliver the 0.2.2 correction.**
166
+ [PR #10](https://github.com/taimoorq/add_auth/pull/10) passed all required
167
+ checks, followed by exact default-branch CI and protected OIDC publication.
168
+ The downloaded package checksum and all 121 files match the reviewed
169
+ candidate; fresh-host installation and the matching public manual pass.
170
+ The existing latest-0.x support policy remains in effect.
122
171
 
123
172
  - [ ] **R6 · Finish the adoption and compatibility review.** R1–R5 findings are
124
173
  completed or explicitly dispositioned, the supported API is accepted, and
@@ -160,19 +209,19 @@ compatible extension could ship in 1.x after an explicit scope decision.
160
209
  before implementation; codes must not silently satisfy passkey-only purposes.
161
210
  - [ ] **F2 · Rails-native password hashing integration.** Recheck available Rails
162
211
  support and real adopter needs before adding an adapter or changing a default.
163
- - [ ] **F3 · Account lifecycle helpers.** Evaluate registration, confirmation,
164
- reset, lockout and password policy individually against repeated needs.
165
- Current host password integration already ships.
212
+ - [ ] **F3 · Account lifecycle helpers.** The bounded migration requirements
213
+ are promoted to R8 above; broader module parity remains outside this scope.
166
214
  - [ ] **F4 · Multiple realms.** Require a concrete identity/session-isolation need
167
215
  and migration design before adding models, cookies or routing APIs.
168
- - [ ] **F4 · API/token authentication.** Decide separately from browser sessions
169
- and realms; require a real non-browser client use case.
216
+ - [ ] **F4 · API/token authentication.** Android/iOS login is the active R8
217
+ work above; multiple realms and general API-key management remain separate.
170
218
  - [ ] **F5 · Standalone test helpers or challenge adapters.** Extract only if
171
219
  independent demand and maintenance capacity justify another package;
172
220
  retain one implementation per concern.
173
221
 
174
- Social/OIDC, SMS/TOTP and enterprise or cross-origin WebAuthn are outside the
175
- current roadmap. Account roles, invitations, authorization and email branding
222
+ Optional provider-library integration is locally verified under R8. SMS/TOTP and enterprise
223
+ or cross-origin WebAuthn remain outside the current roadmap. Account roles,
224
+ invitations, authorization and email branding
176
225
  stay with the host.
177
226
 
178
227
  ## Shipped — 0.2.1 baseline
data/SECURITY.md CHANGED
@@ -34,6 +34,12 @@ mailing list, or social media) until a fix has been released.
34
34
  replacement handling.
35
35
  - Bounded session listing and authentication-history cleanup, including account
36
36
  scoping, eligibility rechecks and active-delivery-lease protection.
37
+ - Optional account lifecycle and migration boundaries: credential
38
+ profile selection, source-write fencing, address ownership, account proofs,
39
+ policy and revocation. These capabilities are introduced in 0.3.0.
40
+ - Optional provider identity binding and mobile sessions/handoffs, including
41
+ nonce/client/purpose binding, exact callbacks, single use and bearer/cookie
42
+ separation. Provider protocol cryptography remains library-owned.
37
43
  - The generated controllers and views this gem ships,
38
44
  including their CSRF, enumeration-safety, and Turbo-Stream behavior.
39
45
  - The GitHub Actions release pipeline (`.github/workflows/push_gem.yml`) and
@@ -0,0 +1,138 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AddAuth
4
+ class AccountsController < ::ApplicationController
5
+ include Rails::AuthenticationPages
6
+
7
+ skip_before_action :require_authentication, raise: false
8
+ layout "add_auth/authentication"
9
+ helper AddAuth::SignInsHelper
10
+ before_action :require_feature
11
+ before_action :private_response
12
+ before_action :require_add_auth_authentication, only: %i[edit_email change_email edit_password change_password confirm_deletion destroy]
13
+ protect_from_forgery with: :exception
14
+ rescue_from AddAuth::Error, "ActiveJob::EnqueueError", with: :service_unavailable
15
+
16
+ def new = page("register")
17
+ def check_email = page("check_email")
18
+
19
+ def create
20
+ admitted = intake(:register)
21
+ return intake_failure(admitted) if admitted.is_a?(Symbol)
22
+ result = Rails::Runtime.accounts.register(identifier: admitted, password: params[:password], profile: registration_profile)
23
+ if result.success?
24
+ redirect_to "/account/check-email", status: :see_other
25
+ else
26
+ @error = "Check your email and choose a password that meets this application's requirements."
27
+ page("register", status: :unprocessable_entity)
28
+ end
29
+ end
30
+
31
+ def request_form
32
+ @purpose = purpose
33
+ page("request")
34
+ end
35
+
36
+ def request_proof
37
+ @purpose = purpose
38
+ admitted = intake(:account_request)
39
+ return intake_failure(admitted) if %i[challenge_rejected challenge_unavailable].include?(admitted)
40
+ Rails::Runtime.enqueue_account_request(identifier: admitted, purpose: @purpose) unless admitted.is_a?(Symbol)
41
+ redirect_to "/account/check-email", status: :see_other
42
+ end
43
+
44
+ def proof
45
+ @purpose = purpose
46
+ result = Rails::Runtime.accounts.preview(token: params[:token], purpose: @purpose)
47
+ page(result.success? ? "proof" : "invalid_proof")
48
+ end
49
+
50
+ def consume
51
+ @purpose = purpose
52
+ admitted = Rails::Runtime.intake.anonymous(ip: request.remote_ip, action: :account_consume)
53
+ return intake_failure(admitted) if admitted.is_a?(Symbol)
54
+ result = Rails::Runtime.accounts.consume(token: params[:token], purpose: @purpose, password: params[:password])
55
+ if result.success?
56
+ redirect_to "/sign-in", status: :see_other, notice: "Your account has been updated. Sign in to start a new session."
57
+ else
58
+ @error = "This request could not be completed. Check the password requirements or request a new link."
59
+ page("proof", status: :unprocessable_entity)
60
+ end
61
+ end
62
+
63
+ def edit_email = page("email")
64
+ def edit_password = page("password")
65
+ def confirm_deletion = page("delete")
66
+
67
+ def destroy
68
+ result = Rails::Runtime.accounts.delete_account(user: Current.user, session: Current.session)
69
+ if result.success?
70
+ terminate_session
71
+ redirect_to "/sign-in", status: :see_other, notice: "Your account was deleted."
72
+ elsif result.reason == :elevation_required
73
+ redirect_to "/reauthenticate?purpose=delete_account", status: :see_other
74
+ else
75
+ @error = "Your account could not be deleted. Contact support if this continues."
76
+ page("delete", status: :unprocessable_entity)
77
+ end
78
+ end
79
+
80
+ def change_password
81
+ result = Rails::Runtime.accounts.change_password(user: Current.user, session: Current.session, password: params[:password])
82
+ if result.success?
83
+ terminate_session
84
+ redirect_to "/sign-in", status: :see_other, notice: "Your password was changed. Sign in with your new password."
85
+ elsif result.reason == :elevation_required
86
+ redirect_to "/reauthenticate?purpose=change_password", status: :see_other
87
+ else
88
+ @error = "This password cannot be used. Check the password requirements and try again."
89
+ page("password", status: :unprocessable_entity)
90
+ end
91
+ end
92
+
93
+ def change_email
94
+ result = Rails::Runtime.accounts.change_email(user: Current.user, session: Current.session, identifier: params[:email_address])
95
+ if result.success?
96
+ redirect_to "/account/check-email", status: :see_other
97
+ elsif result.reason == :elevation_required
98
+ redirect_to "/reauthenticate?purpose=change_email", status: :see_other
99
+ else
100
+ @error = "This email address cannot be used. Check it and try again."
101
+ page("email", status: :unprocessable_entity)
102
+ end
103
+ end
104
+
105
+ private
106
+
107
+ # Host controllers may override with an explicit Rails parameter allowlist.
108
+ # The default does not forward arbitrary account/role parameters.
109
+ def registration_profile = {}
110
+
111
+ def require_feature
112
+ head :not_found unless AddAuth.configuration.lifecycle.enabled
113
+ end
114
+
115
+ def purpose
116
+ Core::AccountLifecycle::PURPOSES.include?(params[:purpose]) ? params[:purpose] : "confirm"
117
+ end
118
+
119
+ def intake(action)
120
+ Rails::Runtime.intake.call(identifier: params[:email_address], ip: request.remote_ip, action: action, challenge_token: challenge_token)
121
+ end
122
+
123
+ def authentication_partial(partial) = "add_auth/accounts/#{partial}"
124
+
125
+ def service_unavailable
126
+ @error = "Account requests are temporarily unavailable. Please try again shortly."
127
+ response.set_header("Retry-After", "60")
128
+ page("unavailable", status: :service_unavailable)
129
+ end
130
+
131
+ def intake_failure(reason)
132
+ @error = "This request could not be verified. Please try again shortly."
133
+ status = {rate_limited: :too_many_requests, challenge_unavailable: :service_unavailable}.fetch(reason, :unprocessable_entity)
134
+ response.set_header("Retry-After", "300") if %i[too_many_requests service_unavailable].include?(status)
135
+ page("unavailable", status: status)
136
+ end
137
+ end
138
+ end
@@ -20,7 +20,8 @@ module AddAuth
20
20
  end
21
21
 
22
22
  def boot
23
- source = 'if (!window.Turbo) await import("/add_auth/turbo.js"); await import("/add_auth/challenge.js");'
23
+ source = Rails::Runtime.config.turbo_enabled ? 'if (!window.Turbo) await import("/add_auth/turbo.js");' : ""
24
+ source += ' await import("/add_auth/challenge.js");'
24
25
  source += ' await import("/add_auth/passkey.js");' if Rails::Runtime.config.passkeys.enabled
25
26
  render body: source, content_type: "text/javascript"
26
27
  end
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AddAuth
4
+ class ExternalIdentitiesController < ::ApplicationController
5
+ include Rails::AuthenticationPages
6
+
7
+ skip_before_action :require_authentication, raise: false
8
+ before_action :private_response
9
+ before_action :require_feature
10
+ before_action :require_add_auth_authentication
11
+ protect_from_forgery with: :exception
12
+ helper AddAuth::SignInsHelper
13
+ layout "add_auth/authentication"
14
+ rescue_from AddAuth::Error, with: :unavailable
15
+
16
+ def index
17
+ load_identities
18
+ page("index")
19
+ end
20
+
21
+ def destroy
22
+ elevation = Rails::Runtime.sessions.with_elevation(user: Current.user, session: Current.session,
23
+ purpose: Core::ExternalIdentities::UNLINK, policy: Rails::Runtime.step_up_policy)
24
+ result = service.unlink(user: Current.user, session: Current.session, identity_id: params[:id], grant: elevation.credential)
25
+ if result.success?
26
+ redirect_to "/account/external-identities", status: :see_other
27
+ elsif result.reason == :elevation_required
28
+ redirect_to "/reauthenticate?purpose=unlink_external_identity", status: :see_other
29
+ else
30
+ @error = (result.reason == :last_credential) ? "Add another usable sign-in method before removing this one." : "This sign-in method could not be changed. Try again."
31
+ load_identities
32
+ page("index", status: :unprocessable_entity)
33
+ end
34
+ end
35
+
36
+ private
37
+
38
+ def service = Rails::Runtime.external_identities
39
+
40
+ def require_feature
41
+ head :not_found unless Rails::Runtime.config.external_identities.enabled
42
+ end
43
+
44
+ def authentication_partial(partial) = "add_auth/external_identities/#{partial}"
45
+
46
+ def load_identities
47
+ @identities = ::AddAuthExternalIdentity.where(user_id: Current.user.id, revoked_at: nil).order(:linked_at)
48
+ @providers = Rails::Runtime.config.external_identities.providers
49
+ end
50
+
51
+ def unavailable
52
+ @error = "External sign-in is temporarily unavailable."
53
+ page("unavailable", status: :service_unavailable)
54
+ end
55
+ end
56
+ end