mirror of
https://github.com/iv-org/invidious.git
synced 2026-09-06 00:52:45 -05:00
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 <noreply@anthropic.com>
This commit is contained in:
parent
fae22d037e
commit
437c6194d6
@ -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
|
||||
# -------------------
|
||||
|
||||
@ -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
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user