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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +33 -0
- data/MIT-LICENSE +20 -0
- data/README.md +116 -0
- data/Rakefile +8 -0
- data/fixtures/myinfo-personas.json +250 -0
- data/lib/generators/standard_singpass/install/install_generator.rb +45 -0
- data/lib/generators/standard_singpass/install/templates/initializer.rb.erb +57 -0
- data/lib/standard_singpass/engine.rb +17 -0
- data/lib/standard_singpass/myinfo/client.rb +429 -0
- data/lib/standard_singpass/myinfo/configuration.rb +233 -0
- data/lib/standard_singpass/myinfo/ecdh_jwe.rb +350 -0
- data/lib/standard_singpass/myinfo/error.rb +14 -0
- data/lib/standard_singpass/myinfo/jwks_generator.rb +116 -0
- data/lib/standard_singpass/myinfo/person_data_parser.rb +356 -0
- data/lib/standard_singpass/myinfo/security.rb +186 -0
- data/lib/standard_singpass/myinfo/test_personas.rb +47 -0
- data/lib/standard_singpass/myinfo.rb +78 -0
- data/lib/standard_singpass/version.rb +3 -0
- data/lib/standard_singpass.rb +6 -0
- data/lib/tasks/standard_singpass.rake +71 -0
- metadata +179 -0
|
@@ -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: []
|