adsedare 0.0.6 → 0.0.7
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/adsedare.gemspec +1 -0
- data/lib/adsedare/version.rb +1 -1
- data/lib/adsedare/xcodeproj.rb +4 -2
- data/lib/adsedare.rb +2 -2
- data/lib/appstoreconnect.rb +0 -1
- data/lib/starship/2fa_provider.rb +5 -11
- data/lib/starship/auth_helper.rb +173 -21
- data/lib/starship.rb +4 -0
- metadata +17 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 690388aeb46674b4df24ccbc320f54604f70671538c38146200bab7579f752dd
|
4
|
+
data.tar.gz: 942a38b80af4938b37a9487ed200f8cef5952a16566a72b175d6f670943c40c1
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f9e0d0d44e7020d5a0c4e6011238ee01996634f313e802d83dd28971c1eb9d97820c3820497029761242a70e9bf9c3d059651685ad299348cd0891b6dac93900
|
7
|
+
data.tar.gz: 40325179dec57602f0c40e5ae745b5098c9922e9af8cf35a3384bab65cc6cdb51222e68d71cfec189496a844001a5d52170a8baa1ecc4eaddf88190f46a2a420
|
data/adsedare.gemspec
CHANGED
data/lib/adsedare/version.rb
CHANGED
data/lib/adsedare/xcodeproj.rb
CHANGED
@@ -35,8 +35,10 @@ module Adsedare
|
|
35
35
|
bundle_by_identifier[bundle_id["attributes"]["identifier"]] = bundle_id
|
36
36
|
end
|
37
37
|
|
38
|
-
bundles_with_profiles["included"]
|
39
|
-
|
38
|
+
if bundles_with_profiles["included"]
|
39
|
+
bundles_with_profiles["included"].each do |profile|
|
40
|
+
profiles_by_id[profile["id"]] = profile
|
41
|
+
end
|
40
42
|
end
|
41
43
|
|
42
44
|
project.targets.each do |target|
|
data/lib/adsedare.rb
CHANGED
@@ -174,8 +174,8 @@ module Adsedare
|
|
174
174
|
need_update = false
|
175
175
|
|
176
176
|
capabilities.each do |capability|
|
177
|
-
if capability.check?(bundle_info)
|
178
|
-
|
177
|
+
if !capability.check?(bundle_info)
|
178
|
+
logger.warn "Bundle '#{bundle_identifier}' is missing capability '#{capability.type}'."
|
179
179
|
need_update = true
|
180
180
|
end
|
181
181
|
end
|
data/lib/appstoreconnect.rb
CHANGED
@@ -7,19 +7,13 @@ module Starship
|
|
7
7
|
def initialize
|
8
8
|
end
|
9
9
|
|
10
|
-
# Get the 2FA code
|
11
|
-
# @param session_id [String] The session ID from Apple
|
12
|
-
# @param scnt [String] The scnt value from Apple
|
13
|
-
# @return [String] The 2FA code
|
14
10
|
def get_code(session_id, scnt)
|
15
11
|
raise NotImplementedError, "Subclasses must implement get_code"
|
16
12
|
end
|
17
13
|
|
18
|
-
#
|
19
|
-
|
20
|
-
|
21
|
-
def can_handle?(type)
|
22
|
-
raise NotImplementedError, "Subclasses must implement can_handle?"
|
14
|
+
# @return [String] The type of 2FA ("phone", "trusteddevice")
|
15
|
+
def two_factor_type
|
16
|
+
raise NotImplementedError, "Subclasses must implement two_factor_type"
|
23
17
|
end
|
24
18
|
end
|
25
19
|
|
@@ -33,8 +27,8 @@ module Starship
|
|
33
27
|
code
|
34
28
|
end
|
35
29
|
|
36
|
-
def
|
37
|
-
|
30
|
+
def two_factor_type
|
31
|
+
"trusteddevice"
|
38
32
|
end
|
39
33
|
end
|
40
34
|
end
|
data/lib/starship/auth_helper.rb
CHANGED
@@ -8,6 +8,7 @@ require "digest"
|
|
8
8
|
require "fileutils"
|
9
9
|
require "openssl"
|
10
10
|
require "securerandom"
|
11
|
+
require "fastlane-sirp"
|
11
12
|
|
12
13
|
require_relative "2fa_provider"
|
13
14
|
require_relative "../logging"
|
@@ -18,13 +19,19 @@ module Starship
|
|
18
19
|
# AuthHelper handles authentication with Apple's developer portal
|
19
20
|
class AuthHelper
|
20
21
|
include Logging
|
21
|
-
|
22
|
+
|
23
|
+
@two_factor_provider = Starship::ManualTwoFactorProvider.new
|
22
24
|
attr_reader :session, :csrf, :csrf_ts, :session_data
|
23
25
|
|
24
26
|
AUTH_ENDPOINT = "https://idmsa.apple.com/appleauth/auth"
|
25
27
|
WIDGET_KEY_URL = "https://appstoreconnect.apple.com/olympus/v1/app/config?hostname=itunesconnect.apple.com"
|
26
28
|
|
27
|
-
def
|
29
|
+
def two_factor_provider=(provider)
|
30
|
+
@two_factor_provider = provider
|
31
|
+
logger.info "Two-factor provider set to #{provider.class.name}"
|
32
|
+
end
|
33
|
+
|
34
|
+
def initialize()
|
28
35
|
# Create session directory if it doesn't exist
|
29
36
|
@session_directory = File.expand_path("~/.starship")
|
30
37
|
FileUtils.mkdir_p(@session_directory)
|
@@ -34,7 +41,6 @@ module Starship
|
|
34
41
|
@csrf_ts = nil
|
35
42
|
@email = nil
|
36
43
|
@session_data = {}
|
37
|
-
@two_factor_provider = two_factor_provider || Starship::ManualTwoFactorProvider.new
|
38
44
|
|
39
45
|
# Initialize Faraday with cookie jar
|
40
46
|
@session = Faraday.new do |builder|
|
@@ -72,8 +78,6 @@ module Starship
|
|
72
78
|
auth_result = authenticate_with_srp(email, password)
|
73
79
|
|
74
80
|
if auth_result == :two_factor_required
|
75
|
-
logger.info "Two-factor authentication required. Requesting verification code..."
|
76
|
-
|
77
81
|
handle_two_factor_auth
|
78
82
|
elsif auth_result
|
79
83
|
# After successful authentication, get CSRF tokens
|
@@ -303,13 +307,60 @@ module Starship
|
|
303
307
|
end
|
304
308
|
end
|
305
309
|
|
310
|
+
def to_hex(str)
|
311
|
+
str.unpack1('H*')
|
312
|
+
end
|
313
|
+
|
314
|
+
def to_byte(str)
|
315
|
+
[str].pack('H*')
|
316
|
+
end
|
317
|
+
|
318
|
+
def pbkdf2(password, salt, iterations, key_length, digest = OpenSSL::Digest::SHA256.new)
|
319
|
+
password = OpenSSL::Digest::SHA256.digest(password)
|
320
|
+
OpenSSL::PKCS5.pbkdf2_hmac(password, salt, iterations, key_length, digest)
|
321
|
+
end
|
322
|
+
|
323
|
+
def fetch_hashcash
|
324
|
+
response = @session.get(
|
325
|
+
"#{AUTH_ENDPOINT}/signin?widgetKey=#{widget_key}",
|
326
|
+
)
|
327
|
+
headers = response.headers
|
328
|
+
|
329
|
+
bits = headers["X-Apple-HC-Bits"]
|
330
|
+
challenge = headers["X-Apple-HC-Challenge"]
|
331
|
+
|
332
|
+
if bits.nil? || challenge.nil?
|
333
|
+
logger.warn "Unable to find 'X-Apple-HC-Bits' and 'X-Apple-HC-Challenge' to make hashcash"
|
334
|
+
return nil
|
335
|
+
end
|
336
|
+
|
337
|
+
return make_hashcash(bits: bits, challenge: challenge)
|
338
|
+
end
|
339
|
+
|
340
|
+
def make_hashcash(bits: nil, challenge: nil)
|
341
|
+
version = 1
|
342
|
+
date = Time.now.strftime("%Y%m%d%H%M%S")
|
343
|
+
|
344
|
+
counter = 0
|
345
|
+
loop do
|
346
|
+
hc = [
|
347
|
+
version, bits, date, challenge, ":#{counter}"
|
348
|
+
].join(":")
|
349
|
+
|
350
|
+
if Digest::SHA1.digest(hc).unpack1('B*')[0, bits.to_i].to_i == 0
|
351
|
+
return hc
|
352
|
+
end
|
353
|
+
counter += 1
|
354
|
+
end
|
355
|
+
end
|
356
|
+
|
306
357
|
# Authenticate with Secure Remote Password protocol
|
307
358
|
# @param email [String] The email address
|
308
359
|
# @param password [String] The password
|
309
360
|
# @return [Boolean, Symbol] True if successful, :two_factor_required if 2FA is needed, false otherwise
|
310
361
|
def authenticate_with_srp(email, password)
|
311
|
-
|
312
|
-
|
362
|
+
client = SIRP::Client.new(2048)
|
363
|
+
a = client.start_authentication()
|
313
364
|
|
314
365
|
headers = {
|
315
366
|
"Accept" => "application/json, text/javascript",
|
@@ -318,6 +369,17 @@ module Starship
|
|
318
369
|
"X-Apple-Widget-Key" => widget_key,
|
319
370
|
}
|
320
371
|
|
372
|
+
federate_data = {
|
373
|
+
"accountName" => email,
|
374
|
+
"rememberMe" => false,
|
375
|
+
}
|
376
|
+
|
377
|
+
response = @session.post(
|
378
|
+
"#{AUTH_ENDPOINT}/federate?isRememberMeEnabled=false",
|
379
|
+
federate_data.to_json,
|
380
|
+
headers
|
381
|
+
)
|
382
|
+
|
321
383
|
if @session_data["session_id"]
|
322
384
|
headers.update({
|
323
385
|
"X-Apple-ID-Session-Id" => @session_data["session_id"],
|
@@ -325,18 +387,54 @@ module Starship
|
|
325
387
|
})
|
326
388
|
end
|
327
389
|
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
"
|
332
|
-
"password" => password,
|
333
|
-
"rememberMe" => true,
|
390
|
+
init_data = {
|
391
|
+
"a": Base64.strict_encode64(to_byte(a)),
|
392
|
+
"accountName": email,
|
393
|
+
"protocols": ["s2k", "s2k_fo"],
|
334
394
|
}
|
335
395
|
|
336
|
-
logger.info "Initializing authentication request to Apple ID..."
|
337
396
|
response = @session.post(
|
338
|
-
"#{AUTH_ENDPOINT}/signin",
|
339
|
-
|
397
|
+
"#{AUTH_ENDPOINT}/signin/init",
|
398
|
+
init_data.to_json,
|
399
|
+
headers
|
400
|
+
)
|
401
|
+
|
402
|
+
body = JSON.parse(response.body)
|
403
|
+
salt = Base64.strict_decode64(body["salt"])
|
404
|
+
b = Base64.strict_decode64(body["b"])
|
405
|
+
c = body["c"]
|
406
|
+
iterations = body["iteration"]
|
407
|
+
key_length = 32
|
408
|
+
|
409
|
+
encrypted_password = pbkdf2(password, salt, iterations, key_length)
|
410
|
+
|
411
|
+
m1 = client.process_challenge(
|
412
|
+
email,
|
413
|
+
to_hex(encrypted_password),
|
414
|
+
to_hex(salt),
|
415
|
+
to_hex(b),
|
416
|
+
is_password_encrypted: true
|
417
|
+
)
|
418
|
+
m2 = client.H_AMK
|
419
|
+
|
420
|
+
complete_data = {
|
421
|
+
"accountName": email,
|
422
|
+
"c": c,
|
423
|
+
"m1": Base64.encode64(to_byte(m1)).strip,
|
424
|
+
"m2": Base64.encode64(to_byte(m2)).strip,
|
425
|
+
"rememberMe": false
|
426
|
+
}
|
427
|
+
|
428
|
+
hashcash = self.fetch_hashcash
|
429
|
+
if hashcash
|
430
|
+
headers.update({
|
431
|
+
"X-Apple-HC" => hashcash,
|
432
|
+
})
|
433
|
+
end
|
434
|
+
|
435
|
+
response = @session.post(
|
436
|
+
"#{AUTH_ENDPOINT}/signin/complete?isRememberMeEnabled=false",
|
437
|
+
complete_data.to_json,
|
340
438
|
headers
|
341
439
|
)
|
342
440
|
|
@@ -391,11 +489,60 @@ module Starship
|
|
391
489
|
end
|
392
490
|
|
393
491
|
begin
|
394
|
-
|
492
|
+
two_factor_type = @two_factor_provider.two_factor_type
|
493
|
+
logger.info "Two-factor authentication required. Requesting #{two_factor_type} verification code..."
|
494
|
+
|
495
|
+
auth_options_headers = {
|
496
|
+
"Accept" => "application/json, text/javascript",
|
497
|
+
"Content-Type" => "application/json",
|
498
|
+
"X-Requested-With" => "XMLHttpRequest",
|
499
|
+
"X-Apple-ID-Session-Id" => session_id,
|
500
|
+
"scnt" => scnt,
|
501
|
+
"X-Apple-Widget-Key" => widget_key,
|
502
|
+
}
|
503
|
+
|
504
|
+
auth_options_response = @session.get(
|
505
|
+
"#{AUTH_ENDPOINT}/auth",
|
506
|
+
nil,
|
507
|
+
auth_options_headers
|
508
|
+
)
|
509
|
+
auth_options = JSON.parse(auth_options_response.body)
|
510
|
+
|
511
|
+
trigger_headers = {
|
512
|
+
"Accept" => "application/json",
|
513
|
+
"Content-Type" => "application/json",
|
514
|
+
"X-Requested-With" => "XMLHttpRequest",
|
515
|
+
"X-Apple-ID-Session-Id" => session_id,
|
516
|
+
"scnt" => scnt,
|
517
|
+
"X-Apple-Widget-Key" => widget_key,
|
518
|
+
}
|
519
|
+
|
520
|
+
trigger_data = {
|
521
|
+
}
|
522
|
+
|
523
|
+
if two_factor_type == "phone"
|
524
|
+
phone_number = auth_options["trustedPhoneNumbers"][0]
|
525
|
+
trigger_data = {
|
526
|
+
"phoneNumber" => {
|
527
|
+
"id" => phone_number["id"],
|
528
|
+
},
|
529
|
+
"mode" => "sms",
|
530
|
+
}
|
531
|
+
logger.info "Sending SMS to #{phone_number["numberWithDialCode"]}"
|
532
|
+
end
|
533
|
+
|
534
|
+
response = @session.put(
|
535
|
+
"#{AUTH_ENDPOINT}/verify/#{two_factor_type}",
|
536
|
+
trigger_data.to_json,
|
537
|
+
trigger_headers
|
538
|
+
)
|
539
|
+
|
395
540
|
code = @two_factor_provider.get_code(session_id, scnt)
|
396
541
|
|
542
|
+
logger.info "Received code: #{code}"
|
543
|
+
|
397
544
|
verify_headers = {
|
398
|
-
"Accept" => "application/json
|
545
|
+
"Accept" => "application/json",
|
399
546
|
"Content-Type" => "application/json",
|
400
547
|
"X-Requested-With" => "XMLHttpRequest",
|
401
548
|
"X-Apple-ID-Session-Id" => session_id,
|
@@ -405,14 +552,19 @@ module Starship
|
|
405
552
|
|
406
553
|
verify_data = { "securityCode" => { "code" => code.strip } }
|
407
554
|
|
555
|
+
if two_factor_type == "phone"
|
556
|
+
verify_data["phoneNumber"] = trigger_data["phoneNumber"]
|
557
|
+
verify_data["mode"] = trigger_data["mode"]
|
558
|
+
end
|
559
|
+
|
408
560
|
# First verify the security code
|
409
561
|
verify_response = @session.post(
|
410
|
-
"#{AUTH_ENDPOINT}/verify/
|
562
|
+
"#{AUTH_ENDPOINT}/verify/#{two_factor_type}/securitycode",
|
411
563
|
verify_data.to_json,
|
412
564
|
verify_headers
|
413
565
|
)
|
414
566
|
|
415
|
-
if verify_response.status == 204
|
567
|
+
if verify_response.status == 204 || verify_response.status == 200
|
416
568
|
logger.info "Two-factor code verified successfully."
|
417
569
|
logger.info "Trusting the session after 2FA verification..."
|
418
570
|
|
@@ -423,7 +575,7 @@ module Starship
|
|
423
575
|
verify_headers
|
424
576
|
)
|
425
577
|
|
426
|
-
if trust_response.status == 204
|
578
|
+
if trust_response.status == 204 || trust_response.status == 200
|
427
579
|
# Store all relevant session data
|
428
580
|
@session_data.update({
|
429
581
|
"session_id" => session_id,
|
data/lib/starship.rb
CHANGED
@@ -8,6 +8,10 @@ require_relative "starship/auth_helper"
|
|
8
8
|
# Starship is a wrapper around Apple's developer portal API
|
9
9
|
# Inspired by Fatslane's Spaceship
|
10
10
|
module Starship
|
11
|
+
def self.set_provider(provider)
|
12
|
+
Client.auth_helper.two_factor_provider = provider
|
13
|
+
end
|
14
|
+
|
11
15
|
class Client
|
12
16
|
DEV_SERVICES_V1 = "https://developer.apple.com/services-account/v1"
|
13
17
|
DEV_SERVICES_QH65B2 = "https://developer.apple.com/services-account/QH65B2"
|
metadata
CHANGED
@@ -1,14 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: adsedare
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.7
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- alexstrnik
|
8
|
-
autorequire:
|
9
8
|
bindir: exe
|
10
9
|
cert_chain: []
|
11
|
-
date: 2025-05-
|
10
|
+
date: 2025-05-27 00:00:00.000000000 Z
|
12
11
|
dependencies:
|
13
12
|
- !ruby/object:Gem::Dependency
|
14
13
|
name: rake
|
@@ -94,6 +93,20 @@ dependencies:
|
|
94
93
|
- - "~>"
|
95
94
|
- !ruby/object:Gem::Version
|
96
95
|
version: 3.2.0
|
96
|
+
- !ruby/object:Gem::Dependency
|
97
|
+
name: fastlane-sirp
|
98
|
+
requirement: !ruby/object:Gem::Requirement
|
99
|
+
requirements:
|
100
|
+
- - "~>"
|
101
|
+
- !ruby/object:Gem::Version
|
102
|
+
version: '1.0'
|
103
|
+
type: :runtime
|
104
|
+
prerelease: false
|
105
|
+
version_requirements: !ruby/object:Gem::Requirement
|
106
|
+
requirements:
|
107
|
+
- - "~>"
|
108
|
+
- !ruby/object:Gem::Version
|
109
|
+
version: '1.0'
|
97
110
|
description: AdSedare is a powerful library designed to simplify the process of iOS
|
98
111
|
ad-hoc distribution. By automating and streamlining the distribution of builds,
|
99
112
|
it ensures a smooth, pain-free experience for developers and testers. AdSedare is
|
@@ -130,7 +143,6 @@ licenses:
|
|
130
143
|
metadata:
|
131
144
|
homepage_uri: https://github.com/AlexStrNik/AdSedare
|
132
145
|
source_code_uri: https://github.com/AlexStrNik/AdSedare
|
133
|
-
post_install_message:
|
134
146
|
rdoc_options: []
|
135
147
|
require_paths:
|
136
148
|
- lib
|
@@ -145,8 +157,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
145
157
|
- !ruby/object:Gem::Version
|
146
158
|
version: '0'
|
147
159
|
requirements: []
|
148
|
-
rubygems_version: 3.
|
149
|
-
signing_key:
|
160
|
+
rubygems_version: 3.6.3
|
150
161
|
specification_version: 4
|
151
162
|
summary: A cross-platform library for seamless, pain-free iOS ad-hoc distribution.
|
152
163
|
test_files: []
|