simplicity_client 0.2.2 → 0.2.3

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.
Files changed (3) hide show
  1. checksums.yaml +4 -4
  2. data/lib/simplicity_client.rb +303 -303
  3. metadata +2 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: af805426700df49eb866015545dac1afa7c833490be0c0f2a6cb825bd9dc2e3e
4
- data.tar.gz: 4dbeed2df67de9a27ff27c5521ad8b581f2e5f9b6642975612429eea7efa4f4f
3
+ metadata.gz: 875f444b4107a2ef0e2955c96bacb84a74b9a02089a8d72917fb4708e2110ce5
4
+ data.tar.gz: 6a6ba44bc27f552b42e97da6b0ff1c1a18e07ff334839107a3c276b376c23e8c
5
5
  SHA512:
6
- metadata.gz: 73e5c8d7f37681956ac2dcddb647522b1cae78d78e9d85c8744c7f0a96ee18c5740e62618365ca6cb7defcdd73de4094eb5870325690f170a42af8643cf4c57e
7
- data.tar.gz: d6f3d25bf87442259549191a82c2d590f7d2cdcabe8b2deba838142da0f800a8c330bacc9e4bce9e553fc887310cb4bf8597b37b472e348c7d33ddd6bd6aa26d
6
+ metadata.gz: 8d9ea9df4d09f765b05758fdeac3ca34f8ae18c8217601195383bf6e1aa8e09bf0faaf12d49e838adc500d4b1eb2bf597299b317c1ebe3613b717bbd25e72fac
7
+ data.tar.gz: e04a5050f749fa4840ab95e34b85c5f320200a1d8df5cf52965d7dc13d2149899a5014eacbe9cd985e5eb57bd495846d1eb9b10e7395ad9fc25a03e8222207bb
@@ -1,303 +1,303 @@
1
- # frozen_string_literal: true
2
-
3
- begin
4
- require "dotenv/load"
5
- rescue LoadError
6
- # Dotenv is not available, so move on without loading it. It's only used for development.
7
- end
8
- require "faraday"
9
- require "faraday-cookie_jar"
10
- require "faraday/follow_redirects"
11
- require "aws-sdk-cognitoidentityprovider"
12
- require "aws-sdk-cognitoidentity"
13
- require "aws-sigv4"
14
- require_relative "fido2_client"
15
-
16
- module SimplicityClient
17
- Params = Struct.new(:email, :credentialId, :keyAlgorithm, :keyCurve, :keyValue, :userHandle, keyword_init: true)
18
- ParamDefinition = Struct.new(:name, :label, :primary_id)
19
-
20
- class Error < StandardError; end
21
-
22
- # For the user interface
23
- PARAMS_INFO = [
24
- ParamDefinition.new(name: "email", label: "Email address", primary_id: true),
25
- ParamDefinition.new(name: "credentialId", label: "Passkey credential ID"),
26
- ParamDefinition.new(name: "keyAlgorithm", label: "Passkey key algorithm"),
27
- ParamDefinition.new(name: "keyCurve", label: "Passkey key curve"),
28
- ParamDefinition.new(name: "keyValue", label: "Passkey value"),
29
- ParamDefinition.new(name: "userHandle", label: "Passkey user handle")
30
- ].freeze
31
-
32
- class Session
33
- def initialize
34
- @logger = Logger.new $stderr
35
- @logger.level = Logger::DEBUG
36
-
37
- @user_agent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.0.0 Safari/537.36"
38
-
39
- @client = Faraday.new(
40
- headers: { "User-Agent" => @user_agent },
41
- # proxy: "http://Thinkbook.local:8080",
42
- # :ssl => {:verify => false}
43
- ) do |builder|
44
- builder.response :follow_redirects
45
- builder.response :logger
46
- builder.adapter Faraday.default_adapter
47
- end
48
-
49
- @authsignal_base = "https://au.api.authsignal.com/v1"
50
- @authsignal_tenant_id = "e768822f-b1a1-404c-a685-0e37905ca5f5"
51
- end
52
-
53
- # Write the session data out to a string so that the session can be restored later
54
- def export
55
- {
56
- auth_data: @auth_data,
57
- }.to_json
58
- end
59
-
60
- # Load the session data from a string to restore a session
61
- def load(str)
62
- json = JSON.parse(str)
63
- @auth_data = json["auth_data"]
64
- end
65
-
66
- def login(params)
67
- client = Aws::CognitoIdentityProvider::Client.new(region: "ap-southeast-2")
68
-
69
- user_pool_id = "ap-southeast-2_abAklW6ap"
70
-
71
- device_id = SecureRandom.uuid
72
- passkey = Fido2Client::Passkey.new(params.credentialId, params.keyAlgorithm, params.keyCurve, params.keyValue, params.userHandle)
73
- challenge_id = fetch_challenge_id
74
- authentication_options = fetch_authentication_options(challenge_id)
75
- challenge = authentication_options["options"]["challenge"]
76
- assertion = Fido2Client::Client.new.get_assertion(passkey, challenge)
77
- passkey_result = present_passkey(challenge_id, assertion, device_id)
78
-
79
- resp = client.initiate_auth(
80
- {
81
- auth_flow: "CUSTOM_AUTH",
82
- auth_parameters: {
83
- USERNAME: params.email,
84
- },
85
- client_metadata: {
86
- anonymousId: device_id,
87
- },
88
- client_id: "kvoiu7unft0c8hqqsa6hkmeu5",
89
- },
90
- )
91
-
92
- resp = client.respond_to_auth_challenge(
93
- {
94
- challenge_name: "CUSTOM_CHALLENGE",
95
- challenge_responses: {
96
- USERNAME: params.email,
97
- ANSWER: passkey_result["accessToken"],
98
- },
99
- client_id: "kvoiu7unft0c8hqqsa6hkmeu5",
100
- session: resp.session,
101
- },
102
- )
103
-
104
- cognito_identity_client = Aws::CognitoIdentity::Client.new(region: "ap-southeast-2")
105
-
106
- # Assuming you have the Identity Pool ID and the ID token
107
- identity_pool_id = "ap-southeast-2:0ed33fc6-4cef-4f2e-b634-31c616e108e2"
108
- id_token = resp.authentication_result.id_token
109
-
110
- # Get ID from the identity pool
111
- id_response = cognito_identity_client.get_id(
112
- {
113
- identity_pool_id: identity_pool_id,
114
- logins: {
115
- "cognito-idp.ap-southeast-2.amazonaws.com/#{user_pool_id}" => id_token,
116
- },
117
- },
118
- )
119
-
120
- # Get credentials for the ID
121
- credentials_response = cognito_identity_client.get_credentials_for_identity(
122
- {
123
- identity_id: id_response.identity_id,
124
- logins: {
125
- "cognito-idp.ap-southeast-2.amazonaws.com/#{user_pool_id}" => id_token,
126
- },
127
- },
128
- )
129
-
130
- access_key_id = credentials_response.credentials.access_key_id
131
- secret_key = credentials_response.credentials.secret_key
132
- session_token = credentials_response.credentials.session_token
133
-
134
- @auth_data = {
135
- email: params.email,
136
- access_key_id: access_key_id,
137
- secret_key: secret_key,
138
- region: "ap-southeast-2",
139
- session_token: session_token,
140
- }.transform_keys(&:to_s)
141
- end
142
-
143
- def logout
144
- # Not required
145
- end
146
-
147
- def list_accounts
148
- # Ensure @auth_data contains the necessary credentials
149
- raise Error, "Authentication data not found. Please login first." unless @auth_data
150
-
151
- service = "execute-api"
152
- region = "ap-southeast-2"
153
- http_method = "POST"
154
- url = "https://h4ku5ofov2.execute-api.ap-southeast-2.amazonaws.com/prod/secure"
155
- email = @auth_data["email"]
156
- body = {
157
- variables: {},
158
- query: <<-GRAPHQL
159
- {
160
- AccountV2(email: \"george@dewar.co.nz\") {
161
- Portfolios {
162
- Portfolio
163
- PortfolioCode
164
- MarketValue
165
- IsDefault
166
- CanWithdraw
167
- InvestorDetails {
168
- InvestmentName
169
- PrimaryBeneficiarySurname
170
- EntityType
171
- __typename
172
- }
173
- __typename
174
- }
175
- Investment {
176
- InvestmentCode
177
- InvestmentType
178
- Status
179
- PriceDate
180
- ExternalReference
181
- __typename
182
- }
183
- __typename
184
- }
185
- }
186
- GRAPHQL
187
- }.to_json
188
-
189
- signer = Aws::Sigv4::Signer.new(
190
- service: service,
191
- region: region,
192
- access_key_id: @auth_data["access_key_id"],
193
- secret_access_key: @auth_data["secret_key"],
194
- session_token: @auth_data["session_token"],
195
- )
196
-
197
- signature = signer.sign_request(
198
- http_method: http_method,
199
- url: url,
200
- body: body,
201
- )
202
-
203
- response = @client.post(url) do |req|
204
- req.headers = signature.headers.merge({
205
- "Content-Type" => "application/json",
206
- })
207
- req.body = body
208
- end
209
-
210
- # Handle the response
211
- if response.success?
212
- obj = JSON.parse(response.body)
213
- obj["data"]["AccountV2"].map do |account|
214
- investment = account["Investment"]
215
- portfolio = account["Portfolios"][0]
216
- investor_details = portfolio["InvestorDetails"]
217
- {
218
- accountNo: investment["InvestmentCode"],
219
- accountType: "#{investment["InvestmentType"]} - #{portfolio["Portfolio"]}",
220
- customerName: investor_details["InvestmentName"],
221
- accountBalance: portfolio["MarketValue"],
222
- isLiabilityType: false,
223
- supportsTransactions: false,
224
- dynamicBalance: true,
225
- }
226
- end
227
- else
228
- raise Error, "Failed to list accounts: #{response.status}, #{response.body}"
229
- end
230
- end
231
-
232
- private
233
-
234
- def fetch_challenge_id
235
- response = @client.post("#{@authsignal_base}/client/challenge") do |req|
236
- req.headers = {
237
- "Authorization" => "Basic #{Base64.encode64(@authsignal_tenant_id)}",
238
- "Content-Type" => "application/json",
239
- }
240
- req.body = {
241
- action: "cognitoAuth",
242
- }.to_json
243
- end
244
-
245
- if response.success?
246
- obj = JSON.parse(response.body)
247
- obj["challengeId"]
248
- else
249
- raise Error, "Failed to get challenge ID: #{response.status}, #{response.body}"
250
- end
251
- end
252
-
253
- def fetch_authentication_options(challenge_id)
254
- response = @client.post("#{@authsignal_base}/client/user-authenticators/passkey/authentication-options") do |req|
255
- req.headers = {
256
- "Authorization" => "Basic #{Base64.encode64(@authsignal_tenant_id)}",
257
- "Content-Type" => "application/json",
258
- }
259
- req.body = {
260
- challengeId: challenge_id,
261
- }.to_json
262
- end
263
-
264
- if response.success?
265
- JSON.parse(response.body)
266
- else
267
- raise Error, "Failed to get authentication options: #{response.status}, #{response.body}"
268
- end
269
- end
270
-
271
- def present_passkey(challenge_id, assertion, device_id)
272
- response = @client.post("#{@authsignal_base}/client/verify/passkey") do |req|
273
- req.headers = {
274
- "Authorization" => "Basic #{Base64.encode64(@authsignal_tenant_id)}",
275
- "Content-Type" => "application/json",
276
- }
277
- req.body = {
278
- challengeId: challenge_id,
279
- authenticationCredential: {
280
- id: assertion.credential_id,
281
- rawId: assertion.credential_id,
282
- response: {
283
- authenticatorData: assertion.authenticator_data,
284
- clientDataJSON: assertion.client_data_json,
285
- signature: assertion.signature,
286
- userHandle: assertion.user_handle,
287
- },
288
- type: "public-key",
289
- clientExtensionResults: {},
290
- authenticatorAttachment: "platform",
291
- },
292
- deviceId: device_id,
293
- }.to_json
294
- end
295
-
296
- if response.success?
297
- JSON.parse(response.body)
298
- else
299
- raise Error, "Failed to get JWT: #{response.status}, #{response.body}"
300
- end
301
- end
302
- end
303
- end
1
+ # frozen_string_literal: true
2
+
3
+ begin
4
+ require "dotenv/load"
5
+ rescue LoadError
6
+ # Dotenv is not available, so move on without loading it. It's only used for development.
7
+ end
8
+ require "faraday"
9
+ require "faraday-cookie_jar"
10
+ require "faraday/follow_redirects"
11
+ require "aws-sdk-cognitoidentityprovider"
12
+ require "aws-sdk-cognitoidentity"
13
+ require "aws-sigv4"
14
+ require_relative "fido2_client"
15
+
16
+ module SimplicityClient
17
+ Params = Struct.new(:email, :credentialId, :keyAlgorithm, :keyCurve, :keyValue, :userHandle, keyword_init: true)
18
+ ParamDefinition = Struct.new(:name, :label, :primary_id)
19
+
20
+ class Error < StandardError; end
21
+
22
+ # For the user interface
23
+ PARAMS_INFO = [
24
+ ParamDefinition.new(name: "email", label: "Email address", primary_id: true),
25
+ ParamDefinition.new(name: "credentialId", label: "Passkey credential ID"),
26
+ ParamDefinition.new(name: "keyAlgorithm", label: "Passkey key algorithm"),
27
+ ParamDefinition.new(name: "keyCurve", label: "Passkey key curve"),
28
+ ParamDefinition.new(name: "keyValue", label: "Passkey value"),
29
+ ParamDefinition.new(name: "userHandle", label: "Passkey user handle")
30
+ ].freeze
31
+
32
+ class Session
33
+ def initialize
34
+ @logger = Logger.new $stderr
35
+ @logger.level = Logger::DEBUG
36
+
37
+ @user_agent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.0.0 Safari/537.36"
38
+
39
+ @client = Faraday.new(
40
+ headers: { "User-Agent" => @user_agent },
41
+ # proxy: "http://Thinkbook.local:8080",
42
+ # :ssl => {:verify => false}
43
+ ) do |builder|
44
+ builder.response :follow_redirects
45
+ builder.response :logger
46
+ builder.adapter Faraday.default_adapter
47
+ end
48
+
49
+ @authsignal_base = "https://au.api.authsignal.com/v1"
50
+ @authsignal_tenant_id = "e768822f-b1a1-404c-a685-0e37905ca5f5"
51
+ end
52
+
53
+ # Write the session data out to a string so that the session can be restored later
54
+ def export
55
+ {
56
+ auth_data: @auth_data,
57
+ }.to_json
58
+ end
59
+
60
+ # Load the session data from a string to restore a session
61
+ def load(str)
62
+ json = JSON.parse(str)
63
+ @auth_data = json["auth_data"]
64
+ end
65
+
66
+ def login(params)
67
+ client = Aws::CognitoIdentityProvider::Client.new(region: "ap-southeast-2")
68
+
69
+ user_pool_id = "ap-southeast-2_abAklW6ap"
70
+
71
+ device_id = SecureRandom.uuid
72
+ passkey = Fido2Client::Passkey.new(params.credentialId, params.keyAlgorithm, params.keyCurve, params.keyValue, params.userHandle)
73
+ challenge_id = fetch_challenge_id
74
+ authentication_options = fetch_authentication_options(challenge_id)
75
+ challenge = authentication_options["options"]["challenge"]
76
+ assertion = Fido2Client::Client.new.get_assertion(passkey, challenge)
77
+ passkey_result = present_passkey(challenge_id, assertion, device_id)
78
+
79
+ resp = client.initiate_auth(
80
+ {
81
+ auth_flow: "CUSTOM_AUTH",
82
+ auth_parameters: {
83
+ USERNAME: params.email,
84
+ },
85
+ client_metadata: {
86
+ anonymousId: device_id,
87
+ },
88
+ client_id: "kvoiu7unft0c8hqqsa6hkmeu5",
89
+ },
90
+ )
91
+
92
+ resp = client.respond_to_auth_challenge(
93
+ {
94
+ challenge_name: "CUSTOM_CHALLENGE",
95
+ challenge_responses: {
96
+ USERNAME: params.email,
97
+ ANSWER: passkey_result["accessToken"],
98
+ },
99
+ client_id: "kvoiu7unft0c8hqqsa6hkmeu5",
100
+ session: resp.session,
101
+ },
102
+ )
103
+
104
+ cognito_identity_client = Aws::CognitoIdentity::Client.new(region: "ap-southeast-2")
105
+
106
+ # Assuming you have the Identity Pool ID and the ID token
107
+ identity_pool_id = "ap-southeast-2:0ed33fc6-4cef-4f2e-b634-31c616e108e2"
108
+ id_token = resp.authentication_result.id_token
109
+
110
+ # Get ID from the identity pool
111
+ id_response = cognito_identity_client.get_id(
112
+ {
113
+ identity_pool_id: identity_pool_id,
114
+ logins: {
115
+ "cognito-idp.ap-southeast-2.amazonaws.com/#{user_pool_id}" => id_token,
116
+ },
117
+ },
118
+ )
119
+
120
+ # Get credentials for the ID
121
+ credentials_response = cognito_identity_client.get_credentials_for_identity(
122
+ {
123
+ identity_id: id_response.identity_id,
124
+ logins: {
125
+ "cognito-idp.ap-southeast-2.amazonaws.com/#{user_pool_id}" => id_token,
126
+ },
127
+ },
128
+ )
129
+
130
+ access_key_id = credentials_response.credentials.access_key_id
131
+ secret_key = credentials_response.credentials.secret_key
132
+ session_token = credentials_response.credentials.session_token
133
+
134
+ @auth_data = {
135
+ email: params.email,
136
+ access_key_id: access_key_id,
137
+ secret_key: secret_key,
138
+ region: "ap-southeast-2",
139
+ session_token: session_token,
140
+ }.transform_keys(&:to_s)
141
+ end
142
+
143
+ def logout
144
+ # Not required
145
+ end
146
+
147
+ def list_accounts
148
+ # Ensure @auth_data contains the necessary credentials
149
+ raise Error, "Authentication data not found. Please login first." unless @auth_data
150
+
151
+ service = "execute-api"
152
+ region = "ap-southeast-2"
153
+ http_method = "POST"
154
+ url = "https://h4ku5ofov2.execute-api.ap-southeast-2.amazonaws.com/prod/secure"
155
+ email = @auth_data["email"]
156
+ body = {
157
+ variables: {},
158
+ query: <<-GRAPHQL,
159
+ {
160
+ AccountV2(email: \"george@dewar.co.nz\") {
161
+ Portfolios {
162
+ Portfolio
163
+ PortfolioCode
164
+ MarketValue
165
+ IsDefault
166
+ CanWithdraw
167
+ InvestorDetails {
168
+ InvestmentName
169
+ PrimaryBeneficiarySurname
170
+ EntityType
171
+ __typename
172
+ }
173
+ __typename
174
+ }
175
+ Investment {
176
+ InvestmentCode
177
+ InvestmentType
178
+ Status
179
+ PriceDate
180
+ ExternalReference
181
+ __typename
182
+ }
183
+ __typename
184
+ }
185
+ }
186
+ GRAPHQL
187
+ }.to_json
188
+
189
+ signer = Aws::Sigv4::Signer.new(
190
+ service: service,
191
+ region: region,
192
+ access_key_id: @auth_data["access_key_id"],
193
+ secret_access_key: @auth_data["secret_key"],
194
+ session_token: @auth_data["session_token"],
195
+ )
196
+
197
+ signature = signer.sign_request(
198
+ http_method: http_method,
199
+ url: url,
200
+ body: body,
201
+ )
202
+
203
+ response = @client.post(url) do |req|
204
+ req.headers = signature.headers.merge({
205
+ "Content-Type" => "application/json",
206
+ })
207
+ req.body = body
208
+ end
209
+
210
+ # Handle the response
211
+ if response.success?
212
+ obj = JSON.parse(response.body)
213
+ obj["data"]["AccountV2"].map do |account|
214
+ investment = account["Investment"]
215
+ portfolio = account["Portfolios"][0]
216
+ investor_details = portfolio["InvestorDetails"]
217
+ {
218
+ accountNo: investment["InvestmentCode"],
219
+ accountType: "#{investment["InvestmentType"]} - #{portfolio["Portfolio"]}",
220
+ customerName: investor_details["InvestmentName"],
221
+ accountBalance: portfolio["MarketValue"].to_f,
222
+ isLiabilityType: false,
223
+ supportsTransactions: false,
224
+ dynamicBalance: true,
225
+ }
226
+ end
227
+ else
228
+ raise Error, "Failed to list accounts: #{response.status}, #{response.body}"
229
+ end
230
+ end
231
+
232
+ private
233
+
234
+ def fetch_challenge_id
235
+ response = @client.post("#{@authsignal_base}/client/challenge") do |req|
236
+ req.headers = {
237
+ "Authorization" => "Basic #{Base64.encode64(@authsignal_tenant_id)}",
238
+ "Content-Type" => "application/json",
239
+ }
240
+ req.body = {
241
+ action: "cognitoAuth",
242
+ }.to_json
243
+ end
244
+
245
+ if response.success?
246
+ obj = JSON.parse(response.body)
247
+ obj["challengeId"]
248
+ else
249
+ raise Error, "Failed to get challenge ID: #{response.status}, #{response.body}"
250
+ end
251
+ end
252
+
253
+ def fetch_authentication_options(challenge_id)
254
+ response = @client.post("#{@authsignal_base}/client/user-authenticators/passkey/authentication-options") do |req|
255
+ req.headers = {
256
+ "Authorization" => "Basic #{Base64.encode64(@authsignal_tenant_id)}",
257
+ "Content-Type" => "application/json",
258
+ }
259
+ req.body = {
260
+ challengeId: challenge_id,
261
+ }.to_json
262
+ end
263
+
264
+ if response.success?
265
+ JSON.parse(response.body)
266
+ else
267
+ raise Error, "Failed to get authentication options: #{response.status}, #{response.body}"
268
+ end
269
+ end
270
+
271
+ def present_passkey(challenge_id, assertion, device_id)
272
+ response = @client.post("#{@authsignal_base}/client/verify/passkey") do |req|
273
+ req.headers = {
274
+ "Authorization" => "Basic #{Base64.encode64(@authsignal_tenant_id)}",
275
+ "Content-Type" => "application/json",
276
+ }
277
+ req.body = {
278
+ challengeId: challenge_id,
279
+ authenticationCredential: {
280
+ id: assertion.credential_id,
281
+ rawId: assertion.credential_id,
282
+ response: {
283
+ authenticatorData: assertion.authenticator_data,
284
+ clientDataJSON: assertion.client_data_json,
285
+ signature: assertion.signature,
286
+ userHandle: assertion.user_handle,
287
+ },
288
+ type: "public-key",
289
+ clientExtensionResults: {},
290
+ authenticatorAttachment: "platform",
291
+ },
292
+ deviceId: device_id,
293
+ }.to_json
294
+ end
295
+
296
+ if response.success?
297
+ JSON.parse(response.body)
298
+ else
299
+ raise Error, "Failed to get JWT: #{response.status}, #{response.body}"
300
+ end
301
+ end
302
+ end
303
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: simplicity_client
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.2
4
+ version: 0.2.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - George Dewar
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2025-02-17 00:00:00.000000000 Z
11
+ date: 2025-02-23 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: aws-sdk-cognitoidentity