Add text CAPTCHA

This commit is contained in:
Omar Roth 2018-11-22 13:26:08 -06:00
parent ca4e8b800c
commit 26eb59e00d
2 changed files with 85 additions and 22 deletions

View file

@ -16,6 +16,7 @@
require "crypto/bcrypt/password" require "crypto/bcrypt/password"
require "detect_language" require "detect_language"
require "digest/md5"
require "kemal" require "kemal"
require "openssl/hmac" require "openssl/hmac"
require "option_parser" require "option_parser"
@ -82,10 +83,11 @@ PG_URL = URI.new(
path: CONFIG.db[:dbname], path: CONFIG.db[:dbname],
) )
PG_DB = DB.open PG_URL PG_DB = DB.open PG_URL
YT_URL = URI.parse("https://www.youtube.com") YT_URL = URI.parse("https://www.youtube.com")
REDDIT_URL = URI.parse("https://www.reddit.com") REDDIT_URL = URI.parse("https://www.reddit.com")
LOGIN_URL = URI.parse("https://accounts.google.com") LOGIN_URL = URI.parse("https://accounts.google.com")
TEXTCAPTCHA_URL = URI.parse("http://textcaptcha.com/omarroth@hotmail.com.json")
crawl_threads.times do crawl_threads.times do
spawn do spawn do
@ -632,8 +634,25 @@ get "/login" do |env|
account_type = env.params.query["type"]? account_type = env.params.query["type"]?
account_type ||= "invidious" account_type ||= "invidious"
captcha_type = env.params.query["captcha"]?
captcha_type ||= "image"
if account_type == "invidious" if account_type == "invidious"
captcha = generate_captcha(HMAC_KEY, PG_DB) if captcha_type == "image"
captcha = generate_captcha(HMAC_KEY, PG_DB)
else
response = HTTP::Client.get(TEXTCAPTCHA_URL).body
response = JSON.parse(response)
tokens = response["a"].as_a.map do |answer|
create_response(answer.as_s, "sign_in", HMAC_KEY, PG_DB)
end
text_captcha = {
question: response["q"].as_s,
tokens: tokens,
}
end
end end
tfa = env.params.query["tfa"]? tfa = env.params.query["tfa"]?
@ -827,27 +846,55 @@ post "/login" do |env|
end end
elsif account_type == "invidious" elsif account_type == "invidious"
answer = env.params.body["answer"]? answer = env.params.body["answer"]?
text_answer = env.params.body["text_answer"]?
if !answer if answer
error_message = "CAPTCHA is a required field" answer = answer.lstrip('0')
next templated "error" answer = OpenSSL::HMAC.hexdigest(:sha256, HMAC_KEY, answer)
end
answer = answer.lstrip('0') challenge = env.params.body["challenge"]?
answer = OpenSSL::HMAC.hexdigest(:sha256, HMAC_KEY, answer) token = env.params.body["token"]?
challenge = env.params.body["challenge"]? begin
token = env.params.body["token"]? validate_response(challenge, token, answer, "sign_in", HMAC_KEY, PG_DB)
rescue ex
if ex.message == "Invalid user"
error_message = "Invalid answer"
else
error_message = ex.message
end
begin next templated "error"
validate_response(challenge, token, answer, "sign_in", HMAC_KEY, PG_DB) end
rescue ex elsif text_answer
if ex.message && ex.message == "Invalid user" text_answer = Digest::MD5.hexdigest(text_answer.downcase.strip)
error_message = "Invalid CAPTCHA response"
else challenges = env.params.body.select { |k, v| k.match(/text_challenge\d+/) }
error_message = ex.message tokens = env.params.body.select { |k, v| k.match(/text_token\d+/) }
found_valid_captcha = false
error_message = "Invalid CAPTCHA"
challenges.each_with_index do |challenge, i|
begin
challenge = challenge[1]
token = tokens[i][1]
validate_response(challenge, token, text_answer, "sign_in", HMAC_KEY, PG_DB)
found_valid_captcha = true
rescue ex
if ex.message == "Invalid user"
error_message = "Invalid answer"
else
error_message = ex.message
end
end
end end
if !found_valid_captcha
next templated "error"
end
else
error_message = "CAPTCHA is a required field"
next templated "error" next templated "error"
end end

View file

@ -24,11 +24,27 @@
<label for="password">Password:</label> <label for="password">Password:</label>
<input required class="pure-input-1" name="password" type="password" placeholder="Password"> <input required class="pure-input-1" name="password" type="password" placeholder="Password">
<% if captcha_type == "image" %>
<img style="width:100%" src='<%= captcha.not_nil![:image] %>'/> <img style="width:100%" src='<%= captcha.not_nil![:image] %>'/>
<input type="hidden" name="token" value="<%= captcha.not_nil![:token] %>"> <input type="hidden" name="token" value="<%= captcha.not_nil![:token] %>">
<input type="hidden" name="challenge" value="<%= captcha.not_nil![:challenge] %>"> <input type="hidden" name="challenge" value="<%= captcha.not_nil![:challenge] %>">
<label for="answer">Time (h:mm):</label> <input required type="text" name="answer" type="text" placeholder="h:mm">
<input required type="text" name="answer" type="text>" placeholder="hh:mm">
<label>
<a href="/login?referer=<%= URI.escape(referer) %>&type=invidious&captcha=text">Text CAPTCHA</a>
</label>
<% else %>
<% text_captcha.not_nil![:tokens].each_with_index do |token, i| %>
<input type="hidden" name="text_challenge<%= i %>" value="<%= token[0] %>">
<input type="hidden" name="text_token<%= i %>" value="<%= token[1] %>">
<% end %>
<label for="text_answer"><%= text_captcha.not_nil![:question] %></label>
<input required type="text" name="text_answer" type="text" placeholder="Answer">
<label>
<a href="/login?referer=<%= URI.escape(referer) %>&type=invidious">Image CAPTCHA</a>
</label>
<% end %>
<button type="submit" name="action" value="signin" class="pure-button pure-button-primary">Sign In</button> <button type="submit" name="action" value="signin" class="pure-button pure-button-primary">Sign In</button>
<button type="submit" name="action" value="register" class="pure-button pure-button-primary">Register</button> <button type="submit" name="action" value="register" class="pure-button pure-button-primary">Register</button>