<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/css/bootstrap.min.css" rel="stylesheet"
        integrity="sha384-GLhlTQ8iRABdZLl6O3oVMWSktQOp6b7In1Zl3/Jr59b6EGGoI1aFkw7cmDA6j6gD" crossorigin="anonymous">
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.3.0/css/all.min.css"
        integrity="sha512-SzlrxWUlpfuzQ+pcUCosxcglQRNAq/DZjVsC0lE40xsADsfeQoEypE+enwcOiGjk/bSuGGKHEyjSoQ1zVisanQ=="
        crossorigin="anonymous" referrerpolicy="no-referrer" />
</head>
</html>
# frozen_string_literal: true

##
# The WebauthnPoller class retrieves an OTP after a user successfully WebAuthns. An instance
# polls the Gem host for the OTP code. The polling request (api/v1/webauthn_verification/<webauthn_token>/status.json)
# is sent to the Gem host every 5 seconds and will timeout after 5 minutes. If the status field in the json response
# is "success", the code field will contain the OTP code.
#
# Example usage:
#
#   thread = Gem::WebauthnPoller.poll_thread(
#     {},
#     "RubyGems.org",
#     "https://rubygems.org/api/v1/webauthn_verification/odow34b93t6aPCdY",
#     { email: "email@example.com", password: "password" }
#   )
#   thread.join
#   otp = thread[:otp]
#   error = thread[:error]
#

module Gem::GemcutterUtilities
  class WebauthnPoller
    include Gem::GemcutterUtilities
    TIMEOUT_IN_SECONDS = 300

    attr_reader :options, :host

    def initialize(options, host)
      @options = options
      @host = host
    end

    def self.poll_thread(options, host, webauthn_url, credentials)
      Thread.new do
        thread = Thread.current
        thread.abort_on_exception = true
        thread.report_on_exception = false
        thread[:otp] = new(options, host).poll_for_otp(webauthn_url, credentials)
      rescue Gem::WebauthnVerificationError, Gem::Timeout::Error => e
        thread[:error] = e
      end
    end

    def poll_for_otp(webauthn_url, credentials)
      Gem::Timeout.timeout(TIMEOUT_IN_SECONDS) do
        loop do
          response = webauthn_verification_poll_response(webauthn_url, credentials)
          raise Gem::WebauthnVerificationError, response.message unless response.is_a?(Gem::Net::HTTPSuccess)

          require "json"
          parsed_response = JSON.parse(response.body)
          case parsed_response["status"]
          when "pending"
            sleep 5
          when "success"
            return parsed_response["code"]
          else
            raise Gem::WebauthnVerificationError, parsed_response.fetch("message", "Invalid response from server")
          end
        end
      end
    end

    private

    def webauthn_verification_poll_response(webauthn_url, credentials)
      webauthn_token = %r{(?<=\/)[^\/]+(?=$)}.match(webauthn_url)[0]
      rubygems_api_request(:get, "api/v1/webauthn_verification/#{webauthn_token}/status.json") do |request|
        if credentials.empty?
          request.add_field "Authorization", api_key
        elsif credentials[:identifier] && credentials[:password]
          request.basic_auth credentials[:identifier], credentials[:password]
        else
          raise Gem::WebauthnVerificationError, "Provided missing credentials"
        end
      end
    end
  end
end
