Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions config/config.example.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions src/invidious/config.cr
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions src/invidious/routes/before_all.cr
Original file line number Diff line number Diff line change
Expand Up @@ -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:"
Expand Down
13 changes: 11 additions & 2 deletions src/invidious/routes/login.cr
Original file line number Diff line number Diff line change
Expand Up @@ -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")

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down
14 changes: 12 additions & 2 deletions src/invidious/routes/preferences.cr
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
20 changes: 17 additions & 3 deletions src/invidious/user/cookies.cr
Original file line number Diff line number Diff line change
Expand Up @@ -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
@@secure = false
end

Comment on lines +14 to +20
Copy link
Member Author

@Fijxu Fijxu Feb 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just as a note, when using .i2p domains, secure has to be false, otherwise, the browser using an I2P proxy will reject the cookie. But this seems to be too hardcoded, so I was thinking about modifying alternative_domains to let the instance administrator to define if they want the Cookie to be secure or not secure, because it really depends on where they are hosting Invidious and how the browser of the client is configured. Something like this basically, but I'm not too sure so suggestions are appreciated!:

alternative_domains:
  - something.i2p
    secure: false
  - othersomething.onion
    secure: true
  - alternative-clearnet.lol
    secure: true

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
)
Expand All @@ -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
@@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
)
Expand Down