Skip to content

Commit 976e54e

Browse files
committed
fix(accounts): distinguish accounts with the same profileId
Signed-off-by: so5iso4ka <so5iso4ka@icloud.com>
1 parent 1d99e3a commit 976e54e

12 files changed

Lines changed: 312 additions & 71 deletions

launcher/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,8 @@ set(MINECRAFT_SOURCES
223223
minecraft/auth/AccountData.h
224224
minecraft/auth/AccountList.cpp
225225
minecraft/auth/AccountList.h
226+
minecraft/auth/AccountIdentifier.cpp
227+
minecraft/auth/AccountIdentifier.h
226228
minecraft/auth/AuthSession.cpp
227229
minecraft/auth/AuthSession.h
228230
minecraft/auth/AuthStep.h

launcher/LaunchController.cpp

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -86,11 +86,11 @@ void LaunchController::decideAccount()
8686
// Select the account to use. If the instance has a specific account set, that will be used. Otherwise, the default account will be used
8787
auto* accounts = APPLICATION->accounts();
8888
const auto instanceAccountId = m_instance->settings()->get("InstanceAccountId").toString();
89-
const auto instanceAccountIndex = accounts->findAccountByProfileId(instanceAccountId);
90-
if (instanceAccountIndex == -1 || instanceAccountId.isEmpty()) {
91-
m_accountToUse = accounts->defaultAccount();
89+
const auto instanceAccount = accounts->findAccountById(AccountIdentifier{ instanceAccountId });
90+
if (instanceAccount.found()) {
91+
m_accountToUse = accounts->at(instanceAccount.index);
9292
} else {
93-
m_accountToUse = accounts->at(instanceAccountIndex);
93+
m_accountToUse = accounts->defaultAccount();
9494
}
9595

9696
if (!accounts->anyAccountIsValid()) {
@@ -342,7 +342,7 @@ bool LaunchController::reauthenticateAccount(const MinecraftAccountPtr& account,
342342
auto* accounts = APPLICATION->accounts();
343343
const bool isDefault = accounts->defaultAccount() == account;
344344
const auto accountType = account->accountType();
345-
accounts->removeAccount(accounts->index(accounts->findAccountByProfileId(account->profileId())));
345+
accounts->removeAccount(accounts->index(accounts->findAccountById(AccountIdentifier{ *account }).index));
346346
MinecraftAccountPtr newAccount;
347347
switch (accountType) {
348348
case AccountType::MSA:

launcher/UrlUtils.cpp

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
#include <QObject>
2+
13
#include "UrlUtils.h"
24

35
bool UrlUtils::isLocalhost(const QUrl& url)
@@ -16,3 +18,37 @@ void UrlUtils::upgradeToHTTPS(QUrl& url)
1618
url.setScheme("https");
1719
}
1820
}
21+
22+
QUrl UrlUtils::httpFromUserInput(QString userInput, QString* errorString)
23+
{
24+
if (errorString) {
25+
errorString->clear();
26+
}
27+
28+
userInput = userInput.trimmed();
29+
30+
bool httpScheme = userInput.startsWith("http://", Qt::CaseInsensitive);
31+
bool httpsScheme = userInput.startsWith("https://", Qt::CaseInsensitive);
32+
33+
if (userInput.contains("://") && !httpsScheme && !httpScheme) {
34+
if (errorString) {
35+
*errorString = QObject::tr("Invalid URL scheme");
36+
}
37+
return {};
38+
}
39+
40+
QUrl deducedUrl = QUrl::fromUserInput(userInput);
41+
42+
if (!deducedUrl.isValid() || deducedUrl.isLocalFile() || deducedUrl.host().isEmpty()) {
43+
if (errorString) {
44+
*errorString = QObject::tr("Invalid URL");
45+
}
46+
return {};
47+
}
48+
49+
if (!httpsScheme && !httpScheme) {
50+
deducedUrl.setScheme("https");
51+
}
52+
53+
return deducedUrl;
54+
}

launcher/UrlUtils.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
#pragma once
22

3+
#include <QString>
34
#include <QUrl>
45

56
namespace UrlUtils {
67
bool isLocalhost(const QUrl& url);
78
bool isUnsafe(const QUrl& url);
89
void upgradeToHTTPS(QUrl& url);
9-
}
10+
QUrl httpFromUserInput(QString userInput, QString* errorString);
11+
} // namespace UrlUtils
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
// SPDX-License-Identifier: GPL-3.0-only
2+
/*
3+
* Freesm Launcher - Minecraft Launcher
4+
* Copyright (C) 2026 so5iso4ka <so5iso4ka@icloud.com>
5+
*
6+
* This program is free software: you can redistribute it and/or modify
7+
* it under the terms of the GNU General Public License as published by
8+
* the Free Software Foundation, version 3.
9+
*
10+
* This program is distributed in the hope that it will be useful,
11+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
* GNU General Public License for more details.
14+
*
15+
* You should have received a copy of the GNU General Public License
16+
* along with this program. If not, see <https://www.gnu.org/licenses/>.
17+
*/
18+
19+
#include <QSet>
20+
#include <utility>
21+
22+
#include "MinecraftAccount.h"
23+
#include "UrlUtils.h"
24+
25+
#include "AccountIdentifier.h"
26+
27+
AccountIdentifier::AccountIdentifier(const QString& str)
28+
{
29+
const auto firstSep = str.indexOf(':');
30+
if (firstSep == -1) {
31+
m_profileId = str;
32+
return;
33+
}
34+
35+
m_profileId = str.left(firstSep);
36+
37+
const auto secondSep = str.indexOf(':', firstSep + 1);
38+
if (secondSep == -1) {
39+
m_accountType = str.mid(firstSep + 1);
40+
return;
41+
}
42+
43+
m_accountType = str.mid(firstSep + 1, secondSep - firstSep - 1);
44+
m_authUrl = str.mid(secondSep + 1);
45+
}
46+
47+
AccountIdentifier::AccountIdentifier(QString profileId, QString accountType, QString authUrl)
48+
: m_profileId(std::move(profileId)), m_accountType(std::move(accountType)), m_authUrl(std::move(authUrl))
49+
{}
50+
51+
AccountIdentifier::AccountIdentifier(const MinecraftAccount& account)
52+
: m_profileId(account.profileId()), m_accountType(account.typeString()), m_authUrl(account.accountData()->authUrl)
53+
{}
54+
55+
bool AccountIdentifier::isValid() const
56+
{
57+
if (m_profileId.size() != 32)
58+
return false;
59+
60+
static const QSet<QString> validAccountTypes = { "", "msa", "elyby", "custom", "offline" };
61+
if (!validAccountTypes.contains(m_accountType))
62+
return false;
63+
64+
if (m_accountType == "custom" && !m_authUrl.isEmpty()) {
65+
QString errorString;
66+
UrlUtils::httpFromUserInput(m_authUrl, &errorString);
67+
if (!errorString.isEmpty())
68+
return false;
69+
}
70+
71+
return true;
72+
}
73+
74+
QString AccountIdentifier::profileId() const
75+
{
76+
return m_profileId;
77+
}
78+
79+
QString AccountIdentifier::accountType() const
80+
{
81+
return m_accountType;
82+
}
83+
84+
QString AccountIdentifier::authUrl() const
85+
{
86+
if (m_authUrl.isEmpty())
87+
return {};
88+
return UrlUtils::httpFromUserInput(m_authUrl, nullptr).toString(QUrl::StripTrailingSlash);
89+
}
90+
91+
bool AccountIdentifier::matches(const MinecraftAccount& account) const
92+
{
93+
if (!isValid())
94+
return false;
95+
96+
if (account.profileId() != m_profileId)
97+
return false;
98+
99+
if (!m_accountType.isEmpty() && m_accountType != account.typeString())
100+
return false;
101+
102+
if (m_accountType == "custom" && !m_authUrl.isEmpty()) {
103+
if (authUrl() != account.accountData()->authUrl)
104+
return false;
105+
}
106+
107+
return true;
108+
}
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
// SPDX-License-Identifier: GPL-3.0-only
2+
/*
3+
* Freesm Launcher - Minecraft Launcher
4+
* Copyright (C) 2026 so5iso4ka <so5iso4ka@icloud.com>
5+
*
6+
* This program is free software: you can redistribute it and/or modify
7+
* it under the terms of the GNU General Public License as published by
8+
* the Free Software Foundation, version 3.
9+
*
10+
* This program is distributed in the hope that it will be useful,
11+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
* GNU General Public License for more details.
14+
*
15+
* You should have received a copy of the GNU General Public License
16+
* along with this program. If not, see <https://www.gnu.org/licenses/>.
17+
*/
18+
19+
#pragma once
20+
21+
#include <QString>
22+
23+
class MinecraftAccount;
24+
25+
/*!
26+
* Account identifier that can disambiguate profile ID conflicts when type/auth URL are present.
27+
*/
28+
class AccountIdentifier {
29+
public:
30+
AccountIdentifier() = delete;
31+
32+
/*!
33+
* Constructs identifier from user provided input.
34+
* If input is invalid, isValid() considered to be false.
35+
*
36+
* @param str "ProfileID[:type[:AuthURL]]" formatted string, where ProfileID is the Minecraft profile ID, type is account type (msa /
37+
* elyby / custom / offline) and AuthURL is authlib-injector auth URL, used for matching only custom accounts.
38+
* Only first two ':' characters are separators.
39+
*/
40+
explicit AccountIdentifier(const QString& str);
41+
42+
/*!
43+
* Constructs identifier from profile ID, account type and authlib-injector auth URL.
44+
*
45+
* @param profileId Minecraft profile ID
46+
* @param accountType Account type ("msa" / "elyby" / "custom" / "offline" / "")
47+
* @param authUrl authlib-injector auth URL, used for matching only custom accounts
48+
*/
49+
explicit AccountIdentifier(QString profileId, QString accountType, QString authUrl = {});
50+
51+
/*!
52+
* Constructs identifier from existing Minecraft account.
53+
*
54+
* @param account a valid Minecraft account
55+
*/
56+
explicit AccountIdentifier(const MinecraftAccount& account);
57+
58+
/*!
59+
* @return whether the identifier is valid
60+
*/
61+
bool isValid() const;
62+
63+
/*!
64+
* @return profile ID
65+
*/
66+
QString profileId() const;
67+
68+
/*!
69+
* @return account type string
70+
*/
71+
QString accountType() const;
72+
73+
/*!
74+
* @return authlib-injector auth URL
75+
*/
76+
QString authUrl() const;
77+
78+
/*!
79+
* Matches specified account with identifier. Returns false if the identifier is not valid.
80+
* Empty account type matches any account with the same profile ID.
81+
* For custom accounts, a non-empty auth URL must also match.
82+
*
83+
* @param account a valid Minecraft account
84+
* @return whether the account match this identifier
85+
*/
86+
bool matches(const MinecraftAccount& account) const;
87+
88+
private:
89+
QString m_profileId;
90+
QString m_accountType;
91+
QString m_authUrl;
92+
};
93+
94+
enum class AccountFindError { NoError, InvalidId, NotFound, Ambiguous };
95+
96+
struct AccountFindResult {
97+
int index = -1;
98+
AccountFindError error = AccountFindError::NotFound;
99+
100+
bool found() const { return index >= 0 && error == AccountFindError::NoError; }
101+
};

0 commit comments

Comments
 (0)