Skip to content
Merged
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
108 changes: 23 additions & 85 deletions App/Server/QJWT.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ tmplConfigurable<quint16> QJWT::TTL(
tmplConfigurable<quint32> QJWT::NormalLoginTTL(
QJWT::makeConfig("NormalLoginTTL"),
"Time to live for the login token",
static_cast<quint16>(24*60*60),
static_cast<quint32>(24*60*60),
ReturnTrueCrossValidator(),
"",
"",
Expand All @@ -91,7 +91,7 @@ tmplConfigurable<quint32> QJWT::NormalLoginTTL(
tmplConfigurable<quint32> QJWT::RememberLoginTTL(
QJWT::makeConfig("RememberLoginTTL"),
"Time to live for the login token when remembered",
static_cast<quint16>(7*24*60*60),
static_cast<quint32>(7*24*60*60),
ReturnTrueCrossValidator(),
"",
"TTL",
Expand Down Expand Up @@ -121,20 +121,27 @@ QString QJWT::createSigned(
{
const QString Header = QString("{\"typ\":\"JWT\",\"alg\":\"%1\"}").arg(enuJWTHashAlgs::toStr(QJWT::HashAlgorithm.value()));

_payload["iat"] = static_cast<qint64>(QDateTime::currentDateTime().toTime_t());
if (_payload.contains("iat") == false)
_payload["iat"] = static_cast<qint64>(QDateTime::currentDateTime().toTime_t());

if (_expiry >= 0)
_payload["exp"] = _payload["iat"].toInt() + _expiry;
else
_payload.remove("exp");

bool ssnRemember = true;
if (_payload.contains("ssnexp") == false)
_payload["ssnexp"] = _payload["iat"].toInt()
+ (qint32)(ssnRemember ? Server::QJWT::RememberLoginTTL.value() : Server::QJWT::NormalLoginTTL.value());

if (_sessionID.size())
_payload["jti"] = _sessionID;
else
_payload.remove("jti");

if (_remoteIP.isEmpty() == false)
_privatePayload.insert("cip", _remoteIP);
_privatePayload["cip"] = _remoteIP;
// _privatePayload.insert("cip", _remoteIP);

if (_privatePayload.isEmpty() == false)
_payload["prv"] = simpleCryptInstance()->encryptToString(QJsonDocument(_privatePayload).toJson());
Expand All @@ -146,10 +153,9 @@ QString QJWT::createSigned(
return Data + "." + QJWT::hash(Data).toBase64();
}

QJsonObject QJWT::verifyReturnPayload(
QString &_jwt,
const QString &_remoteIP,
bool _renewIfExpired
TAPI::JWT_t QJWT::verifyJWT(
const QString &_jwt,
const QString &_remoteIP
)
{
QStringList JWTParts = _jwt.split('.');
Expand All @@ -166,7 +172,7 @@ QJsonObject QJWT::verifyReturnPayload(
if (Payload.isNull())
throw exHTTPForbidden("Invalid JWT payload: " + Error.errorString());

QJsonObject JWTPayload = Payload.object();
TAPI::JWT_t JWTPayload = Payload.object();

if (JWTPayload.empty())
throw exHTTPForbidden("Invalid JWT payload: empty object");
Expand All @@ -185,7 +191,6 @@ QJsonObject QJWT::verifyReturnPayload(
throw exHTTPExpectationFailed("Invalid private JWT payload: " + Error.errorString());

QJsonObject PrivateObject = Private.object();

JWTPayload["prv"] = PrivateObject;

// check client ip ---------------
Expand All @@ -196,82 +201,15 @@ QJsonObject QJWT::verifyReturnPayload(
}
}

if (JWTPayload.contains("exp")
&& static_cast<quint64>(JWTPayload.value("exp").toInt()) <= QDateTime::currentDateTime().toTime_t()
)
{
if (_renewIfExpired == false)
throw exHTTPUnauthorized("JWT expired");

QString SessionKey = JWTPayload["jti"].toString();

DBManager::clsDAC DAC("AAA"); //master db dac -> AAA

//check session
QString Qry = R"(
SELECT TIME_TO_SEC((TIMEDIFF(NOW(), ssnCreationDateTime))) AS LifeSeconds
, tblActiveSessions.*
FROM tblActiveSessions
WHERE ssnKey=?
AND ssnStatus='A'
)";
QJsonDocument Result = DAC.execQuery({}, Qry, { SessionKey })
.toJson(true);

if (Result.object().isEmpty())
throw exHTTPUnauthorized("Active session not found");

QVariantMap SessionInfo = Result.toVariant().toMap();
//check large expiration
if (JWTPayload.contains("ssnexp") == false)
exHTTPForbidden("Invalid ssnexp in JWT");
if (static_cast<quint64>(JWTPayload.value("ssnexp").toInt()) <= QDateTime::currentDateTime().toTime_t())
throw exHTTPUnauthorized("Session expired");

//check old JWT
QString ssnJWT = SessionInfo["ssnJWT"].toString();
if ((ssnJWT.isEmpty() == false) && (_jwt != ssnJWT))
{
_jwt = ssnJWT; //this will add response header X-AUTH-NEW-TOKEN
throw exHTTPForbidden("JWT not replaced by client");
}

//check large expiration
quint64 LifeSeconds = SessionInfo["LifeSeconds"].toUInt();
bool ssnRemember = (SessionInfo["ssnRemember"].toInt() == 1);

if (LifeSeconds >= (ssnRemember ? QJWT::RememberLoginTTL.value() : QJWT::NormalLoginTTL.value()))
{
Qry = R"(
UPDATE tblActiveSessions
SET ssnStatus='E'
WHERE ssnKey=?
)";
DAC.execQuery({}, Qry, { SessionKey });

throw exHTTPUnauthorized("Session expired");
}

//
if (SessionInfo["ssnIPReadable"] != _remoteIP)
throw exHTTPForbidden("Invalid IP");

//ssn_usrID
//ssnFingerPrint

//TODO: check user ban or large expiration

//----------------------------------------
_jwt = QJWT::createSigned(
JWTPayload,
JWTPayload.contains("prv") ? JWTPayload["prv"].toObject() : QJsonObject(),
JWTPayload["exp"].toInt() - JWTPayload["iat"].toInt(),
JWTPayload["jti"].toString(),
_remoteIP
);

Qry = R"(
UPDATE tblActiveSessions
SET ssnJWT=?
WHERE ssnKey=?
)";
DAC.execQuery({}, Qry, { _jwt, SessionKey });
}
if (JWTPayload.contains("exp")
&& static_cast<quint64>(JWTPayload.value("exp").toInt()) <= QDateTime::currentDateTime().toTime_t())
throw exJWTExpired("JWT expired");

return JWTPayload;
}
Expand Down
12 changes: 8 additions & 4 deletions App/Server/QJWT.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,18 @@
#define TARGOMAN_API_SERVER_CLSJWT_H

#include "libTargomanCommon/Configuration/tmplConfigurable.h"
#include "Interfaces/Common/GenericTypes.h"
#include "ServerConfigs.h"

namespace Targoman::API::AAA {
class clsJWT;
}

namespace Targoman::API::Server {

TARGOMAN_ADD_EXCEPTION_HANDLER(exJWT, exTargomanAPI);
TARGOMAN_ADD_EXCEPTION_HANDLER(exJWTExpired, exJWT);

TARGOMAN_DEFINE_ENHANCED_ENUM(enuJWTHashAlgs,
HS256,
HS384,
Expand All @@ -55,10 +60,9 @@ class QJWT
const QString &_remoteIP = {}
);

static QJsonObject verifyReturnPayload(
/*INOUT*/ QString &_jwt,
const QString &_remoteIP,
bool _renewIfExpired = false
static TAPI::JWT_t verifyJWT(
const QString &_jwt,
const QString &_remoteIP
);

private:
Expand Down
27 changes: 15 additions & 12 deletions App/Server/clsRequestHandler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@
#include "APICache.hpp"
#include "OpenAPIGenerator.h"

#include "Interfaces/AAA/Authentication.h"
using namespace Targoman::API::AAA;

namespace Targoman::API::Server {

using namespace qhttp::server;
Expand Down Expand Up @@ -315,7 +318,7 @@ clsRequestHandler::stuResult clsRequestHandler::run(clsAPIObject* _apiObject, QS

qhttp::THeaderHash Headers = this->Request->headers();
qhttp::THeaderHash Cookies;
QJsonObject JWT;
TAPI::JWT_t JWT;

if (_apiObject->requiresJWT())
{
Expand All @@ -325,26 +328,26 @@ clsRequestHandler::stuResult clsRequestHandler::run(clsAPIObject* _apiObject, QS
QString BearerToken = Auth.mid(sizeof("Bearer"));
Headers.remove("authorization");

QString OldBearerToken = BearerToken;
QString RemoteIP = this->toIPv4(this->Request->remoteAddress());

try
{
JWT = QJWT::verifyReturnPayload(
JWT = QJWT::verifyJWT(
BearerToken,
this->toIPv4(this->Request->remoteAddress()),
true
RemoteIP
);

if (BearerToken != OldBearerToken)
ResponseHeaders.insert("X-AUTH-NEW-TOKEN", BearerToken);
}
catch (...)
catch (exJWTExpired &exp)
{
if (BearerToken != OldBearerToken)
ResponseHeaders.insert("X-AUTH-NEW-TOKEN", BearerToken);
QString NewToken = Authentication::renewJWT(
BearerToken,
RemoteIP
);

throw;
BearerToken = NewToken;
ResponseHeaders.insert("X-AUTH-NEW-TOKEN", BearerToken);
}
JWT["encodedJWT"] = BearerToken;
}
else
throw exHTTPForbidden("No valid authentication header is present");
Expand Down
52 changes: 52 additions & 0 deletions Interfaces/AAA/Authentication.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@

#include "Authentication.h"
#include "PrivHelpers.h"
#include "Interfaces/AAA/clsJWT.hpp"
#include "App/Server/QJWT.h"

namespace Targoman::API::AAA::Authentication {

Expand Down Expand Up @@ -56,16 +58,66 @@ stuActiveAccount login(
return PrivHelpers::processUserObject(UserInfo, {}, _requiredServices);
}

/*
stuActiveAccount updatePrivs(const QString& _ip, const QString& _ssid, const QString& _requiredServices)
{
makeAAADAC(DAC);

QJsonObject UserInfo = DAC.callSP({},
"spSession_UpdateActivity", {
{"iIP", _ip},
{"iSSID", _ssid},
}).toJson(true).object();

return PrivHelpers::processUserObject(UserInfo, {}, _requiredServices.split(',', QString::SkipEmptyParts));
}
*/

QString renewJWT(
const QString &_jwt,
const QString &_ip
)
{
QStringList JWTParts = _jwt.split('.');

if (JWTParts.length() != 3)
throw exHTTPForbidden("Invalid JWT Token");

QJsonParseError Error;
QJsonDocument Payload = QJsonDocument::fromJson(QByteArray::fromBase64(JWTParts.at(1).toLatin1()), &Error);

if (Payload.isNull())
throw exHTTPForbidden("Invalid JWT payload: " + Error.errorString());

TAPI::JWT_t JWTPayload = Payload.object();

clsJWT JWT(JWTPayload);
QStringList Services = JWT.privatePart().value("svc").toString().split(',', QString::SkipEmptyParts);

makeAAADAC(DAC);

quint32 Duration = JWTPayload["exp"].toInt() - JWTPayload["iat"].toInt();
QJsonObject UserInfo = DAC.callSP({},
"spSessionRetrieveInfo", {
{ "iSSID", JWT.session() },
{ "iIP", _ip },
{ "iIssuance", JWTPayload["iat"].toInt() },
}).toJson(true).object();


stuActiveAccount ActiveAccount = PrivHelpers::processUserObject(UserInfo, {}, Services);

JWTPayload["iat"] = ActiveAccount.Privs["Issuance"];
JWTPayload["privs"] = ActiveAccount.Privs["privs"];

return Server::QJWT::createSigned(
JWTPayload,
JWTPayload.contains("prv") ? JWTPayload["prv"].toObject() : QJsonObject(),
Duration,
JWTPayload["jti"].toString(),
_ip
);
}

QString retrievePhoto(const QString _url)
{
Expand Down
34 changes: 20 additions & 14 deletions Interfaces/AAA/Authentication.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
#ifndef TARGOMAN_API_AAA_AUTHENTICATION_H
#define TARGOMAN_API_AAA_AUTHENTICATION_H

#include "Interfaces/Common/GenericTypes.h"
#include "Interfaces/AAA/AAADefs.hpp"
#include "Interfaces/AAA/PrivHelpers.h"

Expand All @@ -48,21 +49,26 @@ struct stuOAuthInfo {
};

extern Targoman::API::AAA::stuActiveAccount login(
const QString& _ip,
const QString& _login,
const QString& _pass,
const QString& _salt,
const QStringList& _requiredServices,
bool _rememberMe,
const QJsonObject& _info,
const QString& _fingerPrint
);
const QString &_ip,
const QString &_login,
const QString &_pass,
const QString &_salt,
const QStringList &_requiredServices,
bool _rememberMe,
const QJsonObject &_info,
const QString &_fingerPrint
);

extern Targoman::API::AAA::stuActiveAccount updatePrivs(const QString& _ip, const QString& _ssid, const QString& _requiredServices);
extern stuOAuthInfo retrieveGoogleUserInfo(const QString& _authToken);
extern stuOAuthInfo retrieveLinkedinUserInfo(const QString& _authToken);
extern stuOAuthInfo retrieveYahooUserInfo(const QString& _authToken);
extern stuOAuthInfo retrieveGitHubUserInfo(const QString& _authToken);
//extern Targoman::API::AAA::stuActiveAccount updatePrivs(const QString &_ip, const QString &_ssid, const QString &_requiredServices);
extern QString renewJWT(
const QString &_jwt,
const QString &_ip
);

extern stuOAuthInfo retrieveGoogleUserInfo(const QString &_authToken);
extern stuOAuthInfo retrieveLinkedinUserInfo(const QString &_authToken);
extern stuOAuthInfo retrieveYahooUserInfo(const QString &_authToken);
extern stuOAuthInfo retrieveGitHubUserInfo(const QString &_authToken);

} //namespace Targoman::API::AAA::Authentication

Expand Down
Loading