git-fit 0.10.8 → 0.10.10
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/lib/git_fit/auth/garmin/strategy_a.rb +20 -19
- data/lib/git_fit/auth/garmin/strategy_b.rb +24 -15
- data/lib/git_fit/auth/garmin.rb +6 -0
- data/lib/git_fit/cli.rb +1 -0
- data/lib/git_fit/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: c4cd7d43e318f96dcab83874635c24e7ead0ec772f550f2fdcd9ea7c81a6a44b
|
|
4
|
+
data.tar.gz: e106683db19d232870990bcbc59bcb35f59be4fec8c33d1141ec858632446522
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: da0ac336bbe16839be618bb0ddbcc6db7513630231b89cd690249568908d0fbe0f20110ec2904d589e18b3bb2ff14431e7138b846b9c3dd0ed65b4018f033e29
|
|
7
|
+
data.tar.gz: 101b8bc30d222858e2b61a592027ae701fb4563fdfeb832688929ba01cd7c87588411023cec5057b8af8fe7f81a15c18de62a3b25a2b97577a3f11496de3b672
|
|
@@ -36,6 +36,7 @@ module GitFit
|
|
|
36
36
|
MOBILE_FINGERPRINT = 'safari18_0'
|
|
37
37
|
TLS_FINGERPRINTS = %w[safari18_0 safari17_0 chrome120 edge101 chrome124].freeze
|
|
38
38
|
CURL_BINARY = 'curl-impersonate'
|
|
39
|
+
AUTH_FLAG = '-A'
|
|
39
40
|
|
|
40
41
|
module_function
|
|
41
42
|
|
|
@@ -129,7 +130,7 @@ module GitFit
|
|
|
129
130
|
if resp_type == 'MFA_REQUIRED'
|
|
130
131
|
method = res.dig('customerMfaInfo', 'mfaLastMethodUsed') || 'email'
|
|
131
132
|
res = handle_mfa(sso, method, login_params, post_headers, mfa_code,
|
|
132
|
-
impersonate: MOBILE_FINGERPRINT, jar: jar, output: body)
|
|
133
|
+
impersonate: MOBILE_FINGERPRINT, jar: jar, output: body, email: email)
|
|
133
134
|
resp_type = res.dig('responseStatus', 'type')
|
|
134
135
|
end
|
|
135
136
|
|
|
@@ -215,7 +216,7 @@ module GitFit
|
|
|
215
216
|
if resp_type == 'MFA_REQUIRED'
|
|
216
217
|
method = res.dig('customerMfaInfo', 'mfaLastMethodUsed') || 'email'
|
|
217
218
|
res = handle_mfa(sso, method, login_params, post_headers, mfa_code,
|
|
218
|
-
impersonate: imp, jar: jar, output: body)
|
|
219
|
+
impersonate: imp, jar: jar, output: body, email: email)
|
|
219
220
|
resp_type = res.dig('responseStatus', 'type')
|
|
220
221
|
end
|
|
221
222
|
|
|
@@ -230,8 +231,8 @@ module GitFit
|
|
|
230
231
|
nil
|
|
231
232
|
end
|
|
232
233
|
|
|
233
|
-
def handle_mfa(sso, method, login_params, post_headers, mfa_code, impersonate:, jar:, output:)
|
|
234
|
-
code = resolve_mfa_code(mfa_code)
|
|
234
|
+
def handle_mfa(sso, method, login_params, post_headers, mfa_code, impersonate:, jar:, output:, email:)
|
|
235
|
+
code = resolve_mfa_code(mfa_code, email)
|
|
235
236
|
mfa_data = {
|
|
236
237
|
'mfaMethod' => method,
|
|
237
238
|
'mfaVerificationCode' => code,
|
|
@@ -266,22 +267,22 @@ module GitFit
|
|
|
266
267
|
raise GitFit::Sync::AuthError, 'MFA verification failed on all endpoints'
|
|
267
268
|
end
|
|
268
269
|
|
|
269
|
-
def resolve_mfa_code(mfa_code)
|
|
270
|
-
code = mfa_code
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
$stderr.write('Enter
|
|
274
|
-
$
|
|
275
|
-
code = $stdin.gets
|
|
270
|
+
def resolve_mfa_code(mfa_code, email)
|
|
271
|
+
code = mfa_code.to_s.strip
|
|
272
|
+
if code.empty?
|
|
273
|
+
warn "[garmin_auth] Garmin sent a verification code to #{email}"
|
|
274
|
+
$stderr.write('Enter the 6-digit code: ') if $stdin.tty?
|
|
275
|
+
code = $stdin.gets.to_s.strip
|
|
276
276
|
end
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
277
|
+
return code unless code.empty?
|
|
278
|
+
|
|
279
|
+
raise GitFit::Sync::AuthError,
|
|
280
|
+
"MFA required — check your email (#{email}) for the code.\n" \
|
|
281
|
+
" This code is valid for a few minutes.\n\n" \
|
|
282
|
+
" To continue, choose one:\n" \
|
|
283
|
+
" echo \"<CODE>\" | git fit auth garmin #{AUTH_FLAG}\n" \
|
|
284
|
+
" git fit auth garmin #{AUTH_FLAG} --mfa-code <CODE>\n\n" \
|
|
285
|
+
" If the code has expired, request a new one: git fit auth garmin #{AUTH_FLAG}"
|
|
285
286
|
end
|
|
286
287
|
|
|
287
288
|
def check_curl_available!
|
|
@@ -19,12 +19,12 @@ module GitFit
|
|
|
19
19
|
LOGIN_DELAY_MAX = 45.0
|
|
20
20
|
SESSION_CACHE_DIR = 'data/cache'
|
|
21
21
|
REDIRECT_STATUSES = [301, 302, 303, 307, 308].freeze
|
|
22
|
+
AUTH_FLAG = '-B'
|
|
22
23
|
|
|
23
24
|
module_function
|
|
24
25
|
|
|
25
26
|
def call(email:, password:, domain: 'garmin.com', mfa_code: nil)
|
|
26
27
|
mfa_code = mfa_code.to_s.strip
|
|
27
|
-
mfa_code = ENV['SYNC__GARMIN__MFA_CODE'].to_s.strip if mfa_code.empty?
|
|
28
28
|
sso = "https://sso.#{domain}"
|
|
29
29
|
session_path = _session_file_path(domain)
|
|
30
30
|
chrome_path = _find_chrome
|
|
@@ -33,8 +33,8 @@ module GitFit
|
|
|
33
33
|
session = _load_session(session_path)
|
|
34
34
|
warn "loaded session cookies (#{session[:age_days].round} days old)" if session
|
|
35
35
|
|
|
36
|
-
opts = {
|
|
37
|
-
opts[:
|
|
36
|
+
opts = { userAgent: MOBILE_UA, viewport: { width: 375, height: 812 } }
|
|
37
|
+
opts[:storageState] = session[:path] if session
|
|
38
38
|
context = browser.new_context(**opts)
|
|
39
39
|
|
|
40
40
|
begin
|
|
@@ -63,7 +63,7 @@ module GitFit
|
|
|
63
63
|
"#{sso}/mobile/sso/en_US/sign-in",
|
|
64
64
|
params: { 'clientId' => MOBILE_CLIENT_ID, 'service' => MOBILE_SERVICE },
|
|
65
65
|
headers: { 'User-Agent' => MOBILE_UA, 'Accept' => 'text/html,...', 'Accept-Language' => 'en-US,en;q=0.9' },
|
|
66
|
-
|
|
66
|
+
maxRedirects: 0,
|
|
67
67
|
)
|
|
68
68
|
|
|
69
69
|
if REDIRECT_STATUSES.include?(signin_resp.status)
|
|
@@ -102,16 +102,7 @@ module GitFit
|
|
|
102
102
|
raise GitFit::Sync::AuthError, "Unexpected login response: #{resp_type}" unless resp_type == 'MFA_REQUIRED'
|
|
103
103
|
|
|
104
104
|
mfa_method = data.dig('customerMfaInfo', 'mfaLastMethodUsed') || 'email'
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
code = mfa_code.to_s.strip
|
|
108
|
-
if code.empty? && $stdin.tty?
|
|
109
|
-
warn 'Enter MFA code: '
|
|
110
|
-
code = $stdin.gets.to_s.strip
|
|
111
|
-
end
|
|
112
|
-
if code.empty?
|
|
113
|
-
raise GitFit::Sync::AuthError, 'MFA required but no code provided (set SYNC__GARMIN__MFA_CODE or use stdin)'
|
|
114
|
-
end
|
|
105
|
+
code = resolve_mfa_code(email, mfa_code)
|
|
115
106
|
|
|
116
107
|
warn 'verifying MFA code...'
|
|
117
108
|
mfa_data = {
|
|
@@ -145,6 +136,24 @@ module GitFit
|
|
|
145
136
|
url[/[?&]ticket=(ST-[^&\s]+)/, 1]
|
|
146
137
|
end
|
|
147
138
|
|
|
139
|
+
def resolve_mfa_code(email, mfa_code)
|
|
140
|
+
code = mfa_code.to_s.strip
|
|
141
|
+
if code.empty?
|
|
142
|
+
warn "[garmin_auth_pw] Garmin sent a verification code to #{email}"
|
|
143
|
+
$stderr.write('Enter the 6-digit code: ') if $stdin.tty?
|
|
144
|
+
code = $stdin.gets.to_s.strip
|
|
145
|
+
end
|
|
146
|
+
return code unless code.empty?
|
|
147
|
+
|
|
148
|
+
raise GitFit::Sync::AuthError,
|
|
149
|
+
"MFA required — check your email (#{email}) for the code.\n" \
|
|
150
|
+
" This code is valid for a few minutes.\n\n" \
|
|
151
|
+
" To continue, choose one:\n" \
|
|
152
|
+
" echo \"<CODE>\" | git fit auth garmin #{AUTH_FLAG}\n" \
|
|
153
|
+
" git fit auth garmin #{AUTH_FLAG} --mfa-code <CODE>\n\n" \
|
|
154
|
+
" If the code has expired, request a new one: git fit auth garmin #{AUTH_FLAG}"
|
|
155
|
+
end
|
|
156
|
+
|
|
148
157
|
def _random_delay(label)
|
|
149
158
|
delay = rand(LOGIN_DELAY_MIN..LOGIN_DELAY_MAX)
|
|
150
159
|
warn "[garmin_auth_pw] #{label}: waiting #{delay.round}s for Cloudflare..."
|
|
@@ -197,7 +206,7 @@ module GitFit
|
|
|
197
206
|
|
|
198
207
|
cli = _find_playwright_cli
|
|
199
208
|
Playwright.create(playwright_cli_executable_path: cli) do |playwright|
|
|
200
|
-
browser = playwright.chromium.launch(headless: true,
|
|
209
|
+
browser = playwright.chromium.launch(headless: true, executablePath: chrome_path)
|
|
201
210
|
begin
|
|
202
211
|
yield browser
|
|
203
212
|
ensure
|
data/lib/git_fit/auth/garmin.rb
CHANGED
|
@@ -61,6 +61,7 @@ module GitFit
|
|
|
61
61
|
def sso_token(mode)
|
|
62
62
|
email, password = sso_credentials
|
|
63
63
|
creds = { email: email, password: password, domain: domain }
|
|
64
|
+
creds[:mfa_code] = @options[:mfa_code] if mfa_code_provided?
|
|
64
65
|
case mode
|
|
65
66
|
when :A then single_strategy(:A, creds)
|
|
66
67
|
when :B then single_strategy(:B, creds)
|
|
@@ -68,6 +69,11 @@ module GitFit
|
|
|
68
69
|
end
|
|
69
70
|
end
|
|
70
71
|
|
|
72
|
+
def mfa_code_provided?
|
|
73
|
+
code = @options[:mfa_code]
|
|
74
|
+
!code.nil? && !code.to_s.strip.empty?
|
|
75
|
+
end
|
|
76
|
+
|
|
71
77
|
def sso_credentials
|
|
72
78
|
cfg = @config.sync_config('garmin')
|
|
73
79
|
email = cfg['email'].to_s
|
data/lib/git_fit/cli.rb
CHANGED
|
@@ -77,6 +77,7 @@ module GitFit
|
|
|
77
77
|
option :B, type: :boolean, desc: 'Garmin Strategy B: Playwright (default)'
|
|
78
78
|
option :auto, type: :boolean, desc: 'Garmin auto: B first, fallback to A'
|
|
79
79
|
option :sync, type: :boolean, desc: 'Garmin: promote cached token to auth_seed'
|
|
80
|
+
option :mfa_code, type: :string, desc: 'Garmin MFA code (from email), re-run to continue'
|
|
80
81
|
def auth(source)
|
|
81
82
|
if source == 'garmin'
|
|
82
83
|
GitFit::Auth::Garmin.new(options, git_fit_config).call
|
data/lib/git_fit/version.rb
CHANGED