diff --git a/binaries/data/mods/public/gui/lobby/AccountSettingsPage/AccountSettingsPage.js b/binaries/data/mods/public/gui/lobby/AccountSettingsPage/AccountSettingsPage.js
new file mode 100644
index 0000000000..b24d7dba41
--- /dev/null
+++ b/binaries/data/mods/public/gui/lobby/AccountSettingsPage/AccountSettingsPage.js
@@ -0,0 +1,107 @@
+/**
+ * The account settings page allows the player to change some of their account settings.
+ */
+var AccountSettingsPage = {
+ async openPage(xmppMessages)
+ {
+ const pageElement = Engine.GetGUIObjectByName("accountSettingsPage");
+ const requestResult = Engine.GetGUIObjectByName("as_RequestResult");
+
+ try
+ {
+ pageElement.hidden = false;
+ pageElement.onTick = updateTimers;
+ await Promise.race([
+ new Promise(resolve => {
+ Engine.SetGlobalHotkey("cancel", "Press", resolve);
+ Engine.GetGUIObjectByName("as_Close").onPress = resolve;
+ }),
+ AccountSettingsPage._changePasswordLoop(requestResult, xmppMessages)
+ ]);
+ }
+ finally
+ {
+ requestResult.caption = "";
+ pageElement.hidden = true;
+ }
+ },
+
+ async _changePasswordLoop(...args)
+ {
+ const changePasswordButton = Engine.GetGUIObjectByName("as_ChangePasswordBtn")
+ while (true)
+ {
+ await new Promise(resolve => {
+ changePasswordButton.onPress = resolve;
+ });
+ try
+ {
+ changePasswordButton.enabled = false;
+ await AccountSettingsPage._changePassword(...args);
+ }
+ finally
+ {
+ changePasswordButton.enabled = true;
+ }
+ }
+ },
+
+ async _changePassword(requestResult, xmppMessages)
+ {
+ const SetPasswordError = class extends Error{};
+ let timeout;
+ try
+ {
+ requestResult.caption = "Changing password…";
+ const encryptedPassword = AccountSettingsPage._readAndValidatePassword(SetPasswordError);
+ Engine.LobbyChangePassword(encryptedPassword);
+ await new Promise((resolve, reject) => {
+ xmppMessages.registerXmppMessageHandler("system", "registered", resolve);
+ xmppMessages.registerXmppMessageHandler("system", "error", message => {
+ reject(new SetPasswordError(message.text));
+ });
+ timeout = setTimeout(reject.bind(null,
+ new SetPasswordError(translate("Request timed out."))), 30000);
+ });
+ requestResult.caption = translate("Password changed successfully.");
+ const rememberPassword = Engine.ConfigDB_GetValue("user", "lobby.rememberpassword");
+ const functionSufix = rememberPassword === "true" ? "CreateValue" : "RemoveValue";
+ Engine["ConfigDB_" + functionSufix]("user", "lobby.password", encryptedPassword);
+ Engine.ConfigDB_SaveChanges("user");
+ }
+ catch (e)
+ {
+ if (e instanceof SetPasswordError)
+ requestResult.caption = e.message;
+ else
+ {
+ requestResult.caption = "";
+ error(uneval(e));
+ }
+ }
+ finally
+ {
+ clearTimeout(timeout);
+ }
+ },
+
+ _readAndValidatePassword(SetPasswordError)
+ {
+ const newPass = Engine.GetGUIObjectByName("as_PasswordInput").caption;
+ if (newPass.length < minimumPasswordLength)
+ throw new SetPasswordError(translate("Please choose a longer password"));
+
+ if (Engine.GetGUIObjectByName("as_PasswordInputConfirm").caption !== newPass)
+ throw new SetPasswordError(translate("Passwords do not match"));
+
+ let usn = Engine.LobbyGetJID();
+ let atIndex = usn.indexOf("@");
+ if (atIndex == -1)
+ {
+ // Probably can't happen too easily, so error out.
+ error("Invalid JID");
+ throw new SetPasswordError(translate("Invalid JID, cannot change password."));
+ }
+ return Engine.EncryptPassword(newPass, usn.substring(0, atIndex).toLowerCase());
+ }
+};
diff --git a/binaries/data/mods/public/gui/lobby/AccountSettingsPage/AccountSettingsPage.xml b/binaries/data/mods/public/gui/lobby/AccountSettingsPage/AccountSettingsPage.xml
new file mode 100644
index 0000000000..ef44f8b30e
--- /dev/null
+++ b/binaries/data/mods/public/gui/lobby/AccountSettingsPage/AccountSettingsPage.xml
@@ -0,0 +1,36 @@
+
+
diff --git a/binaries/data/mods/public/gui/lobby/LobbyPage/LobbyPage.js b/binaries/data/mods/public/gui/lobby/LobbyPage/LobbyPage.js
index 8b9887c853..75da3b84f5 100644
--- a/binaries/data/mods/public/gui/lobby/LobbyPage/LobbyPage.js
+++ b/binaries/data/mods/public/gui/lobby/LobbyPage/LobbyPage.js
@@ -9,12 +9,15 @@ class LobbyPage
Engine.ProfileStart("Create LobbyPage");
let mapCache = new MapCache();
let buddyButton = new BuddyButton(xmppMessages);
+ const accountSettingsButton = Engine.GetGUIObjectByName("accountSettingsButton");
+ accountSettingsButton.onPress = AccountSettingsPage.openPage.bind(null, xmppMessages);
let gameList = new GameList(xmppMessages, buddyButton, mapCache);
let playerList = new PlayerList(xmppMessages, buddyButton, gameList);
this.lobbyPage = {
"buttons": {
"buddyButton": buddyButton,
+ "accountSettingsButton": accountSettingsButton,
"hostButton": new HostButton(dialog, xmppMessages),
"joinButton": new JoinButton(dialog, gameList),
"leaderboardButton": new LeaderboardButton(xmppMessages, leaderboardPage),
diff --git a/binaries/data/mods/public/gui/lobby/LobbyPage/LobbyPage.xml b/binaries/data/mods/public/gui/lobby/LobbyPage/LobbyPage.xml
index a1854181be..d09f1c88a4 100644
--- a/binaries/data/mods/public/gui/lobby/LobbyPage/LobbyPage.xml
+++ b/binaries/data/mods/public/gui/lobby/LobbyPage/LobbyPage.xml
@@ -16,16 +16,19 @@
-
+
-
+
-
+
-
-
+
+ Account settings
+
+
+
diff --git a/binaries/data/mods/public/gui/lobby/lobby.xml b/binaries/data/mods/public/gui/lobby/lobby.xml
index 5aaf7c55f3..83536a3401 100644
--- a/binaries/data/mods/public/gui/lobby/lobby.xml
+++ b/binaries/data/mods/public/gui/lobby/lobby.xml
@@ -9,6 +9,7 @@
+
diff --git a/binaries/data/mods/public/gui/lobby/password.js b/binaries/data/mods/public/gui/lobby/password.js
new file mode 100644
index 0000000000..8c1be66689
--- /dev/null
+++ b/binaries/data/mods/public/gui/lobby/password.js
@@ -0,0 +1,4 @@
+/**
+ * Common definitions for the lobby password.
+ */
+const minimumPasswordLength = 8;
diff --git a/binaries/data/mods/public/gui/prelobby/common/credentials/credentials.js b/binaries/data/mods/public/gui/prelobby/common/credentials/credentials.js
index 7c48b614c4..02665ec5c0 100644
--- a/binaries/data/mods/public/gui/prelobby/common/credentials/credentials.js
+++ b/binaries/data/mods/public/gui/prelobby/common/credentials/credentials.js
@@ -19,7 +19,7 @@ function checkPassword(register)
translateWithContext("register", "Please enter your password") :
translateWithContext("login", "Please enter your password");
- if (register && password.length < 8)
+ if (register && password.length < minimumPasswordLength)
return translate("Please choose a longer password");
return "";
diff --git a/binaries/data/mods/public/gui/prelobby/login/login.xml b/binaries/data/mods/public/gui/prelobby/login/login.xml
index e0fe5bfcf3..65d0887346 100644
--- a/binaries/data/mods/public/gui/prelobby/login/login.xml
+++ b/binaries/data/mods/public/gui/prelobby/login/login.xml
@@ -6,6 +6,7 @@
+
diff --git a/binaries/data/mods/public/gui/prelobby/register/register.xml b/binaries/data/mods/public/gui/prelobby/register/register.xml
index 47ef3e4c1b..312837492e 100644
--- a/binaries/data/mods/public/gui/prelobby/register/register.xml
+++ b/binaries/data/mods/public/gui/prelobby/register/register.xml
@@ -6,6 +6,7 @@
+
diff --git a/source/lobby/IXmppClient.h b/source/lobby/IXmppClient.h
index fb241aff48..491da0c60f 100644
--- a/source/lobby/IXmppClient.h
+++ b/source/lobby/IXmppClient.h
@@ -1,4 +1,4 @@
-/* Copyright (C) 2021 Wildfire Games.
+/* Copyright (C) 2024 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@@ -43,6 +43,7 @@ public:
virtual void SetNick(const std::string& nick) = 0;
virtual std::string GetNick() const = 0;
virtual std::string GetJID() const = 0;
+ virtual void ChangePassword(const std::string& newPassword) = 0;
virtual void kick(const std::string& nick, const std::string& reason) = 0;
virtual void ban(const std::string& nick, const std::string& reason) = 0;
virtual void SetPresence(const std::string& presence) = 0;
diff --git a/source/lobby/XmppClient.cpp b/source/lobby/XmppClient.cpp
index f53c0ac310..3773cdd6ee 100644
--- a/source/lobby/XmppClient.cpp
+++ b/source/lobby/XmppClient.cpp
@@ -73,6 +73,7 @@ XmppClient::XmppClient(const ScriptInterface* scriptInterface, const std::string
m_client(nullptr),
m_mucRoom(nullptr),
m_registration(nullptr),
+ m_regOpt(regOpt),
m_username(sUsername),
m_password(sPassword),
m_room(sRoom),
@@ -143,6 +144,9 @@ XmppClient::XmppClient(const ScriptInterface* scriptInterface, const std::string
m_client->registerMessageHandler(this);
+ m_registration = new gloox::Registration(m_client);
+ m_registration->registerRegistrationHandler(this);
+
// Uncomment to see the raw stanzas
// m_client->logInstance().registerLogHandler(gloox::LogLevelDebug, gloox::LogAreaAll, this);
@@ -153,12 +157,6 @@ XmppClient::XmppClient(const ScriptInterface* scriptInterface, const std::string
// Get room history.
m_mucRoom->setRequestHistory(historyRequestSize, gloox::MUCRoom::HistoryMaxStanzas);
}
- else
- {
- // Registration
- m_registration = new gloox::Registration(m_client);
- m_registration->registerRegistrationHandler(this);
- }
m_sessionManager = new gloox::Jingle::SessionManager(m_client, this);
// Register plugins to allow gloox parse them in incoming sessions
@@ -247,7 +245,7 @@ void XmppClient::onConnect()
m_mucRoom->join();
}
- if (m_registration)
+ if (m_regOpt)
m_registration->fetchRegistrationFields();
}
@@ -535,8 +533,6 @@ void XmppClient::handleRegistrationResult(const gloox::JID&, gloox::Registration
CreateGUIMessage("system", "registered", std::time(nullptr));
else
CreateGUIMessage("system", "error", std::time(nullptr), "text", result);
-
- disconnect();
}
void XmppClient::handleAlreadyRegistered(const gloox::JID&)
@@ -1209,6 +1205,16 @@ std::string XmppClient::GetJID() const
return m_client->jid().full();
}
+/**
+ * Change password for authenticated user.
+ *
+ * @param newPassword New password
+ */
+void XmppClient::ChangePassword(const std::string& newPassword)
+{
+ m_registration->changePassword(m_username, newPassword);
+}
+
/**
* Kick a player from the current room.
*
diff --git a/source/lobby/XmppClient.h b/source/lobby/XmppClient.h
index fe7b298f36..634c324c58 100644
--- a/source/lobby/XmppClient.h
+++ b/source/lobby/XmppClient.h
@@ -1,4 +1,4 @@
-/* Copyright (C) 2021 Wildfire Games.
+/* Copyright (C) 2024 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@@ -57,6 +57,7 @@ private:
gloox::CertStatus m_certStatus;
bool m_initialLoadComplete;
bool m_isConnected;
+ bool m_regOpt;
public:
// Basic
@@ -87,6 +88,7 @@ public:
void SetNick(const std::string& nick) override;
std::string GetNick() const override;
std::string GetJID() const override;
+ void ChangePassword(const std::string& newPassword) override;
void kick(const std::string& nick, const std::string& reason) override;
void ban(const std::string& nick, const std::string& reason) override;
void SetPresence(const std::string& presence) override;
diff --git a/source/lobby/scripting/JSInterface_Lobby.cpp b/source/lobby/scripting/JSInterface_Lobby.cpp
index 264c3b17d1..33765c3028 100644
--- a/source/lobby/scripting/JSInterface_Lobby.cpp
+++ b/source/lobby/scripting/JSInterface_Lobby.cpp
@@ -218,6 +218,7 @@ void RegisterScriptFunctions(const ScriptRequest& rq)
REGISTER_XMPP(SetNick, "LobbySetNick");
REGISTER_XMPP(GetNick, "LobbyGetNick");
REGISTER_XMPP(GetJID, "LobbyGetJID");
+ REGISTER_XMPP(ChangePassword, "LobbyChangePassword");
REGISTER_XMPP(kick, "LobbyKick");
REGISTER_XMPP(ban, "LobbyBan");
REGISTER_XMPP(GetPresence, "LobbyGetPlayerPresence");