From 52d64e37ea0379b2e44c869b694e78b32542f0ff Mon Sep 17 00:00:00 2001 From: Fijxu Date: Mon, 23 Feb 2026 19:21:06 -0300 Subject: [PATCH 1/2] Add support for alternative domains for Invidious cookies --- config/config.example.yml | 14 ++++++++++++++ src/invidious/config.cr | 2 ++ src/invidious/routes/before_all.cr | 2 ++ src/invidious/routes/login.cr | 13 +++++++++++-- src/invidious/routes/preferences.cr | 14 ++++++++++++-- src/invidious/user/cookies.cr | 20 +++++++++++++++++--- 6 files changed, 58 insertions(+), 7 deletions(-) diff --git a/config/config.example.yml b/config/config.example.yml index 08005a12..74e12b02 100644 --- a/config/config.example.yml +++ b/config/config.example.yml @@ -151,6 +151,20 @@ db: ## domain: +## +## List of alternative domains where the invidious instance is being served. +## This needs to be set in order to be able to login and update user preferences +## when using a domain that is not the same as the `domain` configuration, +## like a .`onion` address, `.i2p` address, `.b32.i2p` address, etc. +## +## It will detect the alternative domain trough the `X-Forwarded-Host` header. +## https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/X-Forwarded-Host +## +## Accepted values: a list of fully qualified domain names (FQDN) +## Default: +## +alternative_domains: + ## ## Tell Invidious that it is behind a proxy that provides only ## HTTPS, so all links must use the https:// scheme. This diff --git a/src/invidious/config.cr b/src/invidious/config.cr index 7853d9a3..1711e256 100644 --- a/src/invidious/config.cr +++ b/src/invidious/config.cr @@ -120,6 +120,8 @@ class Config property hmac_key : String = "" # Domain to be used for links to resources on the site where an absolute URL is required property domain : String? + # Additional domain list that is going to be used for cookie domain validation + property alternative_domains : Array(String) = [] of String # Subscribe to channels using PubSubHubbub (requires domain, hmac_key) property use_pubsub_feeds : Bool | Int32 = false property popular_enabled : Bool = true diff --git a/src/invidious/routes/before_all.cr b/src/invidious/routes/before_all.cr index 6d374fff..512c1882 100644 --- a/src/invidious/routes/before_all.cr +++ b/src/invidious/routes/before_all.cr @@ -32,6 +32,8 @@ module Invidious::Routes::BeforeAll env.response.headers["X-XSS-Protection"] = "1; mode=block" env.response.headers["X-Content-Type-Options"] = "nosniff" + env.set "header_x-forwarded-host", env.request.headers["X-Forwarded-Host"]? + # Only allow the pages at /embed/* to be embedded if env.request.resource.starts_with?("/embed") frame_ancestors = "'self' file: http: https:" diff --git a/src/invidious/routes/login.cr b/src/invidious/routes/login.cr index 674f0a46..ce1e8508 100644 --- a/src/invidious/routes/login.cr +++ b/src/invidious/routes/login.cr @@ -26,6 +26,7 @@ module Invidious::Routes::Login def self.login(env) locale = env.get("preferences").as(Preferences).locale + host = env.get("header_x-forwarded-host") referer = get_referer(env, "/feed/subscriptions") @@ -57,7 +58,11 @@ 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) + if alt = CONFIG.alternative_domains.index(host) + env.response.cookies["SID"] = Invidious::User::Cookies.sid(CONFIG.alternative_domains[alt], sid) + else + env.response.cookies["SID"] = Invidious::User::Cookies.sid(CONFIG.domain, sid) + end else return error_template(401, "Wrong username or password") end @@ -123,7 +128,11 @@ 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) + if alt = CONFIG.alternative_domains.index(host) + env.response.cookies["SID"] = Invidious::User::Cookies.sid(CONFIG.alternative_domains[alt], sid) + else + env.response.cookies["SID"] = Invidious::User::Cookies.sid(CONFIG.domain, sid) + end if env.request.cookies["PREFS"]? user.preferences = env.get("preferences").as(Preferences) diff --git a/src/invidious/routes/preferences.cr b/src/invidious/routes/preferences.cr index d9fad1b1..80565716 100644 --- a/src/invidious/routes/preferences.cr +++ b/src/invidious/routes/preferences.cr @@ -226,7 +226,12 @@ 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) + host = env.get("header_x-forwarded-host") + if alt = CONFIG.alternative_domains.index(host) + env.response.cookies["PREFS"] = Invidious::User::Cookies.prefs(CONFIG.alternative_domains[alt], preferences) + else + env.response.cookies["PREFS"] = Invidious::User::Cookies.prefs(CONFIG.domain, preferences) + end end env.redirect referer @@ -261,7 +266,12 @@ module Invidious::Routes::PreferencesRoute preferences.dark_mode = "dark" end - env.response.cookies["PREFS"] = Invidious::User::Cookies.prefs(CONFIG.domain, preferences) + host = env.get("header_x-forwarded-host") + if alt = CONFIG.alternative_domains.index(host) + env.response.cookies["PREFS"] = Invidious::User::Cookies.prefs(CONFIG.alternative_domains[alt], preferences) + else + env.response.cookies["PREFS"] = Invidious::User::Cookies.prefs(CONFIG.domain, preferences) + end end if redirect diff --git a/src/invidious/user/cookies.cr b/src/invidious/user/cookies.cr index 654efc15..d022a8ab 100644 --- a/src/invidious/user/cookies.cr +++ b/src/invidious/user/cookies.cr @@ -6,17 +6,24 @@ struct Invidious::User # Note: we use ternary operator because the two variables # used in here are not booleans. - SECURE = (Kemal.config.ssl || CONFIG.https_only) ? true : false + @@secure = (Kemal.config.ssl || CONFIG.https_only) ? true : false # Session ID (SID) cookie # Parameter "domain" comes from the global config def sid(domain : String?, sid) : HTTP::Cookie + # Not secure if it's being accessed from I2P + # Browsers expect the domain to include https. On I2P there is no HTTPS + # Tor browser works fine with secure being true + if domain.try &.split(".").last == "i2p" + @@secure = false + end + return HTTP::Cookie.new( name: "SID", domain: domain, value: sid, expires: Time.utc + 2.years, - secure: SECURE, + secure: @@secure, http_only: true, samesite: HTTP::Cookie::SameSite::Lax ) @@ -25,12 +32,19 @@ struct Invidious::User # Preferences (PREFS) cookie # Parameter "domain" comes from the global config def prefs(domain : String?, preferences : Preferences) : HTTP::Cookie + # Not secure if it's being accessed from I2P + # Browsers expect the domain to include https. On I2P there is no HTTPS + # Tor browser works fine with secure being true + if domain.try &.split(".").last == "i2p" + @@secure = false + end + return HTTP::Cookie.new( name: "PREFS", domain: domain, value: URI.encode_www_form(preferences.to_json), expires: Time.utc + 2.years, - secure: SECURE, + secure: @@secure, http_only: false, samesite: HTTP::Cookie::SameSite::Lax ) From f5bf274a530430c075ab625e11e8b2a2ea87c81c Mon Sep 17 00:00:00 2001 From: Fijxu Date: Mon, 23 Feb 2026 19:36:59 -0300 Subject: [PATCH 2/2] check if @@secure is already true before changing it's value --- src/invidious/user/cookies.cr | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/invidious/user/cookies.cr b/src/invidious/user/cookies.cr index d022a8ab..ed7665fd 100644 --- a/src/invidious/user/cookies.cr +++ b/src/invidious/user/cookies.cr @@ -14,7 +14,7 @@ struct Invidious::User # Not secure if it's being accessed from I2P # Browsers expect the domain to include https. On I2P there is no HTTPS # Tor browser works fine with secure being true - if domain.try &.split(".").last == "i2p" + if (domain.try &.split(".").last == "i2p") && @@secure @@secure = false end @@ -35,7 +35,7 @@ struct Invidious::User # Not secure if it's being accessed from I2P # Browsers expect the domain to include https. On I2P there is no HTTPS # Tor browser works fine with secure being true - if domain.try &.split(".").last == "i2p" + if (domain.try &.split(".").last == "i2p") && @@secure @@secure = false end