fivem icon indicating copy to clipboard operation
fivem copied to clipboard

Custom Steam API domain or proxy support?

Open kasuganosoras opened this issue 2 years ago • 6 comments

There are some political reasons cause Steam API blocked in my country, the FiveM server will throw the SSL connect error randomly when the player join, so the player need to join server many times.

image

Is that possible to add an option, that allow user to change the Steam API domain or add proxy support for it?

Thanks.

kasuganosoras avatar Apr 22 '22 08:04 kasuganosoras

I'm not sure what other Steam API domains exist that you would even use - partner.steam-api.com is very restricted, might it be better to just disable Steam API use (steam_webapikey none) and use other identifiers instead?

blattersturm avatar Apr 22 '22 11:04 blattersturm

umm...I mean, is that possible to change the Steam API domain to another one using convar? Such as steam_apiDomain steamproxy.example.com. Then I can run a reverse proxy on steamproxy.example.com to bypass the steam domain censorship.

https://github.com/citizenfx/fivem/blob/master/code/components/citizen-server-impl/src/SteamIdentityProvider.cpp#L95

Like this:

Code
/*
* This file is part of FiveM: https://fivem.net/
*
* See LICENSE and MENTIONS in the root of the source tree for information
* regarding licensing.
*/

#include "StdInc.h"
#include <ServerIdentityProvider.h>

#define STEAM_APPID 218

// this imports pplxtasks somewhere?
#define _PPLTASK_ASYNC_LOGGING 0

#include <cpr/cpr.h>
#include <json.hpp>

#include <tbb/concurrent_queue.h>

#include <TcpServerManager.h>
#include <ServerInstanceBase.h>

#include <HttpClient.h>

static std::shared_ptr<ConVar<std::string>> g_steamApiKey;
static std::shared_ptr<ConVar<std::string>> g_steamApiDomain;

using json = nlohmann::json;

template<typename Handle, class Class, void(Class::*Callable)()>
void UvCallback(Handle* handle)
{
	(reinterpret_cast<Class*>(handle->data)->*Callable)();
}

static InitFunction initFunction([]()
{
	static fx::ServerInstanceBase* serverInstance;
	
	static struct SteamIdProvider : public fx::ServerIdentityProviderBase
	{
		HttpClient* httpClient;

		SteamIdProvider()
		{
			httpClient = new HttpClient();
		}

		virtual std::string GetIdentifierPrefix() override
		{
			return "steam";
		}

		virtual int GetVarianceLevel() override
		{
			return 1;
		}

		virtual int GetTrustLevel() override
		{
			return 5;
		}

		virtual void RunAuthentication(const fx::ClientSharedPtr& clientPtr, const std::map<std::string, std::string>& postMap, const std::function<void(boost::optional<std::string>)>& cb) override
		{
			auto it = postMap.find("authTicket");

			if (it == postMap.end())
			{
				cb({});
				return;
			}

			if (g_steamApiKey->GetValue().empty())
			{
				trace("A client has tried to authenticate using Steam, but `steam_webApiKey` is unset. Please set steam_webApiKey to a Steam Web API key as registered on "
					"Valve's ^4https://steamcommunity.com/dev/apikey^7 web page. Steam identifier information will be unavailable otherwise.\n");

				trace("To suppress this message, `set steam_webApiKey \"none\"`.\n");

				cb({});
				return;
			}

			if (g_steamApiKey->GetValue() == "none" || g_steamApiDomain->GetValue() == "none")
			{
				cb({});
				return;
			}

			HttpRequestOptions opts;
			opts.addErrorBody = true;

			httpClient->DoGetRequest(
				fmt::format("https://{0}/ISteamUserAuth/AuthenticateUserTicket/v1/?key={1}&appid={2}&ticket={3}", g_steamApiDomain->GetValue(), g_steamApiKey->GetValue(), STEAM_APPID, it->second),
				opts,
				[this, cb, clientPtr](bool success, const char* data, size_t size)
				{
					std::string response{ data, size };

					try
					{
						if (success)
						{
							json object = json::parse(response)["response"];

							if (object.find("error") != object.end())
							{
								cb({ "Steam rejected authentication: " + object["error"]["errordesc"].get<std::string>() });
								return;
							}

							uint64_t steamId = strtoull(object["params"]["steamid"].get<std::string>().c_str(), nullptr, 10);
							clientPtr->AddIdentifier(fmt::sprintf("steam:%015llx", steamId));
						}
						else
						{
							trace("Steam authentication for %s^7 failed: %s\n", clientPtr->GetName(), response);
							if (response.find("<pre>key=</pre>") != std::string::npos)
							{
								trace("^2Your Steam Web API key may be invalid. This can happen if you've changed your Steam password, Steam Guard details or changed/reverted your server's .cfg file. Please re-register a key on ^4https://steamcommunity.com/dev/apikey^2 and insert it in your server startup file.^7\n");
							}
						}

						cb({});
					}
					catch (std::exception & e)
					{
						cb({ fmt::sprintf("SteamIdProvider failure: %s", e.what()) });
					}
				}
			);
		}
	} idp;

	fx::ServerInstanceBase::OnServerCreate.Connect([](fx::ServerInstanceBase* instance)
	{
		g_steamApiKey = instance->AddVariable<std::string>("steam_webApiKey", ConVar_None, "");
		g_steamApiDomain = instance->AddVariable<std::string>("steam_webApiDomain", ConVar_None, "api.steampowered.com");

		serverInstance = instance;
	});

	fx::RegisterServerIdentityProvider(&idp);
}, 152);

kasuganosoras avatar Apr 22 '22 13:04 kasuganosoras

This is obviously not possible due to security concerns.

Disquse avatar Apr 23 '22 07:04 Disquse

This is obviously not possible due to security concerns.

.. I'm not sure how 'security concerns' are related here, though.

blattersturm avatar Apr 24 '22 11:04 blattersturm

This is obviously not possible due to security concerns.

Why 'security concerns'...? I don't think this will cause any security issues.

On Windows, you can use a hosts file and a self-signed SSL certificate to redirect the Steam API requests to a custom IP, but on Linux it doesn't work because it's running in the proot. So I think that can have a better and smarter way to solve this issue.

kasuganosoras avatar Apr 24 '22 12:04 kasuganosoras

I misinterpret a bit what this code was meant to do, nevermind! 😅

Disquse avatar Apr 24 '22 15:04 Disquse