diff --git a/spec/invidious/helpers_spec.cr b/spec/invidious/helpers_spec.cr index 9fbb6d6fe..eef029eb7 100644 --- a/spec/invidious/helpers_spec.cr +++ b/spec/invidious/helpers_spec.cr @@ -3,6 +3,24 @@ require "../spec_helper" CONFIG = Config.from_yaml(File.open("config/config.example.yml")) Spectator.describe "Helper" do + describe "#cookie_domain" do + it "keeps the configured cookie domain for the matching host" do + expect(cookie_domain("example.com", "example.com")).to eq("example.com") + expect(cookie_domain("example.com", "example.com:3000")).to eq("example.com") + end + + it "falls back to host-only cookies for unrelated hosts such as onion mirrors" do + expect(cookie_domain("example.com", "exampleonionaddress.onion")).to be_nil + expect(cookie_domain("example.com", "other.example")).to be_nil + end + + it "preserves shared-subdomain deployments when the configured domain starts with a dot" do + expect(cookie_domain(".example.com", "example.com")).to eq(".example.com") + expect(cookie_domain(".example.com", "www.example.com")).to eq(".example.com") + expect(cookie_domain(".example.com", "deep.www.example.com")).to eq(".example.com") + end + end + describe "#produce_channel_search_continuation" do it "correctly produces token for searching a specific channel" do expect(produce_channel_search_continuation("UCXuqSBlHAE6Xw-yeJA0Tunw", "", 100)).to eq("4qmFsgJqEhhVQ1h1cVNCbEhBRTZYdy15ZUpBMFR1bncaIEVnWnpaV0Z5WTJnd0FUZ0JZQUY2QkVkS2IxaTRBUUE9WgCaAilicm93c2UtZmVlZFVDWHVxU0JsSEFFNlh3LXllSkEwVHVud3NlYXJjaA%3D%3D") diff --git a/src/invidious/helpers/utils.cr b/src/invidious/helpers/utils.cr index 7a262f849..7901aa50d 100644 --- a/src/invidious/helpers/utils.cr +++ b/src/invidious/helpers/utils.cr @@ -279,6 +279,26 @@ def sha256(text) return digest.final.hexstring end +def cookie_domain(configured_domain : String?, request_host : String?) : String? + return nil unless configured_domain + return nil unless request_host + + normalized_domain = configured_domain.downcase.lchop(".") + normalized_host = request_host.downcase.sub(/:\d+\z/, "") + + return configured_domain if normalized_host == normalized_domain + + if configured_domain.starts_with?(".") && normalized_host.ends_with?(".#{normalized_domain}") + return configured_domain + end + + nil +end + +def cookie_domain(env : HTTP::Server::Context) : String? + cookie_domain(CONFIG.domain, env.request.headers["Host"]?) +end + def subscribe_pubsub(topic, key) case topic when .match(/^UC[A-Za-z0-9_-]{22}$/) diff --git a/src/invidious/routes/account.cr b/src/invidious/routes/account.cr index c8db207c2..554da3753 100644 --- a/src/invidious/routes/account.cr +++ b/src/invidious/routes/account.cr @@ -128,10 +128,9 @@ module Invidious::Routes::Account Invidious::Database::SessionIDs.delete(email: user.email) PG_DB.exec("DROP MATERIALIZED VIEW #{view_name}") - env.request.cookies.each do |cookie| - cookie.expires = Time.utc(1990, 1, 1) - env.response.cookies << cookie - end + domain = cookie_domain(env) + env.response.cookies["SID"] = Invidious::User::Cookies.clear_sid(domain) + env.response.cookies["PREFS"] = Invidious::User::Cookies.clear_prefs(domain) env.redirect referer end diff --git a/src/invidious/routes/login.cr b/src/invidious/routes/login.cr index 01a199d32..80ebbc4a0 100644 --- a/src/invidious/routes/login.cr +++ b/src/invidious/routes/login.cr @@ -57,16 +57,14 @@ module Invidious::Routes::Login 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) + env.response.cookies["SID"] = Invidious::User::Cookies.sid(cookie_domain(env), sid) else return error_template(401, "Wrong username or password") end # Since this user has already registered, we don't want to overwrite their preferences if env.request.cookies["PREFS"]? - cookie = env.request.cookies["PREFS"] - cookie.expires = Time.utc(1990, 1, 1) - env.response.cookies << cookie + env.response.cookies["PREFS"] = Invidious::User::Cookies.clear_prefs(cookie_domain(env)) end else if !CONFIG.registration_enabled @@ -123,15 +121,13 @@ module Invidious::Routes::Login view_name = "subscriptions_#{sha256(user.email)}" PG_DB.exec("CREATE MATERIALIZED VIEW #{view_name} AS #{MATERIALIZED_VIEW_SQL.call(user.email)}") - env.response.cookies["SID"] = Invidious::User::Cookies.sid(CONFIG.domain, sid) + env.response.cookies["SID"] = Invidious::User::Cookies.sid(cookie_domain(env), sid) if env.request.cookies["PREFS"]? user.preferences = env.get("preferences").as(Preferences) Invidious::Database::Users.update_preferences(user) - cookie = env.request.cookies["PREFS"] - cookie.expires = Time.utc(1990, 1, 1) - env.response.cookies << cookie + env.response.cookies["PREFS"] = Invidious::User::Cookies.clear_prefs(cookie_domain(env)) end end @@ -164,10 +160,9 @@ module Invidious::Routes::Login Invidious::Database::SessionIDs.delete(sid: sid) - env.request.cookies.each do |cookie| - cookie.expires = Time.utc(1990, 1, 1) - env.response.cookies << cookie - end + domain = cookie_domain(env) + env.response.cookies["SID"] = Invidious::User::Cookies.clear_sid(domain) + env.response.cookies["PREFS"] = Invidious::User::Cookies.clear_prefs(domain) env.redirect referer end diff --git a/src/invidious/routes/preferences.cr b/src/invidious/routes/preferences.cr index d9fad1b18..86639371e 100644 --- a/src/invidious/routes/preferences.cr +++ b/src/invidious/routes/preferences.cr @@ -226,7 +226,7 @@ module Invidious::Routes::PreferencesRoute File.write("config/config.yml", CONFIG.to_yaml) end else - env.response.cookies["PREFS"] = Invidious::User::Cookies.prefs(CONFIG.domain, preferences) + env.response.cookies["PREFS"] = Invidious::User::Cookies.prefs(cookie_domain(env), preferences) end env.redirect referer @@ -261,7 +261,7 @@ module Invidious::Routes::PreferencesRoute preferences.dark_mode = "dark" end - env.response.cookies["PREFS"] = Invidious::User::Cookies.prefs(CONFIG.domain, preferences) + env.response.cookies["PREFS"] = Invidious::User::Cookies.prefs(cookie_domain(env), preferences) end if redirect diff --git a/src/invidious/user/cookies.cr b/src/invidious/user/cookies.cr index 654efc15b..c23120e2c 100644 --- a/src/invidious/user/cookies.cr +++ b/src/invidious/user/cookies.cr @@ -14,6 +14,7 @@ struct Invidious::User return HTTP::Cookie.new( name: "SID", domain: domain, + path: "/", value: sid, expires: Time.utc + 2.years, secure: SECURE, @@ -28,6 +29,7 @@ struct Invidious::User return HTTP::Cookie.new( name: "PREFS", domain: domain, + path: "/", value: URI.encode_www_form(preferences.to_json), expires: Time.utc + 2.years, secure: SECURE, @@ -35,5 +37,26 @@ struct Invidious::User samesite: HTTP::Cookie::SameSite::Lax ) end + + def clear_sid(domain : String?) : HTTP::Cookie + clear("SID", domain, http_only: true) + end + + def clear_prefs(domain : String?) : HTTP::Cookie + clear("PREFS", domain, http_only: false) + end + + private def clear(name : String, domain : String?, http_only : Bool) : HTTP::Cookie + return HTTP::Cookie.new( + name: name, + domain: domain, + path: "/", + value: "", + expires: Time.utc(1990, 1, 1), + secure: SECURE, + http_only: http_only, + samesite: HTTP::Cookie::SameSite::Lax + ) + end end end