add_auth 0.3.0 → 0.4.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 +14 -0
- data/README.md +32 -9
- data/ROADMAP.md +37 -31
- data/SECURITY.md +3 -1
- data/app/controllers/add_auth/accounts_controller.rb +10 -2
- data/app/controllers/add_auth/reauthentications_controller.rb +5 -0
- data/app/views/add_auth/accounts/_register.html.erb +1 -1
- data/app/views/add_auth/accounts/_request.html.erb +3 -0
- data/lib/add_auth/configuration.rb +2 -2
- data/lib/add_auth/core/account_lifecycle.rb +51 -7
- data/lib/add_auth/core/account_policy.rb +21 -3
- data/lib/add_auth/rails/authentication_pages.rb +1 -1
- data/lib/add_auth/rails/doctor.rb +10 -3
- data/lib/add_auth/rails/runtime.rb +13 -4
- data/lib/add_auth/rails/stores/account_tokens.rb +10 -2
- data/lib/add_auth/version.rb +1 -1
- data/lib/generators/add_auth/accounts/accounts_generator.rb +5 -1
- data/lib/generators/add_auth/accounts/templates/add_add_auth_provisioning.rb.tt +9 -0
- data/lib/generators/add_auth/email_link/email_link_generator.rb +4 -1
- data/lib/generators/add_auth/feature_configuration.rb +21 -0
- data/lib/generators/add_auth/install/templates/initializer.rb +154 -50
- data/lib/generators/add_auth/notifications/notifications_generator.rb +3 -4
- data/lib/generators/add_auth/passkeys/passkeys_generator.rb +3 -21
- data/lib/generators/add_auth/session_upgrade/session_upgrade_generator.rb +3 -1
- data/lib/generators/add_auth/step_up/step_up_generator.rb +5 -5
- metadata +17 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 1ed4ef07ed6308f6bb4b82de3e67552af3604472f38e7a379f1d9c863f76954e
|
|
4
|
+
data.tar.gz: 8189c0ddf517a9d06d291738e0d0d20bb383ffc9a7fdd085c9df33ad914312d4
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 643216bb646e0ca6032f0b53f1928eb6da0b4efeffccadd8477fbb7776f6aa411c6ecf36bb4c3f785d94fa435320c04745a2565c70cef47e387f14fc534b805b
|
|
7
|
+
data.tar.gz: 48375790d81d186fa5cfd6f4f3ecf4672c46b2ec8602a3597c5a40b69562661212de41f5df8676a7080ef96ab36915af6dec798400aa97610b5b3c22d76be2d4
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.4.0 — 2026-09-13
|
|
4
|
+
|
|
5
|
+
- Constrain JSON below 3.0 for the supported Rails decoding API; independently
|
|
6
|
+
installed hosts must be able to decrypt account and email proof deliveries.
|
|
7
|
+
|
|
8
|
+
- Add explicit optional password-signup confirmation with atomic provisioning and
|
|
9
|
+
initial session issuance. Unverified email stays unverified; required
|
|
10
|
+
confirmation remains the default.
|
|
11
|
+
- Add an independent opt-in for single-use password reset proofs to unconfirmed
|
|
12
|
+
password accounts, retaining account restrictions and authority revocation.
|
|
13
|
+
- Allow password-only lifecycle/step-up generation without enabling email sign-in.
|
|
14
|
+
- Expand the generated Rails initializer with executable defaults and commented
|
|
15
|
+
alternatives. Feature generators update their own setting in that file.
|
|
16
|
+
|
|
3
17
|
## 0.3.0 — 2026-09-09
|
|
4
18
|
|
|
5
19
|
Optional account lifecycle, bounded Devise migration and native authentication.
|
data/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# AddAuth
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
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,7 +9,7 @@ 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
|
-
|
|
12
|
+
Introduced in 0.3.0: optional account lifecycle, bounded Devise migration,
|
|
13
13
|
provider-library integration and finite Android/iOS sessions. See the
|
|
14
14
|
[migration guide](https://addauthgem.com/migrating-from-devise/) before changing
|
|
15
15
|
an existing Devise app; do not run the Rails generator over its custom files.
|
|
@@ -17,24 +17,38 @@ an existing Devise app; do not run the Rails generator over its custom files.
|
|
|
17
17
|
Read the [documentation](https://addauthgem.com) for setup guides, configuration
|
|
18
18
|
reference and troubleshooting.
|
|
19
19
|
|
|
20
|
+
## Configuration and optional signup confirmation
|
|
21
|
+
|
|
22
|
+
Version 0.4.0 adds optional password-signup confirmation and a
|
|
23
|
+
complete, commented [Rails initializer](lib/generators/add_auth/install/templates/initializer.rb).
|
|
24
|
+
Its default assignments are active; opt-in alternatives are commented out.
|
|
25
|
+
Required confirmation remains the default. See the [configuration guide](https://addauthgem.com/configuration/)
|
|
26
|
+
and [account setup](https://addauthgem.com/configuration-preview/), or the
|
|
27
|
+
[contributor rehearsal](CONTRIBUTING.md#optional-confirmation-integration-rehearsal)
|
|
28
|
+
for verification in a disposable host.
|
|
29
|
+
|
|
20
30
|
## Quickstart
|
|
21
31
|
|
|
32
|
+
These commands target 0.4.0 and are available after its
|
|
33
|
+
[RubyGems publication](https://rubygems.org/gems/add_auth/versions/0.4.0).
|
|
34
|
+
Check package availability before changing your app’s bundle.
|
|
35
|
+
|
|
22
36
|
Install the `add_auth` gem from RubyGems through your Rails app’s Gemfile,
|
|
23
37
|
then enable password and email-link sign-in. Use Ruby 3.3+ and Rails 8.0+
|
|
24
38
|
with Active Record, and run the commands below from your Rails app’s root.
|
|
25
39
|
|
|
26
|
-
**Release availability:** these commands require the published `0.
|
|
40
|
+
**Release availability:** these commands require the published `0.4.0` package.
|
|
27
41
|
If it is not yet listed on [RubyGems](https://rubygems.org/gems/add_auth/versions),
|
|
28
42
|
wait for publication; see [release status](https://addauthgem.com/release-status/).
|
|
29
43
|
Start in your app’s development environment; use the deployment settings
|
|
30
44
|
below before enabling sign-in for users.
|
|
31
45
|
|
|
32
46
|
1. Keep `source "https://rubygems.org"` in your app’s Gemfile and add the
|
|
33
|
-
0.
|
|
47
|
+
0.4 release line:
|
|
34
48
|
|
|
35
49
|
```ruby
|
|
36
50
|
# Gemfile
|
|
37
|
-
gem "add_auth", "~> 0.
|
|
51
|
+
gem "add_auth", "~> 0.4.0"
|
|
38
52
|
```
|
|
39
53
|
|
|
40
54
|
Bundler downloads the package from RubyGems and records the resolved
|
|
@@ -51,10 +65,19 @@ below before enabling sign-in for users.
|
|
|
51
65
|
bin/rails generate authentication
|
|
52
66
|
```
|
|
53
67
|
|
|
54
|
-
3.
|
|
68
|
+
3. Create your configuration file:
|
|
55
69
|
|
|
56
70
|
```sh
|
|
57
71
|
bin/rails generate add_auth:install
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Open `config/initializers/add_auth.rb`. Defaults are active and optional
|
|
75
|
+
alternatives are commented out. This command enables no features and preserves
|
|
76
|
+
an existing initializer. Each feature section names its generator.
|
|
77
|
+
|
|
78
|
+
4. Enable password and email-link sign-in:
|
|
79
|
+
|
|
80
|
+
```sh
|
|
58
81
|
bin/rails generate add_auth:email_link
|
|
59
82
|
bin/rails db:migrate
|
|
60
83
|
```
|
|
@@ -63,7 +86,7 @@ below before enabling sign-in for users.
|
|
|
63
86
|
in, plus a page where a signed-in user can see their active sessions and
|
|
64
87
|
sign out of one remotely.
|
|
65
88
|
|
|
66
|
-
|
|
89
|
+
5. Set these values in `config/initializers/add_auth.rb`:
|
|
67
90
|
|
|
68
91
|
```ruby
|
|
69
92
|
AddAuth.configure do |config|
|
|
@@ -83,7 +106,7 @@ below before enabling sign-in for users.
|
|
|
83
106
|
config.action_mailer.raise_delivery_errors = true
|
|
84
107
|
```
|
|
85
108
|
|
|
86
|
-
|
|
109
|
+
6. Create a trial account in `bin/rails console` using your own test address and
|
|
87
110
|
password, then start `bin/rails server` and visit `/sign-in`. Request a link
|
|
88
111
|
for that account and open the message written under `tmp/mail`. Treat these
|
|
89
112
|
local messages as credentials and delete them after testing. Raw sign-in
|
|
@@ -538,7 +561,7 @@ or recovery procedures work in production.
|
|
|
538
561
|
|
|
539
562
|
## Scope
|
|
540
563
|
|
|
541
|
-
AddAuth
|
|
564
|
+
AddAuth implements password/email/passkey sign-in, session adoption and
|
|
542
565
|
management, purpose-bound verification, passkey recovery and strict account
|
|
543
566
|
policy, durable security mail and challenge adapters. Optional account lifecycle
|
|
544
567
|
adds registration, confirmation/reconfirmation, password reset, lock/unlock and
|
data/ROADMAP.md
CHANGED
|
@@ -4,16 +4,17 @@ 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
|
-
|
|
9
|
-
and
|
|
10
|
-
|
|
11
|
-
|
|
7
|
+
**Reviewed 2026-09-13.** Check [published releases](https://github.com/taimoorq/add_auth/releases) for package availability and provenance. This checklist distinguishes verified implementation from publication.
|
|
8
|
+
It adds the bounded Devise migration, optional account lifecycle, provider-library
|
|
9
|
+
integration and finite native authentication below. Required PR and exact
|
|
10
|
+
release-commit CI/CodeQL, protected OIDC publication, downloaded-package comparison
|
|
11
|
+
and fresh-host installation passed. The matching migration and native guides are
|
|
12
|
+
published in [the manual](https://addauthgem.com/).
|
|
12
13
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
14
|
+
The original strategy baseline shipped in 0.2.1; 0.2.2 supplied the counter-store
|
|
15
|
+
correction. The separate 1.0 support commitment remains under review. Milestones
|
|
16
|
+
describe priorities, not promised dates. See [release status](https://addauthgem.com/release-status/)
|
|
17
|
+
and [the changelog](CHANGELOG.md).
|
|
17
18
|
|
|
18
19
|
An unchecked item is remaining work; deferred candidates need a scope decision
|
|
19
20
|
before implementation. Checked features have passing relevant acceptance specs.
|
|
@@ -22,7 +23,13 @@ can describe tested local work; release/publication remains a separate gate. Det
|
|
|
22
23
|
design and decision records remain in the companion private planning workspace;
|
|
23
24
|
this public checklist is derived from its section 14 and stands on its own.
|
|
24
25
|
|
|
25
|
-
## Now — maintain
|
|
26
|
+
## Now — maintain AddAuth and learn from adoption
|
|
27
|
+
|
|
28
|
+
- [x] Optional password-signup confirmation, immediate transactional provisioning
|
|
29
|
+
and session issuance, explicit unconfirmed-reset policy, password-only lifecycle
|
|
30
|
+
and the complete commented Rails initializer. Local supported-matrix, stock/
|
|
31
|
+
adopted-host, UUID PostgreSQL and three-mode bundled/ejected acceptance passes.
|
|
32
|
+
Added in the 0.4.0 source; see [release availability and provenance](https://github.com/taimoorq/add_auth/releases).
|
|
26
33
|
|
|
27
34
|
- [x] **R1 · Resolve the outstanding dependency update.**
|
|
28
35
|
[PR #9](https://github.com/taimoorq/add_auth/pull/9) updates the immutable
|
|
@@ -52,7 +59,7 @@ passwordless-fixture isolation failure is fixed in
|
|
|
52
59
|
[PR #8](https://github.com/taimoorq/add_auth/pull/8); it is not an open runtime
|
|
53
60
|
defect or a reason by itself to republish 0.2.1.
|
|
54
61
|
|
|
55
|
-
##
|
|
62
|
+
## Roadmap — migration delivery and the future 1.0 contract
|
|
56
63
|
|
|
57
64
|
- [x] **R3 · Rehearse an upgrade from published 0.2.1.** Start with a populated
|
|
58
65
|
host, active sessions, passkeys, strict accounts, pending mail and customized
|
|
@@ -114,26 +121,25 @@ defect or a reason by itself to republish 0.2.1.
|
|
|
114
121
|
supported database/runtime, ordinary HTML with JavaScript and Turbo absent,
|
|
115
122
|
Turbo enhancement and permitted no-JS journeys. Draft step-by-step guides
|
|
116
123
|
and prove the destination works with Devise removed.
|
|
117
|
-
- [
|
|
118
|
-
|
|
119
|
-
|
|
124
|
+
- [x] **R8 · Publish migration and native guides with the feature release.**
|
|
125
|
+
The 0.3.0 package, installation commands, HTTPS and deep links are verified;
|
|
126
|
+
the six guides now describe the published release.
|
|
120
127
|
|
|
121
|
-
|
|
128
|
+
Version 0.3.0 verifies preflight, optional account lifecycle and
|
|
122
129
|
provider-library integration across the supported Ruby/Rails matrix, including
|
|
123
|
-
ordinary HTML, Turbo and permitted no-JS journeys. These additions
|
|
124
|
-
unpublished. Migration/checkpoint/compatible-rollback tests and native client
|
|
130
|
+
ordinary HTML, Turbo and permitted no-JS journeys. These additions ship in 0.3.0. Migration/checkpoint/compatible-rollback tests and native client
|
|
125
131
|
acceptance are complete locally. The full adopter suite passes 1,330 examples
|
|
126
132
|
with zero failures and three existing empty scaffold examples pending. Six
|
|
127
|
-
|
|
128
|
-
|
|
133
|
+
published guides pass build, browser, keyboard and no-JS checks. The proposed
|
|
134
|
+
1.0 commitment remains a separate gate.
|
|
129
135
|
|
|
130
|
-
##
|
|
136
|
+
## Shipped in 0.3.0 — Android and iOS login
|
|
131
137
|
|
|
132
|
-
Mobile support
|
|
138
|
+
Mobile support ships as an optional integration in 0.3.0. These capabilities extend the
|
|
133
139
|
same account and session policies. The verified finite profile uses explicit
|
|
134
140
|
30-day absolute and 14-day idle limits, with fresh sign-in after expiry. Renewable
|
|
135
|
-
refresh-token families remain a distinct optional capability,
|
|
136
|
-
The
|
|
141
|
+
refresh-token families remain a distinct optional capability, not implemented in this release.
|
|
142
|
+
The published 0.3.0 package and installed native-consumer contracts are verified.
|
|
137
143
|
|
|
138
144
|
- [x] **R8 · Password and provider login for native clients.** Return credentials
|
|
139
145
|
over authenticated HTTPS responses; preserve supported client contracts.
|
|
@@ -152,15 +158,15 @@ The owner selected 0.3.0; publication and package verification remain pending.
|
|
|
152
158
|
Each change owns its tests and keeps intermediate releases usable. R3/R4 findings
|
|
153
159
|
feed R5. A patch may address compatible corrections; another 0.x minor is possible
|
|
154
160
|
if integration contracts change. A 0.3 release is not a prerequisite for 1.0.
|
|
155
|
-
R8
|
|
161
|
+
R8 ships in 0.3.0 after the published 0.2.2 correction; its supported source profiles
|
|
156
162
|
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
|
|
163
|
+
remain subject to review before R5 freezes. The bounded Devise migration guide is available for 0.3.0.
|
|
158
164
|
|
|
159
165
|
## Delivery — 0.3.0 feature release, then the proposed 1.0 gate
|
|
160
166
|
|
|
161
167
|
- [x] Select 0.3.0 for the locally verified migration, lifecycle, provider and native scope.
|
|
162
|
-
- [
|
|
163
|
-
- [
|
|
168
|
+
- [x] Pass required PR and exact default-branch CI/CodeQL for 0.3.0.
|
|
169
|
+
- [x] Publish 0.3.0 through protected OIDC and verify the downloaded package and manual.
|
|
164
170
|
|
|
165
171
|
- [x] **R6 · Deliver the 0.2.2 correction.**
|
|
166
172
|
[PR #10](https://github.com/taimoorq/add_auth/pull/10) passed all required
|
|
@@ -172,7 +178,7 @@ remain subject to review before R5 freezes. Devise migration support is not avai
|
|
|
172
178
|
- [ ] **R6 · Finish the adoption and compatibility review.** R1–R5 findings are
|
|
173
179
|
completed or explicitly dispositioned, the supported API is accepted, and
|
|
174
180
|
the published-package upgrade and documented operations profile pass.
|
|
175
|
-
- [ ] **R6 · Verify the exact candidate.** Preserve the complete local
|
|
181
|
+
- [ ] **R6 · Verify the exact 1.0 candidate.** Preserve the complete local
|
|
176
182
|
Ruby/Rails, SQLite/PostgreSQL, generated/ejected browser and operations
|
|
177
183
|
acceptance; resolve release-blocking security and upgrade findings.
|
|
178
184
|
Recheck dependency currency and pass required GitHub CI/CodeQL controls.
|
|
@@ -213,13 +219,13 @@ compatible extension could ship in 1.x after an explicit scope decision.
|
|
|
213
219
|
are promoted to R8 above; broader module parity remains outside this scope.
|
|
214
220
|
- [ ] **F4 · Multiple realms.** Require a concrete identity/session-isolation need
|
|
215
221
|
and migration design before adding models, cookies or routing APIs.
|
|
216
|
-
- [ ] **F4 · API/token authentication.** Android/iOS login
|
|
217
|
-
|
|
222
|
+
- [ ] **F4 · API/token authentication.** Android/iOS login shipped under R8
|
|
223
|
+
above; multiple realms and general API-key management remain separate.
|
|
218
224
|
- [ ] **F5 · Standalone test helpers or challenge adapters.** Extract only if
|
|
219
225
|
independent demand and maintenance capacity justify another package;
|
|
220
226
|
retain one implementation per concern.
|
|
221
227
|
|
|
222
|
-
Optional provider-library integration
|
|
228
|
+
Optional provider-library integration ships under R8 in 0.3.0. SMS/TOTP and enterprise
|
|
223
229
|
or cross-origin WebAuthn remain outside the current roadmap. Account roles,
|
|
224
230
|
invitations, authorization and email branding
|
|
225
231
|
stay with the host.
|
data/SECURITY.md
CHANGED
|
@@ -36,7 +36,9 @@ mailing list, or social media) until a fix has been released.
|
|
|
36
36
|
scoping, eligibility rechecks and active-delivery-lease protection.
|
|
37
37
|
- Optional account lifecycle and migration boundaries: credential
|
|
38
38
|
profile selection, source-write fencing, address ownership, account proofs,
|
|
39
|
-
policy and revocation. These capabilities are introduced in 0.3.0.
|
|
39
|
+
policy and revocation. These capabilities are introduced in 0.3.0; 0.4.0 adds
|
|
40
|
+
optional confirmation, atomic signup provisioning/session issuance and the
|
|
41
|
+
independent unconfirmed-password-reset policy.
|
|
40
42
|
- Optional provider identity binding and mobile sessions/handoffs, including
|
|
41
43
|
nonce/client/purpose binding, exact callbacks, single use and bearer/cookie
|
|
42
44
|
separation. Provider protocol cryptography remains library-owned.
|
|
@@ -19,9 +19,17 @@ module AddAuth
|
|
|
19
19
|
def create
|
|
20
20
|
admitted = intake(:register)
|
|
21
21
|
return intake_failure(admitted) if admitted.is_a?(Symbol)
|
|
22
|
-
|
|
22
|
+
accounts = Rails::Runtime.accounts
|
|
23
|
+
result = accounts.register(identifier: admitted, password: params[:password], profile: registration_profile,
|
|
24
|
+
replacing: add_auth_replacement_session, **add_auth_session_hints)
|
|
23
25
|
if result.success?
|
|
24
|
-
|
|
26
|
+
if result.grant
|
|
27
|
+
destination = after_authentication_url
|
|
28
|
+
add_auth_accept(result.grant)
|
|
29
|
+
redirect_to destination, status: :see_other
|
|
30
|
+
else
|
|
31
|
+
redirect_to accounts.registration_next_path, status: :see_other
|
|
32
|
+
end
|
|
25
33
|
else
|
|
26
34
|
@error = "Check your email and choose a password that meets this application's requirements."
|
|
27
35
|
page("register", status: :unprocessable_entity)
|
|
@@ -8,6 +8,7 @@ module AddAuth
|
|
|
8
8
|
skip_before_action :require_authentication, raise: false
|
|
9
9
|
before_action :private_response
|
|
10
10
|
before_action :enabled_feature
|
|
11
|
+
before_action :enabled_email_feature, only: %i[request_link check_email link confirm]
|
|
11
12
|
before_action :require_add_auth_authentication, except: %i[link confirm]
|
|
12
13
|
before_action :load_purpose, only: %i[new password request_link check_email]
|
|
13
14
|
protect_from_forgery with: :exception
|
|
@@ -54,6 +55,10 @@ module AddAuth
|
|
|
54
55
|
head :not_found unless Rails::Runtime.config.step_up.enabled
|
|
55
56
|
end
|
|
56
57
|
|
|
58
|
+
def enabled_email_feature
|
|
59
|
+
head :not_found unless Core::Intake.email_available?(Rails::Runtime.config)
|
|
60
|
+
end
|
|
61
|
+
|
|
57
62
|
def load_purpose
|
|
58
63
|
@rule = Rails::Runtime.step_up_policy.reauthentication_rule_for(params[:purpose])
|
|
59
64
|
return head :not_found unless @rule
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<% content_for :title, "Create an account" %>
|
|
2
2
|
<h1 id="add_auth-title">Create an account</h1>
|
|
3
|
-
<p class="<%= add_auth_class(:muted) %>"
|
|
3
|
+
<p class="<%= add_auth_class(:muted) %>"><%= AddAuth::Rails::Runtime.accounts.registration_instructions %></p>
|
|
4
4
|
<% if @error %><p role="alert" class="<%= add_auth_class(:notice) %>"><%= @error %></p><% end %>
|
|
5
5
|
<%= form_with url: "/account/sign-up", data: add_auth_challenge_form_data(:register) do |form| %>
|
|
6
6
|
<div class="<%= add_auth_class(:field) %>">
|
|
@@ -2,6 +2,9 @@
|
|
|
2
2
|
<% content_for :title, title %>
|
|
3
3
|
<h1 id="add_auth-title"><%= title %></h1>
|
|
4
4
|
<p class="<%= add_auth_class(:muted) %>">Enter your account email address to request a secure link.</p>
|
|
5
|
+
<% if @purpose == "reset_password" %>
|
|
6
|
+
<p>You can also <%= link_to "request email confirmation", "/account/requests/confirm" %> if you haven’t confirmed your address.</p>
|
|
7
|
+
<% end %>
|
|
5
8
|
<% if @error %><p role="alert" class="<%= add_auth_class(:notice) %>"><%= @error %></p><% end %>
|
|
6
9
|
<%= form_with url: "/account/requests/#{@purpose}", data: add_auth_challenge_form_data(:account_request) do |form| %>
|
|
7
10
|
<div class="<%= add_auth_class(:field) %>">
|
|
@@ -15,7 +15,7 @@ module AddAuth
|
|
|
15
15
|
PasskeyOptions = Struct.new(:enabled, :rp_id, :origins, :name, :anonymous_limit)
|
|
16
16
|
NotificationOptions = Struct.new(:enabled)
|
|
17
17
|
MaintenanceOptions = Struct.new(:batch_size, :session_retention, :email_retention, :notification_retention, :account_retention)
|
|
18
|
-
LifecycleOptions = Struct.new(:enabled, :password_policy, :eligible, :provision, :profile_attributes, :maximum_attempts, :unlock_in, :proof_lifetime, :remember_lifetime, :remember_idle_timeout, :deletion_allowed, :delete_account)
|
|
18
|
+
LifecycleOptions = Struct.new(:enabled, :password_policy, :eligible, :provision, :profile_attributes, :maximum_attempts, :unlock_in, :proof_lifetime, :remember_lifetime, :remember_idle_timeout, :deletion_allowed, :delete_account, :confirmation_required, :reset_unconfirmed)
|
|
19
19
|
class ExternalProvider
|
|
20
20
|
attr_reader :id, :label, :middleware_name, :configuration, :apple_form_post, :reauthentication
|
|
21
21
|
|
|
@@ -88,7 +88,7 @@ module AddAuth
|
|
|
88
88
|
@lifecycle = LifecycleOptions.new(enabled: false, password_policy: ->(password) { password.length >= 12 && password.bytesize <= 72 },
|
|
89
89
|
eligible: ->(_user) { true }, provision: ->(_user) {}, profile_attributes: ->(_profile) { {} }, maximum_attempts: 20, unlock_in: 3600, proof_lifetime: 3600,
|
|
90
90
|
remember_lifetime: 14 * 86_400, remember_idle_timeout: 7 * 86_400,
|
|
91
|
-
deletion_allowed: ->(_user) { true }, delete_account: ->(user) { user.destroy! })
|
|
91
|
+
deletion_allowed: ->(_user) { true }, delete_account: ->(user) { user.destroy! }, confirmation_required: true, reset_unconfirmed: false)
|
|
92
92
|
@session = SessionOptions.new(enabled: false, lifetime: 43_200, idle_timeout: 1800)
|
|
93
93
|
@mobile = MobileOptions.new(enabled: false, clients: [], callbacks: {}, apple_providers: {})
|
|
94
94
|
@email_link = EmailOptions.new(enabled: false, token_lifetime: 1200, same_browser: false)
|
|
@@ -16,6 +16,7 @@ module AddAuth
|
|
|
16
16
|
class Conflict < StandardError; end
|
|
17
17
|
class InvalidPassword < StandardError; end
|
|
18
18
|
class DeletionRejected < StandardError; end
|
|
19
|
+
class RegistrationRejected < StandardError; end
|
|
19
20
|
|
|
20
21
|
class NewAccount
|
|
21
22
|
attr_reader :user, :user_id
|
|
@@ -44,9 +45,21 @@ module AddAuth
|
|
|
44
45
|
@external_identities = external_identities
|
|
45
46
|
end
|
|
46
47
|
|
|
47
|
-
def register(identifier:, password:, profile: {})
|
|
48
|
+
def register(identifier:, password:, profile: {}, replacing: nil, user_agent: nil, ip_address: nil, remember: false)
|
|
48
49
|
return failure unless valid_new_password?(password)
|
|
49
|
-
register_account(identifier: identifier, password: password, profile: profile
|
|
50
|
+
register_account(identifier: identifier, password: password, profile: profile,
|
|
51
|
+
immediate: !@policy.confirmation_required?, replacing: replacing,
|
|
52
|
+
session_hints: {user_agent: user_agent, ip_address: ip_address, remember: remember})
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Generic non-grant result mapping; never authenticate a duplicate result.
|
|
56
|
+
def registration_next_path
|
|
57
|
+
@policy.confirmation_required? ? "/account/check-email" : "/sign-in"
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def registration_instructions
|
|
61
|
+
@policy.confirmation_required? ? "We’ll email you a link to confirm your address before you sign in." :
|
|
62
|
+
"Create your account and sign in right away. Your email address stays unverified until you confirm it."
|
|
50
63
|
end
|
|
51
64
|
|
|
52
65
|
def register_external(identifier:, evidence:, profile: {})
|
|
@@ -98,7 +111,7 @@ module AddAuth
|
|
|
98
111
|
@notify.call(user: user, kind: :email_changed, at: now, recipient: pending)
|
|
99
112
|
else
|
|
100
113
|
@store.update_account(user: user, confirmed_at: now)
|
|
101
|
-
|
|
114
|
+
provision_account(user)
|
|
102
115
|
end
|
|
103
116
|
when "reset_password"
|
|
104
117
|
next failure unless valid_new_password?(password)
|
|
@@ -165,19 +178,47 @@ module AddAuth
|
|
|
165
178
|
|
|
166
179
|
private
|
|
167
180
|
|
|
168
|
-
def register_account(identifier:, password:, profile:)
|
|
181
|
+
def register_account(identifier:, password:, profile:, immediate: false, replacing: nil, session_hints: {})
|
|
169
182
|
email = normalize(identifier)
|
|
170
183
|
return failure unless email && profile.is_a?(Hash) && profile.length <= 20
|
|
171
184
|
attributes = @profile_attributes.call(profile)
|
|
172
185
|
profile_valid = attributes.is_a?(Hash) && attributes.keys.all? { |name| (name.is_a?(String) || name.is_a?(Symbol)) && !SECURITY_ATTRIBUTES.include?(name.to_s) && !name.to_s.start_with?("add_auth_") }
|
|
173
186
|
raise AddAuth::Error, "registration profile must contain host profile fields only" unless profile_valid
|
|
174
|
-
|
|
187
|
+
registered = nil
|
|
188
|
+
outcome = @store.create_account(email: email, password: password, profile: attributes, replacing: replacing) do |user|
|
|
175
189
|
raise Conflict unless @policy.allowed?(user, purpose: :confirm)
|
|
176
190
|
yield user if block_given?
|
|
177
191
|
@store.claim_address(user: user, digest: address_digest(email), address: email, state: "current")
|
|
178
|
-
|
|
192
|
+
if immediate
|
|
193
|
+
raise AddAuth::Error, "optional confirmation requires sessions and the provisioning migration" unless @sessions && user.respond_to?(:add_auth_provisioned_at)
|
|
194
|
+
raise RegistrationRejected unless @policy.allowed?(user)
|
|
195
|
+
password_version = user.password_digest
|
|
196
|
+
provision_account(user)
|
|
197
|
+
unless user.email_address == email && user.password_digest == password_version && !user.confirmed_at && user.unconfirmed_email.to_s.empty?
|
|
198
|
+
raise RegistrationRejected
|
|
199
|
+
end
|
|
200
|
+
grant = nil
|
|
201
|
+
@store.finalize_session(user: user) do |writer|
|
|
202
|
+
grant = @sessions.create_in_transaction(user: user, method: :password, persist: writer, replacing: replacing, **session_hints)
|
|
203
|
+
raise RegistrationRejected unless grant
|
|
204
|
+
grant.session
|
|
205
|
+
end
|
|
206
|
+
registered = Result.success(user: user, strategy: :password, session: grant.session, grant: grant)
|
|
207
|
+
else
|
|
208
|
+
issue_in_transaction(user: user, purpose: "confirm", recipient: email)
|
|
209
|
+
end
|
|
179
210
|
end
|
|
180
|
-
|
|
211
|
+
return failure if outcome == :invalid
|
|
212
|
+
(outcome == :created && registered) || accepted
|
|
213
|
+
rescue RegistrationRejected
|
|
214
|
+
failure
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
def provision_account(user)
|
|
218
|
+
tracked = user.respond_to?(:add_auth_provisioned_at)
|
|
219
|
+
return if tracked && user.add_auth_provisioned_at
|
|
220
|
+
@store.provision(user: user)
|
|
221
|
+
@store.update_account(user: user, add_auth_provisioned_at: @clock.now) if tracked
|
|
181
222
|
end
|
|
182
223
|
|
|
183
224
|
def with_account_change(user:, session:, purpose:)
|
|
@@ -203,7 +244,10 @@ module AddAuth
|
|
|
203
244
|
def recipient_for(user, purpose)
|
|
204
245
|
if purpose == "confirm"
|
|
205
246
|
normalize(user.unconfirmed_email.to_s.empty? ? user.email_address : user.unconfirmed_email)
|
|
247
|
+
elsif purpose == "reset_password" && @policy.unconfirmed_reset?(user)
|
|
248
|
+
normalize(user.email_address)
|
|
206
249
|
else
|
|
250
|
+
return unless @policy.trusted_address?(user)
|
|
207
251
|
address = normalize(@trusted_address.call(user))
|
|
208
252
|
address if address == normalize(user.email_address)
|
|
209
253
|
end
|
|
@@ -3,10 +3,24 @@
|
|
|
3
3
|
module AddAuth
|
|
4
4
|
module Core
|
|
5
5
|
class AccountPolicy
|
|
6
|
-
def initialize(enabled:, eligible: ->(_user) { true }, clock: Time)
|
|
6
|
+
def initialize(enabled:, eligible: ->(_user) { true }, clock: Time, confirmation_required: true, reset_unconfirmed: false)
|
|
7
|
+
unless [confirmation_required, reset_unconfirmed].all? { |value| value == true || value == false }
|
|
8
|
+
raise ArgumentError, "confirmation_required and reset_unconfirmed must be true or false"
|
|
9
|
+
end
|
|
10
|
+
raise ArgumentError, "reset_unconfirmed requires optional confirmation" if confirmation_required && reset_unconfirmed
|
|
7
11
|
@enabled, @eligible, @clock = enabled, eligible, clock
|
|
12
|
+
@confirmation_required, @reset_unconfirmed = confirmation_required, reset_unconfirmed
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def confirmation_required? = @confirmation_required
|
|
16
|
+
|
|
17
|
+
def unconfirmed_reset?(user)
|
|
18
|
+
@enabled && @reset_unconfirmed && !value(user, :confirmed_at) &&
|
|
19
|
+
password_account?(user)
|
|
8
20
|
end
|
|
9
21
|
|
|
22
|
+
def trusted_address?(user) = !@enabled || !!value(user, :confirmed_at)
|
|
23
|
+
|
|
10
24
|
def allowed?(user, purpose: :sign_in)
|
|
11
25
|
return denial(user).nil? if %i[sign_in manage].include?(purpose.to_sym)
|
|
12
26
|
return false unless available?(user)
|
|
@@ -17,7 +31,7 @@ module AddAuth
|
|
|
17
31
|
when :unlock
|
|
18
32
|
locked?(user) && !value(user, :add_auth_manual_lock)
|
|
19
33
|
when :reset_password
|
|
20
|
-
!!value(user, :confirmed_at) && !value(user, :add_auth_strict)
|
|
34
|
+
(!!value(user, :confirmed_at) || unconfirmed_reset?(user)) && !value(user, :add_auth_strict)
|
|
21
35
|
else false
|
|
22
36
|
end
|
|
23
37
|
end
|
|
@@ -26,7 +40,7 @@ module AddAuth
|
|
|
26
40
|
def denial(user)
|
|
27
41
|
return :disabled unless available?(user)
|
|
28
42
|
return unless @enabled
|
|
29
|
-
return :unconfirmed
|
|
43
|
+
return :unconfirmed if !value(user, :confirmed_at) && (confirmation_required? || !password_account?(user))
|
|
30
44
|
:locked if locked?(user)
|
|
31
45
|
end
|
|
32
46
|
|
|
@@ -36,6 +50,10 @@ module AddAuth
|
|
|
36
50
|
|
|
37
51
|
private
|
|
38
52
|
|
|
53
|
+
def password_account?(user)
|
|
54
|
+
value(user, :password_digest).is_a?(String) && !user.password_digest.empty?
|
|
55
|
+
end
|
|
56
|
+
|
|
39
57
|
def available?(user)
|
|
40
58
|
user && @eligible.call(user) == true &&
|
|
41
59
|
(!user.respond_to?(:add_auth_authority) || user.add_auth_authority == "add_auth") &&
|
|
@@ -16,7 +16,7 @@ module AddAuth
|
|
|
16
16
|
add_auth_accept(grant)
|
|
17
17
|
redirect_to destination, status: :see_other
|
|
18
18
|
else
|
|
19
|
-
@error = "Email or password is incorrect. Try again
|
|
19
|
+
@error = "Email or password is incorrect. Try again."
|
|
20
20
|
page("form", status: :unprocessable_entity)
|
|
21
21
|
end
|
|
22
22
|
end
|
|
@@ -120,6 +120,11 @@ module AddAuth
|
|
|
120
120
|
check("Install provider account management routes") { ::Rails.application.routes.recognize_path("/account/external-identities", method: :get) }
|
|
121
121
|
end
|
|
122
122
|
if config.lifecycle.enabled
|
|
123
|
+
check("Set explicit confirmation and unconfirmed-reset policies") { Runtime.account_policy }
|
|
124
|
+
if config.lifecycle.confirmation_required == false
|
|
125
|
+
columns(::User, %w[add_auth_provisioned_at], "optional confirmation provisioning")
|
|
126
|
+
check("Enable password authentication for optional signup confirmation") { config.passwords_enabled }
|
|
127
|
+
end
|
|
123
128
|
if defined?(::PasswordsController)
|
|
124
129
|
check("Retire stock password reset entry points through the shared account flow") { ::PasswordsController < AccountPasswordEntry }
|
|
125
130
|
end
|
|
@@ -179,10 +184,12 @@ module AddAuth
|
|
|
179
184
|
check("Enable security notification delivery errors") { ::AddAuth::SecurityMailer.raise_delivery_errors }
|
|
180
185
|
end
|
|
181
186
|
if config.step_up.enabled
|
|
182
|
-
check("Enable session
|
|
187
|
+
check("Enable session prerequisites for step_up") { config.session.enabled }
|
|
183
188
|
columns(::Session, %w[elevation_version elevation_expires_at], "reauthentication")
|
|
184
|
-
|
|
185
|
-
|
|
189
|
+
if config.email_link.enabled
|
|
190
|
+
columns(defined?(::AddAuthSignInToken) && ::AddAuthSignInToken,
|
|
191
|
+
%w[browser_digest session_id session_digest authentication_purpose], "reauthentication")
|
|
192
|
+
end
|
|
186
193
|
check("Declare valid step-up purposes and safe return destinations") { Runtime.step_up_purposes.any? && Runtime.step_up_policy }
|
|
187
194
|
%w[/reauthenticate /reauthenticate/link].each do |path|
|
|
188
195
|
check("Install route #{path}") { ::Rails.application.routes.recognize_path(path, method: :get) }
|
|
@@ -15,11 +15,17 @@ module AddAuth
|
|
|
15
15
|
def config = AddAuth.configuration
|
|
16
16
|
|
|
17
17
|
def account_policy
|
|
18
|
-
Core::AccountPolicy.new(enabled: config.lifecycle.enabled,
|
|
18
|
+
Core::AccountPolicy.new(enabled: config.lifecycle.enabled,
|
|
19
|
+
eligible: ->(user) { config.eligible.call(user) == true && config.lifecycle.eligible.call(user) == true },
|
|
20
|
+
confirmation_required: config.lifecycle.confirmation_required, reset_unconfirmed: config.lifecycle.reset_unconfirmed)
|
|
19
21
|
end
|
|
20
22
|
|
|
21
23
|
def eligible(user)
|
|
22
|
-
|
|
24
|
+
account_policy.allowed?(user)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def trusted_recovery_address(user)
|
|
28
|
+
config.trusted_recovery_address.call(user) if account_policy.trusted_address?(user)
|
|
23
29
|
end
|
|
24
30
|
|
|
25
31
|
def authority
|
|
@@ -74,11 +80,14 @@ module AddAuth
|
|
|
74
80
|
|
|
75
81
|
def accounts
|
|
76
82
|
raise AddAuth::Error, "enable account lifecycle first" unless config.lifecycle.enabled
|
|
83
|
+
if !account_policy.confirmation_required? && !::User.column_names.include?("add_auth_provisioned_at")
|
|
84
|
+
raise AddAuth::Error, "rerun add_auth:accounts and migrate before enabling optional confirmation"
|
|
85
|
+
end
|
|
77
86
|
require "add_auth/rails/stores/account_tokens"
|
|
78
87
|
Core::AccountLifecycle.new(store: Stores::AccountTokens.new(user_model: ::User, token_model: ::AddAuthAccountToken,
|
|
79
88
|
session_model: ::Session, address_model: ::AddAuthAddressClaim, authority: authority, provision: config.lifecycle.provision, delete_account: config.lifecycle.delete_account),
|
|
80
89
|
digest: config.sign_in_token_digest, delivery_cipher: DeliveryCipher.new(key: key("account-proofs")),
|
|
81
|
-
policy: account_policy, password_policy: config.lifecycle.password_policy, trusted_address:
|
|
90
|
+
policy: account_policy, password_policy: config.lifecycle.password_policy, trusted_address: method(:trusted_recovery_address),
|
|
82
91
|
lifetime: config.lifecycle.proof_lifetime, notify: ->(**event) { security_events.issue(**event) if config.notifications.enabled },
|
|
83
92
|
sessions: sessions, step_up_policy: step_up_policy, profile_attributes: config.lifecycle.profile_attributes, deletion_allowed: config.lifecycle.deletion_allowed,
|
|
84
93
|
external_identities: -> { external_identities })
|
|
@@ -168,7 +177,7 @@ module AddAuth
|
|
|
168
177
|
Core::AccessPolicy.new(credentials: ->(id) {
|
|
169
178
|
::AddAuthCredential.find_by(external_id: id) if defined?(::AddAuthCredential) && ::AddAuthCredential.table_exists?
|
|
170
179
|
}, passkeys_enabled: config.passkeys.enabled, email_enabled: Core::Intake.email_available?(config),
|
|
171
|
-
trusted_recovery_address:
|
|
180
|
+
trusted_recovery_address: method(:trusted_recovery_address), password_enabled: config.passwords_enabled,
|
|
172
181
|
external_enabled: config.external_identities.enabled,
|
|
173
182
|
external_current: ->(**arguments) { external_identities.credential_current?(**arguments) })
|
|
174
183
|
end
|
|
@@ -16,16 +16,19 @@ module AddAuth
|
|
|
16
16
|
@account_credentials = AccountCredentials.new(user_model: user_model, authority: authority)
|
|
17
17
|
end
|
|
18
18
|
|
|
19
|
-
def create_account(email:, password:, profile: {})
|
|
19
|
+
def create_account(email:, password:, profile: {}, replacing: nil)
|
|
20
20
|
raise AddAuth::Error, "registration must own its transaction" if @users.connection.transaction_open?
|
|
21
21
|
completed = false
|
|
22
|
+
saved = false
|
|
22
23
|
@users.transaction do |transaction|
|
|
23
24
|
transaction.after_commit { completed = true }
|
|
25
|
+
AccountLock.new(@users).current_in_transaction(id: replacing.user_id) if replacing
|
|
24
26
|
user = @users.new(profile)
|
|
25
27
|
user.email_address = email
|
|
26
28
|
user.password = password
|
|
27
29
|
user.add_auth_authority = "add_auth"
|
|
28
30
|
user.save!
|
|
31
|
+
saved = true
|
|
29
32
|
yield user
|
|
30
33
|
end
|
|
31
34
|
raise AddAuth::Error, "registration transaction rolled back" unless completed
|
|
@@ -33,6 +36,7 @@ module AddAuth
|
|
|
33
36
|
rescue ActiveRecord::RecordNotUnique, Core::AccountLifecycle::Conflict
|
|
34
37
|
:duplicate
|
|
35
38
|
rescue ActiveRecord::RecordInvalid => error
|
|
39
|
+
raise if saved
|
|
36
40
|
(error.record.errors.attribute_names - [:email_address]).empty? ? :duplicate : :invalid
|
|
37
41
|
end
|
|
38
42
|
|
|
@@ -81,7 +85,11 @@ module AddAuth
|
|
|
81
85
|
end
|
|
82
86
|
|
|
83
87
|
def revoke_authority(user:, at:) = @authority.revoke(user_id: user.id, at: at)
|
|
84
|
-
|
|
88
|
+
|
|
89
|
+
def provision(user:)
|
|
90
|
+
@provision.call(user)
|
|
91
|
+
user.reload
|
|
92
|
+
end
|
|
85
93
|
|
|
86
94
|
def delete_account(user:)
|
|
87
95
|
raise AddAuth::Error, "account deletion requires its owning transaction" unless @users.connection.transaction_open?
|
data/lib/add_auth/version.rb
CHANGED
|
@@ -9,19 +9,23 @@ module AddAuth
|
|
|
9
9
|
include ::Rails::Generators::Migration
|
|
10
10
|
|
|
11
11
|
source_root File.expand_path("templates", __dir__)
|
|
12
|
+
class_option :email_link, type: :boolean, default: true, desc: "Enable email sign-in as a step-up dependency"
|
|
12
13
|
desc "Prepare optional account lifecycle schema and pages; review existing account confirmation and policy before enabling"
|
|
13
14
|
|
|
14
15
|
def self.next_migration_number(dirname) = ::ActiveRecord::Generators::Base.next_migration_number(dirname)
|
|
15
16
|
|
|
16
17
|
def prerequisites
|
|
17
18
|
invoke "add_auth:notifications"
|
|
18
|
-
invoke "add_auth:step_up"
|
|
19
|
+
invoke "add_auth:step_up", [], email_link: options[:email_link]
|
|
19
20
|
end
|
|
20
21
|
|
|
21
22
|
def persistence
|
|
22
23
|
unless Dir[File.join(destination_root, "db/migrate/*_add_add_auth_accounts.rb")].any?
|
|
23
24
|
migration_template "add_add_auth_accounts.rb.tt", "db/migrate/add_add_auth_accounts.rb"
|
|
24
25
|
end
|
|
26
|
+
unless Dir[File.join(destination_root, "db/migrate/*_add_add_auth_provisioning.rb")].any?
|
|
27
|
+
migration_template "add_add_auth_provisioning.rb.tt", "db/migrate/add_add_auth_provisioning.rb"
|
|
28
|
+
end
|
|
25
29
|
%w[add_auth_account_token add_auth_address_claim].each do |name|
|
|
26
30
|
template "#{name}.rb", "app/models/#{name}.rb" unless File.exist?(File.join(destination_root, "app/models/#{name}.rb"))
|
|
27
31
|
end
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
class AddAddAuthProvisioning < ActiveRecord::Migration[8.0]
|
|
2
|
+
def up
|
|
3
|
+
add_column :users, :add_auth_provisioned_at, :datetime unless column_exists?(:users, :add_auth_provisioned_at)
|
|
4
|
+
end
|
|
5
|
+
|
|
6
|
+
def down
|
|
7
|
+
raise ActiveRecord::IrreversibleMigration, "Retain provisioning receipts to prevent repeating account setup"
|
|
8
|
+
end
|
|
9
|
+
end
|
|
@@ -1,14 +1,17 @@
|
|
|
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 EmailLinkGenerator < ::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 :enable, type: :boolean, default: true, desc: "Enable email sign-in after preparing its storage"
|
|
12
15
|
def self.next_migration_number(dirname) = ::ActiveRecord::Generators::Base.next_migration_number(dirname)
|
|
13
16
|
|
|
14
17
|
def dependencies
|
|
@@ -48,7 +51,7 @@ module AddAuth
|
|
|
48
51
|
unless File.read(File.join(destination_root, "config/routes.rb")).include?("add_auth/challenge.js")
|
|
49
52
|
route 'get "add_auth/challenge.js", to: "add_auth/assets#challenge"'
|
|
50
53
|
end
|
|
51
|
-
|
|
54
|
+
enable_feature(:email_link) if options[:enable]
|
|
52
55
|
say "Review and migrate, configure base_url/mail_from, a durable queue, shared rate-limit cache and a recurring add_auth:deliver_pending sweep."
|
|
53
56
|
end
|
|
54
57
|
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module AddAuth
|
|
4
|
+
module Generators
|
|
5
|
+
module FeatureConfiguration
|
|
6
|
+
private
|
|
7
|
+
|
|
8
|
+
def enable_feature(name)
|
|
9
|
+
path = "config/initializers/add_auth.rb"
|
|
10
|
+
source = File.read(File.join(destination_root, path))
|
|
11
|
+
setting = "config.#{name}.enabled"
|
|
12
|
+
pattern = /^(\s*)(?:# #{Regexp.escape(setting)} = true|#{Regexp.escape(setting)} = false)$/
|
|
13
|
+
if source.match?(pattern)
|
|
14
|
+
gsub_file path, pattern, "\\1#{setting} = true"
|
|
15
|
+
elsif !source.match?(/^\s*#{Regexp.escape(setting)}\s*=/)
|
|
16
|
+
append_to_file path, "\nAddAuth.configure do |config|\n #{setting} = true\nend\n"
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -1,52 +1,156 @@
|
|
|
1
|
-
#
|
|
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
|
-
#
|
|
4
|
-
|
|
5
|
-
# config.passwords_enabled =
|
|
6
|
-
|
|
7
|
-
# Ordinary
|
|
8
|
-
|
|
9
|
-
#
|
|
10
|
-
|
|
11
|
-
#
|
|
12
|
-
|
|
13
|
-
#
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
#
|
|
20
|
-
|
|
21
|
-
#
|
|
22
|
-
#
|
|
23
|
-
|
|
24
|
-
#
|
|
25
|
-
|
|
26
|
-
# config.
|
|
27
|
-
#
|
|
28
|
-
#
|
|
29
|
-
|
|
30
|
-
#
|
|
31
|
-
#
|
|
32
|
-
#
|
|
33
|
-
#
|
|
34
|
-
#
|
|
35
|
-
#
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
# config.
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
# config.
|
|
43
|
-
#
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
# config.
|
|
51
|
-
|
|
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/feature_configuration"
|
|
4
5
|
require "rails/generators/active_record"
|
|
5
6
|
|
|
6
7
|
module AddAuth
|
|
7
8
|
module Generators
|
|
8
9
|
class NotificationsGenerator < ::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
|
def self.next_migration_number(dirname) = ::ActiveRecord::Generators::Base.next_migration_number(dirname)
|
|
@@ -21,10 +23,7 @@ module AddAuth
|
|
|
21
23
|
end
|
|
22
24
|
|
|
23
25
|
def configuration
|
|
24
|
-
|
|
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
|
|
26
|
+
enable_feature(:notifications)
|
|
28
27
|
say "Migrate, configure mail_from and a durable queue, and schedule add_auth:deliver_pending every minute."
|
|
29
28
|
end
|
|
30
29
|
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 PasskeysGenerator < ::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
|
def self.next_migration_number(dirname) = ::ActiveRecord::Generators::Base.next_migration_number(dirname)
|
|
@@ -47,27 +49,7 @@ module AddAuth
|
|
|
47
49
|
post "recover/link", to: "add_auth/recoveries#confirm"
|
|
48
50
|
ROUTES
|
|
49
51
|
end
|
|
50
|
-
|
|
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
|
|
52
|
+
enable_feature(:passkeys)
|
|
71
53
|
say "Review and migrate before enabling traffic. Configure RP/origins, trusted recovery and notifications; run add_auth:doctor."
|
|
72
54
|
end
|
|
73
55
|
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
|
|
|
@@ -62,7 +64,7 @@ module AddAuth
|
|
|
62
64
|
unless File.read(File.join(destination_root, path)).include?('require "add_auth/rails/authentication"')
|
|
63
65
|
prepend_to_file path, %(require "add_auth/rails/authentication"\nrequire "add_auth/rails/user_lifecycle"\n)
|
|
64
66
|
end
|
|
65
|
-
|
|
67
|
+
enable_feature(:session)
|
|
66
68
|
routes = File.join(destination_root, "config/routes.rb")
|
|
67
69
|
source = File.read(routes)
|
|
68
70
|
unless source.include?("# AddAuth session management")
|
|
@@ -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
|
-
|
|
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.
|
|
4
|
+
version: 0.4.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
|
|
@@ -364,6 +378,7 @@ files:
|
|
|
364
378
|
- lib/add_auth/version.rb
|
|
365
379
|
- lib/generators/add_auth/accounts/accounts_generator.rb
|
|
366
380
|
- lib/generators/add_auth/accounts/templates/add_add_auth_accounts.rb.tt
|
|
381
|
+
- lib/generators/add_auth/accounts/templates/add_add_auth_provisioning.rb.tt
|
|
367
382
|
- lib/generators/add_auth/accounts/templates/add_auth_account_token.rb
|
|
368
383
|
- lib/generators/add_auth/accounts/templates/add_auth_address_claim.rb
|
|
369
384
|
- lib/generators/add_auth/challenge/challenge_generator.rb
|
|
@@ -388,6 +403,7 @@ files:
|
|
|
388
403
|
- lib/generators/add_auth/external_identities/templates/add_auth_external_transaction.rb
|
|
389
404
|
- lib/generators/add_auth/external_identities/templates/create_add_auth_external_identities.rb.tt
|
|
390
405
|
- lib/generators/add_auth/external_identities/templates/providers.rb
|
|
406
|
+
- lib/generators/add_auth/feature_configuration.rb
|
|
391
407
|
- lib/generators/add_auth/install/install_generator.rb
|
|
392
408
|
- lib/generators/add_auth/install/templates/initializer.rb
|
|
393
409
|
- lib/generators/add_auth/javascript/javascript_generator.rb
|