diff --git a/README.md b/README.md
index 4d7d6204ad..932622600b 100644
--- a/README.md
+++ b/README.md
@@ -15,6 +15,7 @@
* 🔒 Full account management.
* Add multiple accounts and easily switch between them.
+ * Microsoft (OAuth 2.0) + Mojang (Yggdrasil) authentication fully supported.
* Credentials are never stored and transmitted directly to Mojang.
* 📂 Efficient asset management.
* Receive client updates as soon as we release them.
@@ -180,13 +181,15 @@ Note that you **cannot** open the DevTools window while using this debug configu
Please give credit to the original author and provide a link to the original source. This is free software, please do at least this much.
+For instructions on setting up Microsoft Authentication, see https://github.com/dscalzi/HeliosLauncher/blob/feature/ms-auth/docs/MicrosoftAuth.md.
+
---
## Resources
* [Wiki][wiki]
* [Nebula (Create Distribution.json)][nebula]
-* [v2 Rewrite Branch (WIP)][v2branch]
+* [v2 Rewrite Branch (Inactive)][v2branch]
The best way to contact the developers is on Discord.
diff --git a/app/app.ejs b/app/app.ejs
index 499c10d595..e829fa14d7 100644
--- a/app/app.ejs
+++ b/app/app.ejs
@@ -31,6 +31,8 @@
<%- include('welcome') %>
<%- include('login') %>
+ <%- include('waiting') %>
+ <%- include('loginOptions') %>
<%- include('settings') %>
<%- include('landing') %>
diff --git a/app/assets/css/launcher.css b/app/assets/css/launcher.css
index 56816997fb..e67984e4ce 100644
--- a/app/assets/css/launcher.css
+++ b/app/assets/css/launcher.css
@@ -222,6 +222,7 @@ body, button {
align-items: center;
height: 100%;
width: 100%;
+ background: rgba(0, 0, 0, 0.50);
}
#welcomeContent {
@@ -872,6 +873,175 @@ body, button {
}
*/
+/*******************************************************************************
+ * *
+ * Waiting View (waiting.ejs) *
+ * *
+ ******************************************************************************/
+
+#waitingContainer {
+ position: relative;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ height: 100%;
+ width: 100%;
+ transition: filter 0.25s ease;
+ background: rgba(0, 0, 0, 0.50);
+}
+
+#waitingContent {
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+ width: 50%;
+ top: -10%;
+ position: relative;
+}
+
+.waitingSpinner:before {
+ transform: rotateX(60deg) rotateY(45deg) rotateZ(45deg);
+ animation: 750ms rotateBefore infinite linear reverse;
+}
+.waitingSpinner:after {
+ transform: rotateX(240deg) rotateY(45deg) rotateZ(45deg);
+ animation: 750ms rotateAfter infinite linear;
+}
+.waitingSpinner:before,
+.waitingSpinner:after {
+ box-sizing: border-box;
+ content: '';
+ display: block;
+ position: fixed;
+ top: calc(50% - 5em);
+ /* left: 50%; */
+ margin-top: -5em;
+ margin-left: -5em;
+ width: 10em;
+ height: 10em;
+ transform-style: preserve-3d;
+ transform-origin: 50%;
+ transform: rotateY(50%);
+ perspective-origin: 50% 50%;
+ perspective: 340px;
+ background-size: 10em 10em;
+ background-image: url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+Cjxzdmcgd2lkdGg9IjI2NnB4IiBoZWlnaHQ9IjI5N3B4IiB2aWV3Qm94PSIwIDAgMjY2IDI5NyIgdmVyc2lvbj0iMS4xIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB4bWxuczpza2V0Y2g9Imh0dHA6Ly93d3cuYm9oZW1pYW5jb2RpbmcuY29tL3NrZXRjaC9ucyI+CiAgICA8dGl0bGU+c3Bpbm5lcjwvdGl0bGU+CiAgICA8ZGVzY3JpcHRpb24+Q3JlYXRlZCB3aXRoIFNrZXRjaCAoaHR0cDovL3d3dy5ib2hlbWlhbmNvZGluZy5jb20vc2tldGNoKTwvZGVzY3JpcHRpb24+CiAgICA8ZGVmcz48L2RlZnM+CiAgICA8ZyBpZD0iUGFnZS0xIiBzdHJva2U9Im5vbmUiIHN0cm9rZS13aWR0aD0iMSIgZmlsbD0ibm9uZSIgZmlsbC1ydWxlPSJldmVub2RkIiBza2V0Y2g6dHlwZT0iTVNQYWdlIj4KICAgICAgICA8cGF0aCBkPSJNMTcxLjUwNzgxMywzLjI1MDAwMDM4IEMyMjYuMjA4MTgzLDEyLjg1NzcxMTEgMjk3LjExMjcyMiw3MS40OTEyODIzIDI1MC44OTU1OTksMTA4LjQxMDE1NSBDMjE2LjU4MjAyNCwxMzUuODIwMzEgMTg2LjUyODQwNSw5Ny4wNjI0OTY0IDE1Ni44MDA3NzQsODUuNzczNDM0NiBDMTI3LjA3MzE0Myw3NC40ODQzNzIxIDc2Ljg4ODQ2MzIsODQuMjE2MTQ2MiA2MC4xMjg5MDY1LDEwOC40MTAxNTMgQy0xNS45ODA0Njg1LDIxOC4yODEyNDcgMTQ1LjI3NzM0NCwyOTYuNjY3OTY4IDE0NS4yNzczNDQsMjk2LjY2Nzk2OCBDMTQ1LjI3NzM0NCwyOTYuNjY3OTY4IC0yNS40NDkyMTg3LDI1Ny4yNDIxOTggMy4zOTg0Mzc1LDEwOC40MTAxNTUgQzE2LjMwNzA2NjEsNDEuODExNDE3NCA4NC43Mjc1ODI5LC0xMS45OTIyOTg1IDE3MS41MDc4MTMsMy4yNTAwMDAzOCBaIiBpZD0iUGF0aC0xIiBmaWxsPSIjZmZmZmZmIiBza2V0Y2g6dHlwZT0iTVNTaGFwZUdyb3VwIj48L3BhdGg+CiAgICA8L2c+Cjwvc3ZnPg==);
+}
+
+#waitingTextContainer {
+ position: fixed;
+ top: 50%;
+}
+
+@keyframes rotateBefore {
+ from {
+ transform: rotateX(60deg) rotateY(45deg) rotateZ(0deg);
+ }
+ to {
+ transform: rotateX(60deg) rotateY(45deg) rotateZ(-360deg);
+ }
+}
+
+@keyframes rotateAfter {
+ from {
+ transform: rotateX(240deg) rotateY(45deg) rotateZ(0deg);
+ }
+ to {
+ transform: rotateX(240deg) rotateY(45deg) rotateZ(360deg);
+ }
+}
+
+/*******************************************************************************
+ * *
+ * Login Options View (loginOptions.ejs) *
+ * *
+ ******************************************************************************/
+
+#loginOptionsContainer {
+ position: relative;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ height: 100%;
+ width: 100%;
+ transition: filter 0.25s ease;
+ background: rgba(0, 0, 0, 0.50);
+}
+
+#loginOptionsContent {
+ border-radius: 3px;
+ position: relative;
+ top: -5%;
+}
+
+.loginOptionsMainContent {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+}
+
+.loginOptionActions {
+ display: flex;
+ flex-direction: column;
+ row-gap: 10px;
+}
+
+.loginOptionButtonContainer {
+ width: 16em;
+}
+
+.loginOptionButton {
+ background: rgba(0, 0, 0, 0.25);
+ border: 1px solid rgba(126, 126, 126, 0.57);
+ border-radius: 3px;
+ height: 50px;
+ width: 100%;
+ text-align: left;
+ padding: 0px 25px;
+ cursor: pointer;
+ outline: none;
+ transition: 0.25s ease;
+ display: flex;
+ align-items: center;
+ column-gap: 5px;
+}
+.loginOptionButton:hover,
+.loginOptionButton:focus {
+ background: rgba(54, 54, 54, 0.25);
+ text-shadow: 0px 0px 20px white;
+}
+
+#loginOptionCancelContainer {
+ position: absolute;
+ bottom: -100px;
+}
+
+#loginOptionCancelButton {
+ background: none;
+ border: none;
+ padding: 2px 0px;
+ font-size: 16px;
+ font-weight: bold;
+ color: lightgrey;
+ cursor: pointer;
+ outline: none;
+ transition: 0.25s ease;
+}
+#loginOptionCancelButton:hover,
+#loginOptionCancelButton:focus {
+ text-shadow: 0px 0px 20px lightgrey;
+}
+#loginOptionCancelButton:active {
+ text-shadow: 0px 0px 20px rgba(211, 211, 211, 0.75);
+ color: rgba(211, 211, 211, 0.75);
+}
+#loginOptionCancelButton:disabled {
+ color: rgba(211, 211, 211, 0.75);
+ pointer-events: none;
+}
+
+
/*******************************************************************************
* *
* Settings View (sttings.ejs) *
@@ -1269,45 +1439,65 @@ input:checked + .toggleSwitchSlider:before {
* Settings View (Account Tab)
* * */
-/* Add account button styles. */
-#settingsAddAccount {
- background: rgba(0, 0, 0, 0.25);
- border: 1px solid rgba(126, 126, 126, 0.57);
- border-radius: 3px;
- height: 50px;
+.settingsAuthAccountTypeContainer {
+ display: flex;
width: 75%;
+ flex-direction: column;
+}
+
+.settingsAuthAccountTypeHeader {
+ display: flex;
+ align-items: center;
+ width: 100%;
+ justify-content: space-between;
+ padding: 10px 0px;
+ border-bottom: 1px solid #ffffff85;
+ margin-bottom: 30px;
+}
+
+.settingsAuthAccountTypeHeaderLeft {
+ display: flex;
+ column-gap: 5px;
+}
+
+/* Settings add account button styles. */
+.settingsAddAuthAccount {
+ background: none;
+ border: none;
text-align: left;
- padding: 0px 50px;
+ padding: 2px 0px;
+ color: white;
cursor: pointer;
outline: none;
transition: 0.25s ease;
}
-#settingsAddAccount:hover,
-#settingsAddAccount:focus {
- background: rgba(54, 54, 54, 0.25);
- text-shadow: 0px 0px 20px white;
+.settingsAddAuthAccount:hover,
+.settingsAddAuthAccount:focus {
+ text-shadow: 0px 0px 20px white, 0px 0px 20px white, 0px 0px 20px white;
}
-
-/* Settings auth accounts header. */
-#settingsCurrentAccountsHeader {
- margin: 20px 0px;
+.settingsAddAuthAccount:active {
+ text-shadow: 0px 0px 20px rgba(255, 255, 255, 0.75), 0px 0px 20px rgba(255, 255, 255, 0.75), 0px 0px 20px rgba(255, 255, 255, 0.75);
+ color: rgba(255, 255, 255, 0.75);
+}
+.settingsAddAuthAccount:disabled {
+ color: rgba(255, 255, 255, 0.75);
+ pointer-events: none;
}
/* Auth account list container styles. */
-#settingsCurrentAccounts {
+.settingsCurrentAccounts {
margin-bottom: 5%;
}
-#settingsCurrentAccounts > .settingsAuthAccount:not(:last-child) {
+.settingsCurrentAccounts > .settingsAuthAccount:not(:last-child) {
margin-bottom: 10px;
}
-#settingsCurrentAccounts > .settingsAuthAccount:not(:first-child) {
+.settingsCurrentAccounts > .settingsAuthAccount:not(:first-child) {
margin-top: 10px;
}
/* Auth account shared styles. */
.settingsAuthAccount {
display: flex;
- width: 75%;
background: rgba(0, 0, 0, 0.25);
border-radius: 3px;
border: 1px solid rgba(126, 126, 126, 0.57);
diff --git a/app/assets/images/icons/microsoft.svg b/app/assets/images/icons/microsoft.svg
new file mode 100644
index 0000000000..78a4ed9436
--- /dev/null
+++ b/app/assets/images/icons/microsoft.svg
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/assets/images/icons/mojang.svg b/app/assets/images/icons/mojang.svg
new file mode 100644
index 0000000000..e1116b41ba
--- /dev/null
+++ b/app/assets/images/icons/mojang.svg
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/assets/js/authmanager.js b/app/assets/js/authmanager.js
index 5befb72728..5ec852894d 100644
--- a/app/assets/js/authmanager.js
+++ b/app/assets/js/authmanager.js
@@ -9,17 +9,19 @@
* @module authmanager
*/
// Requirements
-const ConfigManager = require('./configmanager')
-const { LoggerUtil } = require('helios-core')
-const { MojangRestAPI, mojangErrorDisplayable, MojangErrorCode } = require('helios-core/mojang')
+const ConfigManager = require('./configmanager')
+const { LoggerUtil } = require('helios-core')
const { RestResponseStatus } = require('helios-core/common')
+const { MojangRestAPI, mojangErrorDisplayable, MojangErrorCode } = require('helios-core/mojang')
+const { MicrosoftAuth, microsoftErrorDisplayable, MicrosoftErrorCode } = require('helios-core/microsoft')
+const { AZURE_CLIENT_ID } = require('./ipcconstants')
const log = LoggerUtil.getLogger('AuthManager')
// Functions
/**
- * Add an account. This will authenticate the given credentials with Mojang's
+ * Add a Mojang account. This will authenticate the given credentials with Mojang's
* authserver. The resultant data will be stored as an auth account in the
* configuration database.
*
@@ -27,7 +29,7 @@ const log = LoggerUtil.getLogger('AuthManager')
* @param {string} password The account password.
* @returns {Promise.} Promise which resolves the resolved authenticated account object.
*/
-exports.addAccount = async function(username, password){
+exports.addMojangAccount = async function(username, password) {
try {
const response = await MojangRestAPI.authenticate(username, password, ConfigManager.getClientToken())
console.log(response)
@@ -35,7 +37,7 @@ exports.addAccount = async function(username, password){
const session = response.data
if(session.selectedProfile != null){
- const ret = ConfigManager.addAuthAccount(session.selectedProfile.id, session.accessToken, username, session.selectedProfile.name)
+ const ret = ConfigManager.addMojangAuthAccount(session.selectedProfile.id, session.accessToken, username, session.selectedProfile.name)
if(ConfigManager.getClientToken() == null){
ConfigManager.setClientToken(session.clientToken)
}
@@ -55,14 +57,113 @@ exports.addAccount = async function(username, password){
}
}
+const AUTH_MODE = { FULL: 0, MS_REFRESH: 1, MC_REFRESH: 2 }
+
+/**
+ * Perform the full MS Auth flow in a given mode.
+ *
+ * AUTH_MODE.FULL = Full authorization for a new account.
+ * AUTH_MODE.MS_REFRESH = Full refresh authorization.
+ * AUTH_MODE.MC_REFRESH = Refresh of the MC token, reusing the MS token.
+ *
+ * @param {string} entryCode FULL-AuthCode. MS_REFRESH=refreshToken, MC_REFRESH=accessToken
+ * @param {*} authMode The auth mode.
+ * @returns An object with all auth data. AccessToken object will be null when mode is MC_REFRESH.
+ */
+async function fullMicrosoftAuthFlow(entryCode, authMode) {
+ try {
+
+ let accessTokenRaw
+ let accessToken
+ if(authMode !== AUTH_MODE.MC_REFRESH) {
+ const accessTokenResponse = await MicrosoftAuth.getAccessToken(entryCode, authMode === AUTH_MODE.MS_REFRESH, AZURE_CLIENT_ID)
+ if(accessTokenResponse.responseStatus === RestResponseStatus.ERROR) {
+ return Promise.reject(microsoftErrorDisplayable(accessTokenResponse.microsoftErrorCode))
+ }
+ accessToken = accessTokenResponse.data
+ accessTokenRaw = accessToken.access_token
+ } else {
+ accessTokenRaw = entryCode
+ }
+
+ const xblResponse = await MicrosoftAuth.getXBLToken(accessTokenRaw)
+ if(xblResponse.responseStatus === RestResponseStatus.ERROR) {
+ return Promise.reject(microsoftErrorDisplayable(xblResponse.microsoftErrorCode))
+ }
+ const xstsResonse = await MicrosoftAuth.getXSTSToken(xblResponse.data)
+ if(xstsResonse.responseStatus === RestResponseStatus.ERROR) {
+ return Promise.reject(microsoftErrorDisplayable(xstsResonse.microsoftErrorCode))
+ }
+ const mcTokenResponse = await MicrosoftAuth.getMCAccessToken(xstsResonse.data)
+ if(mcTokenResponse.responseStatus === RestResponseStatus.ERROR) {
+ return Promise.reject(microsoftErrorDisplayable(mcTokenResponse.microsoftErrorCode))
+ }
+ const mcProfileResponse = await MicrosoftAuth.getMCProfile(mcTokenResponse.data.access_token)
+ if(mcProfileResponse.responseStatus === RestResponseStatus.ERROR) {
+ return Promise.reject(microsoftErrorDisplayable(mcProfileResponse.microsoftErrorCode))
+ }
+ return {
+ accessToken,
+ accessTokenRaw,
+ xbl: xblResponse.data,
+ xsts: xstsResonse.data,
+ mcToken: mcTokenResponse.data,
+ mcProfile: mcProfileResponse.data
+ }
+ } catch(err) {
+ log.error(err)
+ return Promise.reject(microsoftErrorDisplayable(MicrosoftErrorCode.UNKNOWN))
+ }
+}
+
/**
- * Remove an account. This will invalidate the access token associated
+ * Calculate the expiry date. Advance the expiry time by 10 seconds
+ * to reduce the liklihood of working with an expired token.
+ *
+ * @param {number} nowMs Current time milliseconds.
+ * @param {number} epiresInS Expires in (seconds)
+ * @returns
+ */
+async function calculateExpiryDate(nowMs, epiresInS) {
+ return nowMs + ((epiresInS-10)*1000)
+}
+
+/**
+ * Add a Microsoft account. This will pass the provided auth code to Mojang's OAuth2.0 flow.
+ * The resultant data will be stored as an auth account in the configuration database.
+ *
+ * @param {string} authCode The authCode obtained from microsoft.
+ * @returns {Promise.} Promise which resolves the resolved authenticated account object.
+ */
+exports.addMicrosoftAccount = async function(authCode) {
+
+ const fullAuth = await fullMicrosoftAuthFlow(authCode, AUTH_MODE.FULL)
+
+ // Advance expiry by 10 seconds to avoid close calls.
+ const now = new Date().getTime()
+
+ const ret = ConfigManager.addMicrosoftAuthAccount(
+ fullAuth.mcProfile.id,
+ fullAuth.mcToken.access_token,
+ fullAuth.mcProfile.name,
+ calculateExpiryDate(now, fullAuth.mcToken.expires_in),
+ fullAuth.accessToken.access_token,
+ fullAuth.accessToken.refresh_token,
+ calculateExpiryDate(now, fullAuth.accessToken.expires_in)
+ )
+ ConfigManager.save()
+
+ return ret
+}
+
+/**
+ * Remove a Mojang account. This will invalidate the access token associated
* with the account and then remove it from the database.
*
* @param {string} uuid The UUID of the account to be removed.
* @returns {Promise.} Promise which resolves to void when the action is complete.
*/
-exports.removeAccount = async function(uuid){
+exports.removeMojangAccount = async function(uuid){
try {
const authAcc = ConfigManager.getAuthAccount(uuid)
const response = await MojangRestAPI.invalidate(authAcc.accessToken, ConfigManager.getClientToken())
@@ -80,17 +181,33 @@ exports.removeAccount = async function(uuid){
}
}
+/**
+ * Remove a Microsoft account. It is expected that the caller will invoke the OAuth logout
+ * through the ipc renderer.
+ *
+ * @param {string} uuid The UUID of the account to be removed.
+ * @returns {Promise.} Promise which resolves to void when the action is complete.
+ */
+exports.removeMicrosoftAccount = async function(uuid){
+ try {
+ ConfigManager.removeAuthAccount(uuid)
+ ConfigManager.save()
+ return Promise.resolve()
+ } catch (err){
+ log.error('Error while removing account', err)
+ return Promise.reject(err)
+ }
+}
+
/**
* Validate the selected account with Mojang's authserver. If the account is not valid,
* we will attempt to refresh the access token and update that value. If that fails, a
* new login will be required.
*
- * **Function is WIP**
- *
* @returns {Promise.} Promise which resolves to true if the access token is valid,
* otherwise false.
*/
-exports.validateSelected = async function(){
+async function validateSelectedMojangAccount(){
const current = ConfigManager.getSelectedAccount()
const response = await MojangRestAPI.validate(current.accessToken, ConfigManager.getClientToken())
@@ -100,7 +217,7 @@ exports.validateSelected = async function(){
const refreshResponse = await MojangRestAPI.refresh(current.accessToken, ConfigManager.getClientToken())
if(refreshResponse.responseStatus === RestResponseStatus.SUCCESS) {
const session = refreshResponse.data
- ConfigManager.updateAuthAccount(current.uuid, session.accessToken)
+ ConfigManager.updateMojangAuthAccount(current.uuid, session.accessToken)
ConfigManager.save()
} else {
log.error('Error while validating selected profile:', refreshResponse.error)
@@ -115,4 +232,84 @@ exports.validateSelected = async function(){
}
}
+}
+
+/**
+ * Validate the selected account with Microsoft's authserver. If the account is not valid,
+ * we will attempt to refresh the access token and update that value. If that fails, a
+ * new login will be required.
+ *
+ * @returns {Promise.} Promise which resolves to true if the access token is valid,
+ * otherwise false.
+ */
+async function validateSelectedMicrosoftAccount(){
+ const current = ConfigManager.getSelectedAccount()
+ const now = new Date().getTime()
+ const mcExpiresAt = Date.parse(current.expiresAt)
+ const mcExpired = now >= mcExpiresAt
+
+ if(!mcExpired) {
+ return true
+ }
+
+ // MC token expired. Check MS token.
+
+ const msExpiresAt = Date.parse(current.microsoft.expires_at)
+ const msExpired = now >= msExpiresAt
+
+ if(msExpired) {
+ // MS expired, do full refresh.
+ try {
+ const res = await fullMicrosoftAuthFlow(current.microsoft.refresh_token, AUTH_MODE.MS_REFRESH)
+
+ ConfigManager.updateMicrosoftAuthAccount(
+ current.uuid,
+ res.mcToken.access_token,
+ res.accessToken.access_token,
+ res.accessToken.refresh_token,
+ calculateExpiryDate(now, res.accessToken.expires_in),
+ calculateExpiryDate(now, res.mcToken.expires_in)
+ )
+ ConfigManager.save()
+ return true
+ } catch(err) {
+ return false
+ }
+ } else {
+ // Only MC expired, use existing MS token.
+ try {
+ const res = await fullMicrosoftAuthFlow(current.microsoft.access_token, AUTH_MODE.MC_REFRESH)
+
+ ConfigManager.updateMicrosoftAuthAccount(
+ current.uuid,
+ res.mcToken.access_token,
+ current.microsoft.access_token,
+ current.microsoft.refresh_token,
+ current.microsoft.expires_at,
+ calculateExpiryDate(now, res.mcToken.expires_in)
+ )
+ ConfigManager.save()
+ return true
+ }
+ catch(err) {
+ return false
+ }
+ }
+}
+
+/**
+ * Validate the selected auth account.
+ *
+ * @returns {Promise.} Promise which resolves to true if the access token is valid,
+ * otherwise false.
+ */
+exports.validateSelected = async function(){
+ const current = ConfigManager.getSelectedAccount()
+
+ if(current.type === 'microsoft') {
+ return await validateSelectedMicrosoftAccount()
+ } else {
+ return await validateSelectedMojangAccount()
+ }
+
}
\ No newline at end of file
diff --git a/app/assets/js/configmanager.js b/app/assets/js/configmanager.js
index 2c0bb53c73..3dff9502e3 100644
--- a/app/assets/js/configmanager.js
+++ b/app/assets/js/configmanager.js
@@ -318,20 +318,21 @@ exports.getAuthAccount = function(uuid){
}
/**
- * Update the access token of an authenticated account.
+ * Update the access token of an authenticated mojang account.
*
* @param {string} uuid The uuid of the authenticated account.
* @param {string} accessToken The new Access Token.
*
* @returns {Object} The authenticated account object created by this action.
*/
-exports.updateAuthAccount = function(uuid, accessToken){
+exports.updateMojangAuthAccount = function(uuid, accessToken){
config.authenticationDatabase[uuid].accessToken = accessToken
+ config.authenticationDatabase[uuid].type = 'mojang' // For gradual conversion.
return config.authenticationDatabase[uuid]
}
/**
- * Adds an authenticated account to the database to be stored.
+ * Adds an authenticated mojang account to the database to be stored.
*
* @param {string} uuid The uuid of the authenticated account.
* @param {string} accessToken The accessToken of the authenticated account.
@@ -340,9 +341,10 @@ exports.updateAuthAccount = function(uuid, accessToken){
*
* @returns {Object} The authenticated account object created by this action.
*/
-exports.addAuthAccount = function(uuid, accessToken, username, displayName){
+exports.addMojangAuthAccount = function(uuid, accessToken, username, displayName){
config.selectedAccount = uuid
config.authenticationDatabase[uuid] = {
+ type: 'mojang',
accessToken,
username: username.trim(),
uuid: uuid.trim(),
@@ -351,6 +353,58 @@ exports.addAuthAccount = function(uuid, accessToken, username, displayName){
return config.authenticationDatabase[uuid]
}
+/**
+ * Update the tokens of an authenticated microsoft account.
+ *
+ * @param {string} uuid The uuid of the authenticated account.
+ * @param {string} accessToken The new Access Token.
+ * @param {string} msAccessToken The new Microsoft Access Token
+ * @param {string} msRefreshToken The new Microsoft Refresh Token
+ * @param {date} msExpires The date when the microsoft access token expires
+ * @param {date} mcExpires The date when the mojang access token expires
+ *
+ * @returns {Object} The authenticated account object created by this action.
+ */
+exports.updateMicrosoftAuthAccount = function(uuid, accessToken, msAccessToken, msRefreshToken, msExpires, mcExpires) {
+ config.authenticationDatabase[uuid].accessToken = accessToken
+ config.authenticationDatabase[uuid].expiresAt = mcExpires
+ config.authenticationDatabase[uuid].microsoft.access_token = msAccessToken
+ config.authenticationDatabase[uuid].microsoft.refresh_token = msRefreshToken
+ config.authenticationDatabase[uuid].microsoft.expires_at = msExpires
+ return config.authenticationDatabase[uuid]
+}
+
+/**
+ * Adds an authenticated microsoft account to the database to be stored.
+ *
+ * @param {string} uuid The uuid of the authenticated account.
+ * @param {string} accessToken The accessToken of the authenticated account.
+ * @param {string} name The in game name of the authenticated account.
+ * @param {date} mcExpires The date when the mojang access token expires
+ * @param {string} msAccessToken The microsoft access token
+ * @param {string} msRefreshToken The microsoft refresh token
+ * @param {date} msExpires The date when the microsoft access token expires
+ *
+ * @returns {Object} The authenticated account object created by this action.
+ */
+exports.addMicrosoftAuthAccount = function(uuid, accessToken, name, mcExpires, msAccessToken, msRefreshToken, msExpires) {
+ config.selectedAccount = uuid
+ config.authenticationDatabase[uuid] = {
+ type: 'microsoft',
+ accessToken,
+ username: name.trim(),
+ uuid: uuid.trim(),
+ displayName: name.trim(),
+ expiresAt: mcExpires,
+ microsoft: {
+ access_token: msAccessToken,
+ refresh_token: msRefreshToken,
+ expires_at: msExpires
+ }
+ }
+ return config.authenticationDatabase[uuid]
+}
+
/**
* Remove an authenticated account from the database. If the account
* was also the selected account, a new one will be selected. If there
diff --git a/app/assets/js/ipcconstants.js b/app/assets/js/ipcconstants.js
new file mode 100644
index 0000000000..536e948b7f
--- /dev/null
+++ b/app/assets/js/ipcconstants.js
@@ -0,0 +1,24 @@
+// NOTE FOR THIRD-PARTY
+// REPLACE THIS CLIENT ID WITH YOUR APPLICATION ID.
+// SEE https://github.com/dscalzi/HeliosLauncher/blob/feature/ms-auth/docs/MicrosoftAuth.md
+exports.AZURE_CLIENT_ID = '1ce6e35a-126f-48fd-97fb-54d143ac6d45'
+// SEE NOTE ABOVE.
+
+
+// Opcodes
+exports.MSFT_OPCODE = {
+ OPEN_LOGIN: 'MSFT_AUTH_OPEN_LOGIN',
+ OPEN_LOGOUT: 'MSFT_AUTH_OPEN_LOGOUT',
+ REPLY_LOGIN: 'MSFT_AUTH_REPLY_LOGIN',
+ REPLY_LOGOUT: 'MSFT_AUTH_REPLY_LOGOUT'
+}
+// Reply types for REPLY opcode.
+exports.MSFT_REPLY_TYPE = {
+ SUCCESS: 'MSFT_AUTH_REPLY_SUCCESS',
+ ERROR: 'MSFT_AUTH_REPLY_ERROR'
+}
+// Error types for ERROR reply.
+exports.MSFT_ERROR = {
+ ALREADY_OPEN: 'MSFT_AUTH_ERR_ALREADY_OPEN',
+ NOT_FINISHED: 'MSFT_AUTH_ERR_NOT_FINISHED'
+}
\ No newline at end of file
diff --git a/app/assets/js/scripts/landing.js b/app/assets/js/scripts/landing.js
index 9beabb788f..c15896c8c3 100644
--- a/app/assets/js/scripts/landing.js
+++ b/app/assets/js/scripts/landing.js
@@ -10,7 +10,7 @@ const { MojangRestAPI, getServerStatus } = require('helios-core/mojang')
// Internal Requirements
const DiscordWrapper = require('./assets/js/discordwrapper')
const ProcessBuilder = require('./assets/js/processbuilder')
-const { RestResponseStatus } = require('helios-core/common')
+const { RestResponseStatus, isDisplayableError } = require('helios-core/common')
// Launch Elements
const launch_content = document.getElementById('launch_content')
@@ -21,7 +21,7 @@ const launch_details_text = document.getElementById('launch_details_text')
const server_selection_button = document.getElementById('server_selection_button')
const user_text = document.getElementById('user_text')
-const loggerLanding = LoggerUtil('%c[Landing]', 'color: #000668; font-weight: bold')
+const loggerLanding = LoggerUtil1('%c[Landing]', 'color: #000668; font-weight: bold')
/* Launch Progress Wrapper Functions */
@@ -293,7 +293,7 @@ function asyncSystemScan(mcVersion, launchAfter = true){
toggleLaunchArea(true)
setLaunchPercentage(0, 100)
- const loggerSysAEx = LoggerUtil('%c[SysAEx]', 'color: #353232; font-weight: bold')
+ const loggerSysAEx = LoggerUtil1('%c[SysAEx]', 'color: #353232; font-weight: bold')
const forkEnv = JSON.parse(JSON.stringify(process.env))
forkEnv.CONFIG_DIRECT_PATH = ConfigManager.getLauncherDirectory()
@@ -495,8 +495,8 @@ function dlAsync(login = true){
toggleLaunchArea(true)
setLaunchPercentage(0, 100)
- const loggerAEx = LoggerUtil('%c[AEx]', 'color: #353232; font-weight: bold')
- const loggerLaunchSuite = LoggerUtil('%c[LaunchSuite]', 'color: #000668; font-weight: bold')
+ const loggerAEx = LoggerUtil1('%c[AEx]', 'color: #353232; font-weight: bold')
+ const loggerLaunchSuite = LoggerUtil1('%c[LaunchSuite]', 'color: #000668; font-weight: bold')
const forkEnv = JSON.parse(JSON.stringify(process.env))
forkEnv.CONFIG_DIRECT_PATH = ConfigManager.getLauncherDirectory()
diff --git a/app/assets/js/scripts/login.js b/app/assets/js/scripts/login.js
index 5cae2fb7ed..724f09c4d7 100644
--- a/app/assets/js/scripts/login.js
+++ b/app/assets/js/scripts/login.js
@@ -21,7 +21,7 @@ const loginForm = document.getElementById('loginForm')
// Control variables.
let lu = false, lp = false
-const loggerLogin = LoggerUtil('%c[Login]', 'color: #000668; font-weight: bold')
+const loggerLogin = LoggerUtil1('%c[Login]', 'color: #000668; font-weight: bold')
/**
@@ -189,7 +189,7 @@ loginButton.addEventListener('click', () => {
// Show loading stuff.
loginLoading(true)
- AuthManager.addAccount(loginUsername.value, loginPassword.value).then((value) => {
+ AuthManager.addMojangAccount(loginUsername.value, loginPassword.value).then((value) => {
updateSelectedAccount(value)
loginButton.innerHTML = loginButton.innerHTML.replace(Lang.queryJS('login.loggingIn'), Lang.queryJS('login.success'))
$('.circle-loader').toggleClass('load-complete')
@@ -214,13 +214,26 @@ loginButton.addEventListener('click', () => {
}, 1000)
}).catch((displayableError) => {
loginLoading(false)
- setOverlayContent(displayableError.title, displayableError.desc, Lang.queryJS('login.tryAgain'))
+
+ let actualDisplayableError
+ if(isDisplayableError(displayableError)) {
+ msftLoginLogger.error('Error while logging in.', displayableError)
+ actualDisplayableError = displayableError
+ } else {
+ // Uh oh.
+ msftLoginLogger.error('Unhandled error during login.', displayableError)
+ actualDisplayableError = {
+ title: 'Unknown Error During Login',
+ desc: 'An unknown error has occurred. Please see the console for details.'
+ }
+ }
+
+ setOverlayContent(actualDisplayableError.title, actualDisplayableError.desc, Lang.queryJS('login.tryAgain'))
setOverlayHandler(() => {
formDisabled(false)
toggleOverlay(false)
})
toggleOverlay(true)
- loggerLogin.log('Error while logging in.', displayableError)
})
})
\ No newline at end of file
diff --git a/app/assets/js/scripts/loginOptions.js b/app/assets/js/scripts/loginOptions.js
new file mode 100644
index 0000000000..cdb1bc8e34
--- /dev/null
+++ b/app/assets/js/scripts/loginOptions.js
@@ -0,0 +1,50 @@
+const loginOptionsCancelContainer = document.getElementById('loginOptionCancelContainer')
+const loginOptionMicrosoft = document.getElementById('loginOptionMicrosoft')
+const loginOptionMojang = document.getElementById('loginOptionMojang')
+const loginOptionsCancelButton = document.getElementById('loginOptionCancelButton')
+
+let loginOptionsCancellable = false
+
+let loginOptionsViewOnLoginSuccess
+let loginOptionsViewOnLoginCancel
+let loginOptionsViewOnCancel
+let loginOptionsViewCancelHandler
+
+function loginOptionsCancelEnabled(val){
+ if(val){
+ $(loginOptionsCancelContainer).show()
+ } else {
+ $(loginOptionsCancelContainer).hide()
+ }
+}
+
+loginOptionMicrosoft.onclick = (e) => {
+ switchView(getCurrentView(), VIEWS.waiting, 500, 500, () => {
+ ipcRenderer.send(
+ MSFT_OPCODE.OPEN_LOGIN,
+ loginOptionsViewOnLoginSuccess,
+ loginOptionsViewOnLoginCancel
+ )
+ })
+}
+
+loginOptionMojang.onclick = (e) => {
+ switchView(getCurrentView(), VIEWS.login, 500, 500, () => {
+ loginViewOnSuccess = loginOptionsViewOnLoginSuccess
+ loginViewOnCancel = loginOptionsViewOnLoginCancel
+ loginCancelEnabled(true)
+ })
+}
+
+loginOptionsCancelButton.onclick = (e) => {
+ switchView(getCurrentView(), loginOptionsViewOnCancel, 500, 500, () => {
+ // Clear login values (Mojang login)
+ // No cleanup needed for Microsoft.
+ loginUsername.value = ''
+ loginPassword.value = ''
+ if(loginOptionsViewCancelHandler != null){
+ loginOptionsViewCancelHandler()
+ loginOptionsViewCancelHandler = null
+ }
+ })
+}
\ No newline at end of file
diff --git a/app/assets/js/scripts/overlay.js b/app/assets/js/scripts/overlay.js
index 22d81d62c4..cf2c5c9871 100644
--- a/app/assets/js/scripts/overlay.js
+++ b/app/assets/js/scripts/overlay.js
@@ -197,6 +197,9 @@ document.getElementById('accountSelectConfirm').addEventListener('click', () =>
const authAcc = ConfigManager.setSelectedAccount(listings[i].getAttribute('uuid'))
ConfigManager.save()
updateSelectedAccount(authAcc)
+ if(getCurrentView() === VIEWS.settings) {
+ prepareSettings()
+ }
toggleOverlay(false)
validateSelectedAccount()
return
@@ -207,6 +210,9 @@ document.getElementById('accountSelectConfirm').addEventListener('click', () =>
const authAcc = ConfigManager.setSelectedAccount(listings[0].getAttribute('uuid'))
ConfigManager.save()
updateSelectedAccount(authAcc)
+ if(getCurrentView() === VIEWS.settings) {
+ prepareSettings()
+ }
toggleOverlay(false)
validateSelectedAccount()
}
diff --git a/app/assets/js/scripts/settings.js b/app/assets/js/scripts/settings.js
index c70db70241..d673cf4816 100644
--- a/app/assets/js/scripts/settings.js
+++ b/app/assets/js/scripts/settings.js
@@ -4,6 +4,7 @@ const semver = require('semver')
const { JavaGuard } = require('./assets/js/assetguard')
const DropinModUtil = require('./assets/js/dropinmodutil')
+const { MSFT_OPCODE, MSFT_REPLY_TYPE, MSFT_ERROR } = require('./assets/js/ipcconstants')
const settingsState = {
invalid: new Set()
@@ -314,8 +315,11 @@ settingsNavDone.onclick = () => {
* Account Management Tab
*/
-// Bind the add account button.
-document.getElementById('settingsAddAccount').onclick = (e) => {
+const msftLoginLogger = LoggerUtil.getLogger('Microsoft Login')
+const msftLogoutLogger = LoggerUtil.getLogger('Microsoft Logout')
+
+// Bind the add mojang account button.
+document.getElementById('settingsAddMojangAccount').onclick = (e) => {
switchView(getCurrentView(), VIEWS.login, 500, 500, () => {
loginViewOnCancel = VIEWS.settings
loginViewOnSuccess = VIEWS.settings
@@ -323,6 +327,102 @@ document.getElementById('settingsAddAccount').onclick = (e) => {
})
}
+// Bind the add microsoft account button.
+document.getElementById('settingsAddMicrosoftAccount').onclick = (e) => {
+ switchView(getCurrentView(), VIEWS.waiting, 500, 500, () => {
+ ipcRenderer.send(MSFT_OPCODE.OPEN_LOGIN, VIEWS.settings, VIEWS.settings)
+ })
+}
+
+// Bind reply for Microsoft Login.
+ipcRenderer.on(MSFT_OPCODE.REPLY_LOGIN, (_, ...arguments_) => {
+ if (arguments_[0] === MSFT_REPLY_TYPE.ERROR) {
+
+ const viewOnClose = arguments_[2]
+ console.log(arguments_)
+ switchView(getCurrentView(), viewOnClose, 500, 500, () => {
+
+ if(arguments_[1] === MSFT_ERROR.NOT_FINISHED) {
+ // User cancelled.
+ msftLoginLogger.info('Login cancelled by user.')
+ return
+ }
+
+ // Unexpected error.
+ setOverlayContent(
+ 'Something Went Wrong',
+ 'Microsoft authentication failed. Please try again.',
+ 'OK'
+ )
+ setOverlayHandler(() => {
+ toggleOverlay(false)
+ })
+ toggleOverlay(true)
+ })
+ } else if(arguments_[0] === MSFT_REPLY_TYPE.SUCCESS) {
+ const queryMap = arguments_[1]
+ const viewOnClose = arguments_[2]
+
+ // Error from request to Microsoft.
+ if (Object.prototype.hasOwnProperty.call(queryMap, 'error')) {
+ switchView(getCurrentView(), viewOnClose, 500, 500, () => {
+ // TODO Dont know what these errors are. Just show them I guess.
+ // This is probably if you messed up the app registration with Azure.
+ console.log('Error getting authCode, is Azure application registered correctly?')
+ console.log(error)
+ console.log(error_description)
+ console.log('Full query map', queryMap)
+ let error = queryMap.error // Error might be 'access_denied' ?
+ let errorDesc = queryMap.error_description
+ setOverlayContent(
+ error,
+ errorDesc,
+ 'OK'
+ )
+ setOverlayHandler(() => {
+ toggleOverlay(false)
+ })
+ toggleOverlay(true)
+
+ })
+ } else {
+
+ msftLoginLogger.info('Acquired authCode, proceeding with authentication.')
+
+ const authCode = queryMap.code
+ AuthManager.addMicrosoftAccount(authCode).then(value => {
+ updateSelectedAccount(value)
+ switchView(getCurrentView(), viewOnClose, 500, 500, () => {
+ prepareSettings()
+ })
+ })
+ .catch((displayableError) => {
+
+ let actualDisplayableError
+ if(isDisplayableError(displayableError)) {
+ msftLoginLogger.error('Error while logging in.', displayableError)
+ actualDisplayableError = displayableError
+ } else {
+ // Uh oh.
+ msftLoginLogger.error('Unhandled error during login.', displayableError)
+ actualDisplayableError = {
+ title: 'Unknown Error During Login',
+ desc: 'An unknown error has occurred. Please see the console for details.'
+ }
+ }
+
+ switchView(getCurrentView(), viewOnClose, 500, 500, () => {
+ setOverlayContent(actualDisplayableError.title, actualDisplayableError.desc, Lang.queryJS('login.tryAgain'))
+ setOverlayHandler(() => {
+ toggleOverlay(false)
+ })
+ toggleOverlay(true)
+ })
+ })
+ }
+ }
+})
+
/**
* Bind functionality for the account selection buttons. If another account
* is selected, the UI of the previously selected account will be updated.
@@ -367,7 +467,6 @@ function bindAuthAccountLogOut(){
setOverlayHandler(() => {
processLogOut(val, isLastAccount)
toggleOverlay(false)
- switchView(getCurrentView(), VIEWS.login)
})
setDismissHandler(() => {
toggleOverlay(false)
@@ -381,6 +480,7 @@ function bindAuthAccountLogOut(){
})
}
+let msAccDomElementCache
/**
* Process a log out.
*
@@ -391,19 +491,91 @@ function processLogOut(val, isLastAccount){
const parent = val.closest('.settingsAuthAccount')
const uuid = parent.getAttribute('uuid')
const prevSelAcc = ConfigManager.getSelectedAccount()
- AuthManager.removeAccount(uuid).then(() => {
- if(!isLastAccount && uuid === prevSelAcc.uuid){
- const selAcc = ConfigManager.getSelectedAccount()
- refreshAuthAccountSelected(selAcc.uuid)
- updateSelectedAccount(selAcc)
- validateSelectedAccount()
- }
- })
- $(parent).fadeOut(250, () => {
- parent.remove()
- })
+ const targetAcc = ConfigManager.getAuthAccount(uuid)
+ if(targetAcc.type === 'microsoft') {
+ msAccDomElementCache = parent
+ switchView(getCurrentView(), VIEWS.waiting, 500, 500, () => {
+ ipcRenderer.send(MSFT_OPCODE.OPEN_LOGOUT, uuid, isLastAccount)
+ })
+ } else {
+ AuthManager.removeMojangAccount(uuid).then(() => {
+ if(!isLastAccount && uuid === prevSelAcc.uuid){
+ const selAcc = ConfigManager.getSelectedAccount()
+ refreshAuthAccountSelected(selAcc.uuid)
+ updateSelectedAccount(selAcc)
+ validateSelectedAccount()
+ }
+ if(isLastAccount) {
+ loginOptionsCancelEnabled(false)
+ loginOptionsViewOnLoginSuccess = VIEWS.settings
+ loginOptionsViewOnLoginCancel = VIEWS.loginOptions
+ switchView(getCurrentView(), VIEWS.loginOptions)
+ }
+ })
+ $(parent).fadeOut(250, () => {
+ parent.remove()
+ })
+ }
}
+// Bind reply for Microsoft Logout.
+ipcRenderer.on(MSFT_OPCODE.REPLY_LOGOUT, (_, ...arguments_) => {
+ if (arguments_[0] === MSFT_REPLY_TYPE.ERROR) {
+ switchView(getCurrentView(), VIEWS.settings, 500, 500, () => {
+
+ if(arguments_.length > 1 && arguments_[1] === MSFT_ERROR.NOT_FINISHED) {
+ // User cancelled.
+ msftLogoutLogger.info('Logout cancelled by user.')
+ return
+ }
+
+ // Unexpected error.
+ setOverlayContent(
+ 'Something Went Wrong',
+ 'Microsoft logout failed. Please try again.',
+ 'OK'
+ )
+ setOverlayHandler(() => {
+ toggleOverlay(false)
+ })
+ toggleOverlay(true)
+ })
+ } else if(arguments_[0] === MSFT_REPLY_TYPE.SUCCESS) {
+
+ const uuid = arguments_[1]
+ const isLastAccount = arguments_[2]
+ const prevSelAcc = ConfigManager.getSelectedAccount()
+
+ msftLogoutLogger.info('Logout Successful. uuid:', uuid)
+
+ AuthManager.removeMicrosoftAccount(uuid)
+ .then(() => {
+ if(!isLastAccount && uuid === prevSelAcc.uuid){
+ const selAcc = ConfigManager.getSelectedAccount()
+ refreshAuthAccountSelected(selAcc.uuid)
+ updateSelectedAccount(selAcc)
+ validateSelectedAccount()
+ }
+ if(isLastAccount) {
+ loginOptionsCancelEnabled(false)
+ loginOptionsViewOnLoginSuccess = VIEWS.settings
+ loginOptionsViewOnLoginCancel = VIEWS.loginOptions
+ switchView(getCurrentView(), VIEWS.loginOptions)
+ }
+ if(msAccDomElementCache) {
+ msAccDomElementCache.remove()
+ msAccDomElementCache = null
+ }
+ })
+ .finally(() => {
+ if(!isLastAccount) {
+ switchView(getCurrentView(), VIEWS.settings, 500, 500)
+ }
+ })
+
+ }
+})
+
/**
* Refreshes the status of the selected account on the auth account
* elements.
@@ -425,7 +597,8 @@ function refreshAuthAccountSelected(uuid){
})
}
-const settingsCurrentAccounts = document.getElementById('settingsCurrentAccounts')
+const settingsCurrentMicrosoftAccounts = document.getElementById('settingsCurrentMicrosoftAccounts')
+const settingsCurrentMojangAccounts = document.getElementById('settingsCurrentMojangAccounts')
/**
* Add auth account elements for each one stored in the authentication database.
@@ -438,11 +611,13 @@ function populateAuthAccounts(){
}
const selectedUUID = ConfigManager.getSelectedAccount().uuid
- let authAccountStr = ''
+ let microsoftAuthAccountStr = ''
+ let mojangAuthAccountStr = ''
- authKeys.map((val) => {
+ authKeys.forEach((val) => {
const acc = authAccounts[val]
- authAccountStr += `
+
+ const accHtml = `
@@ -465,9 +640,17 @@ function populateAuthAccounts(){
`
+
+ if(acc.type === 'microsoft') {
+ microsoftAuthAccountStr += accHtml
+ } else {
+ mojangAuthAccountStr += accHtml
+ }
+
})
- settingsCurrentAccounts.innerHTML = authAccountStr
+ settingsCurrentMicrosoftAccounts.innerHTML = microsoftAuthAccountStr
+ settingsCurrentMojangAccounts.innerHTML = mojangAuthAccountStr
}
/**
diff --git a/app/assets/js/scripts/uibinder.js b/app/assets/js/scripts/uibinder.js
index 0b080d1bf7..d338514026 100644
--- a/app/assets/js/scripts/uibinder.js
+++ b/app/assets/js/scripts/uibinder.js
@@ -16,9 +16,11 @@ let fatalStartupError = false
// Mapping of each view to their container IDs.
const VIEWS = {
landing: '#landingContainer',
+ loginOptions: '#loginOptionsContainer',
login: '#loginContainer',
settings: '#settingsContainer',
- welcome: '#welcomeContainer'
+ welcome: '#welcomeContainer',
+ waiting: '#waitingContainer'
}
// The currently shown view container.
@@ -86,8 +88,11 @@ function showMainUI(data){
currentView = VIEWS.landing
$(VIEWS.landing).fadeIn(1000)
} else {
- currentView = VIEWS.login
- $(VIEWS.login).fadeIn(1000)
+ loginOptionsCancelEnabled(false)
+ loginOptionsViewOnLoginSuccess = VIEWS.landing
+ loginOptionsViewOnLoginCancel = VIEWS.loginOptions
+ currentView = VIEWS.loginOptions
+ $(VIEWS.loginOptions).fadeIn(1000)
}
}
@@ -329,20 +334,46 @@ async function validateSelectedAccount(){
'Select Another Account'
)
setOverlayHandler(() => {
- document.getElementById('loginUsername').value = selectedAcc.username
- validateEmail(selectedAcc.username)
- loginViewOnSuccess = getCurrentView()
- loginViewOnCancel = getCurrentView()
- if(accLen > 0){
- loginViewCancelHandler = () => {
- ConfigManager.addAuthAccount(selectedAcc.uuid, selectedAcc.accessToken, selectedAcc.username, selectedAcc.displayName)
+
+ const isMicrosoft = selectedAcc.type === 'microsoft'
+
+ if(isMicrosoft) {
+ // Empty for now
+ } else {
+ // Mojang
+ // For convenience, pre-populate the username of the account.
+ document.getElementById('loginUsername').value = selectedAcc.username
+ validateEmail(selectedAcc.username)
+ }
+
+ loginOptionsViewOnLoginSuccess = getCurrentView()
+ loginOptionsViewOnLoginCancel = VIEWS.loginOptions
+
+ if(accLen > 0) {
+ loginOptionsViewOnCancel = getCurrentView()
+ loginOptionsViewCancelHandler = () => {
+ if(isMicrosoft) {
+ ConfigManager.addMicrosoftAuthAccount(
+ selectedAcc.uuid,
+ selectedAcc.accessToken,
+ selectedAcc.username,
+ selectedAcc.expiresAt,
+ selectedAcc.microsoft.access_token,
+ selectedAcc.microsoft.refresh_token,
+ selectedAcc.microsoft.expires_at
+ )
+ } else {
+ ConfigManager.addMojangAuthAccount(selectedAcc.uuid, selectedAcc.accessToken, selectedAcc.username, selectedAcc.displayName)
+ }
ConfigManager.save()
validateSelectedAccount()
}
- loginCancelEnabled(true)
+ loginOptionsCancelEnabled(true)
+ } else {
+ loginOptionsCancelEnabled(false)
}
toggleOverlay(false)
- switchView(getCurrentView(), VIEWS.login)
+ switchView(getCurrentView(), VIEWS.loginOptions)
})
setDismissHandler(() => {
if(accLen > 1){
diff --git a/app/assets/js/scripts/uicore.js b/app/assets/js/scripts/uicore.js
index 7d3cddbd24..cb36c9d546 100644
--- a/app/assets/js/scripts/uicore.js
+++ b/app/assets/js/scripts/uicore.js
@@ -9,11 +9,12 @@ const $ = require('jquery')
const {ipcRenderer, shell, webFrame} = require('electron')
const remote = require('@electron/remote')
const isDev = require('./assets/js/isdev')
-const LoggerUtil = require('./assets/js/loggerutil')
+const { LoggerUtil } = require('helios-core')
+const LoggerUtil1 = require('./assets/js/loggerutil')
-const loggerUICore = LoggerUtil('%c[UICore]', 'color: #000668; font-weight: bold')
-const loggerAutoUpdater = LoggerUtil('%c[AutoUpdater]', 'color: #000668; font-weight: bold')
-const loggerAutoUpdaterSuccess = LoggerUtil('%c[AutoUpdater]', 'color: #209b07; font-weight: bold')
+const loggerUICore = LoggerUtil1('%c[UICore]', 'color: #000668; font-weight: bold')
+const loggerAutoUpdater = LoggerUtil1('%c[AutoUpdater]', 'color: #000668; font-weight: bold')
+const loggerAutoUpdaterSuccess = LoggerUtil1('%c[AutoUpdater]', 'color: #209b07; font-weight: bold')
// Log deprecation and process warnings.
process.traceProcessWarnings = true
diff --git a/app/assets/js/scripts/welcome.js b/app/assets/js/scripts/welcome.js
index e6ff6297fc..ed0399c353 100644
--- a/app/assets/js/scripts/welcome.js
+++ b/app/assets/js/scripts/welcome.js
@@ -2,5 +2,8 @@
* Script for welcome.ejs
*/
document.getElementById('welcomeButton').addEventListener('click', e => {
- switchView(VIEWS.welcome, VIEWS.login)
+ loginOptionsCancelEnabled(false) // False by default, be explicit.
+ loginOptionsViewOnLoginSuccess = VIEWS.landing
+ loginOptionsViewOnLoginCancel = VIEWS.loginOptions
+ switchView(VIEWS.welcome, VIEWS.loginOptions)
})
\ No newline at end of file
diff --git a/app/loginOptions.ejs b/app/loginOptions.ejs
new file mode 100644
index 0000000000..36af37e05d
--- /dev/null
+++ b/app/loginOptions.ejs
@@ -0,0 +1,34 @@
+
+
+
+
Login Options
+
+
+ Cancel
+
+
+
+
+
\ No newline at end of file
diff --git a/app/settings.ejs b/app/settings.ejs
index f5505cfed3..65a1796d1a 100644
--- a/app/settings.ejs
+++ b/app/settings.ejs
@@ -28,16 +28,45 @@
-
-
- + Add Account
-
-
-