From 6376fd55dba2155a1f1a1aa40258eff77147c864 Mon Sep 17 00:00:00 2001
From: Fijxu <fijxu@nadeko.net>
Date: Sat, 17 May 2025 13:17:26 -0400
Subject: [PATCH] Remove text captcha due to textcaptcha.com being down

Fixes https://github.com/iv-org/invidious/issues/5295

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).
---
 src/invidious/routes/login.cr      | 52 +++---------------------------
 src/invidious/user/captcha.cr      | 16 ---------
 src/invidious/views/user/login.ecr | 39 ++++------------------
 3 files changed, 11 insertions(+), 96 deletions(-)

diff --git a/src/invidious/routes/login.cr b/src/invidious/routes/login.cr
index d0f7ac22..e7de5018 100644
--- a/src/invidious/routes/login.cr
+++ b/src/invidious/routes/login.cr
@@ -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
 
diff --git a/src/invidious/user/captcha.cr b/src/invidious/user/captcha.cr
index 8a0f67e5..b175c3b9 100644
--- a/src/invidious/user/captcha.cr
+++ b/src/invidious/user/captcha.cr
@@ -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
diff --git a/src/invidious/views/user/login.ecr b/src/invidious/views/user/login.ecr
index 2b03d280..7ac96bc6 100644
--- a/src/invidious/views/user/login.ecr
+++ b/src/invidious/views/user/login.ecr
@@ -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") %>