Validate that user id matches token

This commit is contained in:
WarmUpTill 2026-03-17 20:36:53 +01:00
parent 49cd54c6c3
commit 54a0d03ca9
8 changed files with 147 additions and 52 deletions

View File

@ -68,10 +68,15 @@ void ContentClassification::SetContentClassification(
data = obs_data_create();
obs_data_set_array(data, "content_classification_labels", ccls);
const auto id = token.GetUserID();
if (!id) {
vblog(LOG_INFO, "%s skip - invalid user id", __func__);
return;
}
auto result = SendPatchRequest(token, "https://api.twitch.tv",
"/helix/channels",
{{"broadcaster_id", token.GetUserID()}},
data.Get());
{{"broadcaster_id", *id}}, data.Get());
if (result.status != 204) {
blog(LOG_INFO,

View File

@ -189,12 +189,17 @@ void LanguageSelection::Save(obs_data_t *obj) const
void LanguageSelection::SetStreamLanguage(const TwitchToken &token) const
{
const auto id = token.GetUserID();
if (!id) {
vblog(LOG_INFO, "%s skip - invalid user id", __func__);
return;
}
OBSDataAutoRelease data = obs_data_create();
obs_data_set_string(data, "broadcaster_language", _language.c_str());
auto result = SendPatchRequest(token, "https://api.twitch.tv",
"/helix/channels",
{{"broadcaster_id", token.GetUserID()}},
data.Get());
{{"broadcaster_id", *id}}, data.Get());
if (result.status != 204) {
blog(LOG_INFO, "Failed to set stream language! (%d)",

View File

@ -123,12 +123,17 @@ void MacroActionTwitch::SetStreamTitle(
return;
}
const auto id = token->GetUserID();
if (!id) {
vblog(LOG_INFO, "%s skip - invalid user id", __func__);
return;
}
OBSDataAutoRelease data = obs_data_create();
obs_data_set_string(data, "title", _streamTitle.c_str());
auto result = SendPatchRequest(*token, "https://api.twitch.tv",
"/helix/channels",
{{"broadcaster_id", token->GetUserID()}},
data.Get());
{{"broadcaster_id", *id}}, data.Get());
if (result.status != 204) {
blog(LOG_INFO, "Failed to set stream title! (%d)",
@ -143,13 +148,18 @@ void MacroActionTwitch::SetStreamCategory(
return;
}
const auto id = token->GetUserID();
if (!id) {
vblog(LOG_INFO, "%s skip - invalid user id", __func__);
return;
}
OBSDataAutoRelease data = obs_data_create();
obs_data_set_string(data, "game_id",
std::to_string(_category.id).c_str());
auto result = SendPatchRequest(*token, "https://api.twitch.tv",
"/helix/channels",
{{"broadcaster_id", token->GetUserID()}},
data.Get());
{{"broadcaster_id", *id}}, data.Get());
if (result.status != 204) {
blog(LOG_INFO, "Failed to set stream category! (%d)",
@ -160,8 +170,14 @@ void MacroActionTwitch::SetStreamCategory(
void MacroActionTwitch::CreateStreamMarker(
const std::shared_ptr<TwitchToken> &token) const
{
const auto id = token->GetUserID();
if (!id) {
vblog(LOG_INFO, "%s skip - invalid user id", __func__);
return;
}
OBSDataAutoRelease data = obs_data_create();
obs_data_set_string(data, "user_id", token->GetUserID().c_str());
obs_data_set_string(data, "user_id", id->c_str());
if (!std::string(_markerDescription).empty()) {
obs_data_set_string(data, "description",
@ -179,12 +195,17 @@ void MacroActionTwitch::CreateStreamMarker(
void MacroActionTwitch::CreateStreamClip(
const std::shared_ptr<TwitchToken> &token) const
{
const auto id = token->GetUserID();
if (!id) {
vblog(LOG_INFO, "%s skip - invalid user id", __func__);
return;
}
auto hasDelay = _clipHasDelay ? "true" : "false";
auto result = SendPostRequest(*token, "https://api.twitch.tv",
"/helix/clips",
{{"broadcaster_id", token->GetUserID()},
{"has_delay", hasDelay}});
auto result = SendPostRequest(
*token, "https://api.twitch.tv", "/helix/clips",
{{"broadcaster_id", *id}, {"has_delay", hasDelay}});
if (result.status != 202) {
blog(LOG_INFO, "Failed to create clip! (%d)", result.status);
@ -194,8 +215,14 @@ void MacroActionTwitch::CreateStreamClip(
void MacroActionTwitch::StartCommercial(
const std::shared_ptr<TwitchToken> &token) const
{
const auto id = token->GetUserID();
if (!id) {
vblog(LOG_INFO, "%s skip - invalid user id", __func__);
return;
}
OBSDataAutoRelease data = obs_data_create();
obs_data_set_string(data, "broadcaster_id", token->GetUserID().c_str());
obs_data_set_string(data, "broadcaster_id", id->c_str());
obs_data_set_int(data, "length", _duration.Seconds());
auto result = SendPostRequest(*token, "https://api.twitch.tv",
"/helix/channels/commercial", {},
@ -227,17 +254,21 @@ void MacroActionTwitch::StartCommercial(
void MacroActionTwitch::SendChatAnnouncement(
const std::shared_ptr<TwitchToken> &token) const
{
const auto id = token->GetUserID();
if (!id) {
vblog(LOG_INFO, "%s skip - invalid user id", __func__);
return;
}
OBSDataAutoRelease data = obs_data_create();
obs_data_set_string(data, "message", _announcementMessage.c_str());
obs_data_set_string(
data, "color",
announcementColorsTwitch.at(_announcementColor).c_str());
auto userId = token->GetUserID();
auto result = SendPostRequest(
*token, "https://api.twitch.tv", "/helix/chat/announcements",
{{"broadcaster_id", userId}, {"moderator_id", userId}},
data.Get());
{{"broadcaster_id", *id}, {"moderator_id", *id}}, data.Get());
if (result.status != 204) {
blog(LOG_INFO, "Failed to send chat announcement! (%d)",
@ -248,14 +279,18 @@ void MacroActionTwitch::SendChatAnnouncement(
void MacroActionTwitch::SetChatEmoteOnlyMode(
const std::shared_ptr<TwitchToken> &token, bool enable) const
{
const auto id = token->GetUserID();
if (!id) {
vblog(LOG_INFO, "%s skip - invalid user id", __func__);
return;
}
OBSDataAutoRelease data = obs_data_create();
obs_data_set_bool(data, "emote_mode", enable);
auto userId = token->GetUserID();
auto result = SendPatchRequest(
*token, "https://api.twitch.tv", "/helix/chat/settings",
{{"broadcaster_id", userId}, {"moderator_id", userId}},
data.Get());
{{"broadcaster_id", *id}, {"moderator_id", *id}}, data.Get());
if (result.status != 200) {
blog(LOG_INFO, "Failed to %s chat's emote-only mode! (%d)",
@ -265,9 +300,14 @@ void MacroActionTwitch::SetChatEmoteOnlyMode(
void MacroActionTwitch::StartRaid(const std::shared_ptr<TwitchToken> &token)
{
const auto id = token->GetUserID();
if (!id) {
vblog(LOG_INFO, "%s skip - invalid user id", __func__);
return;
}
OBSDataAutoRelease data = obs_data_create();
obs_data_set_string(data, "from_broadcaster_id",
token->GetUserID().c_str());
obs_data_set_string(data, "from_broadcaster_id", id->c_str());
obs_data_set_string(data, "to_broadcaster_id",
_channel.GetUserID(*token).c_str());
auto result = SendPostRequest(*token, "https://api.twitch.tv",
@ -398,6 +438,12 @@ bool MacroActionTwitch::ResolveVariableSelectionToRewardId(
void MacroActionTwitch::GetRewardInfo(const std::shared_ptr<TwitchToken> &token)
{
const auto id = token->GetUserID();
if (!id) {
vblog(LOG_INFO, "%s skip - invalid user id", __func__);
return;
}
if (_useVariableForRewardSelection &&
!ResolveVariableSelectionToRewardId(token)) {
if (VerboseLoggingEnabled()) {
@ -416,7 +462,7 @@ void MacroActionTwitch::GetRewardInfo(const std::shared_ptr<TwitchToken> &token)
}
httplib::Params params = {
{"broadcaster_id", token->GetUserID()},
{"broadcaster_id", *id},
{"id", _useVariableForRewardSelection ? _lastResolvedRewardId
: _pointsReward.id},
};

View File

@ -1096,6 +1096,12 @@ void MacroConditionTwitch::AddChannelGenericEventSubscription(
return;
}
const auto id = token->GetUserID();
if (!id) {
vblog(LOG_INFO, "%s skip - invalid user id", __func__);
return;
}
const auto channelID = _channel.GetUserID(*token);
if (!TwitchChannel::IsValid(channelID)) {
vblog(LOG_INFO, "skip %s because of invalid channel selection",
@ -1114,7 +1120,7 @@ void MacroConditionTwitch::AddChannelGenericEventSubscription(
if (includeModeratorId) {
obs_data_set_string(condition, "moderator_user_id",
token->GetUserID().c_str());
id->c_str());
}
obs_data_apply(condition, extraConditions);

View File

@ -26,6 +26,12 @@ void TwitchTagList::Save(obs_data_t *obj) const
void TwitchTagList::SetStreamTags(const TwitchToken &token) const
{
const auto id = token.GetUserID();
if (!id) {
vblog(LOG_INFO, "%s skip - invalid user id", __func__);
return;
}
nlohmann::json j;
j["tags"] = toVector();
@ -47,8 +53,7 @@ void TwitchTagList::SetStreamTags(const TwitchToken &token) const
auto result = SendPatchRequest(token, "https://api.twitch.tv",
"/helix/channels",
{{"broadcaster_id", token.GetUserID()}},
j.dump());
{{"broadcaster_id", *id}}, j.dump());
if (result.status != 204) {
blog(LOG_INFO, "Failed to set stream tags! (%d)",

View File

@ -206,7 +206,9 @@ void TwitchToken::Save(obs_data_t *obj) const
{
Item::Save(obj);
obs_data_set_string(obj, "token", _token.c_str());
obs_data_set_string(obj, "userID", _userID.c_str());
if (_userID) {
obs_data_set_string(obj, "userID", _userID->c_str());
}
obs_data_set_bool(obj, "validateEventSubTimestamps",
_validateEventSubTimestamps);
obs_data_set_bool(obj, "warnIfInvalid", _warnIfInvalid);
@ -266,7 +268,7 @@ void TwitchToken::SetToken(const std::string &value)
SendGetRequest(*this, "https://api.twitch.tv", "/helix/users");
if (res.status != 200) {
blog(LOG_WARNING, "failed to get Twitch user id from token!");
_userID = -1;
_userID = {};
return;
}
@ -318,12 +320,35 @@ bool TwitchToken::IsValid(bool forceUpdate) const
cli.Get("/oauth2/validate", httplib::Params{}, headers);
_lastValidityCheckTime = std::chrono::system_clock::now();
_lastValidityCheckValue = _token;
_lastValidityCheckResult = response && response->status == 200;
if (!_lastValidityCheckResult) {
if (!response || response->status != 200) {
blog(LOG_INFO, "Twitch token %s is not valid!",
_name.c_str());
_lastValidityCheckResult = false;
return false;
}
return _lastValidityCheckResult;
OBSDataAutoRelease replyData =
obs_data_create_from_json(response->body.c_str());
const char *id = obs_data_get_string(replyData, "user_id");
if (!id) {
blog(LOG_INFO,
"Twitch token %s does validity check did not report user_id! Assume invalid!",
_name.c_str());
_lastValidityCheckResult = false;
return false;
}
if (_userID && _userID != id) {
blog(LOG_INFO,
"Twitch token %s does not match expected user (got %s, expected %s)!",
_name.c_str(), id, _userID->c_str());
_lastValidityCheckResult = false;
return false;
}
_lastValidityCheckResult = true;
return true;
};
const bool tokenChanged = _lastValidityCheckValue != _token;
@ -729,6 +754,7 @@ void TwitchTokenSettingsDialog::RequestToken()
auto scope = QString::fromStdString(
generateScopeString(GetEnabledOptions()));
_validationTimer.stop();
_tokenGrabber.SetTokenScope(scope);
_tokenGrabber.start();
_tokenStatus->setText(obs_module_text(
@ -766,6 +792,7 @@ void TwitchTokenSettingsDialog::GotToken(const std::optional<QString> &value)
Q_ARG(const QString &, name));
SetTokenInfoVisible(true);
_requestToken->setEnabled(true);
_validationTimer.start();
}
std::set<TokenOption> TwitchTokenSettingsDialog::GetEnabledOptions()

View File

@ -51,7 +51,7 @@ public:
void SetToken(const std::string &);
bool IsEmpty() const { return _token.empty(); }
std::optional<std::string> GetToken() const;
std::string GetUserID() const { return _userID; }
std::optional<std::string> GetUserID() const { return _userID; }
std::shared_ptr<EventSub> GetEventSub();
bool ValidateTimestamps() const { return _validateEventSubTimestamps; }
bool IsValid(bool forceUpdate = false) const;
@ -65,7 +65,7 @@ private:
mutable std::string _lastValidityCheckValue;
mutable bool _lastValidityCheckResult = false;
mutable std::chrono::system_clock::time_point _lastValidityCheckTime;
std::string _userID;
std::optional<std::string> _userID;
std::set<TokenOption> _tokenOptions = TokenOption::GetAllTokenOptions();
std::shared_ptr<EventSub> _eventSub;
bool _validateEventSubTimestamps = false;

View File

@ -153,6 +153,25 @@ static QStringList getCellLabels(TwitchToken *token, bool addName = true)
return result;
}
static void updateConnectionStatus(QTableWidget *table)
{
for (int row = 0; row < table->rowCount(); row++) {
auto item = table->item(row, 0);
if (!item) {
continue;
}
auto weakToken = GetWeakTwitchTokenByQString(item->text());
auto token = weakToken.lock();
if (!token) {
continue;
}
UpdateItemTableRow(table, row,
getCellLabels(token.get(), false));
}
}
static void openSettingsDialog()
{
auto selectedRows =
@ -174,6 +193,7 @@ static void openSettingsDialog()
TwitchTokenSettingsDialog::AskForSettings(GetSettingsWindow(),
*token.get());
updateConnectionStatus(tabWidget->Table());
}
static const QStringList headers =
@ -202,25 +222,6 @@ TwitchConnectionsTable::TwitchConnectionsTable(QTabWidget *parent)
SetHelpVisible(GetTwitchTokens().empty());
}
static void updateConnectionStatus(QTableWidget *table)
{
for (int row = 0; row < table->rowCount(); row++) {
auto item = table->item(row, 0);
if (!item) {
continue;
}
auto weakToken = GetWeakTwitchTokenByQString(item->text());
auto token = weakToken.lock();
if (!token) {
continue;
}
UpdateItemTableRow(table, row,
getCellLabels(token.get(), false));
}
}
static QStringList getInvalidTokens()
{
QStringList tokens;