Implemented requested changes and added 'auth_enforce_source' option

This commit is contained in:
soraefir 2023-07-13 12:48:18 +02:00
parent 4d14789e7b
commit 8953c105be
No known key found for this signature in database
GPG Key ID: A362EA0491E2EEA0
5 changed files with 68 additions and 82 deletions

View File

@ -142,6 +142,7 @@ class Config
property use_quic : Bool = false
property auth_type : Array(String) = ["invidious", "oauth"]
property auth_enforce_source : Bool = true
property oauth = {} of String => OAuthConfig
# Saved cookies in "name1=value1; name2=value2..." format
@ -170,6 +171,14 @@ class Config
end
end
def auth_oauth_enabled?
return (@auth_type.find(&.== "oauth") && @oauth.size > 0)
end
def auth_internal_enabled?
return (@auth_type.find(&.== "invidious"))
end
def self.load
# Load config from file or YAML string env var
env_config_file = "INVIDIOUS_CONFIG_FILE"

View File

@ -11,7 +11,7 @@ module Invidious::OAuthHelper
end
end
def get(key)
def make_client(key)
if HOST_URL == ""
raise Exception.new("Missing domain and port configuration")
end
@ -27,24 +27,22 @@ module Invidious::OAuthHelper
)
end
def get_uri_host_pair(host, uri)
if (uri.starts_with?("https://"))
res = uri.gsub(/https*\:\/\//, "").split('/', 2)
[res[0], "/" + res[1]]
def get_uri_host_pair(host, url)
if (url.starts_with?(/https*\:\/\//))
uri = URI.parse url
[uri.host || host, uri.path || "/"]
else
[host, uri]
[host, url]
end
end
def get_info(key, token)
provider = self.get_provider(key)
uri_host_pair = self.get_uri_host_pair(provider.host, provider.info_uri)
LOGGER.info(uri_host_pair[0] + " " + uri_host_pair[1])
client = HTTP::Client.new(uri_host_pair[0], tls: true)
token.authenticate(client)
response = client.get uri_host_pair[1]
client.close
LOGGER.info(response.body)
response.body
end

View File

@ -3,11 +3,10 @@
module Invidious::Routes::Login
def self.login_page(env)
locale = env.get("preferences").as(Preferences).locale
referer = get_referer(env, "/feed/subscriptions")
user = env.get? "user"
referer = get_referer(env, "/feed/subscriptions")
return env.redirect referer if user
if !CONFIG.login_enabled
@ -19,17 +18,13 @@ module Invidious::Routes::Login
captcha = nil
account_type = env.params.query["type"]?
account_type ||= "invidious"
account_type ||= ""
if CONFIG.auth_type.find(&.== account_type).nil?
if CONFIG.auth_type.size == 0
account_type = "invidious"
else
return error_template(401, "No authentication backend enabled.")
elsif CONFIG.auth_type.find(&.== account_type).nil? && CONFIG.auth_type.size == 1
account_type = CONFIG.auth_type[0]
end
end
oauth = CONFIG.auth_type.find(&.== "oauth") && (CONFIG.oauth.size > 0)
captcha_type = env.params.query["captcha"]?
captcha_type ||= "image"
@ -39,36 +34,36 @@ module Invidious::Routes::Login
def self.login_oauth(env)
locale = env.get("preferences").as(Preferences).locale
referer = get_referer(env, "/feed/subscriptions")
authorization_code = env.params.query["code"]?
provider_k = env.params.url["provider"]
if authorization_code
begin
token = OAuthHelper.get(provider_k).get_access_token_using_authorization_code(authorization_code)
email = OAuthHelper.info_field(provider_k, token)
if email
user = Invidious::Database::Users.select(email: email)
if user
user_flow_existing(env, email)
if authorization_code.nil?
return error_template(403, "Missing Authorization Code")
end
begin
token = OAuthHelper.make_client(provider_k).get_access_token_using_authorization_code(authorization_code)
if email = OAuthHelper.info_field(provider_k, token)
if user = Invidious::Database::Users.select(email: email)
if CONFIG.auth_enforce_source && user.password != ("oauth:" + provider_k)
return error_template(401, "Wrong provider")
else
user_flow_new(env, email, nil)
user_flow_existing(env, email)
end
else
user_flow_new(env, email, nil, "oauth:" + provider_k)
end
end
rescue ex
return error_template(500, "Internal Error" + (ex.message || ""))
end
else
return error_template(403, "Missing Authorization Code")
return error_template(500, "Internal Error")
end
env.redirect referer
end
def self.login(env)
locale = env.get("preferences").as(Preferences).locale
referer = get_referer(env, "/feed/subscriptions")
if !CONFIG.login_enabled
@ -78,23 +73,20 @@ module Invidious::Routes::Login
# https://stackoverflow.com/a/574698
email = env.params.body["email"]?.try &.downcase.byte_slice(0, 254)
password = env.params.body["password"]?
oauth = CONFIG.auth_type.find(&.== "oauth") && (CONFIG.oauth.size > 0)
account_type = env.params.query["type"]?
account_type ||= "invidious"
account_type ||= ""
if CONFIG.auth_type.size == 0
return error_template(401, "No authentication backend enabled.")
end
if CONFIG.auth_type.find(&.== account_type).nil?
elsif CONFIG.auth_type.find(&.== account_type).nil? && CONFIG.auth_type.size == 1
account_type = CONFIG.auth_type[0]
end
case account_type
when "oauth"
provider_k = env.params.body["provider"]
env.redirect OAuthHelper.get(provider_k).get_authorize_uri("openid email profile")
env.redirect OAuthHelper.make_client(provider_k).get_authorize_uri("openid email profile")
when "saml"
return error_template(501, "Not implemented")
when "ldap"
@ -108,18 +100,14 @@ module Invidious::Routes::Login
return error_template(401, "Password is a required field")
end
user = Invidious::Database::Users.select(email: email)
if user
if Crypto::Bcrypt::Password.new(user.password.not_nil!).verify(password.byte_slice(0, 55))
sid = Base64.urlsafe_encode(Random::Secure.random_bytes(32))
Invidious::Database::SessionIDs.insert(sid, email)
env.response.cookies["SID"] = Invidious::User::Cookies.sid(CONFIG.domain, sid)
if user = Invidious::Database::Users.select(email: email)
if user.password.not_nil!.starts_with? "oauth"
return error_template(401, "Wrong provider")
elsif Crypto::Bcrypt::Password.new(user.password.not_nil!).verify(password.byte_slice(0, 55))
user_flow_existing(env, email)
else
return error_template(401, "Wrong username or password")
end
user_flow_existing(env, email)
else
if !CONFIG.registration_enabled
return error_template(400, "Registration has been disabled by administrator.")
@ -196,7 +184,7 @@ module Invidious::Routes::Login
end
end
end
user_flow_new(env, email, password)
user_flow_new(env, email, password, "internal")
end
env.redirect referer
@ -249,12 +237,12 @@ module Invidious::Routes::Login
end
end
def self.user_flow_new(env, email, password)
def self.user_flow_new(env, email, password, provider)
sid = Base64.urlsafe_encode(Random::Secure.random_bytes(32))
if password
user, sid = create_user(sid, email, password)
if provider == "internal"
user, sid = create_internal_user(sid, email, password)
else
user, sid = create_user(sid, email)
user, sid = create_user(sid, email, provider)
end
if language_header = env.request.headers["Accept-Language"]?

View File

@ -3,7 +3,7 @@ require "crypto/bcrypt/password"
# Materialized views may not be defined using bound parameters (`$1` as used elsewhere)
MATERIALIZED_VIEW_SQL = ->(email : String) { "SELECT cv.* FROM channel_videos cv WHERE EXISTS (SELECT subscriptions FROM users u WHERE cv.ucid = ANY (u.subscriptions) AND u.email = E'#{email.gsub({'\'' => "\\'", '\\' => "\\\\"})}') ORDER BY published DESC" }
def create_user(sid, email)
def create_user(sid, email, password)
token = Base64.urlsafe_encode(Random::Secure.random_bytes(32))
user = Invidious::User.new({
@ -12,7 +12,7 @@ def create_user(sid, email)
subscriptions: [] of String,
email: email,
preferences: Preferences.new(CONFIG.default_user_preferences.to_tuple),
password: nil,
password: password,
token: token,
watched: [] of String,
feed_needs_update: true,
@ -21,23 +21,9 @@ def create_user(sid, email)
return user, sid
end
def create_user(sid, email, password)
password = Crypto::Bcrypt::Password.create(password, cost: 10)
token = Base64.urlsafe_encode(Random::Secure.random_bytes(32))
user = Invidious::User.new({
updated: Time.utc,
notifications: [] of String,
subscriptions: [] of String,
email: email,
preferences: Preferences.new(CONFIG.default_user_preferences.to_tuple),
password: password.to_s,
token: token,
watched: [] of String,
feed_needs_update: true,
})
return user, sid
def create_internal_user(sid, email, password)
password = Crypto::Bcrypt::Password.create(password.not_nil!, cost: 10)
create_user(sid, email, password.to_s)
end
def get_subscription_feed(user, max_results = 40, page = 1)

View File

@ -11,14 +11,14 @@
<form class="pure-form pure-form-stacked" action="/login?referer=<%= URI.encode_www_form(referer) %>&type=oauth" method="post">
<fieldset>
<select name="provider" id="provider">
<% CONFIG.oauth.each_key do |k| %>
<option value="<%= k %>"><%= k %></option>
<% CONFIG.oauth.each_key do |key| %>
<option value="<%= key %>"><%= key %></option>
<% end %>
</select>
<button type="submit" class="pure-button pure-button-primary"><%= translate(locale, "Sign In via OAuth") %></button>
</fieldset>
</form>
<% else # "invidious" %>
<% when "invidious" %>
<form class="pure-form pure-form-stacked" action="/login?referer=<%= URI.encode_www_form(referer) %>&type=invidious" method="post">
<fieldset>
<% if email %>
@ -79,11 +79,16 @@
<%= translate(locale, "Sign In") %>/<%= translate(locale, "Register") %>
</button>
<% end %>
<% if oauth %>
<a class="pure-button pure-button-secondary" href="/login?referer=<%= URI.encode_www_form(referer) %>&type=oauth">OAuth</a>
<% end %>
</fieldset>
</form>
<% else %>
<% if CONFIG.auth_internal_enabled? %>
<a class="pure-button pure-button-secondary" href="/login?referer=<%= URI.encode_www_form(referer) %>&type=invidious">Internal</a>
<% end %>
<% if CONFIG.auth_oauth_enabled? %>
<a class="pure-button pure-button-secondary" href="/login?referer=<%= URI.encode_www_form(referer) %>&type=oauth">OAuth</a>
<% end %>
<label></label>
<% end %>
</div>
</div>