Microsoft Authentication (#216)

This commit is contained in:
Daniel Scalzi
2022-02-11 19:51:28 -05:00
committed by GitHub
parent ad47617cd0
commit 58e68c116c
23 changed files with 1093 additions and 105 deletions

View File

@@ -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()

View File

@@ -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)
})
})

View File

@@ -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
}
})
}

View File

@@ -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()
}

View File

@@ -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 += `<div class="settingsAuthAccount" uuid="${acc.uuid}">
const accHtml = `<div class="settingsAuthAccount" uuid="${acc.uuid}">
<div class="settingsAuthAccountLeft">
<img class="settingsAuthAccountImage" alt="${acc.displayName}" src="https://mc-heads.net/body/${acc.uuid}/60">
</div>
@@ -465,9 +640,17 @@ function populateAuthAccounts(){
</div>
</div>
</div>`
if(acc.type === 'microsoft') {
microsoftAuthAccountStr += accHtml
} else {
mojangAuthAccountStr += accHtml
}
})
settingsCurrentAccounts.innerHTML = authAccountStr
settingsCurrentMicrosoftAccounts.innerHTML = microsoftAuthAccountStr
settingsCurrentMojangAccounts.innerHTML = mojangAuthAccountStr
}
/**

View File

@@ -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){

View File

@@ -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

View File

@@ -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)
})