Explorar o código

Replace mojang.js with helios-core implementation.

Updated: Mojang Auth, status check (pending rework).
Daniel Scalzi %!s(int64=3) %!d(string=hai) anos
pai
achega
ad47617cd0

+ 0 - 7
app/assets/js/assetguard.js

@@ -16,13 +16,6 @@ const ConfigManager = require('./configmanager')
 const DistroManager = require('./distromanager')
 const isDev         = require('./isdev')
 
-// Constants
-// const PLATFORM_MAP = {
-//     win32: '-windows-x64.tar.gz',
-//     darwin: '-macosx-x64.tar.gz',
-//     linux: '-linux-x64.tar.gz'
-// }
-
 // Classes
 
 /** Class representing a base asset. */

+ 53 - 34
app/assets/js/authmanager.js

@@ -10,10 +10,11 @@
  */
 // Requirements
 const ConfigManager = require('./configmanager')
-const LoggerUtil    = require('./loggerutil')
-const Mojang        = require('./mojang')
-const logger        = LoggerUtil('%c[AuthManager]', 'color: #a02d2a; font-weight: bold')
-const loggerSuccess = LoggerUtil('%c[AuthManager]', 'color: #209b07; font-weight: bold')
+const { LoggerUtil } = require('helios-core')
+const { MojangRestAPI, mojangErrorDisplayable, MojangErrorCode } = require('helios-core/mojang')
+const { RestResponseStatus } = require('helios-core/common')
+
+const log = LoggerUtil.getLogger('AuthManager')
 
 // Functions
 
@@ -28,20 +29,29 @@ const loggerSuccess = LoggerUtil('%c[AuthManager]', 'color: #209b07; font-weight
  */
 exports.addAccount = async function(username, password){
     try {
-        const session = await Mojang.authenticate(username, password, ConfigManager.getClientToken())
-        if(session.selectedProfile != null){
-            const ret = ConfigManager.addAuthAccount(session.selectedProfile.id, session.accessToken, username, session.selectedProfile.name)
-            if(ConfigManager.getClientToken() == null){
-                ConfigManager.setClientToken(session.clientToken)
+        const response = await MojangRestAPI.authenticate(username, password, ConfigManager.getClientToken())
+        console.log(response)
+        if(response.responseStatus === RestResponseStatus.SUCCESS) {
+
+            const session = response.data
+            if(session.selectedProfile != null){
+                const ret = ConfigManager.addAuthAccount(session.selectedProfile.id, session.accessToken, username, session.selectedProfile.name)
+                if(ConfigManager.getClientToken() == null){
+                    ConfigManager.setClientToken(session.clientToken)
+                }
+                ConfigManager.save()
+                return ret
+            } else {
+                return Promise.reject(mojangErrorDisplayable(MojangErrorCode.ERROR_NOT_PAID))
             }
-            ConfigManager.save()
-            return ret
+
         } else {
-            throw new Error('NotPaidAccount')
+            return Promise.reject(mojangErrorDisplayable(response.mojangErrorCode))
         }
         
     } catch (err){
-        return Promise.reject(err)
+        log.error(err)
+        return Promise.reject(mojangErrorDisplayable(MojangErrorCode.UNKNOWN))
     }
 }
 
@@ -55,11 +65,17 @@ exports.addAccount = async function(username, password){
 exports.removeAccount = async function(uuid){
     try {
         const authAcc = ConfigManager.getAuthAccount(uuid)
-        await Mojang.invalidate(authAcc.accessToken, ConfigManager.getClientToken())
-        ConfigManager.removeAuthAccount(uuid)
-        ConfigManager.save()
-        return Promise.resolve()
+        const response = await MojangRestAPI.invalidate(authAcc.accessToken, ConfigManager.getClientToken())
+        if(response.responseStatus === RestResponseStatus.SUCCESS) {
+            ConfigManager.removeAuthAccount(uuid)
+            ConfigManager.save()
+            return Promise.resolve()
+        } else {
+            log.error('Error while removing account', response.error)
+            return Promise.reject(response.error)
+        }
     } catch (err){
+        log.error('Error while removing account', err)
         return Promise.reject(err)
     }
 }
@@ -76,24 +92,27 @@ exports.removeAccount = async function(uuid){
  */
 exports.validateSelected = async function(){
     const current = ConfigManager.getSelectedAccount()
-    const isValid = await Mojang.validate(current.accessToken, ConfigManager.getClientToken())
-    if(!isValid){
-        try {
-            const session = await Mojang.refresh(current.accessToken, ConfigManager.getClientToken())
-            ConfigManager.updateAuthAccount(current.uuid, session.accessToken)
-            ConfigManager.save()
-        } catch(err) {
-            logger.debug('Error while validating selected profile:', err)
-            if(err && err.error === 'ForbiddenOperationException'){
-                // What do we do?
+    const response = await MojangRestAPI.validate(current.accessToken, ConfigManager.getClientToken())
+
+    if(response.responseStatus === RestResponseStatus.SUCCESS) {
+        const isValid = response.data
+        if(!isValid){
+            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.save()
+            } else {
+                log.error('Error while validating selected profile:', refreshResponse.error)
+                log.info('Account access token is invalid.')
+                return false
             }
-            logger.log('Account access token is invalid.')
-            return false
+            log.info('Account access token validated.')
+            return true
+        } else {
+            log.info('Account access token validated.')
+            return true
         }
-        loggerSuccess.log('Account access token validated.')
-        return true
-    } else {
-        loggerSuccess.log('Account access token validated.')
-        return true
     }
+    
 }

+ 0 - 271
app/assets/js/mojang.js

@@ -1,271 +0,0 @@
-/**
- * Mojang
- * 
- * This module serves as a minimal wrapper for Mojang's REST api.
- * 
- * @module mojang
- */
-// Requirements
-const request = require('request')
-const logger  = require('./loggerutil')('%c[Mojang]', 'color: #a02d2a; font-weight: bold')
-
-// Constants
-const minecraftAgent = {
-    name: 'Minecraft',
-    version: 1
-}
-const authpath = 'https://authserver.mojang.com'
-const statuses = [
-    {
-        service: 'session.minecraft.net',
-        status: 'grey',
-        name: 'Multiplayer Session Service',
-        essential: true
-    },
-    {
-        service: 'authserver.mojang.com',
-        status: 'grey',
-        name: 'Authentication Service',
-        essential: true
-    },
-    {
-        service: 'textures.minecraft.net',
-        status: 'grey',
-        name: 'Minecraft Skins',
-        essential: false
-    },
-    {
-        service: 'api.mojang.com',
-        status: 'grey',
-        name: 'Public API',
-        essential: false
-    },
-    {
-        service: 'minecraft.net',
-        status: 'grey',
-        name: 'Minecraft.net',
-        essential: false
-    },
-    {
-        service: 'account.mojang.com',
-        status: 'grey',
-        name: 'Mojang Accounts Website',
-        essential: false
-    }
-]
-
-// Functions
-
-/**
- * Converts a Mojang status color to a hex value. Valid statuses
- * are 'green', 'yellow', 'red', and 'grey'. Grey is a custom status
- * to our project which represents an unknown status.
- * 
- * @param {string} status A valid status code.
- * @returns {string} The hex color of the status code.
- */
-exports.statusToHex = function(status){
-    switch(status.toLowerCase()){
-        case 'green':
-            return '#a5c325'
-        case 'yellow':
-            return '#eac918'
-        case 'red':
-            return '#c32625'
-        case 'grey':
-        default:
-            return '#848484'
-    }
-}
-
-/**
- * Retrieves the status of Mojang's services.
- * The response is condensed into a single object. Each service is
- * a key, where the value is an object containing a status and name
- * property.
- * 
- * @see http://wiki.vg/Mojang_API#API_Status
- */
-exports.status = function(){
-    return new Promise((resolve, reject) => {
-        request.get('https://status.mojang.com/check',
-            {
-                json: true,
-                timeout: 2500
-            },
-            function(error, response, body){
-
-                if(error || response.statusCode !== 200){
-                    logger.warn('Unable to retrieve Mojang status.')
-                    logger.debug('Error while retrieving Mojang statuses:', error)
-                    //reject(error || response.statusCode)
-                    for(let i=0; i<statuses.length; i++){
-                        statuses[i].status = 'grey'
-                    }
-                    resolve(statuses)
-                } else {
-                    for(let i=0; i<body.length; i++){
-                        const key = Object.keys(body[i])[0]
-                        inner:
-                        for(let j=0; j<statuses.length; j++){
-                            if(statuses[j].service === key) {
-                                statuses[j].status = body[i][key]
-                                break inner
-                            }
-                        }
-                    }
-                    resolve(statuses)
-                }
-            })
-    })
-}
-
-/**
- * Authenticate a user with their Mojang credentials.
- * 
- * @param {string} username The user's username, this is often an email.
- * @param {string} password The user's password.
- * @param {string} clientToken The launcher's Client Token.
- * @param {boolean} requestUser Optional. Adds user object to the reponse.
- * @param {Object} agent Optional. Provided by default. Adds user info to the response.
- * 
- * @see http://wiki.vg/Authentication#Authenticate
- */
-exports.authenticate = function(username, password, clientToken, requestUser = true, agent = minecraftAgent){
-    return new Promise((resolve, reject) => {
-
-        const body = {
-            agent,
-            username,
-            password,
-            requestUser
-        }
-        if(clientToken != null){
-            body.clientToken = clientToken
-        }
-
-        request.post(authpath + '/authenticate',
-            {
-                json: true,
-                body
-            },
-            function(error, response, body){
-                if(error){
-                    logger.error('Error during authentication.', error)
-                    reject(error)
-                } else {
-                    if(response.statusCode === 200){
-                        resolve(body)
-                    } else {
-                        reject(body || {code: 'ENOTFOUND'})
-                    }
-                }
-            })
-    })
-}
-
-/**
- * Validate an access token. This should always be done before launching.
- * The client token should match the one used to create the access token.
- * 
- * @param {string} accessToken The access token to validate.
- * @param {string} clientToken The launcher's client token.
- * 
- * @see http://wiki.vg/Authentication#Validate
- */
-exports.validate = function(accessToken, clientToken){
-    return new Promise((resolve, reject) => {
-        request.post(authpath + '/validate',
-            {
-                json: true,
-                body: {
-                    accessToken,
-                    clientToken
-                }
-            },
-            function(error, response, body){
-                if(error){
-                    logger.error('Error during validation.', error)
-                    reject(error)
-                } else {
-                    if(response.statusCode === 403){
-                        resolve(false)
-                    } else {
-                    // 204 if valid
-                        resolve(true)
-                    }
-                }
-            })
-    })
-}
-
-/**
- * Invalidates an access token. The clientToken must match the
- * token used to create the provided accessToken.
- * 
- * @param {string} accessToken The access token to invalidate.
- * @param {string} clientToken The launcher's client token.
- * 
- * @see http://wiki.vg/Authentication#Invalidate
- */
-exports.invalidate = function(accessToken, clientToken){
-    return new Promise((resolve, reject) => {
-        request.post(authpath + '/invalidate',
-            {
-                json: true,
-                body: {
-                    accessToken,
-                    clientToken
-                }
-            },
-            function(error, response, body){
-                if(error){
-                    logger.error('Error during invalidation.', error)
-                    reject(error)
-                } else {
-                    if(response.statusCode === 204){
-                        resolve()
-                    } else {
-                        reject(body)
-                    }
-                }
-            })
-    })
-}
-
-/**
- * Refresh a user's authentication. This should be used to keep a user logged
- * in without asking them for their credentials again. A new access token will
- * be generated using a recent invalid access token. See Wiki for more info.
- * 
- * @param {string} accessToken The old access token.
- * @param {string} clientToken The launcher's client token.
- * @param {boolean} requestUser Optional. Adds user object to the reponse.
- * 
- * @see http://wiki.vg/Authentication#Refresh
- */
-exports.refresh = function(accessToken, clientToken, requestUser = true){
-    return new Promise((resolve, reject) => {
-        request.post(authpath + '/refresh',
-            {
-                json: true,
-                body: {
-                    accessToken,
-                    clientToken,
-                    requestUser
-                }
-            },
-            function(error, response, body){
-                if(error){
-                    logger.error('Error during refresh.', error)
-                    reject(error)
-                } else {
-                    if(response.statusCode === 200){
-                        resolve(body)
-                    } else {
-                        reject(body)
-                    }
-                }
-            })
-    })
-}

+ 44 - 43
app/assets/js/scripts/landing.js

@@ -5,13 +5,12 @@
 const cp                      = require('child_process')
 const crypto                  = require('crypto')
 const { URL }                 = require('url')
-const { getServerStatus }     = require('helios-core')
+const { MojangRestAPI, getServerStatus }     = require('helios-core/mojang')
 
 // Internal Requirements
 const DiscordWrapper          = require('./assets/js/discordwrapper')
-const Mojang                  = require('./assets/js/mojang')
 const ProcessBuilder          = require('./assets/js/processbuilder')
-const ServerStatus            = require('./assets/js/serverstatus')
+const { RestResponseStatus } = require('helios-core/common')
 
 // Launch Elements
 const launch_content          = document.getElementById('launch_content')
@@ -166,55 +165,57 @@ const refreshMojangStatuses = async function(){
     let tooltipEssentialHTML = ''
     let tooltipNonEssentialHTML = ''
 
-    try {
-        const statuses = await Mojang.status()
-        greenCount = 0
-        greyCount = 0
-
-        for(let i=0; i<statuses.length; i++){
-            const service = statuses[i]
-
-            if(service.essential){
-                tooltipEssentialHTML += `<div class="mojangStatusContainer">
-                    <span class="mojangStatusIcon" style="color: ${Mojang.statusToHex(service.status)};">&#8226;</span>
-                    <span class="mojangStatusName">${service.name}</span>
-                </div>`
-            } else {
-                tooltipNonEssentialHTML += `<div class="mojangStatusContainer">
-                    <span class="mojangStatusIcon" style="color: ${Mojang.statusToHex(service.status)};">&#8226;</span>
-                    <span class="mojangStatusName">${service.name}</span>
-                </div>`
-            }
-
-            if(service.status === 'yellow' && status !== 'red'){
-                status = 'yellow'
-            } else if(service.status === 'red'){
-                status = 'red'
-            } else {
-                if(service.status === 'grey'){
-                    ++greyCount
-                }
-                ++greenCount
-            }
-
+    const response = await MojangRestAPI.status()
+    let statuses
+    if(response.responseStatus === RestResponseStatus.SUCCESS) {
+        statuses = response.data
+    } else {
+        loggerLanding.warn('Unable to refresh Mojang service status.')
+        statuses = MojangRestAPI.getDefaultStatuses()
+    }
+    
+    greenCount = 0
+    greyCount = 0
+
+    for(let i=0; i<statuses.length; i++){
+        const service = statuses[i]
+
+        if(service.essential){
+            tooltipEssentialHTML += `<div class="mojangStatusContainer">
+                <span class="mojangStatusIcon" style="color: ${MojangRestAPI.statusToHex(service.status)};">&#8226;</span>
+                <span class="mojangStatusName">${service.name}</span>
+            </div>`
+        } else {
+            tooltipNonEssentialHTML += `<div class="mojangStatusContainer">
+                <span class="mojangStatusIcon" style="color: ${MojangRestAPI.statusToHex(service.status)};">&#8226;</span>
+                <span class="mojangStatusName">${service.name}</span>
+            </div>`
         }
 
-        if(greenCount === statuses.length){
-            if(greyCount === statuses.length){
-                status = 'grey'
-            } else {
-                status = 'green'
+        if(service.status === 'yellow' && status !== 'red'){
+            status = 'yellow'
+        } else if(service.status === 'red'){
+            status = 'red'
+        } else {
+            if(service.status === 'grey'){
+                ++greyCount
             }
+            ++greenCount
         }
 
-    } catch (err) {
-        loggerLanding.warn('Unable to refresh Mojang service status.')
-        loggerLanding.debug(err)
+    }
+
+    if(greenCount === statuses.length){
+        if(greyCount === statuses.length){
+            status = 'grey'
+        } else {
+            status = 'green'
+        }
     }
     
     document.getElementById('mojangStatusEssentialContainer').innerHTML = tooltipEssentialHTML
     document.getElementById('mojangStatusNonEssentialContainer').innerHTML = tooltipNonEssentialHTML
-    document.getElementById('mojang_status_icon').style.color = Mojang.statusToHex(status)
+    document.getElementById('mojang_status_icon').style.color = MojangRestAPI.statusToHex(status)
 }
 
 const refreshServerStatus = async function(fade = false){

+ 3 - 77
app/assets/js/scripts/login.js

@@ -154,79 +154,6 @@ function formDisabled(v){
     loginRememberOption.disabled = v
 }
 
-/**
- * Parses an error and returns a user-friendly title and description
- * for our error overlay.
- * 
- * @param {Error | {cause: string, error: string, errorMessage: string}} err A Node.js
- * error or Mojang error response.
- */
-function resolveError(err){
-    // Mojang Response => err.cause | err.error | err.errorMessage
-    // Node error => err.code | err.message
-    if(err.cause != null && err.cause === 'UserMigratedException') {
-        return {
-            title: Lang.queryJS('login.error.userMigrated.title'),
-            desc: Lang.queryJS('login.error.userMigrated.desc')
-        }
-    } else {
-        if(err.error != null){
-            if(err.error === 'ForbiddenOperationException'){
-                if(err.errorMessage != null){
-                    if(err.errorMessage === 'Invalid credentials. Invalid username or password.'){
-                        return {
-                            title: Lang.queryJS('login.error.invalidCredentials.title'),
-                            desc: Lang.queryJS('login.error.invalidCredentials.desc')
-                        }
-                    } else if(err.errorMessage === 'Invalid credentials.'){
-                        return {
-                            title: Lang.queryJS('login.error.rateLimit.title'),
-                            desc: Lang.queryJS('login.error.rateLimit.desc')
-                        }
-                    }
-                }
-            }
-        } else {
-            // Request errors (from Node).
-            if(err.code != null){
-                if(err.code === 'ENOENT'){
-                    // No Internet.
-                    return {
-                        title: Lang.queryJS('login.error.noInternet.title'),
-                        desc: Lang.queryJS('login.error.noInternet.desc')
-                    }
-                } else if(err.code === 'ENOTFOUND'){
-                    // Could not reach server.
-                    return {
-                        title: Lang.queryJS('login.error.authDown.title'),
-                        desc: Lang.queryJS('login.error.authDown.desc')
-                    }
-                }
-            }
-        }
-    }
-    if(err.message != null){
-        if(err.message === 'NotPaidAccount'){
-            return {
-                title: Lang.queryJS('login.error.notPaid.title'),
-                desc: Lang.queryJS('login.error.notPaid.desc')
-            }
-        } else {
-            // Unknown error with request.
-            return {
-                title: Lang.queryJS('login.error.unknown.title'),
-                desc: err.message
-            }
-        }
-    } else {
-        // Unknown Mojang error.
-        return {
-            title: err.error,
-            desc: err.errorMessage
-        }
-    }
-}
-
 let loginViewOnSuccess = VIEWS.landing
 let loginViewOnCancel = VIEWS.settings
 let loginViewCancelHandler
@@ -285,16 +212,15 @@ loginButton.addEventListener('click', () => {
                 formDisabled(false)
             })
         }, 1000)
-    }).catch((err) => {
+    }).catch((displayableError) => {
         loginLoading(false)
-        const errF = resolveError(err)
-        setOverlayContent(errF.title, errF.desc, Lang.queryJS('login.tryAgain'))
+        setOverlayContent(displayableError.title, displayableError.desc, Lang.queryJS('login.tryAgain'))
         setOverlayHandler(() => {
             formDisabled(false)
             toggleOverlay(false)
         })
         toggleOverlay(true)
-        loggerLogin.log('Error while logging in.', err)
+        loggerLogin.log('Error while logging in.', displayableError)
     })
 
 })

A diferenza do arquivo foi suprimida porque é demasiado grande
+ 492 - 299
package-lock.json


+ 7 - 6
package.json

@@ -23,16 +23,17 @@
     "node": "16.x.x"
   },
   "dependencies": {
-    "@electron/remote": "^2.0.1",
+    "@electron/remote": "^2.0.4",
     "adm-zip": "^0.5.9",
     "async": "^3.2.3",
     "discord-rpc-patch": "^4.0.1",
     "ejs": "^3.1.6",
     "ejs-electron": "^2.1.1",
-    "electron-updater": "^4.6.1",
+    "electron-updater": "^4.6.5",
     "fs-extra": "^10.0.0",
     "github-syntax-dark": "^0.5.0",
-    "helios-core": "^0.1.0-alpha.3",
+    "got": "^11.8.3",
+    "helios-core": "^0.1.0-alpha.5",
     "jquery": "^3.6.0",
     "node-stream-zip": "^1.15.0",
     "request": "^2.88.2",
@@ -41,9 +42,9 @@
     "winreg": "^1.2.4"
   },
   "devDependencies": {
-    "electron": "^16.0.7",
-    "electron-builder": "^22.14.5",
-    "eslint": "^8.7.0"
+    "electron": "^16.0.8",
+    "electron-builder": "^22.14.13",
+    "eslint": "^8.8.0"
   },
   "repository": {
     "type": "git",

Algúns arquivos non se mostraron porque demasiados arquivos cambiaron neste cambio