Skip to content

Commit ad4a06f

Browse files
committed
Move user captcha code to its own module
1 parent c04f45d commit ad4a06f

File tree

4 files changed

+87
-79
lines changed

4 files changed

+87
-79
lines changed

src/invidious.cr

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -38,14 +38,13 @@ require "./invidious/jobs/**"
3838
CONFIG = Config.load
3939
HMAC_KEY = CONFIG.hmac_key || Random::Secure.hex(32)
4040

41-
PG_DB = DB.open CONFIG.database_url
42-
ARCHIVE_URL = URI.parse("https://archive.org")
43-
LOGIN_URL = URI.parse("https://accounts.google.com")
44-
PUBSUB_URL = URI.parse("https://pubsubhubbub.appspot.com")
45-
REDDIT_URL = URI.parse("https://www.reddit.com")
46-
TEXTCAPTCHA_URL = URI.parse("https://textcaptcha.com")
47-
YT_URL = URI.parse("https://www.youtube.com")
48-
HOST_URL = make_host_url(Kemal.config)
41+
PG_DB = DB.open CONFIG.database_url
42+
ARCHIVE_URL = URI.parse("https://archive.org")
43+
LOGIN_URL = URI.parse("https://accounts.google.com")
44+
PUBSUB_URL = URI.parse("https://pubsubhubbub.appspot.com")
45+
REDDIT_URL = URI.parse("https://www.reddit.com")
46+
YT_URL = URI.parse("https://www.youtube.com")
47+
HOST_URL = make_host_url(Kemal.config)
4948

5049
CHARS_SAFE = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"
5150
TEST_IDS = {"AgbeGFYluEA", "BaW_jenozKc", "a9LDPn-MO4I", "ddFvjfvPnqk", "iqKdEhx-dD4"}

src/invidious/routes/login.cr

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -393,9 +393,9 @@ module Invidious::Routes::Login
393393
prompt = ""
394394

395395
if captcha_type == "image"
396-
captcha = generate_captcha(HMAC_KEY)
396+
captcha = Invidious::User::Captcha.generate_image(HMAC_KEY)
397397
else
398-
captcha = generate_text_captcha(HMAC_KEY)
398+
captcha = Invidious::User::Captcha.generate_text(HMAC_KEY)
399399
end
400400

401401
return templated "login"

src/invidious/user/captcha.cr

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
require "openssl/hmac"
2+
3+
struct Invidious::User
4+
module Captcha
5+
extend self
6+
7+
private TEXTCAPTCHA_URL = URI.parse("https://textcaptcha.com")
8+
9+
def generate_image(key)
10+
second = Random::Secure.rand(12)
11+
second_angle = second * 30
12+
second = second * 5
13+
14+
minute = Random::Secure.rand(12)
15+
minute_angle = minute * 30
16+
minute = minute * 5
17+
18+
hour = Random::Secure.rand(12)
19+
hour_angle = hour * 30 + minute_angle.to_f / 12
20+
if hour == 0
21+
hour = 12
22+
end
23+
24+
clock_svg = <<-END_SVG
25+
<svg viewBox="0 0 100 100" width="200px" height="200px">
26+
<circle cx="50" cy="50" r="45" fill="#eee" stroke="black" stroke-width="2"></circle>
27+
28+
<text x="69" y="20.091" text-anchor="middle" fill="black" font-family="Arial" font-size="10px"> 1</text>
29+
<text x="82.909" y="34" text-anchor="middle" fill="black" font-family="Arial" font-size="10px"> 2</text>
30+
<text x="88" y="53" text-anchor="middle" fill="black" font-family="Arial" font-size="10px"> 3</text>
31+
<text x="82.909" y="72" text-anchor="middle" fill="black" font-family="Arial" font-size="10px"> 4</text>
32+
<text x="69" y="85.909" text-anchor="middle" fill="black" font-family="Arial" font-size="10px"> 5</text>
33+
<text x="50" y="91" text-anchor="middle" fill="black" font-family="Arial" font-size="10px"> 6</text>
34+
<text x="31" y="85.909" text-anchor="middle" fill="black" font-family="Arial" font-size="10px"> 7</text>
35+
<text x="17.091" y="72" text-anchor="middle" fill="black" font-family="Arial" font-size="10px"> 8</text>
36+
<text x="12" y="53" text-anchor="middle" fill="black" font-family="Arial" font-size="10px"> 9</text>
37+
<text x="17.091" y="34" text-anchor="middle" fill="black" font-family="Arial" font-size="10px">10</text>
38+
<text x="31" y="20.091" text-anchor="middle" fill="black" font-family="Arial" font-size="10px">11</text>
39+
<text x="50" y="15" text-anchor="middle" fill="black" font-family="Arial" font-size="10px">12</text>
40+
41+
<circle cx="50" cy="50" r="3" fill="black"></circle>
42+
<line id="second" transform="rotate(#{second_angle}, 50, 50)" x1="50" y1="50" x2="50" y2="12" fill="black" stroke="black" stroke-width="1"></line>
43+
<line id="minute" transform="rotate(#{minute_angle}, 50, 50)" x1="50" y1="50" x2="50" y2="16" fill="black" stroke="black" stroke-width="2"></line>
44+
<line id="hour" transform="rotate(#{hour_angle}, 50, 50)" x1="50" y1="50" x2="50" y2="24" fill="black" stroke="black" stroke-width="2"></line>
45+
</svg>
46+
END_SVG
47+
48+
image = "data:image/png;base64,"
49+
image += Process.run(%(rsvg-convert -w 400 -h 400 -b none -f png), shell: true,
50+
input: IO::Memory.new(clock_svg), output: Process::Redirect::Pipe
51+
) do |proc|
52+
Base64.strict_encode(proc.output.gets_to_end)
53+
end
54+
55+
answer = "#{hour}:#{minute.to_s.rjust(2, '0')}:#{second.to_s.rjust(2, '0')}"
56+
answer = OpenSSL::HMAC.hexdigest(:sha256, key, answer)
57+
58+
return {
59+
question: image,
60+
tokens: {generate_response(answer, {":login"}, key, use_nonce: true)},
61+
}
62+
end
63+
64+
def generate_text(key)
65+
response = make_client(TEXTCAPTCHA_URL, &.get("/github.com/iv.org/invidious.json").body)
66+
response = JSON.parse(response)
67+
68+
tokens = response["a"].as_a.map do |answer|
69+
generate_response(answer.as_s, {":login"}, key, use_nonce: true)
70+
end
71+
72+
return {
73+
question: response["q"].as_s,
74+
tokens: tokens,
75+
}
76+
end
77+
end
78+
end

src/invidious/users.cr

Lines changed: 0 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -91,75 +91,6 @@ def create_user(sid, email, password)
9191
return user, sid
9292
end
9393

94-
def generate_captcha(key)
95-
second = Random::Secure.rand(12)
96-
second_angle = second * 30
97-
second = second * 5
98-
99-
minute = Random::Secure.rand(12)
100-
minute_angle = minute * 30
101-
minute = minute * 5
102-
103-
hour = Random::Secure.rand(12)
104-
hour_angle = hour * 30 + minute_angle.to_f / 12
105-
if hour == 0
106-
hour = 12
107-
end
108-
109-
clock_svg = <<-END_SVG
110-
<svg viewBox="0 0 100 100" width="200px" height="200px">
111-
<circle cx="50" cy="50" r="45" fill="#eee" stroke="black" stroke-width="2"></circle>
112-
113-
<text x="69" y="20.091" text-anchor="middle" fill="black" font-family="Arial" font-size="10px"> 1</text>
114-
<text x="82.909" y="34" text-anchor="middle" fill="black" font-family="Arial" font-size="10px"> 2</text>
115-
<text x="88" y="53" text-anchor="middle" fill="black" font-family="Arial" font-size="10px"> 3</text>
116-
<text x="82.909" y="72" text-anchor="middle" fill="black" font-family="Arial" font-size="10px"> 4</text>
117-
<text x="69" y="85.909" text-anchor="middle" fill="black" font-family="Arial" font-size="10px"> 5</text>
118-
<text x="50" y="91" text-anchor="middle" fill="black" font-family="Arial" font-size="10px"> 6</text>
119-
<text x="31" y="85.909" text-anchor="middle" fill="black" font-family="Arial" font-size="10px"> 7</text>
120-
<text x="17.091" y="72" text-anchor="middle" fill="black" font-family="Arial" font-size="10px"> 8</text>
121-
<text x="12" y="53" text-anchor="middle" fill="black" font-family="Arial" font-size="10px"> 9</text>
122-
<text x="17.091" y="34" text-anchor="middle" fill="black" font-family="Arial" font-size="10px">10</text>
123-
<text x="31" y="20.091" text-anchor="middle" fill="black" font-family="Arial" font-size="10px">11</text>
124-
<text x="50" y="15" text-anchor="middle" fill="black" font-family="Arial" font-size="10px">12</text>
125-
126-
<circle cx="50" cy="50" r="3" fill="black"></circle>
127-
<line id="second" transform="rotate(#{second_angle}, 50, 50)" x1="50" y1="50" x2="50" y2="12" fill="black" stroke="black" stroke-width="1"></line>
128-
<line id="minute" transform="rotate(#{minute_angle}, 50, 50)" x1="50" y1="50" x2="50" y2="16" fill="black" stroke="black" stroke-width="2"></line>
129-
<line id="hour" transform="rotate(#{hour_angle}, 50, 50)" x1="50" y1="50" x2="50" y2="24" fill="black" stroke="black" stroke-width="2"></line>
130-
</svg>
131-
END_SVG
132-
133-
image = "data:image/png;base64,"
134-
image += Process.run(%(rsvg-convert -w 400 -h 400 -b none -f png), shell: true,
135-
input: IO::Memory.new(clock_svg), output: Process::Redirect::Pipe
136-
) do |proc|
137-
Base64.strict_encode(proc.output.gets_to_end)
138-
end
139-
140-
answer = "#{hour}:#{minute.to_s.rjust(2, '0')}:#{second.to_s.rjust(2, '0')}"
141-
answer = OpenSSL::HMAC.hexdigest(:sha256, key, answer)
142-
143-
return {
144-
question: image,
145-
tokens: {generate_response(answer, {":login"}, key, use_nonce: true)},
146-
}
147-
end
148-
149-
def generate_text_captcha(key)
150-
response = make_client(TEXTCAPTCHA_URL, &.get("/github.com/iv.org/invidious.json").body)
151-
response = JSON.parse(response)
152-
153-
tokens = response["a"].as_a.map do |answer|
154-
generate_response(answer.as_s, {":login"}, key, use_nonce: true)
155-
end
156-
157-
return {
158-
question: response["q"].as_s,
159-
tokens: tokens,
160-
}
161-
end
162-
16394
def subscribe_ajax(channel_id, action, env_headers)
16495
headers = HTTP::Headers.new
16596
headers["Cookie"] = env_headers["Cookie"]

0 commit comments

Comments
 (0)