Remove text captchas from Invidious (#5308)

textcaptcha.com seems to be down since April and it does not appear that
service will be restored.

Text captchas can be easily automated using free LLMs, so keeping the
text captcha is more like a gate to create accounts in mass on public
Invidious instances.

It also gives headaches like bots automating account creation to modify
the videos that appear popular page of each instance (since the popular
page is based on the subscriptions of the registered users).
This commit is contained in:
syeopite 2025-05-17 16:37:55 -07:00
commit 2c857b5ab6
No known key found for this signature in database
GPG Key ID: A73C186DA3955A1A
4 changed files with 11 additions and 98 deletions

View File

@ -64,8 +64,6 @@
"User ID": "User ID",
"Password": "Password",
"Time (h:mm:ss):": "Time (h:mm:ss):",
"Text CAPTCHA": "Text CAPTCHA",
"Image CAPTCHA": "Image CAPTCHA",
"Sign In": "Sign In",
"Register": "Register",
"E-mail": "E-mail",

View File

@ -21,9 +21,6 @@ module Invidious::Routes::Login
account_type = env.params.query["type"]?
account_type ||= "invidious"
captcha_type = env.params.query["captcha"]?
captcha_type ||= "image"
templated "user/login"
end
@ -88,34 +85,14 @@ module Invidious::Routes::Login
password = password.byte_slice(0, 55)
if CONFIG.captcha_enabled
captcha_type = env.params.body["captcha_type"]?
answer = env.params.body["answer"]?
change_type = env.params.body["change_type"]?
if !captcha_type || change_type
if change_type
captcha_type = change_type
end
captcha_type ||= "image"
account_type = "invidious"
if captcha_type == "image"
captcha = Invidious::User::Captcha.generate_image(HMAC_KEY)
else
captcha = Invidious::User::Captcha.generate_text(HMAC_KEY)
end
return templated "user/login"
end
account_type = "invidious"
captcha = Invidious::User::Captcha.generate_image(HMAC_KEY)
tokens = env.params.body.select { |k, _| k.match(/^token\[\d+\]$/) }.map { |_, v| v }
answer ||= ""
captcha_type ||= "image"
case captcha_type
when "image"
if answer
answer = answer.lstrip('0')
answer = OpenSSL::HMAC.hexdigest(:sha256, HMAC_KEY, answer)
@ -124,27 +101,8 @@ module Invidious::Routes::Login
rescue ex
return error_template(400, ex)
end
else # "text"
answer = Digest::MD5.hexdigest(answer.downcase.strip)
if tokens.empty?
return error_template(500, "Erroneous CAPTCHA")
end
found_valid_captcha = false
error_exception = Exception.new
tokens.each do |tok|
begin
validate_request(tok, answer, env.request, HMAC_KEY, locale)
found_valid_captcha = true
rescue ex
error_exception = ex
end
end
if !found_valid_captcha
return error_template(500, error_exception)
end
else
return templated "user/login"
end
end

View File

@ -4,8 +4,6 @@ struct Invidious::User
module Captcha
extend self
private TEXTCAPTCHA_URL = URI.parse("https://textcaptcha.com")
def generate_image(key)
second = Random::Secure.rand(12)
second_angle = second * 30
@ -60,19 +58,5 @@ struct Invidious::User
tokens: {generate_response(answer, {":login"}, key, use_nonce: true)},
}
end
def generate_text(key)
response = make_client(TEXTCAPTCHA_URL, &.get("/github.com/iv.org/invidious.json").body)
response = JSON.parse(response)
tokens = response["a"].as_a.map do |answer|
generate_response(answer.as_s, {":login"}, key, use_nonce: true)
end
return {
question: response["q"].as_s,
tokens: tokens,
}
end
end
end

View File

@ -25,44 +25,17 @@
<% end %>
<% if captcha %>
<% case captcha_type when %>
<% when "image" %>
<% captcha = captcha.not_nil! %>
<img style="width:50%" src='<%= captcha[:question] %>'/>
<% captcha[:tokens].each_with_index do |token, i| %>
<input type="hidden" name="token[<%= i %>]" value="<%= HTML.escape(token) %>">
<% end %>
<input type="hidden" name="captcha_type" value="image">
<label for="answer"><%= translate(locale, "Time (h:mm:ss):") %></label>
<input type="text" name="answer" type="text" placeholder="h:mm:ss">
<% else # "text" %>
<% captcha = captcha.not_nil! %>
<% captcha[:tokens].each_with_index do |token, i| %>
<input type="hidden" name="token[<%= i %>]" value="<%= HTML.escape(token) %>">
<% end %>
<input type="hidden" name="captcha_type" value="text">
<label for="answer"><%= captcha[:question] %></label>
<input type="text" name="answer" type="text" placeholder="<%= translate(locale, "Answer") %>">
<% captcha = captcha.not_nil! %>
<img style="width:50%" src='<%= captcha[:question] %>'/>
<% captcha[:tokens].each_with_index do |token, i| %>
<input type="hidden" name="token[<%= i %>]" value="<%= HTML.escape(token) %>">
<% end %>
<label for="answer"><%= translate(locale, "Time (h:mm:ss):") %></label>
<input type="text" name="answer" type="text" placeholder="h:mm:ss">
<button type="submit" name="action" value="signin" class="pure-button pure-button-primary">
<%= translate(locale, "Register") %>
</button>
<% case captcha_type when %>
<% when "image" %>
<label>
<button type="submit" name="change_type" class="pure-button pure-button-primary" value="text">
<%= translate(locale, "Text CAPTCHA") %>
</button>
</label>
<% else # "text" %>
<label>
<button type="submit" name="change_type" class="pure-button pure-button-primary" value="image">
<%= translate(locale, "Image CAPTCHA") %>
</button>
</label>
<% end %>
<% else %>
<button type="submit" name="action" value="signin" class="pure-button pure-button-primary">
<%= translate(locale, "Sign In") %>/<%= translate(locale, "Register") %>