Class: StandardSingpass::Myinfo::Configuration
- Inherits:
-
Object
- Object
- StandardSingpass::Myinfo::Configuration
- Defined in:
- lib/standard_singpass/myinfo/configuration.rb
Constant Summary collapse
- DEFAULT_SCOPE =
Default MyInfo scope — keep aligned with the Singpass developer-portal approval list. Entries on separate lines so diffs against the portal's ordered dump are reviewable line-by-line. Joined into a single space-delimited string before sending to PAR.
%w[ openid aliasname cpfbalances.oa cpfcontributions cpfemployers cpfhousingwithdrawal dob email employment employmentsector hanyupinyinaliasname hanyupinyinname hdbownership.address hdbownership.balanceloanrepayment hdbownership.hdbtype hdbownership.loangranted hdbownership.monthlyloaninstalment hdbownership.noofowners hdbownership.outstandinginstalment hdbownership.outstandingloanbalance hdbtype housingtype marital marriedname mobileno name nationality noa noa-basic noahistory noahistory-basic occupation ownerprivate passexpirydate passstatus passtype race regadd residentialstatus sex uinfin vehicles.effectiveownership ].join(" ").freeze
- PRODUCTION_ENDPOINTS =
The categories and their lender-underwriting purpose (PDPA §18, Purpose Limitation):
Identity — uinfin, name, alias names, dob, sex, race, nationality, residentialstatus → KYC, contracts Pass (FIN-only) — passtype, passstatus, passexpirydate, employmentsector → tenure-vs-pass-expiry, eligibility Address — regadd, hdbtype, housingtype → KYC + income proxy Contact — mobileno, email → OTP, mailers Family — marital → soft underwriting signal Income — noa, noa-basic, noahistory, noahistory-basic, cpfcontributions → MAS TDSR input Employment — employment, occupation, cpfemployers → continuity + employer stability Assets — cpfbalances.oa (only OA — MA/SA/RA are ring-fenced and not lender-relevant), ownerprivate Liabilities — cpfhousingwithdrawal, hdbownership.* (8 sub-fields) → TDSR housing component Vehicle — vehicles.effectiveownership (asset/liability hint; full vehicle details deliberately not requested)cpfbalances.oa,hdbownership.*, andvehicles.effectiveownershipuse FAPI 2.0 sub-attribute scope notation — sharper data minimisation than parent-keyword grants. { authorize_url: "https://id.singpass.gov.sg/fapi/auth", par_url: "https://id.singpass.gov.sg/fapi/par", token_url: "https://id.singpass.gov.sg/fapi/token", jwks_url: "https://id.singpass.gov.sg/.well-known/keys", issuer: "https://id.singpass.gov.sg/fapi", userinfo_url: "https://id.singpass.gov.sg/fapi/userinfo", userinfo_jwks_url: "https://id.singpass.gov.sg/.well-known/keys" }.freeze
- STAGING_ENDPOINTS =
{ authorize_url: "https://stg-id.singpass.gov.sg/fapi/auth", par_url: "https://stg-id.singpass.gov.sg/fapi/par", token_url: "https://stg-id.singpass.gov.sg/fapi/token", jwks_url: "https://stg-id.singpass.gov.sg/.well-known/keys", issuer: "https://stg-id.singpass.gov.sg/fapi", userinfo_url: "https://stg-id.singpass.gov.sg/fapi/userinfo", userinfo_jwks_url: "https://stg-id.singpass.gov.sg/.well-known/keys" }.freeze
Instance Attribute Summary collapse
-
#authorize_url ⇒ Object
Returns the value of attribute authorize_url.
-
#client_id ⇒ Object
Returns the value of attribute client_id.
-
#encryption_keys ⇒ Object
Returns the value of attribute encryption_keys.
-
#environment ⇒ Object
Returns the value of attribute environment.
-
#issuer ⇒ Object
Returns the value of attribute issuer.
-
#jwks_url ⇒ Object
Returns the value of attribute jwks_url.
-
#minimum_acr ⇒ Object
Returns the value of attribute minimum_acr.
-
#mock_mode ⇒ Object
Returns the value of attribute mock_mode.
-
#network_wrapper ⇒ Object
Returns the value of attribute network_wrapper.
-
#par_url ⇒ Object
Returns the value of attribute par_url.
-
#personas_path ⇒ Object
Returns the value of attribute personas_path.
-
#production_env_detector ⇒ Object
Optional callable deciding whether the current deploy is real production, for the purpose of MockModeGuard.
-
#redirect_url ⇒ Object
Returns the value of attribute redirect_url.
-
#scope ⇒ Object
Returns the value of attribute scope.
-
#signing_key ⇒ Object
Returns the value of attribute signing_key.
-
#signing_kid ⇒ Object
Returns the value of attribute signing_kid.
-
#token_url ⇒ Object
Returns the value of attribute token_url.
-
#userinfo_jwks_url ⇒ Object
Returns the value of attribute userinfo_jwks_url.
-
#userinfo_url ⇒ Object
Returns the value of attribute userinfo_url.
Instance Method Summary collapse
-
#initialize ⇒ Configuration
constructor
A new instance of Configuration.
-
#private_jwks_json=(jwks_json) ⇒ Object
Accepts the raw JWKS JSON string and populates signing_key, signing_kid, and encryption_keys.
Constructor Details
#initialize ⇒ Configuration
Returns a new instance of Configuration.
143 144 145 146 147 148 149 150 |
# File 'lib/standard_singpass/myinfo/configuration.rb', line 143 def initialize self.environment = :staging @scope = DEFAULT_SCOPE @encryption_keys = [] @network_wrapper = ->(&block) { block.call } @mock_mode = false @production_env_detector = nil end |
Instance Attribute Details
#authorize_url ⇒ Object
Returns the value of attribute authorize_url.
126 127 128 |
# File 'lib/standard_singpass/myinfo/configuration.rb', line 126 def @authorize_url end |
#client_id ⇒ Object
Returns the value of attribute client_id.
126 127 128 |
# File 'lib/standard_singpass/myinfo/configuration.rb', line 126 def client_id @client_id end |
#encryption_keys ⇒ Object
Returns the value of attribute encryption_keys.
126 127 128 |
# File 'lib/standard_singpass/myinfo/configuration.rb', line 126 def encryption_keys @encryption_keys end |
#environment ⇒ Object
Returns the value of attribute environment.
164 165 166 |
# File 'lib/standard_singpass/myinfo/configuration.rb', line 164 def environment @environment end |
#issuer ⇒ Object
Returns the value of attribute issuer.
126 127 128 |
# File 'lib/standard_singpass/myinfo/configuration.rb', line 126 def issuer @issuer end |
#jwks_url ⇒ Object
Returns the value of attribute jwks_url.
126 127 128 |
# File 'lib/standard_singpass/myinfo/configuration.rb', line 126 def jwks_url @jwks_url end |
#minimum_acr ⇒ Object
Returns the value of attribute minimum_acr.
126 127 128 |
# File 'lib/standard_singpass/myinfo/configuration.rb', line 126 def minimum_acr @minimum_acr end |
#mock_mode ⇒ Object
Returns the value of attribute mock_mode.
126 127 128 |
# File 'lib/standard_singpass/myinfo/configuration.rb', line 126 def mock_mode @mock_mode end |
#network_wrapper ⇒ Object
Returns the value of attribute network_wrapper.
126 127 128 |
# File 'lib/standard_singpass/myinfo/configuration.rb', line 126 def network_wrapper @network_wrapper end |
#par_url ⇒ Object
Returns the value of attribute par_url.
126 127 128 |
# File 'lib/standard_singpass/myinfo/configuration.rb', line 126 def par_url @par_url end |
#personas_path ⇒ Object
Returns the value of attribute personas_path.
126 127 128 |
# File 'lib/standard_singpass/myinfo/configuration.rb', line 126 def personas_path @personas_path end |
#production_env_detector ⇒ Object
Optional callable deciding whether the current deploy is real production, for the purpose of MockModeGuard. Takes no args, returns a boolean. When nil (the default) the gem falls back to Rails.env.production?, so existing consumers are unchanged.
Hosts that distinguish a physical deploy environment from RAILS_ENV
— staging and preview boxes routinely run RAILS_ENV=production — can
supply -> { AppEnv.production? } so mock mode stays legal on those
while still refusing to boot on real production.
141 142 143 |
# File 'lib/standard_singpass/myinfo/configuration.rb', line 141 def production_env_detector @production_env_detector end |
#redirect_url ⇒ Object
Returns the value of attribute redirect_url.
126 127 128 |
# File 'lib/standard_singpass/myinfo/configuration.rb', line 126 def redirect_url @redirect_url end |
#scope ⇒ Object
Returns the value of attribute scope.
126 127 128 |
# File 'lib/standard_singpass/myinfo/configuration.rb', line 126 def scope @scope end |
#signing_key ⇒ Object
Returns the value of attribute signing_key.
126 127 128 |
# File 'lib/standard_singpass/myinfo/configuration.rb', line 126 def signing_key @signing_key end |
#signing_kid ⇒ Object
Returns the value of attribute signing_kid.
126 127 128 |
# File 'lib/standard_singpass/myinfo/configuration.rb', line 126 def signing_kid @signing_kid end |
#token_url ⇒ Object
Returns the value of attribute token_url.
126 127 128 |
# File 'lib/standard_singpass/myinfo/configuration.rb', line 126 def token_url @token_url end |
#userinfo_jwks_url ⇒ Object
Returns the value of attribute userinfo_jwks_url.
126 127 128 |
# File 'lib/standard_singpass/myinfo/configuration.rb', line 126 def userinfo_jwks_url @userinfo_jwks_url end |
#userinfo_url ⇒ Object
Returns the value of attribute userinfo_url.
126 127 128 |
# File 'lib/standard_singpass/myinfo/configuration.rb', line 126 def userinfo_url @userinfo_url end |
Instance Method Details
#private_jwks_json=(jwks_json) ⇒ Object
Accepts the raw JWKS JSON string and populates signing_key, signing_kid, and encryption_keys. Logs and reports issues via Rails.logger / Rails.error rather than raising — a malformed JWKS silently degrades the Singpass widget at runtime, but the host should boot regardless.
170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 |
# File 'lib/standard_singpass/myinfo/configuration.rb', line 170 def private_jwks_json=(jwks_json) @encryption_keys = [] @signing_key = nil @signing_kid = nil if jwks_json.nil? || jwks_json.to_s.strip.empty? return if mock_mode || (defined?(Rails) && Rails.env.test?) Rails.logger.warn("StandardSingpass::Myinfo: private_jwks_json is not set — Singpass flow will fail at first request") return end jwks = JSON.parse(jwks_json) raise TypeError, "private_jwks_json must be a JSON object with a \"keys\" array, got #{jwks.class}" unless jwks.is_a?(Hash) keys = jwks["keys"] || [] sig_jwks = keys.select { |k| k.is_a?(Hash) && k["use"] == "sig" } Rails.logger.warn("StandardSingpass::Myinfo: multiple sig keys in private_jwks_json — using first") if sig_jwks.size > 1 sig_jwk = sig_jwks.first if sig_jwk @signing_kid = sig_jwk["kid"] @signing_key = jwk_to_private_pem(sig_jwk, role: "signing") # Keep paired: a nil signing_key with a populated signing_kid is # confusing in console triage (which is the scenario this method is # trying to help with). @signing_kid = nil unless @signing_key elsif !mock_mode && !(defined?(Rails) && Rails.env.test?) Rails.logger.error("StandardSingpass::Myinfo: private_jwks_json contains no key with \"use\":\"sig\"") end enc_jwks = keys.select { |k| k.is_a?(Hash) && k["use"] == "enc" } @encryption_keys = enc_jwks.filter_map do |enc_jwk| pem = jwk_to_private_pem(enc_jwk, role: "encryption") next unless pem { kid: enc_jwk["kid"], key: pem } end # Distinguish the two empty-state cases: missing entirely (operator # forgot to include enc keys) vs all-rejected (every enc key was # public-only or otherwise unloadable). The latter is the trap the # rest of this method is built to catch. if @encryption_keys.empty? && !mock_mode && !(defined?(Rails) && Rails.env.test?) if enc_jwks.empty? Rails.logger.error("StandardSingpass::Myinfo: private_jwks_json contains no key with \"use\":\"enc\"") else Rails.logger.error("StandardSingpass::Myinfo: private_jwks_json has \"use\":\"enc\" keys but none are usable (all public-only or invalid)") end end rescue JSON::ParserError, TypeError => e # JSON::ParserError: not valid JSON. TypeError: valid JSON but wrong # shape (e.g. an array, a string) — caught explicitly so an operator # who pastes the wrong file doesn't see a bare TypeError escape. # Reported because a malformed private JWKS silently degrades the # Singpass widget — the request that finally fails will report, but by # then customers have hit the broken page. Rails.logger.error("StandardSingpass::Myinfo: failed to parse private_jwks_json: #{e.class}: #{e.}") Rails.error.report(e, handled: true, context: { component: "StandardSingpass::Myinfo::Configuration", reason: "parse_private_jwks" }) if defined?(Rails.error) @encryption_keys = [] end |