Add support for alternative domains for Invidious cookies

This commit is contained in:
Fijxu 2026-02-23 19:21:06 -03:00
parent fda8d1b528
commit 52d64e37ea
No known key found for this signature in database
GPG Key ID: 32C1DDF333EDA6A4
6 changed files with 58 additions and 7 deletions

View File

@ -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: <none>
##
alternative_domains:
##
## Tell Invidious that it is behind a proxy that provides only
## HTTPS, so all links must use the https:// scheme. This

View File

@ -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

View File

@ -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:"

View File

@ -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)
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)}")
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)

View File

@ -225,9 +225,14 @@ module Invidious::Routes::PreferencesRoute
File.write("config/config.yml", CONFIG.to_yaml)
end
else
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
end
@ -261,8 +266,13 @@ module Invidious::Routes::PreferencesRoute
preferences.dark_mode = "dark"
end
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
env.redirect referer

View File

@ -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
)