standard_singpass 0.3.0 → 0.3.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 +14 -0
- data/README.md +42 -5
- data/lib/generators/standard_singpass/install/templates/initializer.rb.erb +11 -4
- data/lib/standard_singpass/myinfo/client.rb +14 -13
- data/lib/standard_singpass/myinfo/configuration.rb +5 -1
- data/lib/standard_singpass/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: dbda49a3c31fd6844db0d58c66c0bf1c8654882d37105c58dd1537a2e2cb13c5
|
|
4
|
+
data.tar.gz: 5b4ebd78e72bab8b36a2d8d5c298cc8f99e277c018b23a5b42ff7dfb4030292e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 47d70dda7f2948b55b59afac864885452e776aefe9e8806a16e62f31b9ca02bc6a8772e5eff7849b25ca3a8805487d45ea3c63a77412a713ba3c7b11f6e7f3e1
|
|
7
|
+
data.tar.gz: cd7b80ae5bea3214b3d1bb146801c673fe2934b8b245865ef7bd61dee8952a0d2f5dd670f24c08e925fb083fb6e8debe1901ad2ce9fcee57b8fe9174cff503af
|
data/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.3.1] - 2026-09-24
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
|
|
14
|
+
- **`minimum_acr` is no longer sent to Singpass as `acr_values` on the Pushed Authorization Request.** Singpass rejects that parameter for MyInfo — `PAR failed (HTTP 400) invalid_request — "The acr_values parameter can only be used for specific use cases."` — so any host that set `minimum_acr` (as the README and the install generator recommended, via `MYINFO_MIN_ACR`) failed every MyInfo login at the first step. This took down production onboarding for a consuming app for roughly four days before it was traced. `minimum_acr` is now purely a client-side check: the id_token `acr` claim is still validated against it (`AuthenticationError` when below the floor or missing; `ConfigurationError` for an unrecognised URN), and the PAR body never carries `acr_values`.
|
|
15
|
+
|
|
16
|
+
**Behaviour change / upgrade note:** hosts with `minimum_acr` set go from "every PAR fails with HTTP 400" to "PAR succeeds and the returned id_token `acr` is checked locally". Hosts that left it unset see no change. If you had pinned `minimum_acr = nil` as a workaround, you can keep it — MyInfo's assurance level is governed by Singpass server-side and the floor is optional. If you wired it from `MYINFO_MIN_ACR`, consider removing that env wiring (see Documentation below).
|
|
17
|
+
|
|
18
|
+
### Documentation
|
|
19
|
+
|
|
20
|
+
- **Install generator no longer wires `c.minimum_acr = ENV["MYINFO_MIN_ACR"]`** or documents `MYINFO_MIN_ACR`. The option is left commented out with a warning that it is a client-side check only and that a wrong value fails every login closed.
|
|
21
|
+
- **README:** Installation now mentions `bin/rails g standard_singpass:install`; the `MYINFO_MIN_ACR` wiring is removed from the configuration example; a new "Assurance level (`minimum_acr`)" section explains what the option does and does not do; and the default-scope comment now gives the real size of `DEFAULT_SCOPE` (42 scopes — `openid` plus 41 attributes — not "a 36-attribute set").
|
|
22
|
+
- **Consumer list in `CLAUDE.md` narrowed to the one app that actually consumes this gem.** It said "the rarebit-one workspace's web apps", which reads as all five and would send a rollout at four apps with no Singpass integration. The single consumer is `fundbright-web`, in the sibling `~/Workspace/fundbright/` workspace. The list is now bulleted so the new advisory `check-gem-family-drift.sh` can diff it against the canonical matrix in the workspace's `rollout-gem/SKILL.md` — prose that a human reads as one thing and a script as another is how this drifted in the first place.
|
|
23
|
+
|
|
10
24
|
## [0.3.0] - 2026-07-30
|
|
11
25
|
|
|
12
26
|
### Added
|
data/README.md
CHANGED
|
@@ -12,6 +12,15 @@ Add to your Gemfile:
|
|
|
12
12
|
gem "standard_singpass"
|
|
13
13
|
```
|
|
14
14
|
|
|
15
|
+
Then generate the initializer:
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
bin/rails g standard_singpass:install
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
This writes `config/initializers/standard_singpass.rb`, mapping the `MYINFO_*`
|
|
22
|
+
environment variables onto the gem's configuration (see below).
|
|
23
|
+
|
|
15
24
|
## Configuration
|
|
16
25
|
|
|
17
26
|
```ruby
|
|
@@ -24,17 +33,18 @@ StandardSingpass::Myinfo.configure do |c|
|
|
|
24
33
|
c.client_id = ENV["MYINFO_CLIENT_ID"]
|
|
25
34
|
c.redirect_url = ENV["MYINFO_REDIRECT_URL"]
|
|
26
35
|
|
|
27
|
-
# Optional: override default scope (defaults to
|
|
28
|
-
# identity, contact, income, employment, housing,
|
|
36
|
+
# Optional: override default scope (defaults to 42 scopes — `openid` plus 41
|
|
37
|
+
# MyInfo attributes covering identity, contact, income, employment, housing,
|
|
38
|
+
# assets, vehicles).
|
|
29
39
|
# c.scope = "openid name email ..."
|
|
30
40
|
|
|
31
41
|
# Required: full private JWKS JSON containing both sig (ES256) and enc
|
|
32
42
|
# (ECDH-ES+A256KW) keys with the private scalar `d`.
|
|
33
43
|
c.private_jwks_json = ENV["MYINFO_PRIVATE_JWKS"]
|
|
34
44
|
|
|
35
|
-
# Optional:
|
|
36
|
-
#
|
|
37
|
-
c.minimum_acr =
|
|
45
|
+
# Optional: client-side floor on the id_token `acr` claim. Never sent to
|
|
46
|
+
# Singpass — see "Assurance level (`minimum_acr`)" below before setting it.
|
|
47
|
+
# c.minimum_acr = "urn:singpass:authentication:loa:2"
|
|
38
48
|
|
|
39
49
|
# Optional: wrap outbound HTTP calls with a circuit breaker / retry layer.
|
|
40
50
|
# Defaults to identity (no wrapper).
|
|
@@ -54,6 +64,33 @@ StandardSingpass::Myinfo.configure do |c|
|
|
|
54
64
|
end
|
|
55
65
|
```
|
|
56
66
|
|
|
67
|
+
## Assurance level (`minimum_acr`)
|
|
68
|
+
|
|
69
|
+
`config.minimum_acr` is an optional, **client-side** check. When set to a
|
|
70
|
+
Singpass LOA URN (`urn:singpass:authentication:loa:2` or `...:loa:3`), the
|
|
71
|
+
client rejects any id_token whose `acr` claim is below that level — or missing
|
|
72
|
+
— with `StandardSingpass::Myinfo::AuthenticationError`. A value that is not a
|
|
73
|
+
recognised LOA URN raises `ConfigurationError`. Unset or blank disables the
|
|
74
|
+
check.
|
|
75
|
+
|
|
76
|
+
It is **never sent to Singpass.** Singpass rejects `acr_values` on a MyInfo
|
|
77
|
+
Pushed Authorization Request:
|
|
78
|
+
|
|
79
|
+
```
|
|
80
|
+
PAR failed (HTTP 400) invalid_request —
|
|
81
|
+
"The acr_values parameter can only be used for specific use cases."
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Versions up to 0.3.0 forwarded a configured `minimum_acr` as `acr_values`, so
|
|
85
|
+
setting it broke every MyInfo login. From 0.3.1 the PAR request never carries
|
|
86
|
+
`acr_values`. MyInfo's assurance level is governed by Singpass server-side, so
|
|
87
|
+
most hosts should leave `minimum_acr` unset; if you do set it, set it in code
|
|
88
|
+
rather than from a loosely-managed environment variable, because a wrong value
|
|
89
|
+
fails every login closed.
|
|
90
|
+
|
|
91
|
+
The `acr` Singpass actually returned is surfaced as `result[:id_token_acr]`
|
|
92
|
+
from `get_person_data` for audit, whether or not a floor is configured.
|
|
93
|
+
|
|
57
94
|
## Mock mode
|
|
58
95
|
|
|
59
96
|
`config.mock_mode` makes the host serve fixture persona data
|
|
@@ -16,8 +16,6 @@
|
|
|
16
16
|
# MYINFO_SCOPE Space-separated scopes (defaults to DEFAULT_SCOPE)
|
|
17
17
|
# MYINFO_USERINFO_URL Userinfo endpoint override (rarely needed)
|
|
18
18
|
# MYINFO_USERINFO_JWKS_URL Userinfo JWKS endpoint override (rarely needed)
|
|
19
|
-
# MYINFO_MIN_ACR Required Authentication Context Class Reference
|
|
20
|
-
# URN, e.g. urn:singpass:authentication:loa:3
|
|
21
19
|
# MYINFO_MOCK_MODE When set, suppresses missing-key warnings and
|
|
22
20
|
# unlocks any mock-callback routes the host
|
|
23
21
|
# registers. The gem REFUSES TO BOOT if this is set
|
|
@@ -40,8 +38,17 @@ StandardSingpass::Myinfo.configure do |c|
|
|
|
40
38
|
c.userinfo_url = ENV["MYINFO_USERINFO_URL"] if ENV["MYINFO_USERINFO_URL"].present?
|
|
41
39
|
c.userinfo_jwks_url = ENV["MYINFO_USERINFO_JWKS_URL"] if ENV["MYINFO_USERINFO_JWKS_URL"].present?
|
|
42
40
|
|
|
43
|
-
c.
|
|
44
|
-
|
|
41
|
+
c.mock_mode = ENV["MYINFO_MOCK_MODE"].present?
|
|
42
|
+
|
|
43
|
+
# Optional client-side floor on the id_token `acr` claim. The gem rejects an
|
|
44
|
+
# id_token whose assurance level is below it (AuthenticationError); it is
|
|
45
|
+
# NEVER sent to Singpass — `acr_values` on a MyInfo PAR is rejected with
|
|
46
|
+
# HTTP 400 invalid_request. MyInfo's assurance level is governed by Singpass
|
|
47
|
+
# server-side, so most hosts should leave this unset.
|
|
48
|
+
#
|
|
49
|
+
# WARNING: do not wire this to an environment variable without a test that
|
|
50
|
+
# exercises the real flow — a stray value fails every login closed.
|
|
51
|
+
# c.minimum_acr = "urn:singpass:authentication:loa:2"
|
|
45
52
|
|
|
46
53
|
# How the gem decides whether this deploy is real production, for the
|
|
47
54
|
# mock-mode boot guard. Defaults to Rails.env.production?. Staging and
|
|
@@ -60,17 +60,17 @@ module StandardSingpass
|
|
|
60
60
|
)
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
-
#
|
|
64
|
-
#
|
|
65
|
-
#
|
|
66
|
-
#
|
|
67
|
-
#
|
|
68
|
-
#
|
|
69
|
-
#
|
|
70
|
-
#
|
|
71
|
-
#
|
|
72
|
-
|
|
73
|
-
|
|
63
|
+
# `acr_values` is deliberately NEVER sent, even when `minimum_acr` is
|
|
64
|
+
# configured. Singpass rejects it on a MyInfo PAR:
|
|
65
|
+
#
|
|
66
|
+
# PAR failed (HTTP 400) invalid_request —
|
|
67
|
+
# "The acr_values parameter can only be used for specific use cases."
|
|
68
|
+
#
|
|
69
|
+
# Until 0.3.1 a non-empty `minimum_acr` was forwarded here, which broke
|
|
70
|
+
# every MyInfo onboarding for any host that set it. `minimum_acr` is
|
|
71
|
+
# now purely a client-side check of the returned id_token `acr` claim
|
|
72
|
+
# (see validate_id_token_acr); MyInfo's assurance level itself is
|
|
73
|
+
# governed by Singpass server-side.
|
|
74
74
|
|
|
75
75
|
with_network_wrapper do
|
|
76
76
|
response = http_connection.post(@par_url) do |req|
|
|
@@ -299,8 +299,9 @@ module StandardSingpass
|
|
|
299
299
|
# Enforce a minimum Authentication Context Class Reference (`acr`) on the
|
|
300
300
|
# id_token. The floor is configured via `minimum_acr` so staging and
|
|
301
301
|
# production can diverge — staging may tolerate looser values returned by
|
|
302
|
-
# MyInfo sandbox personas. When the attr is unset or blank,
|
|
303
|
-
# validator
|
|
302
|
+
# MyInfo sandbox personas. When the attr is unset or blank, this
|
|
303
|
+
# validator is skipped. This is the ONLY effect of `minimum_acr` — it is
|
|
304
|
+
# never sent to Singpass as `acr_values` (see push_authorization_request).
|
|
304
305
|
#
|
|
305
306
|
# Singpass's `acr` URN format is `urn:singpass:authentication:loa:N`
|
|
306
307
|
# where N is 2 or 3 (no LOA 1 path — Singpass's IdP is 2FA by design).
|
|
@@ -14,7 +14,11 @@
|
|
|
14
14
|
#
|
|
15
15
|
# Optional attributes:
|
|
16
16
|
# c.scope - Space-separated scopes (defaults to DEFAULT_SCOPE)
|
|
17
|
-
# c.minimum_acr -
|
|
17
|
+
# c.minimum_acr - Minimum Authentication Context Class Reference URN
|
|
18
|
+
# (e.g. urn:singpass:authentication:loa:3) checked
|
|
19
|
+
# client-side against the id_token `acr` claim.
|
|
20
|
+
# Never sent to Singpass: `acr_values` on a MyInfo
|
|
21
|
+
# PAR is rejected with HTTP 400 invalid_request.
|
|
18
22
|
# c.network_wrapper - Lambda wrapping outbound Faraday calls (e.g. circuit breaker)
|
|
19
23
|
# c.mock_mode - When true, suppresses missing-key warnings
|
|
20
24
|
# (and is refused outright on a production deploy
|