Don't require CAPTCHA for login
This commit is contained in:
parent
21285d9f6d
commit
9091b36249
175
src/invidious.cr
175
src/invidious.cr
@ -760,37 +760,22 @@ get "/login" do |env|
|
|||||||
|
|
||||||
referer = get_referer(env, "/feed/subscriptions")
|
referer = get_referer(env, "/feed/subscriptions")
|
||||||
|
|
||||||
|
email = nil
|
||||||
|
password = nil
|
||||||
|
captcha = nil
|
||||||
|
|
||||||
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 = env.params.query["captcha"]?
|
||||||
captcha_type ||= "image"
|
captcha_type ||= "image"
|
||||||
|
|
||||||
if account_type == "invidious"
|
|
||||||
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
|
|
||||||
|
|
||||||
tfa = env.params.query["tfa"]?
|
tfa = env.params.query["tfa"]?
|
||||||
tfa ||= false
|
tfa ||= false
|
||||||
|
|
||||||
templated "login"
|
templated "login"
|
||||||
end
|
end
|
||||||
|
|
||||||
# See https://github.com/rg3/youtube-dl/blob/master/youtube_dl/extractor/youtube.py#L79
|
|
||||||
post "/login" do |env|
|
post "/login" do |env|
|
||||||
locale = LOCALES[env.get("preferences").as(Preferences).locale]?
|
locale = LOCALES[env.get("preferences").as(Preferences).locale]?
|
||||||
|
|
||||||
@ -805,11 +790,13 @@ post "/login" do |env|
|
|||||||
password = env.params.body["password"]?
|
password = env.params.body["password"]?
|
||||||
|
|
||||||
account_type = env.params.query["type"]?
|
account_type = env.params.query["type"]?
|
||||||
account_type ||= "google"
|
account_type ||= "invidious"
|
||||||
|
|
||||||
if account_type == "google"
|
case account_type
|
||||||
|
when "google"
|
||||||
tfa_code = env.params.body["tfa"]?.try &.lchop("G-")
|
tfa_code = env.params.body["tfa"]?.try &.lchop("G-")
|
||||||
|
|
||||||
|
# See https://github.com/rg3/youtube-dl/blob/master/youtube_dl/extractor/youtube.py#L79
|
||||||
begin
|
begin
|
||||||
client = make_client(LOGIN_URL)
|
client = make_client(LOGIN_URL)
|
||||||
headers = HTTP::Headers.new
|
headers = HTTP::Headers.new
|
||||||
@ -911,7 +898,11 @@ post "/login" do |env|
|
|||||||
end
|
end
|
||||||
|
|
||||||
if !tfa_code
|
if !tfa_code
|
||||||
next env.redirect "/login?tfa=true&type=google&referer=#{URI.escape(referer)}"
|
account_type = "google"
|
||||||
|
captcha_type = "image"
|
||||||
|
tfa = true
|
||||||
|
captcha = nil
|
||||||
|
next templated "login"
|
||||||
end
|
end
|
||||||
|
|
||||||
tl = challenge_results[1][2]
|
tl = challenge_results[1][2]
|
||||||
@ -976,6 +967,7 @@ post "/login" do |env|
|
|||||||
|
|
||||||
cookie.extension = cookie.extension.not_nil!.gsub(".youtube.com", host)
|
cookie.extension = cookie.extension.not_nil!.gsub(".youtube.com", host)
|
||||||
cookie.extension = cookie.extension.not_nil!.gsub("Secure; ", "")
|
cookie.extension = cookie.extension.not_nil!.gsub("Secure; ", "")
|
||||||
|
env.response.cookies << cookie
|
||||||
end
|
end
|
||||||
|
|
||||||
if env.request.cookies["PREFS"]?
|
if env.request.cookies["PREFS"]?
|
||||||
@ -992,65 +984,7 @@ post "/login" do |env|
|
|||||||
error_message = translate(locale, "Login failed. This may be because two-factor authentication is not enabled on your account.")
|
error_message = translate(locale, "Login failed. This may be because two-factor authentication is not enabled on your account.")
|
||||||
next templated "error"
|
next templated "error"
|
||||||
end
|
end
|
||||||
elsif account_type == "invidious"
|
when "invidious"
|
||||||
answer = env.params.body["answer"]?
|
|
||||||
text_answer = env.params.body["text_answer"]?
|
|
||||||
|
|
||||||
if config.captcha_enabled
|
|
||||||
if answer
|
|
||||||
answer = answer.lstrip('0')
|
|
||||||
answer = OpenSSL::HMAC.hexdigest(:sha256, HMAC_KEY, answer)
|
|
||||||
|
|
||||||
challenge = env.params.body["challenge"]?
|
|
||||||
token = env.params.body["token"]?
|
|
||||||
|
|
||||||
begin
|
|
||||||
validate_response(challenge, token, answer, "sign_in", HMAC_KEY, PG_DB, locale)
|
|
||||||
rescue ex
|
|
||||||
if ex.message == translate(locale, "Invalid user")
|
|
||||||
error_message = translate(locale, "Invalid answer")
|
|
||||||
else
|
|
||||||
error_message = ex.message
|
|
||||||
end
|
|
||||||
|
|
||||||
next templated "error"
|
|
||||||
end
|
|
||||||
elsif text_answer
|
|
||||||
text_answer = Digest::MD5.hexdigest(text_answer.downcase.strip)
|
|
||||||
|
|
||||||
challenges = env.params.body.select { |k, v| k.match(/text_challenge\d+/) }
|
|
||||||
tokens = env.params.body.select { |k, v| k.match(/text_token\d+/) }
|
|
||||||
|
|
||||||
found_valid_captcha = false
|
|
||||||
|
|
||||||
error_message = translate(locale, "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, locale)
|
|
||||||
found_valid_captcha = true
|
|
||||||
rescue ex
|
|
||||||
if ex.message == translate(locale, "Invalid user")
|
|
||||||
error_message = translate(locale, "Invalid answer")
|
|
||||||
else
|
|
||||||
error_message = ex.message
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
if !found_valid_captcha
|
|
||||||
next templated "error"
|
|
||||||
end
|
|
||||||
else
|
|
||||||
error_message = translate(locale, "CAPTCHA is a required field")
|
|
||||||
next templated "error"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
action = env.params.body["action"]?
|
|
||||||
action ||= "signin"
|
|
||||||
|
|
||||||
if !email
|
if !email
|
||||||
error_message = translate(locale, "User ID is a required field")
|
error_message = translate(locale, "User ID is a required field")
|
||||||
next templated "error"
|
next templated "error"
|
||||||
@ -1061,14 +995,9 @@ post "/login" do |env|
|
|||||||
next templated "error"
|
next templated "error"
|
||||||
end
|
end
|
||||||
|
|
||||||
if action == "signin"
|
user = PG_DB.query_one?("SELECT * FROM users WHERE LOWER(email) = LOWER($1)", email, as: User)
|
||||||
user = PG_DB.query_one?("SELECT * FROM users WHERE LOWER(email) = LOWER($1)", email, as: User)
|
|
||||||
|
|
||||||
if !user
|
|
||||||
error_message = translate(locale, "Invalid username or password")
|
|
||||||
next templated "error"
|
|
||||||
end
|
|
||||||
|
|
||||||
|
if user
|
||||||
if !user.password
|
if !user.password
|
||||||
error_message = translate(locale, "Please sign in using 'Sign in with Google'")
|
error_message = translate(locale, "Please sign in using 'Sign in with Google'")
|
||||||
next templated "error"
|
next templated "error"
|
||||||
@ -1102,12 +1031,78 @@ post "/login" do |env|
|
|||||||
cookie.expires = Time.new(1990, 1, 1)
|
cookie.expires = Time.new(1990, 1, 1)
|
||||||
env.response.cookies << cookie
|
env.response.cookies << cookie
|
||||||
end
|
end
|
||||||
elsif action == "register"
|
else
|
||||||
if !config.registration_enabled
|
if !config.registration_enabled
|
||||||
error_message = "Registration has been disabled by administrator."
|
error_message = "Registration has been disabled by administrator."
|
||||||
next templated "error"
|
next templated "error"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
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"
|
||||||
|
tfa = false
|
||||||
|
|
||||||
|
if captcha_type == "image"
|
||||||
|
captcha = generate_captcha(HMAC_KEY, PG_DB)
|
||||||
|
else
|
||||||
|
captcha = generate_text_captcha(HMAC_KEY, PG_DB)
|
||||||
|
end
|
||||||
|
|
||||||
|
next templated "login"
|
||||||
|
end
|
||||||
|
|
||||||
|
challenges = env.params.body.select { |k, v| k.match(/^challenge\[\d+\]$/) }
|
||||||
|
tokens = env.params.body.select { |k, v| k.match(/^token\[\d+\]$/) }
|
||||||
|
|
||||||
|
answer ||= ""
|
||||||
|
captcha_type ||= "image"
|
||||||
|
|
||||||
|
case captcha_type
|
||||||
|
when "image"
|
||||||
|
answer = answer.lstrip('0')
|
||||||
|
answer = OpenSSL::HMAC.hexdigest(:sha256, HMAC_KEY, answer)
|
||||||
|
|
||||||
|
challenge = env.params.body["challenge[0]"]?
|
||||||
|
token = env.params.body["token[0]"]?
|
||||||
|
|
||||||
|
begin
|
||||||
|
validate_response(challenge, token, answer, "sign_in", HMAC_KEY, PG_DB, locale)
|
||||||
|
rescue ex
|
||||||
|
error_message = ex.message
|
||||||
|
next templated "error"
|
||||||
|
end
|
||||||
|
when "text"
|
||||||
|
answer = Digest::MD5.hexdigest(answer.downcase.strip)
|
||||||
|
|
||||||
|
found_valid_captcha = false
|
||||||
|
|
||||||
|
error_message = translate(locale, "Invalid CAPTCHA")
|
||||||
|
challenges.each_with_index do |challenge, i|
|
||||||
|
begin
|
||||||
|
challenge = challenge[1]
|
||||||
|
token = tokens[i][1]
|
||||||
|
validate_response(challenge, token, answer, "sign_in", HMAC_KEY, PG_DB, locale)
|
||||||
|
found_valid_captcha = true
|
||||||
|
rescue ex
|
||||||
|
error_message = ex.message
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if !found_valid_captcha
|
||||||
|
next templated "error"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
if password.empty?
|
if password.empty?
|
||||||
error_message = translate(locale, "Password cannot be empty")
|
error_message = translate(locale, "Password cannot be empty")
|
||||||
next templated "error"
|
next templated "error"
|
||||||
@ -1165,6 +1160,8 @@ post "/login" do |env|
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
env.redirect referer
|
||||||
|
else
|
||||||
env.redirect referer
|
env.redirect referer
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -328,7 +328,22 @@ def generate_captcha(key, db)
|
|||||||
answer = "#{hour}:#{minute.to_s.rjust(2, '0')}:#{second.to_s.rjust(2, '0')}"
|
answer = "#{hour}:#{minute.to_s.rjust(2, '0')}:#{second.to_s.rjust(2, '0')}"
|
||||||
answer = OpenSSL::HMAC.hexdigest(:sha256, key, answer)
|
answer = OpenSSL::HMAC.hexdigest(:sha256, key, answer)
|
||||||
|
|
||||||
challenge, token = create_response(answer, "sign_in", key, db)
|
return {
|
||||||
|
question: image,
|
||||||
return {image: image, challenge: challenge, token: token}
|
tokens: [create_response(answer, "sign_in", key, db)],
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
def generate_text_captcha(key, db)
|
||||||
|
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", key, db)
|
||||||
|
end
|
||||||
|
|
||||||
|
return {
|
||||||
|
question: response["q"].as_s,
|
||||||
|
tokens: tokens,
|
||||||
|
}
|
||||||
end
|
end
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
<div class="h-box">
|
<div class="h-box">
|
||||||
<div class="pure-g">
|
<div class="pure-g">
|
||||||
<div class="pure-u-1-2">
|
<div class="pure-u-1-2">
|
||||||
<a class="pure-button <% if account_type == "invidious" %>pure-button-disabled<% end %>" href="/login">
|
<a class="pure-button <% if account_type == "invidious" %>pure-button-disabled<% end %>" href="/login?type=invidious">
|
||||||
<%= translate(locale, "Login/Register") %>
|
<%= translate(locale, "Login/Register") %>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
@ -22,55 +22,84 @@
|
|||||||
<% if account_type == "invidious" %>
|
<% if account_type == "invidious" %>
|
||||||
<form class="pure-form pure-form-stacked" action="/login?referer=<%= URI.escape(referer) %>&type=invidious" method="post">
|
<form class="pure-form pure-form-stacked" action="/login?referer=<%= URI.escape(referer) %>&type=invidious" method="post">
|
||||||
<fieldset>
|
<fieldset>
|
||||||
|
<% if email %>
|
||||||
|
<input name="email" type="hidden" value="<%= email %>">
|
||||||
|
<% else %>
|
||||||
<label for="email"><%= translate(locale, "User ID:") %></label>
|
<label for="email"><%= translate(locale, "User ID:") %></label>
|
||||||
<input required class="pure-input-1" name="email" type="text" placeholder="User ID">
|
<input required class="pure-input-1" name="email" type="text" placeholder="User ID">
|
||||||
|
<% end %>
|
||||||
|
|
||||||
|
<% if password %>
|
||||||
|
<input name="password" type="hidden" value="<%= password %>">
|
||||||
|
<% else %>
|
||||||
<label for="password"><%= translate(locale, "Password:") %></label>
|
<label for="password"><%= translate(locale, "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">
|
||||||
|
<% end %>
|
||||||
|
|
||||||
<% if config.captcha_enabled %>
|
<% if captcha %>
|
||||||
<% if captcha_type == "image" %>
|
<% case captcha_type when %>
|
||||||
<img style="width:100%" src='<%= captcha.not_nil![:image] %>'/>
|
<% when "image" %>
|
||||||
<input type="hidden" name="token" value="<%= captcha.not_nil![:token] %>">
|
<% captcha = captcha.not_nil! %>
|
||||||
<input type="hidden" name="challenge" value="<%= captcha.not_nil![:challenge] %>">
|
<img style="width:100%" src='<%= captcha[:question] %>'/>
|
||||||
<label for="answer"><%= translate(locale, "Time (h:mm:ss):") %></label>
|
<% captcha[:tokens].each_with_index do |token, i| %>
|
||||||
<input required type="text" name="answer" type="text" placeholder="h:mm:ss">
|
<input type="hidden" name="challenge[<%= i %>]" value="<%= token[0] %>">
|
||||||
|
<input type="hidden" name="token[<%= i %>]" value="<%= token[1] %>">
|
||||||
<label>
|
|
||||||
<a href="/login?referer=<%= URI.escape(referer) %>&type=invidious&captcha=text">
|
|
||||||
<%= translate(locale, "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 %>
|
<% end %>
|
||||||
<label for="text_answer"><%= text_captcha.not_nil![:question] %></label>
|
<input type="hidden" name="captcha_type" value="image">
|
||||||
<input required type="text" name="text_answer" type="text" placeholder="Answer">
|
<label for="answer"><%= translate(locale, "Time (h:mm:ss):") %></label>
|
||||||
|
<input type="text" name="answer" type="text" placeholder="h:mm:ss">
|
||||||
|
<% when "text" %>
|
||||||
|
<% captcha = captcha.not_nil! %>
|
||||||
|
<% captcha[:tokens].each_with_index do |token, i| %>
|
||||||
|
<input type="hidden" name="challenge[<%= i %>]" value="<%= token[0] %>">
|
||||||
|
<input type="hidden" name="token[<%= i %>]" value="<%= token[1] %>">
|
||||||
|
<% end %>
|
||||||
|
<input type="hidden" name="captcha_type" value="text">
|
||||||
|
<label for="answer"><%= captcha[:question] %></label>
|
||||||
|
<input type="text" name="answer" type="text" placeholder="Answer">
|
||||||
|
<% end %>
|
||||||
|
|
||||||
|
<button type="submit" name="action" value="signin" class="pure-button pure-button-primary">
|
||||||
|
<%= translate(locale, "Register") %>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<% case captcha_type when %>
|
||||||
|
<% when "image" %>
|
||||||
<label>
|
<label>
|
||||||
<a href="/login?referer=<%= URI.escape(referer) %>&type=invidious">
|
<button type="submit" name="change_type" class="pure-button pure-button-primary" value="text">
|
||||||
|
<%= translate(locale, "Text CAPTCHA") %>
|
||||||
|
</button>
|
||||||
|
</label>
|
||||||
|
<% when "text" %>
|
||||||
|
<label>
|
||||||
|
<button type="submit" name="change_type" class="pure-button pure-button-primary" value="image">
|
||||||
<%= translate(locale, "Image CAPTCHA") %>
|
<%= translate(locale, "Image CAPTCHA") %>
|
||||||
</a>
|
</button>
|
||||||
</label>
|
</label>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
<% else %>
|
||||||
|
<button type="submit" name="action" value="signin" class="pure-button pure-button-primary">
|
||||||
|
<%= translate(locale, "Sign In") %>/<%= translate(locale, "Register") %>
|
||||||
|
</button>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
<button type="submit" name="action" value="signin" class="pure-button pure-button-primary"><%= translate(locale, "Sign In") %></button>
|
|
||||||
<% if config.registration_enabled %>
|
|
||||||
<button type="submit" name="action" value="register" class="pure-button pure-button-primary"><%= translate(locale, "Register") %></button>
|
|
||||||
<% end %>
|
|
||||||
</fieldset>
|
</fieldset>
|
||||||
</form>
|
</form>
|
||||||
<% elsif account_type == "google" %>
|
<% elsif account_type == "google" %>
|
||||||
<form class="pure-form pure-form-stacked" action="/login?referer=<%= URI.escape(referer) %>" method="post">
|
<form class="pure-form pure-form-stacked" action="/login?referer=<%= URI.escape(referer) %>&type=google" method="post">
|
||||||
<fieldset>
|
<fieldset>
|
||||||
|
<% if email %>
|
||||||
|
<input name="email" type="hidden" value="<%= email %>">
|
||||||
|
<% else %>
|
||||||
<label for="email"><%= translate(locale, "Email:") %></label>
|
<label for="email"><%= translate(locale, "Email:") %></label>
|
||||||
<input required class="pure-input-1" name="email" type="email" placeholder="Email">
|
<input required class="pure-input-1" name="email" type="email" placeholder="Email">
|
||||||
|
<% end %>
|
||||||
|
|
||||||
|
<% if password %>
|
||||||
|
<input name="password" type="hidden" value="<%= password %>">
|
||||||
|
<% else %>
|
||||||
<label for="password"><%= translate(locale, "Password:") %></label>
|
<label for="password"><%= translate(locale, "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">
|
||||||
|
<% end %>
|
||||||
|
|
||||||
<% if tfa %>
|
<% if tfa %>
|
||||||
<label for="tfa"><%= translate(locale, "Google verification code:") %></label>
|
<label for="tfa"><%= translate(locale, "Google verification code:") %></label>
|
||||||
|
Loading…
Reference in New Issue
Block a user