Method: Spaceship::Client#handle_two_factor

Defined in:
spaceship/lib/spaceship/two_step_or_factor_client.rb

#handle_two_factor(response, depth = 0) ⇒ Object



112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
# File 'spaceship/lib/spaceship/two_step_or_factor_client.rb', line 112

def handle_two_factor(response, depth = 0)
  if depth == 0
    puts("Two-factor Authentication (6 digits code) is enabled for account '#{self.user}'")
    puts("More information about Two-factor Authentication: https://support.apple.com/en-us/HT204915")
    puts("")

    two_factor_url = "https://github.com/fastlane/fastlane/tree/master/spaceship#2-step-verification"
    puts("If you're running this in a non-interactive session (e.g. server or CI)")
    puts("check out #{two_factor_url}")
  end

  # "verification code" has already be pushed to devices

  security_code = response.body["securityCode"]
  # "securityCode": {
  # 	"length": 6,
  # 	"tooManyCodesSent": false,
  # 	"tooManyCodesValidated": false,
  # 	"securityCodeLocked": false
  # },
  code_length = security_code["length"]

  puts("")
  env_2fa_sms_default_phone_number = ENV["SPACESHIP_2FA_SMS_DEFAULT_PHONE_NUMBER"]

  if env_2fa_sms_default_phone_number
    raise Tunes::Error.new, "Environment variable SPACESHIP_2FA_SMS_DEFAULT_PHONE_NUMBER is set, but empty." if env_2fa_sms_default_phone_number.empty?

    puts("Environment variable `SPACESHIP_2FA_SMS_DEFAULT_PHONE_NUMBER` is set, automatically requesting 2FA token via SMS to that number")
    puts("SPACESHIP_2FA_SMS_DEFAULT_PHONE_NUMBER = #{env_2fa_sms_default_phone_number}")
    puts("")

    phone_number = env_2fa_sms_default_phone_number
    phone_id = phone_id_from_number(response.body["trustedPhoneNumbers"], phone_number)
    push_mode = push_mode_from_number(response.body["trustedPhoneNumbers"], phone_number)
    # don't request sms if no trusted devices and env default is the only trusted number,
    # code was automatically sent
    should_request_code = !sms_automatically_sent(response)
    code_type = 'phone'
    body = request_two_factor_code_from_phone(phone_id, phone_number, code_length, push_mode, should_request_code)
  elsif sms_automatically_sent(response) # sms fallback, code was automatically sent
    fallback_number = response.body["trustedPhoneNumbers"].first
    phone_number = fallback_number["numberWithDialCode"]
    phone_id = fallback_number["id"]
    push_mode = fallback_number['pushMode']

    code_type = 'phone'
    body = request_two_factor_code_from_phone(phone_id, phone_number, code_length, push_mode, false)
  elsif sms_fallback(response) # sms fallback but code wasn't sent bec > 1 phone number
    code_type = 'phone'
    body = request_two_factor_code_from_phone_choose(response.body["trustedPhoneNumbers"], code_length)
  else
    puts("(Input `sms` to escape this prompt and select a trusted phone number to send the code as a text message)")
    puts("")
    puts("(You can also set the environment variable `SPACESHIP_2FA_SMS_DEFAULT_PHONE_NUMBER` to automate this)")
    puts("(Read more at: https://github.com/fastlane/fastlane/blob/master/spaceship/docs/Authentication.md#auto-select-sms-via-spaceship_2fa_sms_default_phone_number)")
    puts("")

    code = ask_for_2fa_code("Please enter the #{code_length} digit code:")
    code_type = 'trusteddevice'
    body = { "securityCode" => { "code" => code.to_s } }.to_json

    # User exited by entering `sms` and wants to choose phone number for SMS
    if code.casecmp?("sms")
      code_type = 'phone'
      body = request_two_factor_code_from_phone_choose(response.body["trustedPhoneNumbers"], code_length)
    end
  end

  puts("Requesting session...")

  # Send "verification code" back to server to get a valid session
  r = request(:post) do |req|
    req.url("https://idmsa.apple.com/appleauth/auth/verify/#{code_type}/securitycode")
    req.headers['Content-Type'] = 'application/json'
    req.body = body
    update_request_headers(req)
  end

  begin
    # we use `Spaceship::TunesClient.new.handle_itc_response`
    # since this might be from the Dev Portal, but for 2 factor
    Spaceship::TunesClient.new.handle_itc_response(r.body) # this will fail if the code is invalid
  rescue => ex
    # If the code was entered wrong
    # {
    #   "service_errors": [{
    #     "code": "-21669",
    #     "title": "Incorrect Verification Code",
    #     "message": "Incorrect verification code."
    #   }],
    #   "hasError": true
    # }

    if ex.to_s.include?("verification code") # to have a nicer output
      puts("Error: Incorrect verification code")
      depth += 1
      return handle_two_factor(response, depth)
    end

    raise ex
  end

  store_session

  return true
end