standard_id 0.24.0 → 0.26.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/README.md +11 -1
- data/lib/standard_id/config/schema.rb +16 -2
- data/lib/standard_id/passwordless/verification_service.rb +12 -1
- data/lib/standard_id/version.rb +1 -1
- data/lib/standard_id/web/session_manager.rb +18 -2
- 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: a0d8132ce80b99059528d033a05dc3d6ec4c421d6eaea14725e51357639f4808
|
|
4
|
+
data.tar.gz: 2c8386bc4ea25b23e54553ed0ad3ba5816fc47efcb64003aa02252e0af54e54d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: e69d8a20c4419deec38b8cc3c69b51a6b99b6a6c2a9043bf0a16625bb7539faf8ebf9095856d2fcad9a6ed2eda011863ba374d7872d681dc229e6a682d273e99
|
|
7
|
+
data.tar.gz: ed39942791e51fff0b333d9026e4707a52c8acdfffc5a40c447939b5f486d971597d3711417453bb1ecb66f736b78af607bf8bb9607c0a9ae1594139d3d4a7ff
|
data/README.md
CHANGED
|
@@ -450,7 +450,17 @@ end
|
|
|
450
450
|
|
|
451
451
|
**Realm isolation.** `realm:` is a free-form string that partitions challenges by purpose. A code issued for realm `"widget_contact_verification"` cannot be used to verify against realm `"authentication"` (or any other realm) — even for the same `target`. Choose a stable string per flow.
|
|
452
452
|
|
|
453
|
-
**Bypass code (E2E testing).** When `StandardId.config.passwordless.bypass_code` is set (and
|
|
453
|
+
**Bypass code (E2E testing).** When `StandardId.config.passwordless.bypass_code` is set (and the deploy is not production), `Otp.verify` accepts the bypass code for **any realm**. This replaces per-app bypass ENV checks and works consistently across `Otp.verify` and the built-in passwordless login flow. Never set `bypass_code` in production — it will raise if you try.
|
|
454
|
+
|
|
455
|
+
By default "production" means `Rails.env.production?`. Apps that distinguish a physical deploy environment from `RAILS_ENV` (e.g. a staging box that still runs `RAILS_ENV=production`) can override the decision with a callable:
|
|
456
|
+
|
|
457
|
+
```ruby
|
|
458
|
+
# Allow the bypass code on a physically-staging box, still refused on real prod.
|
|
459
|
+
c.passwordless.production_env_detector = -> { AppEnv.production? }
|
|
460
|
+
c.passwordless.bypass_code = ENV["STANDARD_ID_BYPASS_CODE"] unless AppEnv.production?
|
|
461
|
+
```
|
|
462
|
+
|
|
463
|
+
`production_env_detector` takes no arguments and returns a boolean; when `nil` (the default) the gem falls back to `Rails.env.production?`, so existing consumers are unaffected.
|
|
454
464
|
|
|
455
465
|
**Back-compat.** The existing passwordless authentication flow continues to work unchanged. `Otp.issue`/`Otp.verify` are a new addition — you can migrate direct `CodeChallenge.create!` calls at your own pace.
|
|
456
466
|
|
|
@@ -102,13 +102,27 @@ StandardId::ConfigSchema.define do
|
|
|
102
102
|
field :max_attempts_per_challenge, type: :integer, default: nil
|
|
103
103
|
|
|
104
104
|
field :retry_delay, type: :integer, default: 30 # 30 seconds
|
|
105
|
-
# Bypass code for E2E testing —
|
|
106
|
-
# When set and
|
|
105
|
+
# Bypass code for E2E testing — refused on production deploys (raises).
|
|
106
|
+
# When set and the deploy is *not* production, this code is accepted by
|
|
107
107
|
# both the built-in passwordless login and by StandardId::Otp.verify
|
|
108
108
|
# for *any* realm. Use a long, non-guessable value and unset it
|
|
109
109
|
# outside test environments.
|
|
110
|
+
#
|
|
111
|
+
# "Production" is decided by production_env_detector below (default
|
|
112
|
+
# Rails.env.production?), so host apps that distinguish a physical deploy
|
|
113
|
+
# environment from RAILS_ENV can still gate this correctly.
|
|
110
114
|
field :bypass_code, type: :string, default: nil
|
|
111
115
|
|
|
116
|
+
# Optional callable deciding whether the current deploy is "production"
|
|
117
|
+
# for the purpose of the bypass-code guard. Takes no args, returns a
|
|
118
|
+
# boolean. When nil (default), falls back to Rails.env.production? — so
|
|
119
|
+
# existing consumers are unchanged. Host apps that distinguish a physical
|
|
120
|
+
# deploy environment from RAILS_ENV (e.g. APP_ENVIRONMENT, which stays
|
|
121
|
+
# RAILS_ENV=production on a physically-staging box) can supply
|
|
122
|
+
# `-> { AppEnv.production? }` to allow a bypass code on staging while it
|
|
123
|
+
# stays refused on real production.
|
|
124
|
+
field :production_env_detector, type: :any, default: nil
|
|
125
|
+
|
|
112
126
|
# Custom username validator for passwordless flows.
|
|
113
127
|
# When set, called before OTP generation to validate the recipient address.
|
|
114
128
|
# Must be a callable (lambda/proc) that receives (username, connection_type)
|
|
@@ -204,7 +204,7 @@ module StandardId
|
|
|
204
204
|
bypass_code = StandardId.config.passwordless.bypass_code
|
|
205
205
|
return unless bypass_code.present?
|
|
206
206
|
|
|
207
|
-
if
|
|
207
|
+
if bypass_production_deploy?
|
|
208
208
|
raise "STANDARD_ID_BYPASS_CODE must not be set in production"
|
|
209
209
|
end
|
|
210
210
|
|
|
@@ -246,6 +246,17 @@ module StandardId
|
|
|
246
246
|
end
|
|
247
247
|
end
|
|
248
248
|
|
|
249
|
+
# Whether the bypass code is forbidden because this is a production
|
|
250
|
+
# deploy. Defers to config.passwordless.production_env_detector when set
|
|
251
|
+
# (host apps that distinguish a physical deploy env from RAILS_ENV), else
|
|
252
|
+
# falls back to Rails.env.production?.
|
|
253
|
+
def bypass_production_deploy?
|
|
254
|
+
detector = StandardId.config.passwordless.production_env_detector
|
|
255
|
+
return !!detector.call if detector
|
|
256
|
+
|
|
257
|
+
defined?(Rails) && Rails.env.production?
|
|
258
|
+
end
|
|
259
|
+
|
|
249
260
|
def resolve_target_and_channel!(email, phone)
|
|
250
261
|
if email.present?
|
|
251
262
|
@target = email.to_s.strip
|
data/lib/standard_id/version.rb
CHANGED
|
@@ -35,7 +35,7 @@ module StandardId
|
|
|
35
35
|
# Store in both session and encrypted cookie for backward compatibility
|
|
36
36
|
# Action Cable will use the encrypted cookie
|
|
37
37
|
session[:session_token] = browser_session.token
|
|
38
|
-
|
|
38
|
+
write_session_cookie(browser_session)
|
|
39
39
|
if scope_name
|
|
40
40
|
scopes = Array(session[:standard_id_scopes])
|
|
41
41
|
scopes << scope_name.to_s unless scopes.include?(scope_name.to_s)
|
|
@@ -71,6 +71,22 @@ module StandardId
|
|
|
71
71
|
|
|
72
72
|
private
|
|
73
73
|
|
|
74
|
+
# Persist the session token in an encrypted cookie whose lifetime matches
|
|
75
|
+
# the DB session's expires_at, so an authenticated session survives a full
|
|
76
|
+
# browser restart (a bare session cookie would be cleared on browser close,
|
|
77
|
+
# logging the user out well before the BrowserSession actually expires).
|
|
78
|
+
# httponly/secure/same_site harden the cookie; httponly does not affect
|
|
79
|
+
# Action Cable, which reads the cookie server-side.
|
|
80
|
+
def write_session_cookie(browser_session)
|
|
81
|
+
cookies.encrypted[:session_token] = {
|
|
82
|
+
value: browser_session.token,
|
|
83
|
+
expires: browser_session.expires_at,
|
|
84
|
+
httponly: true,
|
|
85
|
+
secure: request.ssl?,
|
|
86
|
+
same_site: :lax
|
|
87
|
+
}
|
|
88
|
+
end
|
|
89
|
+
|
|
74
90
|
def load_current_account
|
|
75
91
|
if StandardId.config.account_scope
|
|
76
92
|
account_id = current_session&.account_id
|
|
@@ -121,7 +137,7 @@ module StandardId
|
|
|
121
137
|
token_manager.create_browser_session(password_credential.account, remember_me: true).tap do |browser_session|
|
|
122
138
|
# Store in both session and encrypted cookie for backward compatibility
|
|
123
139
|
session[:session_token] = browser_session.token
|
|
124
|
-
|
|
140
|
+
write_session_cookie(browser_session)
|
|
125
141
|
cookies[:remember_token] = token_manager.create_remember_token(password_credential)
|
|
126
142
|
end
|
|
127
143
|
end
|