Skip to content

Commit 521ce55

Browse files
committed
Create listener to receive WebAuthn OTP code from Rubygems.org
1 parent 54bba16 commit 521ce55

File tree

1 file changed

+87
-3
lines changed

1 file changed

+87
-3
lines changed

lib/rubygems/gemcutter_utilities.rb

Lines changed: 87 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -245,13 +245,97 @@ def request_with_otp(method, uri, &block)
245245

246246
def ask_otp(email, password)
247247
webauthn_url = webauthn_verification_url(email, password)
248-
unless webauthn_url
248+
options[:otp] = unless webauthn_url
249249
say 'You have enabled multi-factor authentication. Please enter OTP code.'
250+
ask 'Code: '
250251
else
251-
say "You have enabled multi-factor authentication. Please enter OTP code from your security device by visiting #{webauthn_url} or your authenticator app."
252+
wait_for_response(webauthn_url)
252253
end
254+
end
255+
256+
def wait_for_response(webauthn_url)
257+
require "cgi"
258+
# server = TCPServer.new 0
259+
# server_addr = server.addr[1].to_s
260+
261+
# for the prototype we hardcode this port
262+
server = TCPServer.new 5678
263+
server_addr = 5678
264+
265+
webserv = Thread.new do
266+
begin
267+
loop do
268+
# loop until a non preflight response is sent and code is received
269+
break if code_received?(server)
270+
end
271+
ensure
272+
server.close
273+
end
274+
end
275+
276+
webserv.abort_on_exception = true
277+
278+
redirect_uri = "http://localhost:5678"
279+
uri = URI.parse(webauthn_url)
280+
uri.query = URI.encode_www_form(URI.decode_www_form(uri.query || '') << ["redirect_uri", redirect_uri])
281+
say "You have enabled multi-factor authentication. Please visit #{uri} to authenticate via security device."
282+
283+
webserv.join
284+
webserv[:code]
285+
end
286+
287+
def code_received?(server)
288+
response = "Webauthn success"
289+
response_code = "200 OK"
290+
291+
connection = server.accept
292+
while (input = connection.gets)
293+
begin
294+
http_req = input.split(' ')
295+
if http_req.length != 3
296+
raise "invalid HTTP request received on callback"
297+
end
298+
299+
if http_req[0] == "OPTIONS" # if request is a preflight request
300+
send_response(connection, 204)
301+
return false
302+
end
303+
304+
params = URI.parse(http_req[1]).query
305+
if params.nil? # also should raise if there is a missing code for params
306+
raise "no params"
307+
end
308+
Thread.current[:code] = CGI.parse(params)["code"][0]
309+
rescue StandardError => e
310+
response = "Error processing request: #{e.message}"
311+
response_code = "400 Bad Request"
312+
end
313+
314+
send_response(connection, response_code, response)
315+
316+
if response_code != "200 OK"
317+
raise response
318+
end
319+
break
320+
end
321+
true
322+
end
323+
324+
def send_response(connection, code, body = nil)
325+
connection.puts "HTTP/1.1 #{code}"
326+
connection.puts "Content-Type: text/plain"
327+
connection.puts "Content-Length: #{body.bytesize}" if body
328+
connection.print access_control_headers
329+
connection.puts "Connection: close\r\n"
330+
connection.puts
331+
connection.print body if body
332+
connection.close
333+
end
253334

254-
options[:otp] = ask 'Code: '
335+
def access_control_headers
336+
"Access-Control-Allow-Origin: http://localhost:3000\r\n"+ # localhost needs to be changed
337+
"Access-Control-Allow-Methods: POST\r\n"+
338+
"Access-Control-Allow-Headers: Content-Type, Authorization, x-csrf-token\r\n"
255339
end
256340

257341
def webauthn_verification_url(email, password)

0 commit comments

Comments
 (0)