From 437c6194d6cf78cb5e1bc06913f703ecdcd0fc8a Mon Sep 17 00:00:00 2001 From: NeskireDK <10115530+NeskireDK@users.noreply.github.com> Date: Thu, 13 Aug 2026 06:25:14 +0200 Subject: [PATCH] Auto-approve token consent for SSO sessions on listed origins A client behind the same authenticating proxy - Materialious behind Authelia - still had to click through the token consent page, although the proxy had just authenticated the same person for the same origin. trusted_header_auth.auto_approve_token_callbacks lists exact origins. When /authorize_token is reached with a callback on one of them, and the trusted header asserts the session user on that very request, the token is issued with the requested scopes and the browser is sent back to the callback exactly as the consent POST would have sent it. Every rail is a whole-value check: - the callback origin is normalized (scheme and host lowercased, default port dropped) and compared whole against normalized entries, so neither "https://yt.example.com.evil.tld" nor a path can match; - a URL carrying credentials is refused outright, which is what stops "https://yt.example.com@evil.tld"; - a password-login session never qualifies, header auth must be on, and an empty list (the default) leaves the consent page exactly as it was. The redirect URL is now built in one place, shared with the consent POST, so both hand the client the identical URL. Co-Authored-By: Claude Fable 5 --- src/invidious/routes/account.cr | 47 ++++++++++++++++++---------- src/invidious/trusted_header_auth.cr | 18 +++++++++++ 2 files changed, 49 insertions(+), 16 deletions(-) diff --git a/src/invidious/routes/account.cr b/src/invidious/routes/account.cr index 90e5fd440..91975fd34 100644 --- a/src/invidious/routes/account.cr +++ b/src/invidious/routes/account.cr @@ -219,12 +219,21 @@ module Invidious::Routes::Account scopes ||= [] of String callback_url = env.params.query["callback_url"]? + expire = env.params.query["expire"]?.try &.to_i? + + # ArikTube: a client the admin listed, reached through a session the + # trusted-header proxy vouches for, is answered as the consent POST + # below would answer it. The proxy already authenticated this person + # for this origin, so the extra click proves nothing. + if callback_url && Invidious::TrustedHeaderAuth.auto_approve_token?(env, user, callback_url) + access_token = generate_token(user.email, scopes, expire, HMAC_KEY) + return env.redirect self.token_callback_url(callback_url, user, access_token) + end + if callback_url callback_url = URI.parse(callback_url) end - expire = env.params.query["expire"]?.try &.to_i? - templated "user/authorize_token" end @@ -257,20 +266,7 @@ module Invidious::Routes::Account access_token = generate_token(user.email, scopes, expire, HMAC_KEY) if callback_url - access_token = URI.encode_www_form(access_token) - url = URI.parse(callback_url) - - if url.query - query = HTTP::Params.parse(url.query.not_nil!) - else - query = HTTP::Params.new - end - - query["token"] = access_token - query["username"] = URI.encode_path_segment(user.email) - url.query = query.to_s - - env.redirect url.to_s + env.redirect self.token_callback_url(callback_url, user, access_token) else csrf_token = "" env.set "access_token", access_token @@ -278,6 +274,25 @@ module Invidious::Routes::Account end end + # The client's callback URL with the granted token and the user name + # appended. Shared by the consent POST and the ArikTube auto-approval, so + # an approved client is handed exactly the same URL either way. + private def token_callback_url(callback_url : String, user : User, access_token : String) : String + url = URI.parse(callback_url) + + if url.query + query = HTTP::Params.parse(url.query.not_nil!) + else + query = HTTP::Params.new + end + + query["token"] = URI.encode_www_form(access_token) + query["username"] = URI.encode_path_segment(user.email) + url.query = query.to_s + + url.to_s + end + # ------------------- # Manage tokens # ------------------- diff --git a/src/invidious/trusted_header_auth.cr b/src/invidious/trusted_header_auth.cr index 564a3bde8..bd15d6c02 100644 --- a/src/invidious/trusted_header_auth.cr +++ b/src/invidious/trusted_header_auth.cr @@ -45,6 +45,24 @@ module Invidious::TrustedHeaderAuth email end + # Whether a token authorization request may skip the consent page. + # + # Only for a session the proxy vouches for, and only when the callback + # lands on an origin the admin listed. The token still goes to that origin + # and nowhere else, so an attacker who talks the browser into this route + # hands the token to the admin's own client, not to themselves. + def auto_approve_token?(env, user, callback_url : String?) : Bool + config = CONFIG.trusted_header_auth + + Invidious::ArikSettings.auto_approve_token?( + config.enabled, + config.auto_approve_token_callbacks, + asserted_email(env), + user.email, + callback_url + ) + end + # Whether `user` may change their password without typing the current one. # # The header has to assert this very user on this very request, so a