class Gem::WebauthnListener
The WebauthnListener
class retrieves an OTP after a user successfully WebAuthns with the Gem
host. An instance opens a socket using the TCPServer
instance given and listens for a request from the Gem
host. The request should be a GET request to the root path and contains the OTP code in the form of a query parameter ‘code`. The listener will return the code which will be used as the OTP for API requests.
Types of responses sent by the listener after receiving a request:
- 200 OK: OTP code was successfully retrieved - 204 No Content: If the request was an OPTIONS request - 400 Bad Request: If the request did not contain a query parameter `code` - 404 Not Found: The request was not to the root path - 405 Method Not Allowed: OTP code was not retrieved because the request was not a GET/OPTIONS request
Example usage:
server = TCPServer.new(0) otp = Gem::WebauthnListener.wait_for_otp_code("https://rubygems.example", server)
The WebauthnListener
Response
class is used by the WebauthnListener
to create responses to be sent to the Gem
host. It creates a Net::HTTPResponse
instance when initialized and can be converted to the appropriate format to be sent by a socket using ‘to_s`. Net::HTTPResponse
instances cannot be directly sent over a socket.
Types of response classes:
- OkResponse - NoContentResponse - BadRequestResponse - NotFoundResponse - MethodNotAllowedResponse
Example usage:
server = TCPServer.new(0) socket = server.accept response = OkResponse.for("https://rubygems.example") socket.print response.to_s socket.close
Attributes
Public Class Methods
# File lib/rubygems/webauthn_listener.rb, line 28 def initialize(host) @host = host end
# File lib/rubygems/webauthn_listener.rb, line 32 def self.wait_for_otp_code(host, server) new(host).fetch_otp_from_connection(server) end
Public Instance Methods
# File lib/rubygems/webauthn_listener.rb, line 36 def fetch_otp_from_connection(server) loop do socket = server.accept request_line = socket.gets method, req_uri, _protocol = request_line.split(" ") req_uri = URI.parse(req_uri) responder = SocketResponder.new(socket) unless root_path?(req_uri) responder.send(NotFoundResponse.for(host)) raise Gem::WebauthnVerificationError, "Page at #{req_uri.path} not found." end case method.upcase when "OPTIONS" responder.send(NoContentResponse.for(host)) next # will be GET when "GET" if otp = parse_otp_from_uri(req_uri) responder.send(OkResponse.for(host)) return otp end responder.send(BadRequestResponse.for(host)) raise Gem::WebauthnVerificationError, "Did not receive OTP from #{host}." else responder.send(MethodNotAllowedResponse.for(host)) raise Gem::WebauthnVerificationError, "Invalid HTTP method #{method.upcase} received." end end end
Private Instance Methods
# File lib/rubygems/webauthn_listener.rb, line 75 def parse_otp_from_uri(uri) require "cgi" return if uri.query.nil? CGI.parse(uri.query).dig("code", 0) end
# File lib/rubygems/webauthn_listener.rb, line 71 def root_path?(uri) uri.path == "/" end