standard_id 0.57.0 → 0.58.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 +24 -0
- data/README.md +9 -3
- data/app/controllers/concerns/standard_id/social_authentication.rb +8 -4
- data/db/migrate/20261028100000_remove_redundant_standard_id_indexes.rb +89 -0
- data/lib/standard_id/config/schema.rb +14 -0
- data/lib/standard_id/version.rb +1 -1
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: d7d42a5b149e2cd0cdfd3329781a9a7cb56f0397fce40b9aad0388bc2b3c2824
|
|
4
|
+
data.tar.gz: 2f21ad5ca597432fe8522c1b4e97f913fb2899d29323f6387be3cdd054f0f4e7
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 39b4a7a01eef961b0314a06c9208efe24ed7f8de2f4616dbfb2a333f657f0e7686d09dea307e687757d1e5aa380cb277daa3bcd00849a5f05134397af9c936c4
|
|
7
|
+
data.tar.gz: 95349f954d9c465944299871811796d02473afae6d0583513c5c18d52466fd87628a0137df2b5b8eff9caceace26d7a827f5cf1adb4d93fce168cdc8f63ab518
|
data/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,30 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.58.0] - 2026-10-28
|
|
11
|
+
|
|
12
|
+
### Security
|
|
13
|
+
|
|
14
|
+
- **`SOCIAL_AUTH_COMPLETED` no longer publishes the provider's raw OAuth tokens (DBP-028).** The
|
|
15
|
+
`tokens:` key (access_token, refresh_token, id_token) is gone from the payload by default. An audit
|
|
16
|
+
subscriber that copies the whole payload (`standard_audit` subscribed to `standard_id.social.*`)
|
|
17
|
+
wrote the id_token to append-only rows, because `standard_audit` redacts `access_token` and
|
|
18
|
+
`refresh_token` but not `id_token`. A host that needs the tokens gets `provider_tokens:` in the
|
|
19
|
+
`social.before_account` / `before_mint` hooks, or can set the new
|
|
20
|
+
`config.events.publish_provider_tokens = true` to restore `event[:tokens]`. Rows already written
|
|
21
|
+
are not touched.
|
|
22
|
+
|
|
23
|
+
### Changed
|
|
24
|
+
|
|
25
|
+
- **Migration `20261028100000_remove_redundant_standard_id_indexes` (DBP-009).** Drops 11 indexes the
|
|
26
|
+
gem's own migrations created that no query needs, which cuts write amplification on sessions, refresh
|
|
27
|
+
tokens and authorization codes. Eight are redundant: another index leads with the same columns, or
|
|
28
|
+
`lookup_hash` is already unique, or (on `client_applications`) the index exactly duplicates the one
|
|
29
|
+
`t.references` created. The other three are the never-queried GIN `metadata` indexes on sessions,
|
|
30
|
+
authorization_codes and client_applications (PostgreSQL only). No unique index is touched.
|
|
31
|
+
`CONCURRENTLY` on PostgreSQL and idempotent; `down` rebuilds them. Run
|
|
32
|
+
`bin/rails standard_id:install:migrations && bin/rails db:migrate`.
|
|
33
|
+
|
|
10
34
|
## [0.57.0] - 2026-10-28
|
|
11
35
|
|
|
12
36
|
### Added
|
data/README.md
CHANGED
|
@@ -609,12 +609,18 @@ To handle social login completion (e.g., for analytics or audit logging), subscr
|
|
|
609
609
|
StandardId::Events.subscribe(StandardId::Events::SOCIAL_AUTH_COMPLETED) do |event|
|
|
610
610
|
Analytics.track_social_login(
|
|
611
611
|
provider: event[:provider],
|
|
612
|
-
account_id: event[:account].id
|
|
613
|
-
tokens: event[:tokens]
|
|
612
|
+
account_id: event[:account].id
|
|
614
613
|
)
|
|
615
614
|
end
|
|
616
615
|
```
|
|
617
616
|
|
|
617
|
+
The event does not carry the provider's raw OAuth tokens (access, refresh or
|
|
618
|
+
id_token) since 0.58: a subscriber that stores the whole payload, such as an
|
|
619
|
+
audit log, would keep them. A host that needs them to call the provider's API
|
|
620
|
+
gets `provider_tokens:` in the `social.before_account` / `before_mint` hooks.
|
|
621
|
+
Setting `config.events.publish_provider_tokens = true` restores `event[:tokens]`;
|
|
622
|
+
the subscriber that reads it then has to keep them out of audit rows and logs.
|
|
623
|
+
|
|
618
624
|
### Inertia.js Integration
|
|
619
625
|
|
|
620
626
|
StandardId supports [Inertia.js](https://inertiajs.com/) for modern React, Vue, or Svelte frontends. When enabled, web controllers render Inertia components instead of ERB views.
|
|
@@ -1003,7 +1009,7 @@ Every StandardId event automatically carries tracing metadata (`event_id`, `time
|
|
|
1003
1009
|
| | `social.user_info.fetched` | `provider`, `social_info`, `email` | After fetching user info from the provider |
|
|
1004
1010
|
| | `social.account.created` | `account`, `provider`, `social_info` | When a social login creates a new account |
|
|
1005
1011
|
| | `social.account.linked` | `account`, `provider`, `identifier` | When a social login links to an existing account by email (published once the link is written, never for a refused login), or matches an existing link by `sub` |
|
|
1006
|
-
| | `social.auth.completed` | `account`, `provider`, `tokens` | After social login completes |
|
|
1012
|
+
| | `social.auth.completed` | `account`, `provider`, `social_info`, `original_request_params` (`tokens` only with `events.publish_provider_tokens`) | After social login completes |
|
|
1007
1013
|
| | `social.auth.failed` | `provider`, `error`, `error_class`, `account` | When social login fails due to an infrastructure error (HTTP/DNS/SSL/timeout) |
|
|
1008
1014
|
| Credential | `credential.password.created` | `credential`, `account` | After a password credential is created |
|
|
1009
1015
|
| | `credential.password.reset_initiated` | `credential`, `account`, `reset_token_expires_at` | After a password reset is initiated |
|
|
@@ -538,15 +538,19 @@ module StandardId
|
|
|
538
538
|
)
|
|
539
539
|
end
|
|
540
540
|
|
|
541
|
+
# Raw provider tokens stay off the bus unless the host opts in: subscribers
|
|
542
|
+
# that persist the whole payload (audit) would otherwise store them. See
|
|
543
|
+
# events.publish_provider_tokens.
|
|
541
544
|
def emit_social_auth_completed(provider, social_info, provider_tokens, account, original_request_params)
|
|
542
|
-
|
|
543
|
-
StandardId::Events::SOCIAL_AUTH_COMPLETED,
|
|
545
|
+
payload = {
|
|
544
546
|
account: account,
|
|
545
547
|
provider: provider,
|
|
546
548
|
social_info: social_info,
|
|
547
|
-
tokens: provider_tokens,
|
|
548
549
|
original_request_params: original_request_params
|
|
549
|
-
|
|
550
|
+
}
|
|
551
|
+
payload[:tokens] = provider_tokens if StandardId.config.events.publish_provider_tokens
|
|
552
|
+
|
|
553
|
+
StandardId::Events.publish(StandardId::Events::SOCIAL_AUTH_COMPLETED, payload)
|
|
550
554
|
end
|
|
551
555
|
|
|
552
556
|
def emit_account_creating_from_social(social_info)
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
class RemoveRedundantStandardIdIndexes < ActiveRecord::Migration[8.0]
|
|
2
|
+
# Drops indexes the gem's own migrations created that no query needs (deep
|
|
3
|
+
# audit DBP-009, 2026-10-27). Each is write amplification on the tables the
|
|
4
|
+
# auth path writes most: every sign-in inserts a session, every refresh a
|
|
5
|
+
# refresh token.
|
|
6
|
+
#
|
|
7
|
+
# Redundant: another index leads with the same column(s), so every lookup
|
|
8
|
+
# and every foreign-key check it served is served by that one. None of them
|
|
9
|
+
# is unique; no unique index is touched.
|
|
10
|
+
#
|
|
11
|
+
# sessions [account_id] <- [account_id, type, expires_at]
|
|
12
|
+
# sessions [lookup_hash, expires_at, revoked_at]
|
|
13
|
+
# <- [lookup_hash] UNIQUE
|
|
14
|
+
# refresh_tokens [account_id] <- [account_id, revoked_at]
|
|
15
|
+
# refresh_tokens [session_id] <- [session_id, revoked_at]
|
|
16
|
+
# authorization_codes [account_id] <- [account_id, expires_at]
|
|
17
|
+
# client_grants [account_id] <- [account_id, client_id] UNIQUE
|
|
18
|
+
# identifiers [account_id] <- [account_id, type, value] UNIQUE
|
|
19
|
+
# client_applications [owner_type, owner_id] "idx_on_owner_type_owner_id_..."
|
|
20
|
+
# <- the identical index t.references
|
|
21
|
+
# created ("..._on_owner")
|
|
22
|
+
#
|
|
23
|
+
# Unused (PostgreSQL only; SQLite never had them): the GIN `metadata` indexes
|
|
24
|
+
# on sessions, authorization_codes and client_applications. Nothing in the
|
|
25
|
+
# gem, the provider plugins or the five consumer apps queries those columns
|
|
26
|
+
# with a containment operator (@>, ?, ?|, ?&); the one host query on
|
|
27
|
+
# client_applications.metadata uses `->> =`, which a GIN jsonb_ops index
|
|
28
|
+
# cannot serve. 20260416180511 dropped the code_challenges GIN for the same
|
|
29
|
+
# reason.
|
|
30
|
+
#
|
|
31
|
+
# Every name below was checked against the schema of all five consumer apps
|
|
32
|
+
# (identical everywhere, as is the covering index). Idempotent (if_exists /
|
|
33
|
+
# if_not_exists) and CONCURRENTLY on Postgres, like 20261028000000; `down`
|
|
34
|
+
# rebuilds each one as it was.
|
|
35
|
+
disable_ddl_transaction!
|
|
36
|
+
|
|
37
|
+
INDEXES = [
|
|
38
|
+
{ table: :standard_id_sessions, columns: :account_id,
|
|
39
|
+
name: "index_standard_id_sessions_on_account_id" },
|
|
40
|
+
{ table: :standard_id_sessions, columns: [:lookup_hash, :expires_at, :revoked_at],
|
|
41
|
+
name: "idx_on_lookup_hash_expires_at_revoked_at_34a4504c19" },
|
|
42
|
+
{ table: :standard_id_refresh_tokens, columns: :account_id,
|
|
43
|
+
name: "index_standard_id_refresh_tokens_on_account_id" },
|
|
44
|
+
{ table: :standard_id_refresh_tokens, columns: :session_id,
|
|
45
|
+
name: "index_standard_id_refresh_tokens_on_session_id" },
|
|
46
|
+
{ table: :standard_id_authorization_codes, columns: :account_id,
|
|
47
|
+
name: "index_standard_id_authorization_codes_on_account_id" },
|
|
48
|
+
{ table: :standard_id_client_grants, columns: :account_id,
|
|
49
|
+
name: "index_standard_id_client_grants_on_account_id" },
|
|
50
|
+
{ table: :standard_id_identifiers, columns: :account_id,
|
|
51
|
+
name: "index_standard_id_identifiers_on_account_id" },
|
|
52
|
+
{ table: :standard_id_client_applications, columns: [:owner_type, :owner_id],
|
|
53
|
+
name: "idx_on_owner_type_owner_id_936e856298" },
|
|
54
|
+
{ table: :standard_id_sessions, columns: :metadata, using: :gin, postgres_only: true,
|
|
55
|
+
name: "index_standard_id_sessions_on_metadata" },
|
|
56
|
+
{ table: :standard_id_authorization_codes, columns: :metadata, using: :gin, postgres_only: true,
|
|
57
|
+
name: "index_standard_id_authorization_codes_on_metadata" },
|
|
58
|
+
{ table: :standard_id_client_applications, columns: :metadata, using: :gin, postgres_only: true,
|
|
59
|
+
name: "index_standard_id_client_applications_on_metadata" }
|
|
60
|
+
].freeze
|
|
61
|
+
|
|
62
|
+
def up
|
|
63
|
+
INDEXES.each do |index|
|
|
64
|
+
next if index[:postgres_only] && !postgres?
|
|
65
|
+
|
|
66
|
+
remove_index index[:table], name: index[:name], if_exists: true, **concurrent
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def down
|
|
71
|
+
INDEXES.each do |index|
|
|
72
|
+
next if index[:postgres_only] && !postgres?
|
|
73
|
+
|
|
74
|
+
options = { name: index[:name], if_not_exists: true, **concurrent }
|
|
75
|
+
options[:using] = index[:using] if index[:using]
|
|
76
|
+
add_index index[:table], index[:columns], **options
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
private
|
|
81
|
+
|
|
82
|
+
def postgres?
|
|
83
|
+
connection.adapter_name.downcase.include?("postgres")
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def concurrent
|
|
87
|
+
postgres? ? { algorithm: :concurrently } : {}
|
|
88
|
+
end
|
|
89
|
+
end
|
|
@@ -194,6 +194,20 @@ StandardId::ConfigSchema.define do
|
|
|
194
194
|
# the records going away in the one after. Until a host opts in, behaviour
|
|
195
195
|
# is exactly as it was.
|
|
196
196
|
field :publish_records, type: :boolean, default: true
|
|
197
|
+
|
|
198
|
+
# Whether SOCIAL_AUTH_COMPLETED carries the provider's raw OAuth tokens
|
|
199
|
+
# (`tokens:` — access_token, refresh_token, id_token).
|
|
200
|
+
#
|
|
201
|
+
# Off by default since 0.58 (DBP-028). An audit subscriber that copies the
|
|
202
|
+
# whole payload wrote them to append-only rows: `standard_audit` redacts
|
|
203
|
+
# `access_token`/`refresh_token` but not `id_token`, which is a bearer
|
|
204
|
+
# credential carrying the user's email. The bus is the wrong place for a
|
|
205
|
+
# secret; a host that needs the tokens (to call the provider's API) gets
|
|
206
|
+
# them as `provider_tokens:` in `social.before_account`/`before_mint`.
|
|
207
|
+
#
|
|
208
|
+
# Turn it on only if a subscriber of yours reads `event[:tokens]`, and keep
|
|
209
|
+
# that subscriber's output out of audit rows, logs and error reports.
|
|
210
|
+
field :publish_provider_tokens, type: :boolean, default: false
|
|
197
211
|
end
|
|
198
212
|
|
|
199
213
|
scope :passwordless do
|
data/lib/standard_id/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: standard_id
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.58.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Jaryl Sim
|
|
@@ -250,6 +250,7 @@ files:
|
|
|
250
250
|
- db/migrate/20261020120000_add_registration_access_token_digest_to_standard_id_client_applications.rb
|
|
251
251
|
- db/migrate/20261022000000_add_signup_source_to_standard_id_identifiers.rb
|
|
252
252
|
- db/migrate/20261028000000_add_value_lookup_indexes_to_standard_id_tables.rb
|
|
253
|
+
- db/migrate/20261028100000_remove_redundant_standard_id_indexes.rb
|
|
253
254
|
- lib/generators/standard_id/install/install_generator.rb
|
|
254
255
|
- lib/generators/standard_id/install/templates/standard_id.rb
|
|
255
256
|
- lib/generators/standard_id/install/templates/standard_id_reference.rb
|