add_auth 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +13 -0
- data/README.md +34 -5
- data/ROADMAP.md +6 -0
- data/SECURITY.md +4 -0
- data/app/controllers/add_auth/sessions_controller.rb +1 -1
- data/lib/add_auth/core/session_cursor.rb +38 -0
- data/lib/add_auth/core/session_key.rb +32 -0
- data/lib/add_auth/core/sessions.rb +19 -8
- data/lib/add_auth/rails/migration/key_type.rb +3 -3
- data/lib/add_auth/rails/stores/sessions.rb +10 -2
- data/lib/add_auth/version.rb +1 -1
- data/lib/generators/add_auth/accounts/accounts_generator.rb +2 -0
- data/lib/generators/add_auth/accounts/templates/add_add_auth_accounts.rb.tt +2 -2
- data/lib/generators/add_auth/email_tokens/email_tokens_generator.rb +2 -0
- data/lib/generators/add_auth/email_tokens/templates/create_add_auth_sign_in_tokens.rb.tt +1 -1
- data/lib/generators/add_auth/external_identities/external_identities_generator.rb +2 -0
- data/lib/generators/add_auth/external_identities/templates/create_add_auth_external_identities.rb.tt +6 -12
- data/lib/generators/add_auth/mobile_sessions/mobile_sessions_generator.rb +2 -0
- data/lib/generators/add_auth/mobile_sessions/templates/create_add_auth_mobile_handoffs.rb.tt +4 -4
- data/lib/generators/add_auth/notifications/notifications_generator.rb +2 -0
- data/lib/generators/add_auth/notifications/templates/create_add_auth_security_events.rb.tt +1 -1
- data/lib/generators/add_auth/passkeys/passkeys_generator.rb +2 -0
- data/lib/generators/add_auth/passkeys/templates/add_add_auth_passkeys.rb.tt +2 -2
- data/lib/generators/add_auth/primary_key.rb +19 -0
- data/lib/generators/add_auth/session_upgrade/session_upgrade_generator.rb +3 -0
- data/lib/generators/add_auth/session_upgrade/templates/add_add_auth_session_cursor_index.rb.tt +5 -0
- metadata +5 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: b83eabd84666412ae2a83ffc20ac54c9765ef0133e0e401cffebce9e3f73f444
|
|
4
|
+
data.tar.gz: b187c3b70d0c43139f870a50231e5e024f71885218ed68770c98218c257b4be3
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 65eb201de7ebebca70a786c9664a46df8c27404e74dcd2704b25ef33673ab0f42f7a9c5a865c707cb33a943a4a933c8903fc5dafd1479c32bf0ad0266263f7d6
|
|
7
|
+
data.tar.gz: a48427dfe1145c18be90ddb4ec027b70a7179f7e742d3806ee99ee2ddbf7e9f865abbb0486d96cce600e7e5284ee7487ea7a4b17c71639b94c1801dc95b1b8ee
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.5.0 — 2026-09-18
|
|
4
|
+
|
|
5
|
+
- Honor Rails' `primary_key_type` generator setting for all AddAuth-owned tables.
|
|
6
|
+
Support PostgreSQL UUID sessions and mixed integer/UUID account/session keys,
|
|
7
|
+
retaining bigint defaults and deriving references from the existing schema.
|
|
8
|
+
- Use creation time and a typed ID in opaque session-page cursors; validate
|
|
9
|
+
revocation and explicitly enabled legacy session adoption for either key type.
|
|
10
|
+
For apps already using hardened sessions, re-run `add_auth:session_upgrade`
|
|
11
|
+
for the new pagination index. Existing tables retain their IDs; refresh ejected
|
|
12
|
+
controllers through the normal review process.
|
|
13
|
+
Follow the [upgrade guide](https://addauthgem.com/upgrading/#uuid-support) for
|
|
14
|
+
the release gate, migration review, opaque client cursors and rollback boundary.
|
|
15
|
+
|
|
3
16
|
## 0.4.0 — 2026-09-13
|
|
4
17
|
|
|
5
18
|
- Constrain JSON below 3.0 for the supported Rails decoding API; independently
|
data/README.md
CHANGED
|
@@ -29,26 +29,26 @@ for verification in a disposable host.
|
|
|
29
29
|
|
|
30
30
|
## Quickstart
|
|
31
31
|
|
|
32
|
-
These commands target 0.
|
|
33
|
-
[RubyGems publication](https://rubygems.org/gems/add_auth/versions/0.
|
|
32
|
+
These commands target 0.5.0 and are available after its
|
|
33
|
+
[RubyGems publication](https://rubygems.org/gems/add_auth/versions/0.5.0).
|
|
34
34
|
Check package availability before changing your app’s bundle.
|
|
35
35
|
|
|
36
36
|
Install the `add_auth` gem from RubyGems through your Rails app’s Gemfile,
|
|
37
37
|
then enable password and email-link sign-in. Use Ruby 3.3+ and Rails 8.0+
|
|
38
38
|
with Active Record, and run the commands below from your Rails app’s root.
|
|
39
39
|
|
|
40
|
-
**Release availability:** these commands require the published `0.
|
|
40
|
+
**Release availability:** these commands require the published `0.5.0` package.
|
|
41
41
|
If it is not yet listed on [RubyGems](https://rubygems.org/gems/add_auth/versions),
|
|
42
42
|
wait for publication; see [release status](https://addauthgem.com/release-status/).
|
|
43
43
|
Start in your app’s development environment; use the deployment settings
|
|
44
44
|
below before enabling sign-in for users.
|
|
45
45
|
|
|
46
46
|
1. Keep `source "https://rubygems.org"` in your app’s Gemfile and add the
|
|
47
|
-
0.
|
|
47
|
+
0.5 release line:
|
|
48
48
|
|
|
49
49
|
```ruby
|
|
50
50
|
# Gemfile
|
|
51
|
-
gem "add_auth", "~> 0.
|
|
51
|
+
gem "add_auth", "~> 0.5.0"
|
|
52
52
|
```
|
|
53
53
|
|
|
54
54
|
Bundler downloads the package from RubyGems and records the resolved
|
|
@@ -568,6 +568,35 @@ adds registration, confirmation/reconfirmation, password reset, lock/unlock and
|
|
|
568
568
|
account changes. Hosts retain account eligibility, resource authorization and
|
|
569
569
|
provider registrations.
|
|
570
570
|
|
|
571
|
+
### UUID and integer primary keys in 0.5.0
|
|
572
|
+
|
|
573
|
+
Version 0.5.0 adds PostgreSQL UUID sessions and configurable primary keys for all
|
|
574
|
+
AddAuth authentication tables. Integer apps retain the normal Rails defaults.
|
|
575
|
+
These instructions become available after the 0.5.0 package is published.
|
|
576
|
+
|
|
577
|
+
For a new PostgreSQL app, set Rails' generator preference in `config/application.rb`
|
|
578
|
+
before generating authentication or AddAuth features:
|
|
579
|
+
|
|
580
|
+
```ruby
|
|
581
|
+
config.generators do |g|
|
|
582
|
+
g.orm :active_record, primary_key_type: :uuid
|
|
583
|
+
end
|
|
584
|
+
```
|
|
585
|
+
|
|
586
|
+
AddAuth uses this setting for every table it creates. Leave it unset for Rails'
|
|
587
|
+
bigint default, or set `:bigint` or `:integer` explicitly. References follow the
|
|
588
|
+
actual target table, so UUID users can have bigint sessions and vice versa.
|
|
589
|
+
The generated migration records its chosen type; later configuration changes do
|
|
590
|
+
not change that migration or convert existing rows. Other key types are unsupported.
|
|
591
|
+
|
|
592
|
+
For an existing installation, follow the
|
|
593
|
+
[version-gated package upgrade](https://addauthgem.com/upgrading/#uuid-support).
|
|
594
|
+
It covers the additive pagination index, ejected controller review, opaque
|
|
595
|
+
pagination cursors, verification and rollback from older gems. Integer apps need
|
|
596
|
+
no ID conversion. The [primary-key guide](https://addauthgem.com/uuid-primary-keys/)
|
|
597
|
+
explains mixed schemas and the separate, app-owned migration needed to convert
|
|
598
|
+
existing IDs. The published 0.4.0 package cannot manage UUID sessions.
|
|
599
|
+
|
|
571
600
|
## Optional integrations in 0.3.0
|
|
572
601
|
|
|
573
602
|
Enable bounded [Devise migration](https://addauthgem.com/migrating-from-devise/),
|
data/ROADMAP.md
CHANGED
|
@@ -25,6 +25,12 @@ this public checklist is derived from its section 14 and stands on its own.
|
|
|
25
25
|
|
|
26
26
|
## Now — maintain AddAuth and learn from adoption
|
|
27
27
|
|
|
28
|
+
- [x] Honor the Rails UUID/bigint generator setting across all authentication
|
|
29
|
+
tables, including UUID sessions, mixed references and three-mode bundled/ejected
|
|
30
|
+
session management. Local PostgreSQL and browser acceptance passes on Rails
|
|
31
|
+
8.0/8.1. Added in 0.5.0; see
|
|
32
|
+
[release availability](https://github.com/taimoorq/add_auth/releases).
|
|
33
|
+
|
|
28
34
|
- [x] Optional password-signup confirmation, immediate transactional provisioning
|
|
29
35
|
and session issuance, explicit unconfirmed-reset policy, password-only lifecycle
|
|
30
36
|
and the complete commented Rails initializer. Local supported-matrix, stock/
|
data/SECURITY.md
CHANGED
|
@@ -54,6 +54,10 @@ the currency policy).
|
|
|
54
54
|
|
|
55
55
|
## Supported versions
|
|
56
56
|
|
|
57
|
+
UUID/integer session lookup, account-scoped pagination and revocation are also
|
|
58
|
+
in scope. Version 0.5.0 adds UUID sessions and UUID authentication-table generation;
|
|
59
|
+
primary keys must never substitute for browser/mobile bearer secrets.
|
|
60
|
+
|
|
57
61
|
Until a 1.0 is released, only the latest published version receives security
|
|
58
62
|
fixes. This table will be expanded once there are stable release lines to
|
|
59
63
|
support.
|
|
@@ -25,7 +25,7 @@ module AddAuth
|
|
|
25
25
|
|
|
26
26
|
def destroy
|
|
27
27
|
target_id = params[:id].to_s
|
|
28
|
-
if
|
|
28
|
+
if Rails::Runtime.sessions.current_session?(session: Current.session, session_id: target_id)
|
|
29
29
|
terminate_session
|
|
30
30
|
return redirect_to Rails::Runtime.sign_in_path, status: :see_other
|
|
31
31
|
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "base64"
|
|
4
|
+
require "json"
|
|
5
|
+
require "time"
|
|
6
|
+
|
|
7
|
+
module AddAuth
|
|
8
|
+
module Core
|
|
9
|
+
class SessionCursor
|
|
10
|
+
def initialize(key:)
|
|
11
|
+
@key = key
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def encode(row)
|
|
15
|
+
"sc1:" + Base64.urlsafe_encode64(JSON.generate([row.created_at.utc.iso8601(6), row.id]), padding: false)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def decode(value)
|
|
19
|
+
# Retain links issued by releases with integer-only pagination.
|
|
20
|
+
legacy = @key.parse(value)
|
|
21
|
+
return {legacy_id: legacy} if legacy.is_a?(Integer)
|
|
22
|
+
return unless value.is_a?(String) && value.ascii_only? && value.bytesize <= 256 && value.match?(/\Asc1:[A-Za-z0-9_-]+\z/)
|
|
23
|
+
|
|
24
|
+
fields = JSON.parse(Base64.urlsafe_decode64(value.delete_prefix("sc1:")))
|
|
25
|
+
return unless fields.is_a?(Array) && fields.length == 2
|
|
26
|
+
timestamp, id = fields
|
|
27
|
+
return unless timestamp.is_a?(String) && timestamp.match?(/\A\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{6}Z\z/)
|
|
28
|
+
id = @key.parse(id)
|
|
29
|
+
return unless id
|
|
30
|
+
time = Time.iso8601(timestamp)
|
|
31
|
+
return unless time.year.positive? && time.utc.iso8601(6) == timestamp
|
|
32
|
+
{created_at: time, id: id}
|
|
33
|
+
rescue ArgumentError, JSON::ParserError
|
|
34
|
+
nil
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module AddAuth
|
|
4
|
+
module Core
|
|
5
|
+
class SessionKey
|
|
6
|
+
UUID = /\A[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\z/i
|
|
7
|
+
MAX_INTEGER = (2**63) - 1
|
|
8
|
+
|
|
9
|
+
def initialize(type:)
|
|
10
|
+
raise ArgumentError, "unsupported session key type" unless %i[integer bigint uuid].include?(type)
|
|
11
|
+
@uuid = type == :uuid
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def parse(value)
|
|
15
|
+
if @uuid
|
|
16
|
+
value.downcase if value.is_a?(String) && value.ascii_only? && UUID.match?(value)
|
|
17
|
+
elsif value.is_a?(Integer)
|
|
18
|
+
value if value.between?(1, MAX_INTEGER)
|
|
19
|
+
elsif value.is_a?(String) && value.ascii_only? && value.match?(/\A[1-9]\d{0,18}\z/)
|
|
20
|
+
integer = value.to_i
|
|
21
|
+
integer if integer <= MAX_INTEGER
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Stock Rails signed cookies serialize integer IDs as integers, UUIDs as
|
|
26
|
+
# strings. Numeric strings must not broaden the legacy integer bridge.
|
|
27
|
+
def legacy(value)
|
|
28
|
+
parse(value) if @uuid || value.is_a?(Integer)
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
require "securerandom"
|
|
4
4
|
require "add_auth/core/mobile_profile"
|
|
5
|
+
require "add_auth/core/session_key"
|
|
6
|
+
require "add_auth/core/session_cursor"
|
|
5
7
|
|
|
6
8
|
module AddAuth
|
|
7
9
|
module Core
|
|
@@ -37,6 +39,8 @@ module AddAuth
|
|
|
37
39
|
raise ArgumentError, "legacy_bridge_until must be an absolute Time or nil"
|
|
38
40
|
end
|
|
39
41
|
@store, @digest, @eligible, @clock = store, digest, eligible, clock
|
|
42
|
+
@key = SessionKey.new(type: store.respond_to?(:id_type) ? store.id_type : :integer)
|
|
43
|
+
@cursor = SessionCursor.new(key: @key)
|
|
40
44
|
@access_policy = access_policy
|
|
41
45
|
@password_lifecycle = password_lifecycle
|
|
42
46
|
@on_sign_in = on_sign_in
|
|
@@ -191,13 +195,14 @@ module AddAuth
|
|
|
191
195
|
def list_page(user:, current_session_id: nil, before: nil)
|
|
192
196
|
empty = Page.new(entries: [])
|
|
193
197
|
return empty unless user && @eligible.call(user) == true
|
|
194
|
-
|
|
198
|
+
boundary = @cursor.decode(before) unless before.nil?
|
|
199
|
+
return empty unless before.nil? || boundary
|
|
195
200
|
|
|
196
201
|
now = @clock.now
|
|
197
|
-
candidates = @store.list_for_user(user_id: user.id, before:
|
|
202
|
+
candidates = @store.list_for_user(user_id: user.id, before: boundary,
|
|
198
203
|
excluding: current_session_id, limit: PAGE_SIZE + 1, now: now,
|
|
199
204
|
active_after: now - [@idle, @remembered&.fetch(:idle_timeout) || @idle, @mobile&.idle_timeout || @idle].max, legacy: !!(@deadline && now < @deadline))
|
|
200
|
-
cursor = candidates[PAGE_SIZE - 1]
|
|
205
|
+
cursor = @cursor.encode(candidates[PAGE_SIZE - 1]) if candidates.length > PAGE_SIZE
|
|
201
206
|
rows = candidates.first(PAGE_SIZE)
|
|
202
207
|
if before.nil? && current_session_id
|
|
203
208
|
current = @store.find_for_user(user_id: user.id, session_id: current_session_id)
|
|
@@ -217,16 +222,22 @@ module AddAuth
|
|
|
217
222
|
|
|
218
223
|
# Both the initiating bearer and target ownership are checked under lock.
|
|
219
224
|
def revoke_one(user:, session:, session_id:)
|
|
220
|
-
|
|
225
|
+
target_id = @key.parse(session_id)
|
|
226
|
+
return false unless target_id
|
|
221
227
|
|
|
222
228
|
with_live_session(user: user, session: session) do |account, _current, now|
|
|
223
|
-
target = @store.find_for_user_in_transaction(user_id: account.id, session_id:
|
|
229
|
+
target = @store.find_for_user_in_transaction(user_id: account.id, session_id: target_id)
|
|
224
230
|
next false unless target
|
|
225
231
|
@store.update(target, revoked_at: now) unless target.revoked_at
|
|
226
232
|
true
|
|
227
233
|
end || false
|
|
228
234
|
end
|
|
229
235
|
|
|
236
|
+
def current_session?(session:, session_id:)
|
|
237
|
+
id = @key.parse(session_id)
|
|
238
|
+
!!(id && session && id == session.id)
|
|
239
|
+
end
|
|
240
|
+
|
|
230
241
|
# Password proof is obtained from the locked, current account. Intake
|
|
231
242
|
# limits must be applied by the caller before reaching this expensive port.
|
|
232
243
|
def reauthenticate(user:, session:, purpose:, policy:)
|
|
@@ -315,10 +326,10 @@ module AddAuth
|
|
|
315
326
|
private
|
|
316
327
|
|
|
317
328
|
def cookie_lookup(value)
|
|
318
|
-
if value.is_a?(String) && PATTERN.match?(value)
|
|
329
|
+
if value.is_a?(String) && value.ascii_only? && PATTERN.match?(value)
|
|
319
330
|
{digest: @digest.digest(value)}
|
|
320
|
-
elsif
|
|
321
|
-
{id:
|
|
331
|
+
elsif @deadline && @clock.now < @deadline && (id = @key.legacy(value))
|
|
332
|
+
{id: id}
|
|
322
333
|
end
|
|
323
334
|
end
|
|
324
335
|
|
|
@@ -7,15 +7,15 @@ module AddAuth
|
|
|
7
7
|
module_function
|
|
8
8
|
|
|
9
9
|
# Resolve when the migration runs, after the host's account table exists.
|
|
10
|
-
#
|
|
10
|
+
# Keep the session keyword for migrations generated by earlier releases.
|
|
11
11
|
def for(connection, table, session: false)
|
|
12
12
|
key = connection.primary_key(table)
|
|
13
13
|
column = connection.columns(table).find { |entry| entry.name == key }
|
|
14
14
|
raise ArgumentError, "AddAuth requires a single integer or UUID primary key on #{table}" unless column
|
|
15
15
|
return :bigint if column.type == :integer && column.limit == 8
|
|
16
16
|
return :integer if column.type == :integer
|
|
17
|
-
return :uuid if column.type == :uuid
|
|
18
|
-
raise ArgumentError,
|
|
17
|
+
return :uuid if column.type == :uuid
|
|
18
|
+
raise ArgumentError, "AddAuth requires an integer or UUID primary key on #{table}"
|
|
19
19
|
end
|
|
20
20
|
end
|
|
21
21
|
end
|
|
@@ -6,8 +6,11 @@ module AddAuth
|
|
|
6
6
|
module Rails
|
|
7
7
|
module Stores
|
|
8
8
|
class Sessions
|
|
9
|
+
attr_reader :id_type
|
|
10
|
+
|
|
9
11
|
def initialize(user_model:, session_model:)
|
|
10
12
|
@users, @sessions = user_model, session_model
|
|
13
|
+
@id_type = @sessions.columns_hash.fetch(@sessions.primary_key).type
|
|
11
14
|
raise ArgumentError, "authentication models must share one connection pool" unless @users.connection_pool.equal?(@sessions.connection_pool)
|
|
12
15
|
@lock = AccountLock.new(@users)
|
|
13
16
|
end
|
|
@@ -38,11 +41,16 @@ module AddAuth
|
|
|
38
41
|
# Query cutoffs come from Core; Core still authorizes every returned row.
|
|
39
42
|
def list_for_user(user_id:, before:, excluding:, limit:, now:, active_after:, legacy:)
|
|
40
43
|
scope = @sessions.where(user_id: user_id, revoked_at: nil)
|
|
41
|
-
|
|
44
|
+
if before&.key?(:legacy_id)
|
|
45
|
+
scope = scope.where("id < ?", before.fetch(:legacy_id))
|
|
46
|
+
elsif before
|
|
47
|
+
scope = scope.where("created_at < ? OR (created_at = ? AND id < ?)",
|
|
48
|
+
before.fetch(:created_at), before.fetch(:created_at), before.fetch(:id))
|
|
49
|
+
end
|
|
42
50
|
scope = scope.where.not(id: excluding) if excluding
|
|
43
51
|
scope = scope.where("(expires_at IS NULL OR expires_at > ?) AND (last_seen_at IS NULL OR last_seen_at > ?)", now, active_after)
|
|
44
52
|
scope = scope.where.not(expires_at: nil).where.not(last_seen_at: nil) unless legacy
|
|
45
|
-
scope.order(id: :desc).limit(limit).to_a
|
|
53
|
+
scope.order(created_at: :desc, id: :desc).limit(limit).to_a
|
|
46
54
|
end
|
|
47
55
|
|
|
48
56
|
def find_for_user(user_id:, session_id:) = @sessions.find_by(user_id: user_id, id: session_id)
|
data/lib/add_auth/version.rb
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "rails/generators"
|
|
4
|
+
require "generators/add_auth/primary_key"
|
|
4
5
|
require "rails/generators/active_record"
|
|
5
6
|
|
|
6
7
|
module AddAuth
|
|
7
8
|
module Generators
|
|
8
9
|
class AccountsGenerator < ::Rails::Generators::Base
|
|
9
10
|
include ::Rails::Generators::Migration
|
|
11
|
+
include PrimaryKey
|
|
10
12
|
|
|
11
13
|
source_root File.expand_path("templates", __dir__)
|
|
12
14
|
class_option :email_link, type: :boolean, default: true, desc: "Enable email sign-in as a step-up dependency"
|
|
@@ -14,7 +14,7 @@ class AddAddAuthAccounts < ActiveRecord::Migration[8.0]
|
|
|
14
14
|
add_column :users, :add_auth_authority, :string, default: "add_auth", null: false unless column_exists?(:users, :add_auth_authority)
|
|
15
15
|
add_column :sessions, :remembered, :boolean, default: false_default, null: false unless column_exists?(:sessions, :remembered)
|
|
16
16
|
add_column :sessions, :idle_timeout, :integer unless column_exists?(:sessions, :idle_timeout)
|
|
17
|
-
create_table :add_auth_address_claims do |t|
|
|
17
|
+
create_table :add_auth_address_claims, id: <%= authentication_primary_key_type %> do |t|
|
|
18
18
|
t.references :user, type: user_key_type, null: false, foreign_key: {on_delete: :cascade}
|
|
19
19
|
t.string :digest, null: false
|
|
20
20
|
t.string :state, null: false
|
|
@@ -22,7 +22,7 @@ class AddAddAuthAccounts < ActiveRecord::Migration[8.0]
|
|
|
22
22
|
end
|
|
23
23
|
add_index :add_auth_address_claims, :digest, unique: true
|
|
24
24
|
add_index :add_auth_address_claims, [:user_id, :state], unique: true
|
|
25
|
-
create_table :add_auth_account_tokens do |t|
|
|
25
|
+
create_table :add_auth_account_tokens, id: <%= authentication_primary_key_type %> do |t|
|
|
26
26
|
t.references :user, type: user_key_type, null: false, foreign_key: {on_delete: :cascade}
|
|
27
27
|
t.string :digest, null: false
|
|
28
28
|
t.string :purpose, null: false
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "rails/generators"
|
|
4
|
+
require "generators/add_auth/primary_key"
|
|
4
5
|
require "rails/generators/active_record"
|
|
5
6
|
|
|
6
7
|
module AddAuth
|
|
@@ -8,6 +9,7 @@ module AddAuth
|
|
|
8
9
|
# Internal persistence foundation only. Does not install sign-in routes.
|
|
9
10
|
class EmailTokensGenerator < ::Rails::Generators::Base
|
|
10
11
|
include ::Rails::Generators::Migration
|
|
12
|
+
include PrimaryKey
|
|
11
13
|
|
|
12
14
|
source_root File.expand_path("templates", __dir__)
|
|
13
15
|
|
|
@@ -2,7 +2,7 @@ require "add_auth/rails/migration/key_type"
|
|
|
2
2
|
|
|
3
3
|
class CreateAddAuthSignInTokens < ActiveRecord::Migration[8.0]
|
|
4
4
|
def change
|
|
5
|
-
create_table :add_auth_sign_in_tokens do |t|
|
|
5
|
+
create_table :add_auth_sign_in_tokens, id: <%= authentication_primary_key_type %> do |t|
|
|
6
6
|
t.references :user, type: AddAuth::Rails::Migration::KeyType.for(connection, :users), null: false, foreign_key: true
|
|
7
7
|
t.string :digest, limit: 64, null: false
|
|
8
8
|
t.string :identifier_digest, limit: 64, null: false
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "rails/generators"
|
|
4
|
+
require "generators/add_auth/primary_key"
|
|
4
5
|
require "rails/generators/active_record"
|
|
5
6
|
|
|
6
7
|
module AddAuth
|
|
@@ -8,6 +9,7 @@ module AddAuth
|
|
|
8
9
|
# Optional persistence and routes; provider strategies remain disabled until configured.
|
|
9
10
|
class ExternalIdentitiesGenerator < ::Rails::Generators::Base
|
|
10
11
|
include ::Rails::Generators::Migration
|
|
12
|
+
include PrimaryKey
|
|
11
13
|
|
|
12
14
|
source_root File.expand_path("templates", __dir__)
|
|
13
15
|
def self.next_migration_number(dirname) = ::ActiveRecord::Generators::Base.next_migration_number(dirname)
|
data/lib/generators/add_auth/external_identities/templates/create_add_auth_external_identities.rb.tt
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
|
+
require "add_auth/rails/migration/key_type"
|
|
2
|
+
|
|
1
3
|
class CreateAddAuthExternalIdentities < ActiveRecord::Migration[8.0]
|
|
2
4
|
def change
|
|
3
|
-
user_type =
|
|
4
|
-
session_type =
|
|
5
|
-
create_table :add_auth_external_identities do |t|
|
|
5
|
+
user_type = AddAuth::Rails::Migration::KeyType.for(connection, :users)
|
|
6
|
+
session_type = AddAuth::Rails::Migration::KeyType.for(connection, :sessions, session: true)
|
|
7
|
+
create_table :add_auth_external_identities, id: <%= authentication_primary_key_type %> do |t|
|
|
6
8
|
t.references :user, type: user_type, null: false, foreign_key: {on_delete: :cascade}
|
|
7
9
|
# SHA256 of the exact JSON [configuration, issuer, audience, subject].
|
|
8
10
|
# Indexing this key preserves case-sensitive subjects on either store.
|
|
@@ -18,7 +20,7 @@ class CreateAddAuthExternalIdentities < ActiveRecord::Migration[8.0]
|
|
|
18
20
|
t.timestamps
|
|
19
21
|
end
|
|
20
22
|
add_index :add_auth_external_identities, :namespace, unique: true
|
|
21
|
-
create_table :add_auth_external_transactions do |t|
|
|
23
|
+
create_table :add_auth_external_transactions, id: <%= authentication_primary_key_type %> do |t|
|
|
22
24
|
t.string :digest, null: false
|
|
23
25
|
t.string :browser_digest, null: false
|
|
24
26
|
t.string :provider_id, limit: 2048, null: false
|
|
@@ -39,12 +41,4 @@ class CreateAddAuthExternalIdentities < ActiveRecord::Migration[8.0]
|
|
|
39
41
|
add_column :sessions, :authentication_external_id, :string
|
|
40
42
|
add_column :sessions, :authentication_external_version, :string
|
|
41
43
|
end
|
|
42
|
-
|
|
43
|
-
private
|
|
44
|
-
|
|
45
|
-
def reference_type(table)
|
|
46
|
-
column = connection.columns(table).find { |item| item.name == connection.primary_key(table) }
|
|
47
|
-
raise "Unsupported authentication primary key" unless column && [:integer, :uuid].include?(column.type)
|
|
48
|
-
(column.type == :integer && column.limit == 8) ? :bigint : column.type
|
|
49
|
-
end
|
|
50
44
|
end
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "rails/generators"
|
|
4
|
+
require "generators/add_auth/primary_key"
|
|
4
5
|
require "rails/generators/active_record"
|
|
5
6
|
|
|
6
7
|
module AddAuth
|
|
7
8
|
module Generators
|
|
8
9
|
class MobileSessionsGenerator < ::Rails::Generators::Base
|
|
9
10
|
include ::Rails::Generators::Migration
|
|
11
|
+
include PrimaryKey
|
|
10
12
|
|
|
11
13
|
source_root File.expand_path("templates", __dir__)
|
|
12
14
|
def self.next_migration_number(dirname) = ::ActiveRecord::Generators::Base.next_migration_number(dirname)
|
data/lib/generators/add_auth/mobile_sessions/templates/create_add_auth_mobile_handoffs.rb.tt
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
+
require "add_auth/rails/migration/key_type"
|
|
2
|
+
|
|
1
3
|
class CreateAddAuthMobileHandoffs < ActiveRecord::Migration[8.0]
|
|
2
4
|
def change
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
user_type = (user_key.type == :integer && user_key.limit == 8) ? :bigint : user_key.type
|
|
6
|
-
create_table :add_auth_mobile_handoffs do |t|
|
|
5
|
+
user_type = AddAuth::Rails::Migration::KeyType.for(connection, :users)
|
|
6
|
+
create_table :add_auth_mobile_handoffs, id: <%= authentication_primary_key_type %> do |t|
|
|
7
7
|
t.references :user, type: user_type, foreign_key: {on_delete: :cascade}
|
|
8
8
|
t.string :external_digest, null: false
|
|
9
9
|
t.string :digest
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "rails/generators"
|
|
4
|
+
require "generators/add_auth/primary_key"
|
|
4
5
|
require "generators/add_auth/feature_configuration"
|
|
5
6
|
require "rails/generators/active_record"
|
|
6
7
|
|
|
@@ -8,6 +9,7 @@ module AddAuth
|
|
|
8
9
|
module Generators
|
|
9
10
|
class NotificationsGenerator < ::Rails::Generators::Base
|
|
10
11
|
include ::Rails::Generators::Migration
|
|
12
|
+
include PrimaryKey
|
|
11
13
|
include FeatureConfiguration
|
|
12
14
|
|
|
13
15
|
source_root File.expand_path("templates", __dir__)
|
|
@@ -2,7 +2,7 @@ require "add_auth/rails/migration/key_type"
|
|
|
2
2
|
|
|
3
3
|
class CreateAddAuthSecurityEvents < ActiveRecord::Migration[8.0]
|
|
4
4
|
def change
|
|
5
|
-
create_table :add_auth_security_events do |t|
|
|
5
|
+
create_table :add_auth_security_events, id: <%= authentication_primary_key_type %> do |t|
|
|
6
6
|
t.references :user, type: AddAuth::Rails::Migration::KeyType.for(connection, :users), foreign_key: {on_delete: :nullify}
|
|
7
7
|
t.string :kind, null: false
|
|
8
8
|
t.string :digest, null: false
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "rails/generators"
|
|
4
|
+
require "generators/add_auth/primary_key"
|
|
4
5
|
require "generators/add_auth/feature_configuration"
|
|
5
6
|
require "rails/generators/active_record"
|
|
6
7
|
|
|
@@ -8,6 +9,7 @@ module AddAuth
|
|
|
8
9
|
module Generators
|
|
9
10
|
class PasskeysGenerator < ::Rails::Generators::Base
|
|
10
11
|
include ::Rails::Generators::Migration
|
|
12
|
+
include PrimaryKey
|
|
11
13
|
include FeatureConfiguration
|
|
12
14
|
|
|
13
15
|
source_root File.expand_path("templates", __dir__)
|
|
@@ -14,7 +14,7 @@ class AddAddAuthPasskeys < ActiveRecord::Migration[8.0]
|
|
|
14
14
|
add_column :sessions, :authentication_policy_version, :integer, default: 0, null: false
|
|
15
15
|
add_column :sessions, :authentication_credential_id, :string
|
|
16
16
|
add_column :sessions, :authentication_uv, :boolean, default: false_default, null: false
|
|
17
|
-
create_table :add_auth_credentials do |t|
|
|
17
|
+
create_table :add_auth_credentials, id: <%= authentication_primary_key_type %> do |t|
|
|
18
18
|
t.references :user, type: user_key_type, null: false, foreign_key: {on_delete: :cascade}
|
|
19
19
|
t.string :external_id, null: false
|
|
20
20
|
t.text :public_key, null: false
|
|
@@ -29,7 +29,7 @@ class AddAddAuthPasskeys < ActiveRecord::Migration[8.0]
|
|
|
29
29
|
t.timestamps
|
|
30
30
|
end
|
|
31
31
|
add_index :add_auth_credentials, :external_id, unique: true
|
|
32
|
-
create_table :add_auth_ceremonies do |t|
|
|
32
|
+
create_table :add_auth_ceremonies, id: <%= authentication_primary_key_type %> do |t|
|
|
33
33
|
t.string :digest, null: false
|
|
34
34
|
t.string :challenge, null: false
|
|
35
35
|
t.string :kind, null: false
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module AddAuth
|
|
4
|
+
module Generators
|
|
5
|
+
module PrimaryKey
|
|
6
|
+
private
|
|
7
|
+
|
|
8
|
+
# Freeze Rails' generator preference into the migration source. Foreign
|
|
9
|
+
# keys independently follow the actual referenced table when it runs.
|
|
10
|
+
def authentication_primary_key_type
|
|
11
|
+
type = (::Rails.application.config.generators.options.dig(:active_record, :primary_key_type) || :bigint).to_s
|
|
12
|
+
unless %w[bigint integer uuid].include?(type)
|
|
13
|
+
raise Thor::Error, "AddAuth supports Rails primary_key_type :bigint, :integer or :uuid"
|
|
14
|
+
end
|
|
15
|
+
":#{type}"
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -25,6 +25,9 @@ module AddAuth
|
|
|
25
25
|
end
|
|
26
26
|
|
|
27
27
|
def migration
|
|
28
|
+
unless Dir[File.join(destination_root, "db/migrate/*_add_add_auth_session_cursor_index.rb")].any?
|
|
29
|
+
migration_template "add_add_auth_session_cursor_index.rb.tt", "db/migrate/add_add_auth_session_cursor_index.rb"
|
|
30
|
+
end
|
|
28
31
|
unless Dir[File.join(destination_root, "db/migrate/*_extend_sessions_for_add_auth.rb")].any?
|
|
29
32
|
migration_template "extend_sessions_for_add_auth.rb.tt", "db/migrate/extend_sessions_for_add_auth.rb"
|
|
30
33
|
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.5.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Taimoor Qureshi
|
|
@@ -329,6 +329,8 @@ files:
|
|
|
329
329
|
- lib/add_auth/core/rate_limit.rb
|
|
330
330
|
- lib/add_auth/core/remaining_factors.rb
|
|
331
331
|
- lib/add_auth/core/security_events.rb
|
|
332
|
+
- lib/add_auth/core/session_cursor.rb
|
|
333
|
+
- lib/add_auth/core/session_key.rb
|
|
332
334
|
- lib/add_auth/core/sessions.rb
|
|
333
335
|
- lib/add_auth/core/step_up.rb
|
|
334
336
|
- lib/add_auth/core/strategies/email_link.rb
|
|
@@ -423,8 +425,10 @@ files:
|
|
|
423
425
|
- lib/generators/add_auth/passkeys/templates/add_add_auth_passkeys.rb.tt
|
|
424
426
|
- lib/generators/add_auth/passkeys/templates/add_auth_ceremony.rb
|
|
425
427
|
- lib/generators/add_auth/passkeys/templates/add_auth_credential.rb
|
|
428
|
+
- lib/generators/add_auth/primary_key.rb
|
|
426
429
|
- lib/generators/add_auth/session_upgrade/session_upgrade_generator.rb
|
|
427
430
|
- lib/generators/add_auth/session_upgrade/templates/add_add_auth_elevation.rb.tt
|
|
431
|
+
- lib/generators/add_auth/session_upgrade/templates/add_add_auth_session_cursor_index.rb.tt
|
|
428
432
|
- lib/generators/add_auth/session_upgrade/templates/extend_sessions_for_add_auth.rb.tt
|
|
429
433
|
- lib/generators/add_auth/step_up/step_up_generator.rb
|
|
430
434
|
- lib/generators/add_auth/step_up/templates/add_add_auth_reauthentication.rb.tt
|