Class: AtProto::DpopHandler

Inherits:
Object
  • Object
show all
Defined in:
lib/atproto_client/dpop_handler.rb

Overview

Handler for DPoP (Demonstrating Proof-of-Possession) protocol implementation

Instance Method Summary collapse

Constructor Details

#initialize(private_key = nil, access_token = nil) ⇒ DpopHandler

Initialize a new DPoP handler

Parameters:

  • private_key (OpenSSL::PKey::EC, nil) (defaults to: nil)

    Optional private key for signing tokens

  • access_token (String) (defaults to: nil)

    Optional access_token



7
8
9
10
11
12
13
# File 'lib/atproto_client/dpop_handler.rb', line 7

def initialize(private_key = nil, access_token = nil)
  @private_key = private_key || generate_private_key
  @current_nonce = nil
  @nonce_mutex = Mutex.new
  @token_mutex = Mutex.new
  @access_token = access_token
end

Instance Method Details

#generate_token(http_method, url, nonce = @current_nonce) ⇒ String

Generates a DPoP token for a request

Parameters:

  • http_method (String)

    The HTTP method of the request

  • url (String)

    The target URL of the request

  • nonce (String, nil) (defaults to: @current_nonce)

    Optional nonce value

Returns:

  • (String)

    The generated DPoP token



20
21
22
23
24
# File 'lib/atproto_client/dpop_handler.rb', line 20

def generate_token(http_method, url, nonce = @current_nonce)
  @token_mutex.synchronize do
    create_dpop_token(http_method, url, nonce)
  end
end

#make_request(uri, method, headers: {}, body: nil) ⇒ Net::HTTPResponse

Makes an HTTP request with DPoP handling, when no nonce is used for the first try, takes it from the response and retry

Parameters:

  • uri (String)

    The target URI

  • method (String)

    The HTTP method

  • headers (Hash) (defaults to: {})

    Optional request headers

  • body (Hash, nil) (defaults to: nil)

    Optional request body

Returns:

  • (Net::HTTPResponse)

    The HTTP response

Raises:



43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
# File 'lib/atproto_client/dpop_handler.rb', line 43

def make_request(uri, method, headers: {}, body: nil)
  retried = false
  begin
    dpop_token = generate_token(method.to_s.upcase, uri.to_s)
    request = Request.new(method, uri, headers.merge('DPoP' => dpop_token))
    request.body = body.to_json if body
    request.run
  rescue Net::HTTPClientException => e
    unless retried
      update_nonce(e.response)
      retried = true
      retry
    end
    raise APIError, "Request failed: #{e.response.code} - #{e.response.body}"
  end
end

#update_nonce(response) ⇒ Object

Updates the current nonce from response headers

Parameters:

  • response (Net::HTTPResponse)

    Response containing dpop-nonce header



28
29
30
31
32
33
# File 'lib/atproto_client/dpop_handler.rb', line 28

def update_nonce(response)
  new_nonce = response.to_hash.dig('dpop-nonce', 0)
  @nonce_mutex.synchronize do
    @current_nonce = new_nonce if new_nonce
  end
end