standard_id 0.51.2 → 0.53.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: 18c7955706f1fbe444d4bd7db052f615acfb01d7310f1a9c7088717017b864c5
4
- data.tar.gz: 65435d9e1d57e2bfcbd479b5ecd81353d2961f831ad3f53db911bf830d20d89e
3
+ metadata.gz: 1aa1e111cbaf2abc19d4b43e57a7c276054bf0522418c7303f855659cb3c259f
4
+ data.tar.gz: 583e0568a53fbf9880c11cd0fbf4e17ae2b98d73b52b2db2725ed5dbe4a5dab0
5
5
  SHA512:
6
- metadata.gz: ec9734a8609d74823eb5b5a1b7b395d28f11c38268a19ff305618145777543ac13a5671c54f66cf2ecae5f68a9924e54dfeed495726dbc554f591d7f52f07078
7
- data.tar.gz: 8616544546bd160e81490b665f40e5d4b64b03b2a612128efe896c4aacea446dbd240a8518e9d9c9a1e8bed1ef491421a406deb22ccfc52bd73f03fc6dab6a8e
6
+ metadata.gz: d5410924e96b90aca074e3a322cfa7e59fc54c5412e41927157b19f03c723355724295a67cc9ddb3dec70ca38e548c1bf8062431a46859c6ab153c6416f3b9e6
7
+ data.tar.gz: 3216249e86cd957cc0196518374b2b21073ef03f3f134988f97bedc064ecbbbd9e10a7be88a48606ae7502c9b1dcdecbd8fdd606bea9aa81b361269e2cc9cbbc
data/CHANGELOG.md CHANGED
@@ -7,6 +7,56 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.53.0] - 2026-10-22
11
+
12
+ ### Added
13
+
14
+ - **`StandardId::Testing::RequestHelpers#sign_in_as(account, scope: nil)`.** Signs an
15
+ account in for the following requests in a request spec: it creates a real
16
+ `BrowserSession` and writes the encrypted `session_token` cookie a real sign-in
17
+ writes, built with the host's own cookie jar, so no test-only route is added to
18
+ the app. `scope:` also writes `standard_id_scopes` into the Rails session cookie
19
+ (cookie session store only; any other store raises `ArgumentError`, before
20
+ anything is created). Like a real sign-in it always replaces the Rails session
21
+ cookie, so a later `sign_in_as` never inherits an earlier one's scopes or any
22
+ other session data (flash, CSRF token). Replaces per-app controller
23
+ stubs and `/test/set-session` routes.
24
+ - **`RequestHelpers#bearer_headers_for(account, **jwt_options)`:** `build_jwt` plus
25
+ `bearer_auth_header` in one call.
26
+
27
+ ### Changed
28
+
29
+ - **"Production" follows `APP_ENVIRONMENT`, failing closed.** The new
30
+ `StandardId::AppEnvironment.production?` decides every production check in the
31
+ gem: the bypass-code guard (when `passwordless.production_env_detector` is
32
+ unset), the provider-misconfiguration `:raise` mode, and the empty
33
+ `allowed_audiences` boot warning. It reads `APP_ENVIRONMENT` when set (only
34
+ `staging`, `preview`, `development` and `test` are non-production; any other
35
+ value is production), else `Rails.env.production?`. A staging box with
36
+ `RAILS_ENV=production` and `APP_ENVIRONMENT=staging` is therefore no longer
37
+ treated as production, and `-> { AppEnv.production? }` detectors can be
38
+ deleted. Same convention as standard_audit, standard_health and
39
+ standard_singpass.
40
+ - **`rails g standard_id:install` writes a minimal initializer.** It holds only
41
+ `account_class_name`, plus commented `issuer` and `c.preset :inertia_web` lines,
42
+ instead of several hundred lines of commented defaults that apps then kept
43
+ verbatim. `--with-reference` writes
44
+ the annotated initializer as before. Existing initializers are not touched.
45
+
46
+ ## [0.52.0] - 2026-10-22
47
+
48
+ ### Added
49
+
50
+ - **`c.preset :inertia_web`.** Applies the settings an Inertia app with
51
+ passwordless or social sign-in flips: `use_inertia` and `alias_current_user`
52
+ true; `web.signup`, `web.password_login` and `web.password_reset` false;
53
+ `oauth.discovery_endpoint_base = :request`. Each setting is hand-set in four
54
+ of the five estate apps, though the full bundle matches only two of them. A
55
+ preset fills only settings the host has not assigned (via
56
+ `Scope#assigned?`), so explicit assignments win in either order; an unknown
57
+ name raises `ArgumentError`. The public defaults are unchanged.
58
+ Presets live in `StandardId::Config::Presets`.
59
+
10
60
  ## [0.51.2] - 2026-10-21
11
61
 
12
62
  ### Fixed
data/README.md CHANGED
@@ -63,6 +63,13 @@ $ bundle install
63
63
  rails generate standard_id:install
64
64
  ```
65
65
 
66
+ This writes a short `config/initializers/standard_id.rb` holding only what an
67
+ app must decide (`account_class_name`, plus commented `issuer` and
68
+ `c.preset :inertia_web` lines). Add a setting only when you change it from the
69
+ default. `--with-reference` writes the annotated initializer instead, with every
70
+ commonly changed setting and its default commented out; it is a reference to
71
+ read, not a file to keep whole.
72
+
66
73
  ### 2. Configure Your Account Model
67
74
 
68
75
  ```ruby
@@ -629,6 +636,23 @@ StandardId.configure do |config|
629
636
  end
630
637
  ```
631
638
 
639
+ Or start from the `:inertia_web` preset, which bundles the settings an
640
+ Inertia app with passwordless or social sign-in usually flips:
641
+
642
+ ```ruby
643
+ StandardId.configure do |config|
644
+ config.preset :inertia_web
645
+ # use_inertia = true, alias_current_user = true,
646
+ # web.signup / web.password_login / web.password_reset = false,
647
+ # oauth.discovery_endpoint_base = :request
648
+ config.web.passwordless_login = true # not part of the preset: your call
649
+ end
650
+ ```
651
+
652
+ A preset fills only the settings you haven't assigned, so an explicit
653
+ assignment wins whether it comes before or after `preset`. An unknown name
654
+ raises `ArgumentError`. The public defaults are unchanged.
655
+
632
656
  3. Create the corresponding frontend components. The component path follows the pattern:
633
657
  `{namespace}/{ControllerName}/{action}`
634
658
 
@@ -852,15 +876,19 @@ end
852
876
 
853
877
  **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.
854
878
 
855
- 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:
879
+ "Production" is decided by `StandardId::AppEnvironment`, the same rule every
880
+ check in the gem uses: `APP_ENVIRONMENT` when it is set, where only `staging`,
881
+ `preview`, `development` and `test` count as non-production (any other value,
882
+ including a typo, is production), else `Rails.env.production?`. So a staging box
883
+ running `RAILS_ENV=production` with `APP_ENVIRONMENT=staging` is not
884
+ production, and needs no extra wiring:
856
885
 
857
886
  ```ruby
858
- # Allow the bypass code on a physically-staging box, still refused on real prod.
859
- c.passwordless.production_env_detector = -> { AppEnv.production? }
860
- c.passwordless.bypass_code = ENV["STANDARD_ID_BYPASS_CODE"] unless AppEnv.production?
887
+ c.passwordless.bypass_code = ENV["STANDARD_ID_BYPASS_CODE"] unless StandardId::AppEnvironment.production?
861
888
  ```
862
889
 
863
- `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.
890
+ `production_env_detector` (a callable taking no arguments, returning a boolean)
891
+ still overrides that, for an app with a different discriminator.
864
892
 
865
893
  **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.
866
894
 
@@ -1926,6 +1954,38 @@ Notes:
1926
1954
 
1927
1955
  ## Testing
1928
1956
 
1957
+ ### Signing in from your app's request specs
1958
+
1959
+ ```ruby
1960
+ # spec/rails_helper.rb
1961
+ require "standard_id/testing"
1962
+
1963
+ RSpec.configure do |config|
1964
+ config.include StandardId::Testing::RequestHelpers, type: :request
1965
+ end
1966
+ ```
1967
+
1968
+ ```ruby
1969
+ sign_in_as(account) # a real BrowserSession, via the session_token cookie
1970
+ sign_in_as(account, scope: :platform) # also grants standard_id_scopes (current_scope_names)
1971
+ get "/dashboard"
1972
+
1973
+ get "/api/orders", headers: bearer_headers_for(account, scope: "orders.read")
1974
+ ```
1975
+
1976
+ `sign_in_as` creates a real `BrowserSession` and writes the same encrypted
1977
+ `session_token` cookie a real sign-in writes, so `current_account`,
1978
+ `require_browser_session!`, expiry and revocation behave exactly as in
1979
+ production. It adds nothing to your app: the cookies are built with your app's
1980
+ own encrypted cookie jar, so there is no test-only route. `scope:` writes the
1981
+ Rails session cookie and so needs the cookie session store (the Rails default);
1982
+ with another store it raises `ArgumentError`, and you sign in through your own
1983
+ scoped login request instead. It replaces controller stubbing
1984
+ (`stub_web_authentication`, `allow_any_instance_of(...).to receive(:current_user)`)
1985
+ and per-app `/test/set-session` routes.
1986
+
1987
+ ### This gem's own suite
1988
+
1929
1989
  StandardId includes comprehensive test coverage:
1930
1990
 
1931
1991
  ```bash
@@ -28,6 +28,8 @@ module StandardId
28
28
 
29
29
  class_option :skip_initializer, type: :boolean, default: false,
30
30
  desc: "Do not write config/initializers/standard_id.rb"
31
+ class_option :with_reference, type: :boolean, default: false,
32
+ desc: "Write the annotated initializer (every common setting with its default) instead of the minimal one"
31
33
  class_option :skip_routes, type: :boolean, default: false,
32
34
  desc: "Do not append engine mount lines to config/routes.rb"
33
35
  class_option :skip_migrations, type: :boolean, default: false,
@@ -51,7 +53,8 @@ module StandardId
51
53
  def create_initializer_file
52
54
  return say_status("skip", "config/initializers/standard_id.rb (--skip-initializer)", :yellow) if options[:skip_initializer]
53
55
 
54
- template "standard_id.rb", "config/initializers/standard_id.rb"
56
+ source = options[:with_reference] ? "standard_id_reference.rb" : "standard_id.rb"
57
+ template source, "config/initializers/standard_id.rb"
55
58
  end
56
59
 
57
60
  def mount_engines