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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +22 -0
- data/README.md +38 -14
- data/ROADMAP.md +87 -38
- data/SECURITY.md +6 -0
- data/app/controllers/add_auth/accounts_controller.rb +138 -0
- data/app/controllers/add_auth/assets_controller.rb +2 -1
- data/app/controllers/add_auth/external_identities_controller.rb +56 -0
- data/app/controllers/add_auth/mobile_sessions_controller.rb +120 -0
- data/app/controllers/add_auth/provider_sign_ins_controller.rb +248 -0
- data/app/helpers/add_auth/sign_ins_helper.rb +2 -1
- data/app/jobs/add_auth/account_delivery_job.rb +14 -0
- data/app/jobs/add_auth/account_request_job.rb +17 -0
- data/app/mailers/add_auth/account_mailer.rb +12 -0
- data/app/views/add_auth/account_mailer/link.text.erb +7 -0
- data/app/views/add_auth/accounts/_check_email.html.erb +5 -0
- data/app/views/add_auth/accounts/_delete.html.erb +6 -0
- data/app/views/add_auth/accounts/_email.html.erb +12 -0
- data/app/views/add_auth/accounts/_invalid_proof.html.erb +5 -0
- data/app/views/add_auth/accounts/_password.html.erb +12 -0
- data/app/views/add_auth/accounts/_proof.html.erb +16 -0
- data/app/views/add_auth/accounts/_register.html.erb +17 -0
- data/app/views/add_auth/accounts/_request.html.erb +14 -0
- data/app/views/add_auth/accounts/_unavailable.html.erb +3 -0
- data/app/views/add_auth/external_identities/_index.html.erb +22 -0
- data/app/views/add_auth/external_identities/_unavailable.html.erb +3 -0
- data/app/views/add_auth/passkeys/_controls.html.erb +3 -1
- data/app/views/add_auth/provider_sign_ins/_enrollment.html.erb +12 -0
- data/app/views/add_auth/provider_sign_ins/_failure.html.erb +3 -0
- data/app/views/add_auth/provider_sign_ins/_prepare.html.erb +7 -0
- data/app/views/add_auth/reauthentications/_form.html.erb +9 -0
- data/app/views/add_auth/sessions/_list.html.erb +1 -1
- data/app/views/add_auth/sessions/index.html.erb +7 -0
- data/app/views/add_auth/sign_ins/_confirmation.html.erb +1 -0
- data/app/views/add_auth/sign_ins/_form.html.erb +17 -0
- data/app/views/add_auth/sign_ins/_remember.html.erb +6 -0
- data/app/views/layouts/add_auth/authentication.html.erb +4 -3
- data/exe/add_auth-preflight +26 -0
- data/lib/add_auth/configuration.rb +78 -4
- data/lib/add_auth/core/access_policy.rb +11 -1
- data/lib/add_auth/core/account_lifecycle.rb +251 -0
- data/lib/add_auth/core/account_policy.rb +50 -0
- data/lib/add_auth/core/external_identities/evidence.rb +98 -0
- data/lib/add_auth/core/external_identities.rb +278 -0
- data/lib/add_auth/core/intake.rb +6 -1
- data/lib/add_auth/core/maintenance.rb +5 -3
- data/lib/add_auth/core/migration/account_adoption.rb +79 -0
- data/lib/add_auth/core/migration/readiness.rb +41 -0
- data/lib/add_auth/core/mobile_authentication.rb +35 -0
- data/lib/add_auth/core/mobile_handoffs.rb +83 -0
- data/lib/add_auth/core/mobile_profile.rb +46 -0
- data/lib/add_auth/core/mobile_response.rb +12 -0
- data/lib/add_auth/core/native_authentication.rb +77 -0
- data/lib/add_auth/core/passwords/bcrypt_support.rb +28 -0
- data/lib/add_auth/core/passwords/credential.rb +49 -0
- data/lib/add_auth/core/passwords/legacy_bcrypt.rb +43 -0
- data/lib/add_auth/core/passwords/lifecycle.rb +60 -0
- data/lib/add_auth/core/remaining_factors.rb +24 -0
- data/lib/add_auth/core/sessions.rb +102 -17
- data/lib/add_auth/core/step_up.rb +13 -1
- data/lib/add_auth/migration/static_inventory.rb +116 -0
- data/lib/add_auth/rails/account_password_entry.rb +24 -0
- data/lib/add_auth/rails/authentication.rb +1 -1
- data/lib/add_auth/rails/authentication_pages.rb +5 -0
- data/lib/add_auth/rails/doctor.rb +70 -3
- data/lib/add_auth/rails/ejection.rb +1 -1
- data/lib/add_auth/rails/engine.rb +6 -1
- data/lib/add_auth/rails/migration/account_store.rb +41 -0
- data/lib/add_auth/rails/migration/effective_inventory.rb +162 -0
- data/lib/add_auth/rails/migration/key_type.rb +23 -0
- data/lib/add_auth/rails/migration/source_bridge.rb +53 -0
- data/lib/add_auth/rails/password_adoption.rb +37 -0
- data/lib/add_auth/rails/provider_enrollment.rb +61 -0
- data/lib/add_auth/rails/provider_libraries/apple_form_post_correlation.rb +127 -0
- data/lib/add_auth/rails/provider_libraries/apple_jwks.rb +68 -0
- data/lib/add_auth/rails/provider_libraries/apple_native.rb +55 -0
- data/lib/add_auth/rails/provider_libraries/callback_route.rb +16 -0
- data/lib/add_auth/rails/provider_libraries/omniauth.rb +136 -0
- data/lib/add_auth/rails/provider_libraries/omniauth_correlation.rb +107 -0
- data/lib/add_auth/rails/provider_libraries/openid_connect.rb +62 -0
- data/lib/add_auth/rails/provider_libraries/request_protection.rb +43 -0
- data/lib/add_auth/rails/runtime.rb +170 -8
- data/lib/add_auth/rails/stores/account_credentials.rb +44 -0
- data/lib/add_auth/rails/stores/account_lock.rb +16 -7
- data/lib/add_auth/rails/stores/account_tokens.rb +96 -0
- data/lib/add_auth/rails/stores/authority.rb +28 -0
- data/lib/add_auth/rails/stores/commit_dispatch.rb +32 -0
- data/lib/add_auth/rails/stores/external_identities.rb +91 -0
- data/lib/add_auth/rails/stores/maintenance.rb +1 -1
- data/lib/add_auth/rails/stores/mobile_handoffs.rb +53 -0
- data/lib/add_auth/rails/stores/security_events.rb +3 -6
- data/lib/add_auth/rails/user_lifecycle.rb +8 -10
- data/lib/add_auth/result.rb +1 -0
- data/lib/add_auth/version.rb +1 -1
- data/lib/add_auth.rb +4 -0
- data/lib/generators/add_auth/accounts/accounts_generator.rb +52 -0
- data/lib/generators/add_auth/accounts/templates/add_add_auth_accounts.rb.tt +49 -0
- data/lib/generators/add_auth/accounts/templates/add_auth_account_token.rb +6 -0
- data/lib/generators/add_auth/accounts/templates/add_auth_address_claim.rb +6 -0
- data/lib/generators/add_auth/devise_accounts/devise_accounts_generator.rb +23 -0
- data/lib/generators/add_auth/devise_accounts/templates/prepare_add_auth_devise_accounts.rb.tt +17 -0
- data/lib/generators/add_auth/email_link/templates/add_auth_challenge.js +1 -0
- data/lib/generators/add_auth/email_tokens/templates/create_add_auth_sign_in_tokens.rb.tt +3 -1
- data/lib/generators/add_auth/external_identities/external_identities_generator.rb +52 -0
- data/lib/generators/add_auth/external_identities/templates/add_add_auth_external_enrollment.rb.tt +5 -0
- data/lib/generators/add_auth/external_identities/templates/add_add_auth_external_identity_validity.rb.tt +5 -0
- data/lib/generators/add_auth/external_identities/templates/add_auth_external_identity.rb +4 -0
- data/lib/generators/add_auth/external_identities/templates/add_auth_external_transaction.rb +6 -0
- data/lib/generators/add_auth/external_identities/templates/create_add_auth_external_identities.rb.tt +50 -0
- data/lib/generators/add_auth/external_identities/templates/providers.rb +51 -0
- data/lib/generators/add_auth/install/templates/initializer.rb +4 -0
- data/lib/generators/add_auth/javascript/templates/passkey.js +8 -5
- data/lib/generators/add_auth/mobile_sessions/mobile_sessions_generator.rb +48 -0
- data/lib/generators/add_auth/mobile_sessions/templates/add_add_auth_mobile_sessions.rb.tt +9 -0
- data/lib/generators/add_auth/mobile_sessions/templates/add_auth_mobile_handoff.rb +5 -0
- data/lib/generators/add_auth/mobile_sessions/templates/create_add_auth_mobile_handoffs.rb.tt +27 -0
- data/lib/generators/add_auth/mobile_sessions/templates/mobile.rb +8 -0
- data/lib/generators/add_auth/notifications/templates/create_add_auth_security_events.rb.tt +3 -1
- data/lib/generators/add_auth/passkeys/templates/add_add_auth_passkeys.rb.tt +7 -3
- data/lib/generators/add_auth/step_up/templates/add_add_auth_reauthentication.rb.tt +3 -1
- data/lib/tasks/add_auth.rake +18 -1
- metadata +85 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 5856e424011a6e4b49dabd271324aadd29862aad6e0dc5a8b5e06c6e1ea27186
|
|
4
|
+
data.tar.gz: 5169c854a4428c4daee558abb68208eae29a1f5ae98b91478c9e803ccb3383d4
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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.
|
|
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.
|
|
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.
|
|
33
|
+
0.3 release line:
|
|
29
34
|
|
|
30
35
|
```ruby
|
|
31
36
|
# Gemfile
|
|
32
|
-
gem "add_auth", "~> 0.
|
|
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.
|
|
43
|
-
|
|
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.
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
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-
|
|
8
|
-
have shipped.** The
|
|
9
|
-
commitment
|
|
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
|
|
14
|
-
correction and upgrade/operations coverage
|
|
15
|
-
|
|
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
|
-
- [
|
|
27
|
-
[
|
|
28
|
-
|
|
29
|
-
|
|
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
|
-
|
|
48
|
+
published for 0.2.2; build, browser and live deployment checks pass.
|
|
49
49
|
|
|
50
|
-
The
|
|
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;
|
|
71
|
-
|
|
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
|
|
83
|
-
|
|
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
|
-
- [
|
|
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
|
-
- [
|
|
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
|
-
- [
|
|
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
|
-
- [
|
|
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
|
|
107
|
-
|
|
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
|
|
113
|
-
|
|
114
|
-
|
|
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
|
-
|
|
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
|
-
- [
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
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.**
|
|
164
|
-
|
|
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.**
|
|
169
|
-
|
|
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
|
-
|
|
175
|
-
current roadmap. Account roles,
|
|
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");
|
|
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
|