Skip to content

Commit ab169c2

Browse files
authored
fix: rework custom auth (#174)
2 parents 9d4ea22 + 86fa4a3 commit ab169c2

7 files changed

Lines changed: 268 additions & 113 deletions

File tree

launcher/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -271,6 +271,8 @@ set(MINECRAFT_SOURCES
271271

272272
minecraft/auth/custom/steps/CustomAuthStep.cpp
273273
minecraft/auth/custom/steps/CustomAuthStep.h
274+
minecraft/auth/custom/steps/CustomRefreshStep.cpp
275+
minecraft/auth/custom/steps/CustomRefreshStep.h
274276
minecraft/auth/custom/steps/CustomGetSkinStep.cpp
275277
minecraft/auth/custom/steps/CustomGetSkinStep.h
276278

launcher/minecraft/auth/AccountData.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,4 +130,5 @@ struct AccountData {
130130
QString errorString;
131131
AccountState accountState = AccountState::Unchecked;
132132
QString accountLogin;
133+
bool profileSelectedExplicitly = false;
133134
};

launcher/minecraft/auth/AuthFlow.cpp

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
1+
#include "AuthFlow.h"
2+
13
#include <QDebug>
24
#include <QNetworkReply>
35
#include <QNetworkRequest>
46
#include <utility>
57

68
#include "minecraft/auth/AccountData.h"
9+
#include "tasks/Task.h"
710

811
// MSA
912
#include "minecraft/auth/msa/steps/EntitlementsStep.h"
@@ -22,16 +25,9 @@
2225
#include "elyby/steps/MinecraftProfileStepEly.h"
2326

2427
// Custom
25-
#include "AuthFlow.h"
26-
2728
#include "custom/steps/CustomAuthStep.h"
2829
#include "custom/steps/CustomGetSkinStep.h"
29-
30-
#include "tasks/Task.h"
31-
32-
#include "AuthFlow.h"
33-
34-
#include <Application.h>
30+
#include "custom/steps/CustomRefreshStep.h"
3531

3632
AuthFlow::AuthFlow(AccountData* data, Action action, QString password) : Task(), m_data(data)
3733
{
@@ -73,10 +69,10 @@ AuthFlow::AuthFlow(AccountData* data, Action action, QString password) : Task(),
7369
} break;
7470
case AccountType::Custom: {
7571
if (action == Action::Login) {
76-
m_steps.append(makeShared<CustomAuthStep>(m_data, Action::Login, std::move(password)));
77-
m_steps.append(makeShared<CustomAuthStep>(m_data, Action::Refresh, QString()));
72+
m_steps.append(makeShared<CustomAuthStep>(m_data, std::move(password)));
73+
m_steps.append(makeShared<CustomRefreshStep>(m_data));
7874
} else {
79-
m_steps.append(makeShared<CustomAuthStep>(m_data, action, std::move(password)));
75+
m_steps.append(makeShared<CustomRefreshStep>(m_data));
8076
}
8177
m_steps.append(makeShared<CustomGetSkinStep>(m_data));
8278
} break;
Lines changed: 68 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// SPDX-License-Identifier: GPL-3.0-only
22
/*
33
* Freesm Launcher - Minecraft Launcher
4-
* Copyright (C) 2025 so5iso4ka <so5iso4ka@icloud.com>
4+
* Copyright (C) 2026 so5iso4ka <so5iso4ka@icloud.com>
55
*
66
* This program is free software: you can redistribute it and/or modify
77
* it under the terms of the GNU General Public License as published by
@@ -18,109 +18,105 @@
1818

1919
#include "CustomAuthStep.h"
2020

21+
#include <QDateTime>
2122
#include <QInputDialog>
23+
#include <QJsonDocument>
24+
#include <utility>
2225

2326
#include "Application.h"
2427
#include "Logging.h"
2528
#include "net/NetUtils.h"
2629
#include "net/RawHeaderProxy.h"
2730

28-
#include <utility>
31+
CustomAuthStep::CustomAuthStep(AccountData* data, QString password) : AuthStep(data), m_password(std::move(password)) {}
2932

30-
CustomAuthStep::CustomAuthStep(AccountData* data, AuthFlow::Action action, QString password)
31-
: AuthStep(data), m_password(std::move(password)), m_action(action)
32-
{}
33+
CustomAuthStep::~CustomAuthStep() = default;
3334

3435
void CustomAuthStep::perform()
3536
{
36-
const QUrl url(authUrl() + requestUrl());
37-
const QString requestData = fillRequest();
37+
if (m_data == nullptr) {
38+
emit finished(AccountTaskState::STATE_FAILED_SOFT, tr("Account data is a null pointer"));
39+
return;
40+
}
41+
42+
const QUrl url(m_data->authUrl + m_data->loginUrl);
43+
const QJsonDocument request(fillRequest());
3844

39-
m_response.reset(new QByteArray());
40-
m_request = Net::Upload::makeByteArray(url, m_response, requestData.toUtf8());
45+
m_response = std::make_shared<QByteArray>();
46+
m_request = Net::Upload::makeByteArray(url, m_response, request.toJson());
4147

42-
const auto headerProxy =
43-
new Net::RawHeaderProxy(QList<Net::HeaderPair>{ { "Content-Type", "application/json" }, { "Accept", "application/json" } });
44-
m_request->addHeaderProxy(headerProxy);
4548
// RawHeaderProxy::addHeaderProxy takes ownership of the proxy, so no cleanup is required
49+
m_request->addHeaderProxy(new Net::RawHeaderProxy(
50+
QList<Net::HeaderPair>{ { "Content-Type", "application/json; charset=utf-8" }, { "Accept", "application/json" } }));
4651

47-
m_task.reset(new NetJob(authType() + "AuthStep", APPLICATION->network()));
52+
m_task.reset(new NetJob("CustomAuthStep", APPLICATION->network()));
4853
m_task->setAskRetry(false);
4954
m_task->addNetAction(m_request);
5055

5156
connect(m_task.get(), &Task::finished, this, &CustomAuthStep::onRequestDone);
5257

5358
m_task->start();
54-
qDebug() << "Getting authorization token for " + authType() + " account";
59+
qDebug() << "Getting authorization token for custom account";
5560
}
5661

57-
QString CustomAuthStep::requestUrl()
62+
QJsonObject CustomAuthStep::fillRequest() const
5863
{
59-
return m_action == AuthFlow::Action::Login ? m_data->loginUrl : m_data->refreshUrl;
60-
}
64+
QJsonObject root;
65+
root.insert("username", m_data->accountLogin);
66+
root.insert("password", m_password);
6167

62-
QString CustomAuthStep::requestTemplate()
63-
{
64-
if (m_action == AuthFlow::Action::Login) {
65-
return R"XXX(
66-
{
67-
"username": "%1",
68-
"password": "%2",
69-
"clientToken": "%3",
70-
"requestUser": false,
71-
"agent": {
72-
"name":"Minecraft",
73-
"version":1
74-
}
75-
}
76-
)XXX";
77-
} else {
78-
return R"XXX(
79-
{
80-
"accessToken": "%1",
81-
"clientToken": "%2",
82-
"requestUser": false,
83-
"selectedProfile": {
84-
"id": "%3",
85-
"name": "%4"
86-
}
87-
}
88-
)XXX";
89-
}
90-
}
68+
QJsonObject agent;
69+
agent.insert("name", "Minecraft");
70+
agent.insert("version", 1);
9171

92-
QString CustomAuthStep::fillRequest()
93-
{
94-
if (m_action == AuthFlow::Action::Login) {
95-
return requestTemplate().arg(m_data->accountLogin, m_password, clientID());
96-
} else {
97-
return requestTemplate().arg(m_data->yggdrasilToken.token, m_data->clientID, m_data->minecraftProfile.id,
98-
m_data->minecraftProfile.name);
99-
}
72+
root.insert("agent", agent);
73+
74+
return root;
10075
}
10176

102-
bool CustomAuthStep::parseResponse()
77+
void CustomAuthStep::onRequestDone()
10378
{
10479
qCDebug(authCredentials()) << *m_response;
105-
if (m_request->error() != QNetworkReply::NoError) {
106-
qWarning() << "Reply error:" << m_request->error();
107-
return false;
80+
81+
if (m_request->error() != QNetworkReply::NoError && m_request->error() != QNetworkReply::ContentAccessDenied) {
82+
emit finished(AccountTaskState::STATE_OFFLINE, m_request->errorString());
83+
return;
10884
}
10985

110-
auto jsonResponse = QJsonDocument::fromJson(*m_response);
86+
QJsonParseError err;
87+
auto jsonResponse = QJsonDocument::fromJson(*m_response, &err);
88+
89+
if (err.error != QJsonParseError::NoError) {
90+
emit finished(AccountTaskState::STATE_FAILED_SOFT, tr("Error while parsing JSON response: %1").arg(err.errorString()));
91+
return;
92+
}
93+
94+
if (m_request->error() == QNetworkReply::ContentAccessDenied) {
95+
const QString msg = jsonResponse["errorMessage"].toString() == "Invalid credentials. Invalid username or password."
96+
? tr("Invalid credentials. Invalid username or password.")
97+
: m_request->errorString();
98+
99+
emit finished(AccountTaskState::STATE_FAILED_HARD, msg);
100+
return;
101+
}
111102

112103
m_data->yggdrasilToken.token = jsonResponse["accessToken"].toString();
104+
m_data->yggdrasilToken.validity = Validity::Certain;
105+
m_data->yggdrasilToken.issueInstant = QDateTime::currentDateTimeUtc();
113106

114107
m_data->clientID = jsonResponse["clientToken"].toString();
115108

116-
if (!jsonResponse["selectedProfile"].isNull()) {
117-
auto profile = jsonResponse["selectedProfile"].toObject();
118-
m_data->minecraftProfile.id = profile["id"].toString();
119-
m_data->minecraftProfile.name = profile["name"].toString();
109+
QJsonObject selectedProfile = jsonResponse["selectedProfile"].toObject();
110+
if (!selectedProfile.isEmpty()) {
111+
m_data->minecraftProfile.id = selectedProfile["id"].toString();
112+
m_data->minecraftProfile.name = selectedProfile["name"].toString();
113+
114+
emit finished(AccountTaskState::STATE_WORKING, tr("Got authorization for custom account"));
115+
return;
120116
}
121117

122118
const QJsonArray profiles = jsonResponse["availableProfiles"].toArray();
123-
if (profiles.size() > 1 && m_data->minecraftProfile.id.isEmpty()) {
119+
if (profiles.size() > 1) {
124120
const auto profileName = [](const auto& profile) {
125121
auto obj = profile.toObject();
126122
return obj["name"].toString();
@@ -134,32 +130,25 @@ bool CustomAuthStep::parseResponse()
134130
QInputDialog::getItem(nullptr, tr("Select profile"), tr("Select profile for this account"), list, 0, false, &ok);
135131

136132
if (!ok) {
137-
return false;
133+
emit finished(AccountTaskState::STATE_FAILED_SOFT, tr("Profile selection cancelled"));
134+
return;
138135
}
139136

140137
const auto it = std::ranges::find(profiles, selectedProfileName, profileName);
141138
if (it != profiles.end()) {
142139
auto profileObj = it->toObject();
143140
m_data->minecraftProfile = MinecraftProfile{ .id = profileObj["id"].toString(), .name = profileObj["name"].toString() };
144141
} else {
145-
return false;
142+
// assuming that this will never happen
143+
emit finished(AccountTaskState::STATE_FAILED_SOFT, tr("Something went wrong"));
144+
return;
146145
}
147-
}
148-
149-
if (profiles.size() == 1 && m_data->minecraftProfile.id.isEmpty()) {
146+
} else if (profiles.size() == 1) {
150147
auto profileObj = profiles.first().toObject();
151148
m_data->minecraftProfile = MinecraftProfile{ .id = profileObj["id"].toString(), .name = profileObj["name"].toString() };
152149
}
153150

154-
return true;
155-
}
151+
m_data->profileSelectedExplicitly = true;
156152

157-
void CustomAuthStep::onRequestDone()
158-
{
159-
if (!parseResponse()) {
160-
emit finished(AccountTaskState::STATE_OFFLINE,
161-
tr("Failed to get authorization for %1 account: %2").arg(authType(), m_request->errorString()));
162-
return;
163-
}
164-
emit finished(AccountTaskState::STATE_WORKING, tr("Got authorization for %1 account").arg(authType()));
153+
emit finished(AccountTaskState::STATE_WORKING, tr("Got authorization for custom account"));
165154
}
Lines changed: 15 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// SPDX-License-Identifier: GPL-3.0-only
22
/*
33
* Freesm Launcher - Minecraft Launcher
4-
* Copyright (C) 2025 so5iso4ka <so5iso4ka@icloud.com>
4+
* Copyright (C) 2026 so5iso4ka <so5iso4ka@icloud.com>
55
*
66
* This program is free software: you can redistribute it and/or modify
77
* it under the terms of the GNU General Public License as published by
@@ -18,46 +18,38 @@
1818

1919
#pragma once
2020

21-
#include "BuildConfig.h"
22-
#include "minecraft/auth/AuthFlow.h"
21+
#include <QByteArray>
22+
#include <QJsonObject>
23+
#include <QString>
24+
#include <memory>
25+
2326
#include "minecraft/auth/AuthStep.h"
2427
#include "net/NetJob.h"
2528
#include "net/Upload.h"
2629

30+
struct AccountData;
31+
2732
class CustomAuthStep : public AuthStep {
2833
Q_OBJECT
2934

3035
public:
31-
CustomAuthStep(AccountData* data, AuthFlow::Action action, QString password);
32-
virtual ~CustomAuthStep() noexcept = default;
36+
CustomAuthStep(AccountData* data, QString password);
37+
~CustomAuthStep() override;
3338

3439
void perform() override;
3540

3641
QString describe() override { return tr("Custom account authentication"); }
3742

38-
protected:
39-
virtual QString authType() { return "Custom"; }
40-
41-
virtual QString authUrl() { return m_data->authUrl; }
42-
43-
virtual QString clientID() { return BuildConfig.LAUNCHER_NAME; }
44-
45-
virtual QString requestUrl();
46-
47-
QString requestTemplate();
48-
49-
QString fillRequest();
50-
51-
bool parseResponse();
43+
private:
44+
QJsonObject fillRequest() const;
5245

53-
protected slots:
46+
private slots:
5447
virtual void onRequestDone();
5548

56-
protected:
49+
private:
5750
std::shared_ptr<QByteArray> m_response;
5851
Net::Upload::Ptr m_request;
5952
NetJob::Ptr m_task;
6053

61-
const QString m_password;
62-
const AuthFlow::Action m_action;
54+
QString m_password;
6355
};

0 commit comments

Comments
 (0)