From c28be6720fe1c73a382935248391db238588f092 Mon Sep 17 00:00:00 2001 From: Fijxu Date: Mon, 19 Jan 2026 01:46:04 -0300 Subject: [PATCH] Add Invidious check ID for storyboards (used for video storyboards in the video timeline) The storyboards API endpoint is unprotected and it allows anyone, including bots and abusers to spam that endpoint without having to enter the `/watch` endpoint (most of the time, is protected by some sort of bot protection by current instance owners to prevent abuse) I wonder if there is a better way to do this, but this works fine --- assets/js/player.js | 2 +- config/config.example.yml | 10 +++++++++ src/invidious/config.cr | 3 +++ src/invidious/helpers/utils.cr | 26 +++++++++++++++++++---- src/invidious/routes/api/v1/videos.cr | 12 +++++++++++ src/invidious/views/components/player.ecr | 3 --- src/invidious/views/embed.ecr | 4 ++++ src/invidious/views/watch.ecr | 4 +++- 8 files changed, 55 insertions(+), 9 deletions(-) diff --git a/assets/js/player.js b/assets/js/player.js index ecdc04485..7c3481094 100644 --- a/assets/js/player.js +++ b/assets/js/player.js @@ -434,7 +434,7 @@ if (!video_data.params.listen && video_data.params.quality === 'dash') { } player.vttThumbnails({ - src: '/api/v1/storyboards/' + video_data.id + '?height=90', + src: '/api/v1/storyboards/' + video_data.id + '?height=90' + `${video_data.invidious_companion_check_id ? `&check=${video_data.invidious_companion_check_id}` : ""}`, showTimestamp: true }); diff --git a/config/config.example.yml b/config/config.example.yml index 7cc480c64..c058c56c2 100644 --- a/config/config.example.yml +++ b/config/config.example.yml @@ -89,6 +89,16 @@ db: ## #invidious_companion_key: "CHANGE_ME!!" +## +## API key for Invidious companion, used for securing the communication +## between Invidious and Invidious companion. +## The key needs to be exactly 16 characters long. +## +## Accepted values: true, false +## Default: true +## +#invidious_companion_verify_requests: true + ######################################### # # Server config diff --git a/src/invidious/config.cr b/src/invidious/config.cr index 7853d9a3b..2ee6e89c2 100644 --- a/src/invidious/config.cr +++ b/src/invidious/config.cr @@ -173,6 +173,9 @@ class Config # Invidious companion API key property invidious_companion_key : String = "" + # Verify requests on endpoints that use Invidious companion + property invidious_companion_verify_requests : Bool = true + # Saved cookies in "name1=value1; name2=value2..." format @[YAML::Field(converter: Preferences::StringToCookies)] property cookies : HTTP::Cookies = HTTP::Cookies.new diff --git a/src/invidious/helpers/utils.cr b/src/invidious/helpers/utils.cr index 5637e5338..29936010c 100644 --- a/src/invidious/helpers/utils.cr +++ b/src/invidious/helpers/utils.cr @@ -384,9 +384,13 @@ def parse_link_endpoint(endpoint : JSON::Any, text : String, video_id : String) return text end -def encrypt_ecb_without_salt(data, key) +def ecb_without_salt(data, key, encrypt : Bool) cipher = OpenSSL::Cipher.new("aes-128-ecb") - cipher.encrypt + if encrypt + cipher.encrypt + else + cipher.decrypt + end cipher.key = key io = IO::Memory.new @@ -394,11 +398,25 @@ def encrypt_ecb_without_salt(data, key) io.write(cipher.final) io.rewind - return io + if encrypt + return io + else + return io.gets_to_end + end end def invidious_companion_encrypt(data) timestamp = Time.utc.to_unix - encrypted_data = encrypt_ecb_without_salt("#{timestamp}|#{data}", CONFIG.invidious_companion_key) + encrypted_data = ecb_without_salt("#{timestamp}|#{data}", CONFIG.invidious_companion_key, encrypt: true) return Base64.urlsafe_encode(encrypted_data) end + +def invidious_companion_decrypt(check_id) + check_id_decoded = Base64.decode_string(check_id) + begin + decrypted_data = ecb_without_salt(check_id_decoded, CONFIG.invidious_companion_key, encrypt: false) + rescue + return nil + end + return decrypted_data.as(String).split("|") +end diff --git a/src/invidious/routes/api/v1/videos.cr b/src/invidious/routes/api/v1/videos.cr index 6a3eb8ae3..6c191ba2a 100644 --- a/src/invidious/routes/api/v1/videos.cr +++ b/src/invidious/routes/api/v1/videos.cr @@ -181,6 +181,18 @@ module Invidious::Routes::API::V1::Videos id = env.params.url["id"] region = env.params.query["region"]? + if CONFIG.invidious_companion.present? && CONFIG.invidious_companion_verify_requests + invidious_companion_check_id = env.params.query["check"]? + if check_id = invidious_companion_check_id + video_id = invidious_companion_decrypt(check_id).try &.[1] + if id != video_id + haltf env, 401, "ID incorrect." + end + else + haltf env, 401, "No check ID." + end + end + begin video = get_video(id, region: region) rescue ex : NotFoundException diff --git a/src/invidious/views/components/player.ecr b/src/invidious/views/components/player.ecr index 26ba65f74..a740f02e8 100644 --- a/src/invidious/views/components/player.ecr +++ b/src/invidious/views/components/player.ecr @@ -1,6 +1,3 @@ -<% - invidious_companion_check_id = invidious_companion_encrypt(video.id) if invidious_companion -%>