summaryrefslogtreecommitdiff
path: root/protocols
diff options
context:
space:
mode:
authorGeorge Hazan <george.hazan@gmail.com>2024-01-14 18:43:24 +0300
committerGeorge Hazan <george.hazan@gmail.com>2024-01-14 18:43:24 +0300
commita8c9c34bead54d469f126db194b571e2fe90b078 (patch)
treee4348568eb8a966f4d87abf50beb98b2808a78b2 /protocols
parenta76d633fcee305009b2c8798d577e16f8dd0d931 (diff)
Discord: add MFA support
Diffstat (limited to 'protocols')
-rw-r--r--protocols/Discord/discord.vcxproj1
-rw-r--r--protocols/Discord/discord.vcxproj.filters3
-rw-r--r--protocols/Discord/res/discord.rc26
-rw-r--r--protocols/Discord/src/http.cpp2
-rw-r--r--protocols/Discord/src/mfa.cpp112
-rw-r--r--protocols/Discord/src/options.cpp4
-rw-r--r--protocols/Discord/src/proto.h10
-rw-r--r--protocols/Discord/src/resource.h10
-rw-r--r--protocols/Discord/src/server.cpp17
9 files changed, 173 insertions, 12 deletions
diff --git a/protocols/Discord/discord.vcxproj b/protocols/Discord/discord.vcxproj
index 59139a349e..dc4a3679bc 100644
--- a/protocols/Discord/discord.vcxproj
+++ b/protocols/Discord/discord.vcxproj
@@ -35,6 +35,7 @@
<ClCompile Include="src\http.cpp" />
<ClCompile Include="src\main.cpp" />
<ClCompile Include="src\menus.cpp" />
+ <ClCompile Include="src\mfa.cpp" />
<ClCompile Include="src\options.cpp" />
<ClCompile Include="src\proto.cpp" />
<ClCompile Include="src\server.cpp" />
diff --git a/protocols/Discord/discord.vcxproj.filters b/protocols/Discord/discord.vcxproj.filters
index f8b955739b..61ee857295 100644
--- a/protocols/Discord/discord.vcxproj.filters
+++ b/protocols/Discord/discord.vcxproj.filters
@@ -47,6 +47,9 @@
<ClCompile Include="src\voice.cpp">
<Filter>Source Files</Filter>
</ClCompile>
+ <ClCompile Include="src\mfa.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="src\proto.h">
diff --git a/protocols/Discord/res/discord.rc b/protocols/Discord/res/discord.rc
index 780cae5613..ce433c060f 100644
--- a/protocols/Discord/res/discord.rc
+++ b/protocols/Discord/res/discord.rc
@@ -112,6 +112,19 @@ BEGIN
EDITTEXT IDC_NICK,3,18,103,12,0,WS_EX_CLIENTEDGE
END
+IDD_MFA DIALOGEX 0, 0, 316, 52
+STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | DS_CENTER | WS_POPUP | WS_CAPTION | WS_SYSMENU
+EXSTYLE WS_EX_APPWINDOW
+CAPTION "2-step verification"
+FONT 8, "MS Shell Dlg", 400, 0, 0x1
+BEGIN
+ DEFPUSHBUTTON "OK",IDOK,205,31,50,14
+ PUSHBUTTON "Cancel",IDCANCEL,259,31,50,14
+ LTEXT "",IDC_LABEL,7,8,238,12
+ EDITTEXT IDC_CODE,250,7,59,16,ES_AUTOHSCROLL
+ PUSHBUTTON "Use another method",IDC_ANOTHER,7,31,131,14
+END
+
/////////////////////////////////////////////////////////////////////////////
//
@@ -128,6 +141,14 @@ BEGIN
IDD_OPTIONS_ACCMGR, DIALOG
BEGIN
END
+
+ IDD_MFA, DIALOG
+ BEGIN
+ LEFTMARGIN, 7
+ RIGHTMARGIN, 309
+ TOPMARGIN, 7
+ BOTTOMMARGIN, 45
+ END
END
#endif // APSTUDIO_INVOKED
@@ -142,6 +163,11 @@ BEGIN
0
END
+IDD_MFA AFX_DIALOG_LAYOUT
+BEGIN
+ 0
+END
+
#endif // English (United States) resources
/////////////////////////////////////////////////////////////////////////////
diff --git a/protocols/Discord/src/http.cpp b/protocols/Discord/src/http.cpp
index 82e8d495d0..70cfe9db80 100644
--- a/protocols/Discord/src/http.cpp
+++ b/protocols/Discord/src/http.cpp
@@ -41,7 +41,7 @@ static LONG g_reqNum = 0;
AsyncHttpRequest::AsyncHttpRequest(CDiscordProto *ppro, int iRequestType, LPCSTR _url, MTHttpRequestHandler pFunc, JSONNode *pRoot)
{
if (*_url == '/') { // relative url leads to a site
- m_szUrl = "https://discord.com/api/v8";
+ m_szUrl = "https://discord.com/api/v9";
m_szUrl += _url;
m_bMainSite = true;
}
diff --git a/protocols/Discord/src/mfa.cpp b/protocols/Discord/src/mfa.cpp
new file mode 100644
index 0000000000..9de79283fa
--- /dev/null
+++ b/protocols/Discord/src/mfa.cpp
@@ -0,0 +1,112 @@
+/*
+Copyright © 2016-22 Miranda NG team
+
+This program 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 2 of the License, or
+(at your option) any later version.
+
+This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "stdafx.h"
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+class CMfaDialog : public CDiscordDlgBase
+{
+ bool m_bHasSms, m_bHasTotp, m_bUseTotp = true;
+ CMStringA m_szTicket;
+
+ CCtrlBase m_label;
+ CCtrlEdit edtCode;
+ CCtrlButton btnCancel, btnAnother;
+
+public:
+ CMfaDialog(CDiscordProto *ppro, const JSONNode &pRoot) :
+ CDiscordDlgBase(ppro, IDD_MFA),
+ m_label(this, IDC_LABEL),
+ edtCode(this, IDC_CODE),
+ btnCancel(this, IDCANCEL),
+ btnAnother(this, IDC_ANOTHER)
+ {
+ m_bHasSms = pRoot["sms"].as_bool();
+ m_bHasTotp = pRoot["totp"].as_bool();
+ m_szTicket = pRoot["ticket"].as_mstring();
+ }
+
+ bool OnInitDialog() override
+ {
+ if (m_bUseTotp)
+ m_label.SetText(TranslateT("Enter Discord verification code:"));
+ else
+ m_label.SetText(TranslateT("Enter SMS code"));
+
+ // if (!m_bHasSms)
+ btnAnother.Disable();
+ return true;
+ }
+
+ bool OnApply() override
+ {
+ ptrW wszCode(edtCode.GetText());
+
+ if (m_bUseTotp) {
+ JSONNode root;
+ root << WCHAR_PARAM("code", wszCode) << CHAR_PARAM("ticket", m_szTicket);
+
+ auto *pReq = new AsyncHttpRequest(m_proto, REQUEST_POST, "/auth/mfa/totp", &CDiscordProto::OnSendTotp, &root);
+ pReq->pUserInfo = this;
+ m_proto->Push(pReq);
+ }
+ return false;
+ }
+
+ void onClick_Cancel()
+ {
+ m_proto->ConnectionFailed(LOGINERR_WRONGPASSWORD);
+ }
+
+ void WrongCode()
+ {
+ edtCode.SetText(L"");
+ Beep(470, 200);
+ }
+};
+
+static void CALLBACK LaunchDialog(void *param)
+{
+ ((CMfaDialog *)param)->Show();
+}
+
+void CDiscordProto::ShowMfaDialog(const JSONNode &pRoot)
+{
+ CallFunctionAsync(LaunchDialog, new CMfaDialog(this, pRoot));
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+void CDiscordProto::OnSendTotp(MHttpResponse *pReply, struct AsyncHttpRequest *pReq)
+{
+ auto *pDlg = (CMfaDialog *)pReq->pUserInfo;
+
+ JsonReply root(pReply);
+ if (!root) {
+ pDlg->WrongCode();
+ return;
+ }
+
+ pDlg->Close();
+
+ auto &data = root.data();
+ CMStringA szToken = data["token"].as_mstring();
+ m_szAccessToken = szToken.Detach();
+ setString("AccessToken", m_szAccessToken);
+ RetrieveMyInfo();
+}
diff --git a/protocols/Discord/src/options.cpp b/protocols/Discord/src/options.cpp
index 4716584b96..dc4a6d60b5 100644
--- a/protocols/Discord/src/options.cpp
+++ b/protocols/Discord/src/options.cpp
@@ -19,7 +19,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
/////////////////////////////////////////////////////////////////////////////////////////
-class CDiscardAccountOptions : public CProtoDlgBase<CDiscordProto>
+class CDiscardAccountOptions : public CDiscordDlgBase
{
CCtrlCheck chkUseChats, chkHideChats, chkUseGroups, chkDeleteMsgs;
CCtrlEdit m_edGroup, m_edUserName, m_edPassword;
@@ -27,7 +27,7 @@ class CDiscardAccountOptions : public CProtoDlgBase<CDiscordProto>
public:
CDiscardAccountOptions(CDiscordProto *ppro, int iDlgID, bool bFullDlg) :
- CProtoDlgBase<CDiscordProto>(ppro, iDlgID),
+ CDiscordDlgBase(ppro, iDlgID),
m_edGroup(this, IDC_GROUP),
m_edUserName(this, IDC_USERNAME),
m_edPassword(this, IDC_PASSWORD),
diff --git a/protocols/Discord/src/proto.h b/protocols/Discord/src/proto.h
index fdb5e6a33b..bb99d2efbc 100644
--- a/protocols/Discord/src/proto.h
+++ b/protocols/Discord/src/proto.h
@@ -227,6 +227,7 @@ class CDiscordProto : public PROTO<CDiscordProto>
{
friend struct AsyncHttpRequest;
friend class CDiscardAccountOptions;
+ friend class CMfaDialog;
class CDiscordProtoImpl
{
@@ -395,6 +396,13 @@ class CDiscordProto : public PROTO<CDiscordProto>
void ParseSpecialChars(SESSION_INFO *si, CMStringW &str);
//////////////////////////////////////////////////////////////////////////////////////
+ // two-factor auth
+
+ void ShowMfaDialog(const JSONNode &pRoot);
+
+ void OnSendTotp(MHttpResponse *, struct AsyncHttpRequest *);
+
+ //////////////////////////////////////////////////////////////////////////////////////
// misc methods
SnowFlake getId(const char *szName);
@@ -526,6 +534,8 @@ public:
void CheckAvatarChange(MCONTACT hContact, const CMStringW &wszNewHash);
};
+typedef CProtoDlgBase<CDiscordProto> CDiscordDlgBase;
+
/////////////////////////////////////////////////////////////////////////////////////////
struct CMPlugin : public ACCPROTOPLUGIN<CDiscordProto>
diff --git a/protocols/Discord/src/resource.h b/protocols/Discord/src/resource.h
index 099a4af3af..dc1c9706f1 100644
--- a/protocols/Discord/src/resource.h
+++ b/protocols/Discord/src/resource.h
@@ -1,6 +1,6 @@
//{{NO_DEPENDENCIES}}
// Microsoft Visual C++ generated include file.
-// Used by w:\miranda-ng\protocols\Discord\res\discord.rc
+// Used by W:\miranda-ng\protocols\Discord\res\discord.rc
//
#define IDI_MAIN 101
#define IDI_GROUPCHAT 102
@@ -9,6 +9,7 @@
#define IDD_OPTIONS_ACCMGR 105
#define IDI_VOICE_CALL 106
#define IDI_VOICE_ENDED 107
+#define IDD_MFA 108
#define IDC_PASSWORD 1001
#define IDC_USERNAME 1002
#define IDC_GROUP 1003
@@ -16,15 +17,18 @@
#define IDC_HIDECHATS 1005
#define IDC_USEGROUPS 1006
#define IDC_USEGUILDS 1007
+#define IDC_CODE 1008
#define IDC_DELETE_MSGS 1009
+#define IDC_ANOTHER 1009
+#define IDC_LABEL 1010
// Next default values for new objects
//
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
-#define _APS_NEXT_RESOURCE_VALUE 104
+#define _APS_NEXT_RESOURCE_VALUE 108
#define _APS_NEXT_COMMAND_VALUE 40001
-#define _APS_NEXT_CONTROL_VALUE 1008
+#define _APS_NEXT_CONTROL_VALUE 1011
#define _APS_NEXT_SYMED_VALUE 101
#endif
#endif
diff --git a/protocols/Discord/src/server.cpp b/protocols/Discord/src/server.cpp
index 104d45435d..75167a7a60 100644
--- a/protocols/Discord/src/server.cpp
+++ b/protocols/Discord/src/server.cpp
@@ -281,13 +281,18 @@ void CDiscordProto::OnReceiveToken(MHttpResponse *pReply, AsyncHttpRequest*)
}
auto &data = root.data();
- CMStringA szToken = data["token"].as_mstring();
- if (szToken.IsEmpty()) {
- debugLogA("Strange empty token received, exiting");
+ if (auto &token = data["token"]) {
+ CMStringA szToken = token.as_mstring();
+ m_szAccessToken = szToken.Detach();
+ setString("AccessToken", m_szAccessToken);
+ RetrieveMyInfo();
return;
}
- m_szAccessToken = szToken.Detach();
- setString("AccessToken", m_szAccessToken);
- RetrieveMyInfo();
+ if (data["mfa"].as_bool())
+ ShowMfaDialog(data);
+ else {
+ ConnectionFailed(LOGINERR_WRONGPASSWORD);
+ debugLogA("Strange empty token received, exiting");
+ }
}