Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,12 @@ src/*.bak
CMakeLists.txt.user
edit
/CMakeFiles
.idea
.idea/*
!.idea/filetypes/
!.idea/filetypes/qt-translations.xml
/msbuild.log
/*std*.log
/*build

/src/version.aps
.idea/
7 changes: 5 additions & 2 deletions src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ find_package(mo2-esptk CONFIG REQUIRED)
find_package(mo2-dds-header CONFIG REQUIRED)
find_package(mo2-libbsarch CONFIG REQUIRED)

find_package(Qt6 REQUIRED COMPONENTS WebEngineWidgets WebSockets)
find_package(Qt6 REQUIRED COMPONENTS WebEngineWidgets WebSockets NetworkAuth)
find_package(Boost CONFIG REQUIRED COMPONENTS program_options thread interprocess signals2 uuid accumulators)
find_package(7zip CONFIG REQUIRED)
find_package(lz4 CONFIG REQUIRED)
Expand Down Expand Up @@ -41,7 +41,7 @@ target_link_libraries(organizer PRIVATE
usvfs::usvfs mo2::uibase mo2::archive mo2::libbsarch
mo2::bsatk mo2::esptk mo2::lootcli-header
Boost::program_options Boost::signals2 Boost::uuid Boost::accumulators
Qt6::WebEngineWidgets Qt6::WebSockets Version Dbghelp)
Qt6::WebEngineWidgets Qt6::WebSockets Qt6::NetworkAuth Version Dbghelp)

install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/dlls.manifest.qt6"
DESTINATION ${_bin}/dlls
Expand Down Expand Up @@ -130,6 +130,9 @@ mo2_add_filter(NAME src/core GROUPS
githubpp
installationmanager
nexusinterface
nexusoauthlogin
nexusoauthtokens
nexusoauthconfig
nxmaccessmanager
organizercore
game_features
Expand Down
19 changes: 15 additions & 4 deletions src/apiuseraccount.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,17 @@ APIUserAccount::APIUserAccount() : m_type(APIUserAccountTypes::None) {}

bool APIUserAccount::isValid() const
{
return !m_key.isEmpty();
return !m_accessToken.isEmpty() || !m_apiKey.isEmpty();
}

const QString& APIUserAccount::accessToken() const
{
return m_accessToken;
}

const QString& APIUserAccount::apiKey() const
{
return m_key;
return m_apiKey;
}

const QString& APIUserAccount::id() const
Expand All @@ -47,9 +52,15 @@ const APILimits& APIUserAccount::limits() const
return m_limits;
}

APIUserAccount& APIUserAccount::apiKey(const QString& key)
APIUserAccount& APIUserAccount::accessToken(const QString& token)
{
m_accessToken = token;
return *this;
}

APIUserAccount& APIUserAccount::apiKey(const QString& apiKey)
{
m_key = key;
m_apiKey = apiKey;
return *this;
}

Expand Down
18 changes: 14 additions & 4 deletions src/apiuseraccount.h
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,12 @@ class APIUserAccount
bool isValid() const;

/**
* api key
* OAuth access token
*/
const QString& accessToken() const;

/**
* OAuth access token
*/
const QString& apiKey() const;

Expand All @@ -90,9 +95,14 @@ class APIUserAccount
const APILimits& limits() const;

/**
* sets the api key
* sets the OAuth access token
*/
APIUserAccount& accessToken(const QString& token);

/**
* sets the OAuth access token
*/
APIUserAccount& apiKey(const QString& key);
APIUserAccount& apiKey(const QString& apiKey);

/**
* sets the user id
Expand Down Expand Up @@ -132,7 +142,7 @@ class APIUserAccount
bool exhausted() const;

private:
QString m_key, m_id, m_name;
QString m_accessToken, m_apiKey, m_id, m_name;
APIUserAccountTypes m_type;
APILimits m_limits;
};
Expand Down
2 changes: 1 addition & 1 deletion src/createinstancedialog.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ class Settings;
//
// pages can be disabled if they return true in skip(), which happens globally
// for some (IntroPage has a setting in the registry), depending on context
// (NexusPage is skipped if the API key already exists) or explicitly (when
// (NexusPage is skipped if the Nexus authorization already exists) or explicitly (when
// only some info about the instance is missing on startup, such as a game
// variant)
//
Expand Down
3 changes: 2 additions & 1 deletion src/createinstancedialogpages.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ void Page::next()

bool Page::action(CreateInstanceDialog::Actions a)
{
Q_UNUSED(a);
// no-op
return false;
}
Expand Down Expand Up @@ -1203,7 +1204,7 @@ NexusPage::NexusPage(CreateInstanceDialog& dlg) : Page(dlg), m_skip(false)

// just check it once, or connecting and then going back and forth would skip
// the page, which would be unexpected
m_skip = GlobalSettings::hasNexusApiKey();
m_skip = GlobalSettings::hasNexusOAuthTokens() || GlobalSettings::hasNexusApiKey();
}

NexusPage::~NexusPage() = default;
Expand Down
1 change: 1 addition & 0 deletions src/dlls.manifest.debug.qt6
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
<file name="Qt6Cored.dll" />
<file name="Qt6Guid.dll" />
<file name="Qt6Networkd.dll" />
<file name="Qt6NetworkAuthd.dll" />
<file name="Qt6OpenGLd.dll" />
<file name="Qt6OpenGLWidgetsd.dll" />
<file name="Qt6Positioningd.dll" />
Expand Down
1 change: 1 addition & 0 deletions src/dlls.manifest.qt6
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
<file name="Qt6Core.dll" />
<file name="Qt6Gui.dll" />
<file name="Qt6Network.dll" />
<file name="Qt6NetworkAuth.dll" />
<file name="Qt6OpenGL.dll" />
<file name="Qt6OpenGLWidgets.dll" />
<file name="Qt6Positioning.dll" />
Expand Down
6 changes: 4 additions & 2 deletions src/instancemanager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,10 @@ QString Instance::displayName() const
{
if (isPortable())
return QObject::tr("Portable");
else
return QDir(m_dir).dirName();
else {
QDir instanceDir(m_dir.toUtf8());
return instanceDir.dirName();
}
}

QString Instance::gameName() const
Expand Down
7 changes: 4 additions & 3 deletions src/moapplication.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -319,9 +319,10 @@ int MOApplication::run(MOMultiProcess& multiProcess)
tt.start("MOApplication::doOneRun() finishing");

// start an api check
QString apiKey;
if (GlobalSettings::nexusApiKey(apiKey)) {
m_nexus->getAccessManager()->apiCheck(apiKey);
NexusOAuthTokens tokens;
if (GlobalSettings::nexusOAuthTokens(tokens) ||
GlobalSettings::nexusApiKey(tokens.apiKey)) {
m_nexus->getAccessManager()->apiCheck(tokens);
}

// tutorials
Expand Down
14 changes: 8 additions & 6 deletions src/modlistviewactions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -230,12 +230,13 @@ void ModListViewActions::checkModsForUpdates() const
QString());
NexusInterface::instance().requestTrackingInfo(m_receiver, QVariant(), QString());
} else {
QString apiKey;
if (GlobalSettings::nexusApiKey(apiKey)) {
NexusOAuthTokens tokens;
if (GlobalSettings::nexusOAuthTokens(tokens) ||
GlobalSettings::nexusApiKey(tokens.apiKey)) {
m_core.doAfterLogin([=]() {
checkModsForUpdates();
});
NexusInterface::instance().getAccessManager()->apiCheck(apiKey);
NexusInterface::instance().getAccessManager()->apiCheck(tokens);
} else {
log::warn("{}", tr("You are not currently authenticated with Nexus. Please do so "
"under Settings -> Nexus."));
Expand Down Expand Up @@ -310,12 +311,13 @@ void ModListViewActions::checkModsForUpdates(
if (NexusInterface::instance().getAccessManager()->validated()) {
ModInfo::manualUpdateCheck(m_receiver, IDs);
} else {
QString apiKey;
if (GlobalSettings::nexusApiKey(apiKey)) {
NexusOAuthTokens tokens;
if (GlobalSettings::nexusOAuthTokens(tokens) ||
GlobalSettings::nexusApiKey(tokens.apiKey)) {
m_core.doAfterLogin([=]() {
checkModsForUpdates(IDs);
});
NexusInterface::instance().getAccessManager()->apiCheck(apiKey);
NexusInterface::instance().getAccessManager()->apiCheck(tokens);
} else
log::warn("{}", tr("You are not currently authenticated with Nexus. Please do so "
"under Settings -> Nexus."));
Expand Down
67 changes: 44 additions & 23 deletions src/nexusinterface.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -985,33 +985,54 @@ void NexusInterface::nextRequest()
} else {
url = info.m_URL;
}

const auto currentTokens = m_AccessManager->tokens();
if (!currentTokens ||
(currentTokens->accessToken.isEmpty() && currentTokens->apiKey.isEmpty())) {
log::error("nexus: no OAuth token available, request aborted");
info.m_Reply = nullptr;
return;
}

QNetworkRequest request(url);
request.setAttribute(QNetworkRequest::CacheSaveControlAttribute, false);
request.setAttribute(QNetworkRequest::CacheLoadControlAttribute,
QNetworkRequest::AlwaysNetwork);
request.setRawHeader("APIKEY", m_User.apiKey().toUtf8());
request.setHeader(QNetworkRequest::KnownHeaders::UserAgentHeader,
m_AccessManager->userAgent(info.m_SubModule));
request.setHeader(QNetworkRequest::KnownHeaders::ContentTypeHeader,
"application/json");
request.setRawHeader("Protocol-Version", "1.0.0");
request.setRawHeader("Application-Name", "MO2");
request.setRawHeader("Application-Version",
QApplication::applicationVersion().toUtf8());

if (postData.object().isEmpty()) {
if (!requestIsDelete) {
info.m_Reply = m_AccessManager->get(request);
if (!currentTokens->accessToken.isEmpty()) {
if (currentTokens->isExpired()) {
m_AccessManager->connectOrRefresh(*currentTokens);
return;
}
if (postData.object().isEmpty()) {
if (!requestIsDelete) {
info.m_Reply = m_AccessManager->makeOAuthGetRequest(url);
} else {
m_AccessManager->addAPIHeaders(request);
info.m_Reply = m_AccessManager->makeOAuthDeleteRequest(request);
}
} else if (!requestIsDelete) {
info.m_Reply = m_AccessManager->makeOAuthPostRequest(url, postData.toJson());
} else {
info.m_Reply = m_AccessManager->deleteResource(request);
// Qt doesn't support DELETE with a payload as that's technically against the HTTP
// standard...
info.m_Reply =
m_AccessManager->makeOAuthCustomRequest(request, "DELETE", postData.toJson());
}
} else if (!requestIsDelete) {
info.m_Reply = m_AccessManager->post(request, postData.toJson());
} else {
// Qt doesn't support DELETE with a payload as that's technically against the HTTP
// standard...
info.m_Reply =
m_AccessManager->sendCustomRequest(request, "DELETE", postData.toJson());
request.setRawHeader("APIKEY", currentTokens->apiKey.toUtf8());
m_AccessManager->addAPIHeaders(request);

if (postData.object().isEmpty()) {
if (!requestIsDelete) {
info.m_Reply = m_AccessManager->get(request);
} else {
info.m_Reply = m_AccessManager->deleteResource(request);
}
} else if (!requestIsDelete) {
info.m_Reply = m_AccessManager->post(request, postData.toJson());
} else {
// Qt doesn't support DELETE with a payload as that's technically against the HTTP
// standard...
info.m_Reply =
m_AccessManager->sendCustomRequest(request, "DELETE", postData.toJson());
}
}

connect(info.m_Reply, SIGNAL(finished()), this, SLOT(requestFinished()));
Expand Down
62 changes: 62 additions & 0 deletions src/nexusoauthconfig.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/*
Copyright (C) 2012 Sebastian Herbord. All rights reserved.

This file is part of Mod Organizer.

Mod Organizer is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

Mod Organizer is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
*/

#include "nexusoauthconfig.h"
#include <QProcessEnvironment>

namespace
{
QString envOrDefault(const char* name, const QString& fallback)
{
const auto value = qEnvironmentVariable(name);
if (!value.isEmpty()) {
return value;
}

return fallback;
}
} // namespace

namespace NexusOAuth
{
QString clientId()
{
return envOrDefault("MO2_NEXUS_CLIENT_ID", QStringLiteral("modorganizer2"));
}

quint16 redirectPort()
{
return 28635;
}

QString redirectUri()
{
return QStringLiteral("http://127.0.0.1:%1/callback").arg(redirectPort());
}

QString authorizeUrl()
{
return QStringLiteral("https://users.nexusmods.com/oauth/authorize");
}

QString tokenUrl()
{
return QStringLiteral("https://users.nexusmods.com/oauth/token");
}
} // namespace NexusOAuth
34 changes: 34 additions & 0 deletions src/nexusoauthconfig.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*
Copyright (C) 2012 Sebastian Herbord. All rights reserved.

This file is part of Mod Organizer.

Mod Organizer is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

Mod Organizer is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
*/

#ifndef NEXUSOAUTHCONFIG_H
#define NEXUSOAUTHCONFIG_H

#include <QString>

namespace NexusOAuth
{
QString clientId();
QString redirectUri();
quint16 redirectPort();
QString authorizeUrl();
QString tokenUrl();
} // namespace NexusOAuth

#endif // NEXUSOAUTHCONFIG_H
Loading
Loading