From 8953c105be1350a2372caefd5d9f919b15835c36 Mon Sep 17 00:00:00 2001 From: soraefir Date: Thu, 13 Jul 2023 12:48:18 +0200 Subject: [PATCH] Implemented requested changes and added 'auth_enforce_source' option --- src/invidious/config.cr | 9 ++++ src/invidious/helpers/oauth.cr | 14 +++-- src/invidious/routes/login.cr | 86 +++++++++++++----------------- src/invidious/users.cr | 24 ++------- src/invidious/views/user/login.ecr | 17 +++--- 5 files changed, 68 insertions(+), 82 deletions(-) diff --git a/src/invidious/config.cr b/src/invidious/config.cr index 96963c5a..654fc82b 100644 --- a/src/invidious/config.cr +++ b/src/invidious/config.cr @@ -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" diff --git a/src/invidious/helpers/oauth.cr b/src/invidious/helpers/oauth.cr index 2a372b7c..2ef6c91c 100644 --- a/src/invidious/helpers/oauth.cr +++ b/src/invidious/helpers/oauth.cr @@ -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 diff --git a/src/invidious/routes/login.cr b/src/invidious/routes/login.cr index 55edbe81..6ca0e9f2 100644 --- a/src/invidious/routes/login.cr +++ b/src/invidious/routes/login.cr @@ -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,18 +18,14 @@ 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 - account_type = CONFIG.auth_type[0] - end + if CONFIG.auth_type.size == 0 + 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 - 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) - else - user_flow_new(env, email, nil) - end - end - rescue ex - return error_template(500, "Internal Error" + (ex.message || "")) - end - else + 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_existing(env, email) + end + else + user_flow_new(env, email, nil, "oauth:" + provider_k) + end + end + rescue ex + 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"]? diff --git a/src/invidious/users.cr b/src/invidious/users.cr index d78d4561..5d45ce6e 100644 --- a/src/invidious/users.cr +++ b/src/invidious/users.cr @@ -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) diff --git a/src/invidious/views/user/login.ecr b/src/invidious/views/user/login.ecr index ae71240a..b44f6066 100644 --- a/src/invidious/views/user/login.ecr +++ b/src/invidious/views/user/login.ecr @@ -11,14 +11,14 @@
- <% else # "invidious" %> + <% when "invidious" %>
<% if email %> @@ -79,11 +79,16 @@ <%= translate(locale, "Sign In") %>/<%= translate(locale, "Register") %> <% end %> - <% if oauth %> - OAuth - <% end %>
+ <% else %> + <% if CONFIG.auth_internal_enabled? %> + Internal + <% end %> + <% if CONFIG.auth_oauth_enabled? %> + OAuth + <% end %> + <% end %>