standard_id 0.55.1 → 0.56.1
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 +42 -0
- data/app/controllers/standard_id/web/logout_controller.rb +2 -1
- data/app/controllers/standard_id/web/sessions_controller.rb +2 -1
- data/lib/standard_id/config/schema.rb +14 -0
- data/lib/standard_id/email_password/registration.rb +7 -1
- data/lib/standard_id/engine.rb +17 -4
- data/lib/standard_id/events/subscribers/logging_subscriber.rb +38 -1
- data/lib/standard_id/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 076ceacf137b604ea48f42f6955f7eb9786656cab0112adc5967f06d5b6a178b
|
|
4
|
+
data.tar.gz: cb179e91df123b9d5c996626a6af219b170faa1e458c6d185dc6ad597f32c044
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: e8f41357e84dbe80520836d321dd3f65e1b81329fe50c34e5c005975612fb77a1bf3d919eb87289c6ddf3ba29f95a22bc84ac45c7d1c9815411ae3efcb6161ae
|
|
7
|
+
data.tar.gz: 7c9637e8e58f301576d8d5564843b19ae2d58599aa94427cf8ffd3985385badbdb5a9b2b2fe9052fa4ef3a55989b59c1362e5a95e39d933c1cd444062830f0c7
|
data/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,48 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.56.1] - 2026-10-27
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
|
|
14
|
+
- **`credential.password.created` from the API email + password sign-up now carries a usable
|
|
15
|
+
credential chain.** `EmailPassword::Registration` created the `Credential` row with
|
|
16
|
+
`Credential.create!`, but `PasswordCredential`'s `has_one :credential, touch: true` had already
|
|
17
|
+
read (and cached) the association as `nil` in `after_create`. A subscriber walking
|
|
18
|
+
`event[:credential].credential.identifier.account` got `nil`; it now gets the account, with the
|
|
19
|
+
whole chain loaded in memory, so strict-loading hosts never load it lazily.
|
|
20
|
+
`Web::SignupForm` was unaffected (it builds the chain with nested attributes); it now has the
|
|
21
|
+
same regression spec.
|
|
22
|
+
|
|
23
|
+
## [0.56.0] - 2026-10-26
|
|
24
|
+
|
|
25
|
+
### Added
|
|
26
|
+
|
|
27
|
+
- **`c.events.log_login_identifiers` (default `false`).** `LoggingSubscriber` now redacts the submitted
|
|
28
|
+
email/login in `AUTHENTICATION_ATTEMPT_STARTED` (debug) and `AUTHENTICATION_FAILED` (warn) log lines by
|
|
29
|
+
default: email-shaped values are logged as `lo***@domain`; everything else as `lo***`. Set
|
|
30
|
+
`c.events.log_login_identifiers = true` to restore the previous behaviour (full value logged) for apps
|
|
31
|
+
that need it for debugging.
|
|
32
|
+
|
|
33
|
+
- **Boot-time warning when `verify_issuer: false` is combined with a configured `issuer`.** The existing
|
|
34
|
+
warning fires when `issuer` is blank; this companion fires when `issuer` is set but `verify_issuer` is
|
|
35
|
+
explicitly `false`, reminding operators it is a migration flag to remove once all in-flight tokens have
|
|
36
|
+
expired.
|
|
37
|
+
|
|
38
|
+
### Fixed
|
|
39
|
+
|
|
40
|
+
- **Inertia history is now cleared on sign-out** for hosts that set `use_inertia = true`.
|
|
41
|
+
`Web::LogoutController#create` and the current-session branch of
|
|
42
|
+
`Web::SessionsController#destroy` now pass `inertia: { clear_history: true }` on
|
|
43
|
+
the redirect, which stores a session flag that the next Inertia render consumes
|
|
44
|
+
(`clearHistory: true` in the page JSON). Without this, the old history-encryption
|
|
45
|
+
key remained in `sessionStorage` and `Back` could still decrypt pages viewed while
|
|
46
|
+
signed in. Hosts without `use_inertia` are unaffected.
|
|
47
|
+
|
|
48
|
+
Rolling this out requires a release followed by a `/rollout-gem` to all five
|
|
49
|
+
consumers (fundbright-web, nutripod-web, jumpdrive-web, luminality-web,
|
|
50
|
+
sidekick-web).
|
|
51
|
+
|
|
10
52
|
## [0.55.1] - 2026-10-23
|
|
11
53
|
|
|
12
54
|
### Fixed
|
|
@@ -14,7 +14,8 @@ module StandardId
|
|
|
14
14
|
|
|
15
15
|
def create
|
|
16
16
|
revoke_current_session!
|
|
17
|
-
redirect_to logout_destination, notice: "Successfully signed out"
|
|
17
|
+
redirect_to logout_destination, notice: "Successfully signed out",
|
|
18
|
+
**(use_inertia? ? { inertia: { clear_history: true } } : {})
|
|
18
19
|
end
|
|
19
20
|
|
|
20
21
|
private
|
|
@@ -15,7 +15,8 @@ module StandardId
|
|
|
15
15
|
if session == current_session
|
|
16
16
|
# If revoking current session, sign out and redirect
|
|
17
17
|
revoke_current_session!
|
|
18
|
-
redirect_to "/", notice: "Session revoked. You have been signed out."
|
|
18
|
+
redirect_to "/", notice: "Session revoked. You have been signed out.",
|
|
19
|
+
**(use_inertia? ? { inertia: { clear_history: true } } : {})
|
|
19
20
|
else
|
|
20
21
|
# Revoke other session
|
|
21
22
|
session.revoke!
|
|
@@ -153,6 +153,20 @@ StandardId::ConfigSchema.define do
|
|
|
153
153
|
scope :events do
|
|
154
154
|
field :enable_logging, type: :boolean, default: false
|
|
155
155
|
|
|
156
|
+
# Whether to log the full login identifier (email / username) in
|
|
157
|
+
# `AUTHENTICATION_ATTEMPT_STARTED` and `AUTHENTICATION_FAILED` log lines.
|
|
158
|
+
#
|
|
159
|
+
# Defaults to `false`: a redacted form is logged instead
|
|
160
|
+
# (`local-part[0..1]***@domain` for email-shaped values, first two
|
|
161
|
+
# characters + *** for everything else). This prevents email addresses from
|
|
162
|
+
# accumulating in log sinks (Sentry, Datadog, etc.) on every failed login
|
|
163
|
+
# attempt, including attacker probes.
|
|
164
|
+
#
|
|
165
|
+
# Set to `true` only for apps that have already accepted the privacy
|
|
166
|
+
# trade-off and need the full value for debugging — typically behind an
|
|
167
|
+
# environment flag that is off in production.
|
|
168
|
+
field :log_login_identifiers, type: :boolean, default: false
|
|
169
|
+
|
|
156
170
|
# Publish `<key>_id` / `<key>_gid` alongside every record-valued payload key
|
|
157
171
|
# (rarebit-one/rarebit-ops#296).
|
|
158
172
|
#
|
|
@@ -105,7 +105,13 @@ module StandardId
|
|
|
105
105
|
account: account, value: email, signup_source: EmailPassword::SIGNUP_SOURCE
|
|
106
106
|
)
|
|
107
107
|
password_credential = StandardId::PasswordCredential.create!(login: email, password: password)
|
|
108
|
-
|
|
108
|
+
# Created through the association, not Credential.create!: the
|
|
109
|
+
# has_one's touch: true reads password_credential.credential in
|
|
110
|
+
# after_create, caching nil, so a subscriber walking
|
|
111
|
+
# credential.identifier.account would get nil. Going through the
|
|
112
|
+
# association leaves the whole chain loaded in memory, which also
|
|
113
|
+
# keeps strict-loading hosts from lazily loading identifier/account.
|
|
114
|
+
password_credential.create_credential!(identifier: identifier)
|
|
109
115
|
|
|
110
116
|
StandardId::Events.publish(
|
|
111
117
|
StandardId::Events::ACCOUNT_CREATED,
|
data/lib/standard_id/engine.rb
CHANGED
|
@@ -81,10 +81,7 @@ module StandardId
|
|
|
81
81
|
StandardId::Events::Subscribers::PasswordlessDeliverySubscriber.attach
|
|
82
82
|
StandardId::Events::Subscribers::PasswordResetDeliverySubscriber.attach
|
|
83
83
|
|
|
84
|
-
|
|
85
|
-
Rails.logger.warn("[StandardId] No issuer configured. JWT tokens will not include or verify the 'iss' claim. " \
|
|
86
|
-
"Set StandardId.config.issuer in your initializer for production use.")
|
|
87
|
-
end
|
|
84
|
+
StandardId::Engine.warn_about_issuer_config!
|
|
88
85
|
|
|
89
86
|
# Fail loudly if `association_strict_loading` was assigned after the
|
|
90
87
|
# host's Account class body already ran — that assignment has no effect,
|
|
@@ -175,5 +172,21 @@ module StandardId
|
|
|
175
172
|
"audiences (e.g., ['web', 'api']) to close cross-audience replay vectors."
|
|
176
173
|
)
|
|
177
174
|
end
|
|
175
|
+
|
|
176
|
+
# Warn when the issuer configuration is missing or when `verify_issuer`
|
|
177
|
+
# is explicitly `false` with an issuer set (migration flag left behind).
|
|
178
|
+
# Extracted to a module method so specs can exercise it without booting
|
|
179
|
+
# a second Rails app.
|
|
180
|
+
def self.warn_about_issuer_config!
|
|
181
|
+
if StandardId.config.issuer.blank?
|
|
182
|
+
Rails.logger.warn("[StandardId] No issuer configured. JWT tokens will not include or verify the 'iss' claim. " \
|
|
183
|
+
"Set StandardId.config.issuer in your initializer for production use.")
|
|
184
|
+
elsif StandardId.config.verify_issuer == false
|
|
185
|
+
Rails.logger.warn("[StandardId] verify_issuer is explicitly false but an issuer is configured " \
|
|
186
|
+
"(#{StandardId.config.issuer.inspect}). Tokens will include the 'iss' claim but " \
|
|
187
|
+
"incoming tokens are NOT required to carry it. This is a migration flag — remove " \
|
|
188
|
+
"verify_issuer: false from your initializer once all in-flight tokens have expired.")
|
|
189
|
+
end
|
|
190
|
+
end
|
|
178
191
|
end
|
|
179
192
|
end
|
|
@@ -110,7 +110,10 @@ module StandardId
|
|
|
110
110
|
payload[:account_id] = event[:account_id]
|
|
111
111
|
end
|
|
112
112
|
|
|
113
|
-
|
|
113
|
+
raw_login = event[:account_lookup] || event[:login] || event[:username]
|
|
114
|
+
if raw_login
|
|
115
|
+
payload[:login] = log_login_identifiers? ? raw_login : redact_login(raw_login)
|
|
116
|
+
end
|
|
114
117
|
payload[:auth_method] = event[:auth_method] if event[:auth_method]
|
|
115
118
|
payload[:grant_type] = event[:grant_type] if event[:grant_type]
|
|
116
119
|
payload[:provider] = event[:provider] if event[:provider]
|
|
@@ -120,6 +123,40 @@ module StandardId
|
|
|
120
123
|
|
|
121
124
|
payload
|
|
122
125
|
end
|
|
126
|
+
|
|
127
|
+
def log_login_identifiers?
|
|
128
|
+
config = StandardId.config
|
|
129
|
+
return false unless config.respond_to?(:events)
|
|
130
|
+
|
|
131
|
+
config.events.log_login_identifiers
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
# Redact a login identifier for logging.
|
|
135
|
+
#
|
|
136
|
+
# Email-shaped values (`local@domain`): keep the first two characters of
|
|
137
|
+
# the local part, mask the rest as `***`, keep the domain verbatim.
|
|
138
|
+
# Everything else: keep the first two characters, mask the rest as `***`.
|
|
139
|
+
#
|
|
140
|
+
# The local part's length is not exposed (all masks are `***`), and the
|
|
141
|
+
# domain is kept so operators can still tell at a glance which service
|
|
142
|
+
# the attempt targeted (e.g. gmail.com vs corporate SSO).
|
|
143
|
+
def redact_login(value)
|
|
144
|
+
str = value.to_s
|
|
145
|
+
# Split on the FIRST '@' only (limit 2) to detect email shape without
|
|
146
|
+
# a regex. A regex such as /\A(.+)@(.+)\z/ is polynomial on strings
|
|
147
|
+
# like "a@a@a@…" because the two greedy `.+` quantifiers can match the
|
|
148
|
+
# same characters in exponentially many ways — a ReDoS vector on
|
|
149
|
+
# attacker-controlled login values. String#split is linear.
|
|
150
|
+
parts = str.split("@", 2)
|
|
151
|
+
if parts.length == 2
|
|
152
|
+
local, domain = parts
|
|
153
|
+
prefix = local.length > 2 ? local[0, 2] : local[0, 1]
|
|
154
|
+
"#{prefix}***@#{domain}"
|
|
155
|
+
else
|
|
156
|
+
prefix = str.length > 2 ? str[0, 2] : str[0, 1]
|
|
157
|
+
"#{prefix}***"
|
|
158
|
+
end
|
|
159
|
+
end
|
|
123
160
|
end
|
|
124
161
|
end
|
|
125
162
|
end
|
data/lib/standard_id/version.rb
CHANGED