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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c50961cc2c4446bd495efdebca4ad54a871c371fab3c63e0ff1dd6792b44e1ff
4
- data.tar.gz: be9ebdaedf84eedfed63f98ade1e8fb37bfc90112b7ba4532aa8d179aefb03b9
3
+ metadata.gz: d7d42a5b149e2cd0cdfd3329781a9a7cb56f0397fce40b9aad0388bc2b3c2824
4
+ data.tar.gz: 2f21ad5ca597432fe8522c1b4e97f913fb2899d29323f6387be3cdd054f0f4e7
5
5
  SHA512:
6
- metadata.gz: 55f8de3f738e3363e452842bcea0b68fbff7798e59039bdac698de7b0b5d1c1fffdb58fe693e60671aa6c9cc2743059fc3d3269e11bde54a84d7bbb23e64b8f5
7
- data.tar.gz: 9c0e08fdb33cc59058c5be1be6f65b7308bb497738a8a338de1072e5cbd105f59417cbd65c4902e6f937ff53544bbd755cf7e8b8e349b2bd058583cc75eaa2bc
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
- StandardId::Events.publish(
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
@@ -1,3 +1,3 @@
1
1
  module StandardId
2
- VERSION = "0.57.0"
2
+ VERSION = "0.58.0"
3
3
  end
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.57.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