standard_singpass 0.1.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.
@@ -0,0 +1,71 @@
1
+ # typed: false
2
+
3
+ # Singpass MyInfo operational tasks.
4
+ #
5
+ # These run on a trusted local machine — never CI, never a shared shell.
6
+ # The output of `generate_jwks` contains private key material.
7
+ #
8
+ # bin/rails standard_singpass:myinfo:generate_jwks
9
+ # bin/rails 'standard_singpass:myinfo:generate_jwks[my-sig-2,my-enc-2]'
10
+ # bin/rails 'standard_singpass:myinfo:validate_jwks[path/to/private-jwks.json]'
11
+ # cat private-jwks.json | bin/rails standard_singpass:myinfo:validate_jwks
12
+
13
+ namespace :standard_singpass do
14
+ namespace :myinfo do
15
+ desc "Generate a fresh MyInfo private JWKS (sig + enc, EC P-256). JSON to stdout, narrative to stderr."
16
+ task :generate_jwks, [:sig_kid, :enc_kid] => :environment do |_, args|
17
+ sig_kid = args[:sig_kid].presence || "singpass-sig-#{Date.current.strftime('%Y%m%d')}"
18
+ enc_kid = args[:enc_kid].presence || "singpass-enc-#{Date.current.strftime('%Y%m%d')}"
19
+
20
+ jwks = StandardSingpass::Myinfo::JwksGenerator.generate(sig_kid:, enc_kid:)
21
+
22
+ # Narrative on stderr so `> private-jwks.json` captures clean JSON.
23
+ warn ""
24
+ warn "Generated MYINFO private JWKS:"
25
+ jwks[:keys].each do |k|
26
+ warn " kid=#{k[:kid]} use=#{k[:use]} alg=#{k[:alg]} crv=#{k[:crv]} has_d=true"
27
+ end
28
+ warn ""
29
+ warn "Next steps:"
30
+ warn " 1. Store the JSON below in your secret manager (e.g. 1Password)."
31
+ warn " 2. Paste into the env var your host uses to populate"
32
+ warn " StandardSingpass::Myinfo.configure { |c| c.private_jwks_json = ... }"
33
+ warn " (mark as Secret/Encrypted; do not shell-escape, paste raw)."
34
+ warn " 3. After redeploy, confirm your public JWKS endpoint shows the same"
35
+ warn " kids with NO 'd' field."
36
+ warn ""
37
+
38
+ puts JSON.generate(jwks)
39
+ end
40
+
41
+ desc "Validate a MyInfo private JWKS payload. Catches the public-only-key trap before paste."
42
+ task :validate_jwks, [:path] => :environment do |_, args|
43
+ raw = if args[:path].present?
44
+ File.read(args[:path])
45
+ elsif !$stdin.tty?
46
+ $stdin.read
47
+ else
48
+ abort "Provide a path or pipe JSON:\n" \
49
+ " bin/rails 'standard_singpass:myinfo:validate_jwks[path/to/jwks.json]'\n" \
50
+ " cat private-jwks.json | bin/rails standard_singpass:myinfo:validate_jwks"
51
+ end
52
+
53
+ begin
54
+ jwks = JSON.parse(raw)
55
+ rescue JSON::ParserError => e
56
+ abort "Not valid JSON: #{e.message}"
57
+ end
58
+
59
+ issues = StandardSingpass::Myinfo::JwksGenerator.validate(jwks)
60
+
61
+ if issues.empty?
62
+ key_count = (jwks["keys"] || []).size
63
+ warn "OK — JWKS validates: #{key_count} keys, all private (have 'd'), all EC P-256, alg matches use."
64
+ else
65
+ warn "JWKS validation FAILED — #{issues.size} issue(s):"
66
+ issues.each { |i| warn " - #{i}" }
67
+ exit 1
68
+ end
69
+ end
70
+ end
71
+ end
metadata ADDED
@@ -0,0 +1,179 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: standard_singpass
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Jaryl Sim
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: rails
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - ">="
17
+ - !ruby/object:Gem::Version
18
+ version: '8.0'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - ">="
24
+ - !ruby/object:Gem::Version
25
+ version: '8.0'
26
+ - !ruby/object:Gem::Dependency
27
+ name: faraday
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: '2.0'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: '2.0'
40
+ - !ruby/object:Gem::Dependency
41
+ name: jwt
42
+ requirement: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ version: '2.7'
47
+ type: :runtime
48
+ prerelease: false
49
+ version_requirements: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: '2.7'
54
+ - !ruby/object:Gem::Dependency
55
+ name: aes_key_wrap
56
+ requirement: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - "~>"
59
+ - !ruby/object:Gem::Version
60
+ version: '1.1'
61
+ type: :runtime
62
+ prerelease: false
63
+ version_requirements: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - "~>"
66
+ - !ruby/object:Gem::Version
67
+ version: '1.1'
68
+ - !ruby/object:Gem::Dependency
69
+ name: sorbet-runtime
70
+ requirement: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - "~>"
73
+ - !ruby/object:Gem::Version
74
+ version: '0.5'
75
+ type: :runtime
76
+ prerelease: false
77
+ version_requirements: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - "~>"
80
+ - !ruby/object:Gem::Version
81
+ version: '0.5'
82
+ - !ruby/object:Gem::Dependency
83
+ name: simplecov
84
+ requirement: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - "~>"
87
+ - !ruby/object:Gem::Version
88
+ version: '0.22'
89
+ type: :development
90
+ prerelease: false
91
+ version_requirements: !ruby/object:Gem::Requirement
92
+ requirements:
93
+ - - "~>"
94
+ - !ruby/object:Gem::Version
95
+ version: '0.22'
96
+ - !ruby/object:Gem::Dependency
97
+ name: sorbet
98
+ requirement: !ruby/object:Gem::Requirement
99
+ requirements:
100
+ - - "~>"
101
+ - !ruby/object:Gem::Version
102
+ version: '0.5'
103
+ type: :development
104
+ prerelease: false
105
+ version_requirements: !ruby/object:Gem::Requirement
106
+ requirements:
107
+ - - "~>"
108
+ - !ruby/object:Gem::Version
109
+ version: '0.5'
110
+ - !ruby/object:Gem::Dependency
111
+ name: tapioca
112
+ requirement: !ruby/object:Gem::Requirement
113
+ requirements:
114
+ - - "~>"
115
+ - !ruby/object:Gem::Version
116
+ version: '0.16'
117
+ type: :development
118
+ prerelease: false
119
+ version_requirements: !ruby/object:Gem::Requirement
120
+ requirements:
121
+ - - "~>"
122
+ - !ruby/object:Gem::Version
123
+ version: '0.16'
124
+ description: StandardSingpass packages the FAPI 2.0 OAuth client, DPoP/PKCE primitives,
125
+ native ECDH-ES JWE decryption, and person-data parser needed to integrate with Singpass
126
+ MyInfo. Designed as a reusable Rails engine; the host owns persistence, orchestration,
127
+ and UI.
128
+ email:
129
+ - code@jaryl.dev
130
+ executables: []
131
+ extensions: []
132
+ extra_rdoc_files: []
133
+ files:
134
+ - CHANGELOG.md
135
+ - MIT-LICENSE
136
+ - README.md
137
+ - Rakefile
138
+ - fixtures/myinfo-personas.json
139
+ - lib/generators/standard_singpass/install/install_generator.rb
140
+ - lib/generators/standard_singpass/install/templates/initializer.rb.erb
141
+ - lib/standard_singpass.rb
142
+ - lib/standard_singpass/engine.rb
143
+ - lib/standard_singpass/myinfo.rb
144
+ - lib/standard_singpass/myinfo/client.rb
145
+ - lib/standard_singpass/myinfo/configuration.rb
146
+ - lib/standard_singpass/myinfo/ecdh_jwe.rb
147
+ - lib/standard_singpass/myinfo/error.rb
148
+ - lib/standard_singpass/myinfo/jwks_generator.rb
149
+ - lib/standard_singpass/myinfo/person_data_parser.rb
150
+ - lib/standard_singpass/myinfo/security.rb
151
+ - lib/standard_singpass/myinfo/test_personas.rb
152
+ - lib/standard_singpass/version.rb
153
+ - lib/tasks/standard_singpass.rake
154
+ homepage: https://github.com/rarebit-one/standard_singpass
155
+ licenses:
156
+ - MIT
157
+ metadata:
158
+ homepage_uri: https://github.com/rarebit-one/standard_singpass
159
+ source_code_uri: https://github.com/rarebit-one/standard_singpass
160
+ changelog_uri: https://github.com/rarebit-one/standard_singpass/blob/main/CHANGELOG.md
161
+ bug_tracker_uri: https://github.com/rarebit-one/standard_singpass/issues
162
+ rdoc_options: []
163
+ require_paths:
164
+ - lib
165
+ required_ruby_version: !ruby/object:Gem::Requirement
166
+ requirements:
167
+ - - ">="
168
+ - !ruby/object:Gem::Version
169
+ version: '4.0'
170
+ required_rubygems_version: !ruby/object:Gem::Requirement
171
+ requirements:
172
+ - - ">="
173
+ - !ruby/object:Gem::Version
174
+ version: '0'
175
+ requirements: []
176
+ rubygems_version: 4.0.10
177
+ specification_version: 4
178
+ summary: Singpass MyInfo (and future Singpass Sign-In) client for Rails apps.
179
+ test_files: []