Always require lobby authentication for lobby matches, refs #3549 / rP21520 / D897.
[0ad.git] / source / lobby / scripting / JSInterface_Lobby.cpp
blobe3a90f2ad9f4de3918c5d562576b967598348e68
1 /* Copyright (C) 2018 Wildfire Games.
2 * This file is part of 0 A.D.
4 * 0 A.D. is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, either version 2 of the License, or
7 * (at your option) any later version.
9 * 0 A.D. is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.
18 #include "precompiled.h"
20 #include "JSInterface_Lobby.h"
22 #include "gui/GUIManager.h"
23 #include "lib/utf8.h"
24 #include "lobby/IXmppClient.h"
25 #include "network/NetServer.h"
26 #include "ps/CLogger.h"
27 #include "ps/CStr.h"
28 #include "ps/Util.h"
29 #include "scriptinterface/ScriptInterface.h"
30 #include "scriptinterface/ScriptVal.h"
32 #include "third_party/encryption/pkcs5_pbkdf2.h"
34 #include <string>
36 void JSI_Lobby::RegisterScriptFunctions(const ScriptInterface& scriptInterface)
38 // Lobby functions
39 scriptInterface.RegisterFunction<bool, &JSI_Lobby::HasXmppClient>("HasXmppClient");
40 scriptInterface.RegisterFunction<bool, &JSI_Lobby::IsRankedGame>("IsRankedGame");
41 scriptInterface.RegisterFunction<void, bool, &JSI_Lobby::SetRankedGame>("SetRankedGame");
42 #if CONFIG2_LOBBY // Allow the lobby to be disabled
43 scriptInterface.RegisterFunction<void, std::wstring, std::wstring, std::wstring, std::wstring, int, &JSI_Lobby::StartXmppClient>("StartXmppClient");
44 scriptInterface.RegisterFunction<void, std::wstring, std::wstring, &JSI_Lobby::StartRegisterXmppClient>("StartRegisterXmppClient");
45 scriptInterface.RegisterFunction<void, &JSI_Lobby::StopXmppClient>("StopXmppClient");
46 scriptInterface.RegisterFunction<void, &JSI_Lobby::ConnectXmppClient>("ConnectXmppClient");
47 scriptInterface.RegisterFunction<void, &JSI_Lobby::DisconnectXmppClient>("DisconnectXmppClient");
48 scriptInterface.RegisterFunction<bool, &JSI_Lobby::IsXmppClientConnected>("IsXmppClientConnected");
49 scriptInterface.RegisterFunction<void, &JSI_Lobby::SendGetBoardList>("SendGetBoardList");
50 scriptInterface.RegisterFunction<void, std::wstring, &JSI_Lobby::SendGetProfile>("SendGetProfile");
51 scriptInterface.RegisterFunction<void, JS::HandleValue, &JSI_Lobby::SendRegisterGame>("SendRegisterGame");
52 scriptInterface.RegisterFunction<void, JS::HandleValue, &JSI_Lobby::SendGameReport>("SendGameReport");
53 scriptInterface.RegisterFunction<void, &JSI_Lobby::SendUnregisterGame>("SendUnregisterGame");
54 scriptInterface.RegisterFunction<void, std::wstring, std::wstring, &JSI_Lobby::SendChangeStateGame>("SendChangeStateGame");
55 scriptInterface.RegisterFunction<JS::Value, &JSI_Lobby::GetPlayerList>("GetPlayerList");
56 scriptInterface.RegisterFunction<void, &JSI_Lobby::LobbyClearPresenceUpdates>("LobbyClearPresenceUpdates");
57 scriptInterface.RegisterFunction<JS::Value, &JSI_Lobby::GetGameList>("GetGameList");
58 scriptInterface.RegisterFunction<JS::Value, &JSI_Lobby::GetBoardList>("GetBoardList");
59 scriptInterface.RegisterFunction<JS::Value, &JSI_Lobby::GetProfile>("GetProfile");
60 scriptInterface.RegisterFunction<JS::Value, &JSI_Lobby::LobbyGuiPollNewMessage>("LobbyGuiPollNewMessage");
61 scriptInterface.RegisterFunction<JS::Value, &JSI_Lobby::LobbyGuiPollHistoricMessages>("LobbyGuiPollHistoricMessages");
62 scriptInterface.RegisterFunction<void, std::wstring, &JSI_Lobby::LobbySendMessage>("LobbySendMessage");
63 scriptInterface.RegisterFunction<void, std::wstring, &JSI_Lobby::LobbySetPlayerPresence>("LobbySetPlayerPresence");
64 scriptInterface.RegisterFunction<void, std::wstring, &JSI_Lobby::LobbySetNick>("LobbySetNick");
65 scriptInterface.RegisterFunction<std::wstring, &JSI_Lobby::LobbyGetNick>("LobbyGetNick");
66 scriptInterface.RegisterFunction<void, std::wstring, std::wstring, &JSI_Lobby::LobbyKick>("LobbyKick");
67 scriptInterface.RegisterFunction<void, std::wstring, std::wstring, &JSI_Lobby::LobbyBan>("LobbyBan");
68 scriptInterface.RegisterFunction<std::wstring, std::wstring, &JSI_Lobby::LobbyGetPlayerPresence>("LobbyGetPlayerPresence");
69 scriptInterface.RegisterFunction<std::wstring, std::wstring, &JSI_Lobby::LobbyGetPlayerRole>("LobbyGetPlayerRole");
70 scriptInterface.RegisterFunction<std::wstring, std::wstring, std::wstring, &JSI_Lobby::EncryptPassword>("EncryptPassword");
71 scriptInterface.RegisterFunction<std::wstring, &JSI_Lobby::LobbyGetRoomSubject>("LobbyGetRoomSubject");
72 #endif // CONFIG2_LOBBY
75 bool JSI_Lobby::HasXmppClient(ScriptInterface::CxPrivate* UNUSED(pCxPrivate))
77 return g_XmppClient;
80 bool JSI_Lobby::IsRankedGame(ScriptInterface::CxPrivate* UNUSED(pCxPrivate))
82 return g_rankedGame;
85 void JSI_Lobby::SetRankedGame(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), bool isRanked)
87 g_rankedGame = isRanked;
90 #if CONFIG2_LOBBY
92 void JSI_Lobby::StartXmppClient(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), const std::wstring& username, const std::wstring& password, const std::wstring& room, const std::wstring& nick, int historyRequestSize)
94 ENSURE(!g_XmppClient);
96 g_XmppClient = IXmppClient::create(utf8_from_wstring(username), utf8_from_wstring(password),
97 utf8_from_wstring(room), utf8_from_wstring(nick), historyRequestSize);
98 g_rankedGame = true;
101 void JSI_Lobby::StartRegisterXmppClient(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), const std::wstring& username, const std::wstring& password)
103 ENSURE(!g_XmppClient);
105 g_XmppClient = IXmppClient::create(utf8_from_wstring(username), utf8_from_wstring(password),
106 "", "", 0, true);
109 void JSI_Lobby::StopXmppClient(ScriptInterface::CxPrivate* UNUSED(pCxPrivate))
111 ENSURE(g_XmppClient);
112 SAFE_DELETE(g_XmppClient);
113 g_rankedGame = false;
116 void JSI_Lobby::ConnectXmppClient(ScriptInterface::CxPrivate* UNUSED(pCxPrivate))
118 ENSURE(g_XmppClient);
119 g_XmppClient->connect();
122 void JSI_Lobby::DisconnectXmppClient(ScriptInterface::CxPrivate* UNUSED(pCxPrivate))
124 ENSURE(g_XmppClient);
125 g_XmppClient->disconnect();
128 bool JSI_Lobby::IsXmppClientConnected(ScriptInterface::CxPrivate* UNUSED(pCxPrivate))
130 ENSURE(g_XmppClient);
131 return g_XmppClient->isConnected();
134 void JSI_Lobby::SendGetBoardList(ScriptInterface::CxPrivate* UNUSED(pCxPrivate))
136 if (!g_XmppClient)
137 return;
138 g_XmppClient->SendIqGetBoardList();
141 void JSI_Lobby::SendGetProfile(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), const std::wstring& player)
143 if (!g_XmppClient)
144 return;
145 g_XmppClient->SendIqGetProfile(utf8_from_wstring(player));
148 void JSI_Lobby::SendGameReport(ScriptInterface::CxPrivate* pCxPrivate, JS::HandleValue data)
150 if (!g_XmppClient)
151 return;
153 g_XmppClient->SendIqGameReport(*(pCxPrivate->pScriptInterface), data);
156 void JSI_Lobby::SendRegisterGame(ScriptInterface::CxPrivate* pCxPrivate, JS::HandleValue data)
158 if (!g_XmppClient)
159 return;
161 // Prevent JS mods to register matches in the lobby that were started with lobby authentication disabled
162 if (!g_NetServer || !g_NetServer->UseLobbyAuth())
164 LOGERROR("Registering games in the lobby requires lobby authentication to be enabled!");
165 return;
168 g_XmppClient->SendIqRegisterGame(*(pCxPrivate->pScriptInterface), data);
171 void JSI_Lobby::SendUnregisterGame(ScriptInterface::CxPrivate* UNUSED(pCxPrivate))
173 if (!g_XmppClient)
174 return;
175 g_XmppClient->SendIqUnregisterGame();
178 void JSI_Lobby::SendChangeStateGame(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), const std::wstring& nbp, const std::wstring& players)
180 if (!g_XmppClient)
181 return;
182 g_XmppClient->SendIqChangeStateGame(utf8_from_wstring(nbp), utf8_from_wstring(players));
185 JS::Value JSI_Lobby::GetPlayerList(ScriptInterface::CxPrivate* pCxPrivate)
187 if (!g_XmppClient)
188 return JS::UndefinedValue();
190 JSContext* cx = pCxPrivate->pScriptInterface->GetContext();
191 JSAutoRequest rq(cx);
193 JS::RootedValue playerList(cx);
194 g_XmppClient->GUIGetPlayerList(*(pCxPrivate->pScriptInterface), &playerList);
196 return playerList;
199 void JSI_Lobby::LobbyClearPresenceUpdates(ScriptInterface::CxPrivate* UNUSED(pCxPrivate))
201 if (!g_XmppClient)
202 return;
204 g_XmppClient->ClearPresenceUpdates();
207 JS::Value JSI_Lobby::GetGameList(ScriptInterface::CxPrivate* pCxPrivate)
209 if (!g_XmppClient)
210 return JS::UndefinedValue();
212 JSContext* cx = pCxPrivate->pScriptInterface->GetContext();
213 JSAutoRequest rq(cx);
215 JS::RootedValue gameList(cx);
216 g_XmppClient->GUIGetGameList(*(pCxPrivate->pScriptInterface), &gameList);
218 return gameList;
221 JS::Value JSI_Lobby::GetBoardList(ScriptInterface::CxPrivate* pCxPrivate)
223 if (!g_XmppClient)
224 return JS::UndefinedValue();
226 JSContext* cx = pCxPrivate->pScriptInterface->GetContext();
227 JSAutoRequest rq(cx);
229 JS::RootedValue boardList(cx);
230 g_XmppClient->GUIGetBoardList(*(pCxPrivate->pScriptInterface), &boardList);
232 return boardList;
235 JS::Value JSI_Lobby::GetProfile(ScriptInterface::CxPrivate* pCxPrivate)
237 if (!g_XmppClient)
238 return JS::UndefinedValue();
240 JSContext* cx = pCxPrivate->pScriptInterface->GetContext();
241 JSAutoRequest rq(cx);
243 JS::RootedValue profileFetch(cx);
244 g_XmppClient->GUIGetProfile(*(pCxPrivate->pScriptInterface), &profileFetch);
246 return profileFetch;
249 JS::Value JSI_Lobby::LobbyGuiPollNewMessage(ScriptInterface::CxPrivate* pCxPrivate)
251 if (!g_XmppClient)
252 return JS::UndefinedValue();
254 return g_XmppClient->GuiPollNewMessage(*(pCxPrivate->pScriptInterface));
257 JS::Value JSI_Lobby::LobbyGuiPollHistoricMessages(ScriptInterface::CxPrivate* pCxPrivate)
259 if (!g_XmppClient)
260 return JS::UndefinedValue();
262 return g_XmppClient->GuiPollHistoricMessages(*(pCxPrivate->pScriptInterface));
265 void JSI_Lobby::LobbySendMessage(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), const std::wstring& message)
267 if (!g_XmppClient)
268 return;
270 g_XmppClient->SendMUCMessage(utf8_from_wstring(message));
273 void JSI_Lobby::LobbySetPlayerPresence(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), const std::wstring& presence)
275 if (!g_XmppClient)
276 return;
278 g_XmppClient->SetPresence(utf8_from_wstring(presence));
281 void JSI_Lobby::LobbySetNick(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), const std::wstring& nick)
283 if (!g_XmppClient)
284 return;
286 g_XmppClient->SetNick(utf8_from_wstring(nick));
289 std::wstring JSI_Lobby::LobbyGetNick(ScriptInterface::CxPrivate* UNUSED(pCxPrivate))
291 if (!g_XmppClient)
292 return L"";
294 std::string nick;
295 g_XmppClient->GetNick(nick);
296 return wstring_from_utf8(nick);
299 void JSI_Lobby::LobbyKick(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), const std::wstring& nick, const std::wstring& reason)
301 if (!g_XmppClient)
302 return;
304 g_XmppClient->kick(utf8_from_wstring(nick), utf8_from_wstring(reason));
307 void JSI_Lobby::LobbyBan(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), const std::wstring& nick, const std::wstring& reason)
309 if (!g_XmppClient)
310 return;
312 g_XmppClient->ban(utf8_from_wstring(nick), utf8_from_wstring(reason));
315 std::wstring JSI_Lobby::LobbyGetPlayerPresence(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), const std::wstring& nickname)
317 if (!g_XmppClient)
318 return L"";
320 std::string presence;
321 g_XmppClient->GetPresence(utf8_from_wstring(nickname), presence);
322 return wstring_from_utf8(presence);
325 std::wstring JSI_Lobby::LobbyGetPlayerRole(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), const std::wstring& nickname)
327 if (!g_XmppClient)
328 return L"";
330 std::string role;
331 g_XmppClient->GetRole(utf8_from_wstring(nickname), role);
332 return wstring_from_utf8(role);
335 // Non-public secure PBKDF2 hash function with salting and 1,337 iterations
337 // TODO: We should use libsodium's crypto_pwhash instead of this. The first reason is that
338 // libsodium doesn't propose a bare PBKDF2 hash in its API and it's too bad to rely on custom
339 // code when we have a fully-fledged library available; the second reason is that Argon2 (the
340 // default algorithm for crypto_pwhash) is better than what we use (and it's the default one
341 // in the lib for a reason).
342 // However changing the hashing method should be planned carefully, by trying to login with a
343 // password hashed the old way, and, if successful, updating the password in the database using
344 // the new hashing method. Dropping the old hashing code can only be done either by giving users
345 // a way to reset their password, or by keeping track of successful password updates and dropping
346 // old unused accounts after some time.
347 std::string JSI_Lobby::EncryptPassword(const std::string& password, const std::string& username)
349 ENSURE(sodium_init() >= 0);
351 const int DIGESTSIZE = crypto_hash_sha256_BYTES;
352 const int ITERATIONS = 1337;
354 cassert(DIGESTSIZE == 32);
356 static const unsigned char salt_base[DIGESTSIZE] = {
357 244, 243, 249, 244, 32, 33, 34, 35, 10, 11, 12, 13, 14, 15, 16, 17,
358 18, 19, 20, 32, 33, 244, 224, 127, 129, 130, 140, 153, 133, 123, 234, 123 };
360 // initialize the salt buffer
361 unsigned char salt_buffer[DIGESTSIZE] = {0};
362 crypto_hash_sha256_state state;
363 crypto_hash_sha256_init(&state);
365 crypto_hash_sha256_update(&state, salt_base, sizeof(salt_base));
366 crypto_hash_sha256_update(&state, (unsigned char*)username.c_str(), username.length());
368 crypto_hash_sha256_final(&state, salt_buffer);
370 // PBKDF2 to create the buffer
371 unsigned char encrypted[DIGESTSIZE];
372 pbkdf2(encrypted, (unsigned char*)password.c_str(), password.length(), salt_buffer, DIGESTSIZE, ITERATIONS);
374 return CStr(Hexify(encrypted, DIGESTSIZE)).UpperCase();
377 std::wstring JSI_Lobby::EncryptPassword(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), const std::wstring& pass, const std::wstring& user)
379 return wstring_from_utf8(JSI_Lobby::EncryptPassword(utf8_from_wstring(pass), utf8_from_wstring(user)));
382 std::wstring JSI_Lobby::LobbyGetRoomSubject(ScriptInterface::CxPrivate* UNUSED(pCxPrivate))
384 if (!g_XmppClient)
385 return L"";
387 std::string subject;
388 g_XmppClient->GetSubject(subject);
389 return wstring_from_utf8(subject);
392 #endif