Merge remote-tracking branch 'MS/master' into master
This commit is contained in:
commit
1f0008d444
@ -613,8 +613,7 @@ body, button {
|
||||
font-weight: bold;
|
||||
letter-spacing: 2px;
|
||||
border: none;
|
||||
padding: 15px 5px;
|
||||
margin: 10px 0px;
|
||||
margin: 15px 0px;
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
right: -20px;
|
||||
@ -859,6 +858,28 @@ body, button {
|
||||
transform: rotate(45deg);
|
||||
}
|
||||
|
||||
#loginMSButton {
|
||||
background: none;
|
||||
border: none;
|
||||
margin-top: 5px;
|
||||
margin-bottom: 10px;
|
||||
font-size: 20px;
|
||||
}
|
||||
#loginMSButton:disabled {
|
||||
color: rgba(255, 255, 255, 0.75);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
#loginMSButton:hover,
|
||||
#loginMSButton:focus {
|
||||
text-shadow: 0px 0px 20px #fff;
|
||||
outline: none;
|
||||
}
|
||||
#loginMSButton:active {
|
||||
color: #c7c7c7;
|
||||
text-shadow: 0px 0px 20px #c7c7c7;
|
||||
}
|
||||
|
||||
/*
|
||||
#login_filter {
|
||||
height: calc(100% - 22px);
|
||||
|
@ -12,9 +12,64 @@
|
||||
const ConfigManager = require('./configmanager')
|
||||
const LoggerUtil = require('./loggerutil')
|
||||
const Mojang = require('./mojang')
|
||||
const Microsoft = require('./microsoft')
|
||||
const logger = LoggerUtil('%c[AuthManager]', 'color: #a02d2a; font-weight: bold')
|
||||
const loggerSuccess = LoggerUtil('%c[AuthManager]', 'color: #209b07; font-weight: bold')
|
||||
|
||||
async function validateSelectedMojang() {
|
||||
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?
|
||||
}
|
||||
logger.log('Account access token is invalid.')
|
||||
return false
|
||||
}
|
||||
loggerSuccess.log('Account access token validated.')
|
||||
return true
|
||||
} else {
|
||||
loggerSuccess.log('Account access token validated.')
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
async function validateSelectedMicrosoft() {
|
||||
try {
|
||||
const current = ConfigManager.getSelectedAccount()
|
||||
const now = new Date().getTime()
|
||||
const MCExpiresAt = Date.parse(current.expiresAt)
|
||||
const MCExpired = now > MCExpiresAt
|
||||
|
||||
if(MCExpired) {
|
||||
const MSExpiresAt = Date.parse(ConfigManager.getMicrosoftAuth().expires_at)
|
||||
const MSExpired = now > MSExpiresAt
|
||||
|
||||
if (MSExpired) {
|
||||
const newAccessToken = await Microsoft.refreshAccessToken(ConfigManager.getMicrosoftAuth)
|
||||
ConfigManager.updateMicrosoftAuth(newAccessToken.access_token, newAccessToken.expires_at)
|
||||
ConfigManager.save()
|
||||
}
|
||||
const newMCAccessToken = await Microsoft.authMinecraft(ConfigManager.getMicrosoftAuth().access_token)
|
||||
ConfigManager.updateAuthAccount(current.uuid, newMCAccessToken.access_token, newMCAccessToken.expires_at)
|
||||
ConfigManager.save()
|
||||
|
||||
return true
|
||||
} else {
|
||||
return true
|
||||
}
|
||||
} catch (error) {
|
||||
return Promise.reject(error)
|
||||
}
|
||||
}
|
||||
|
||||
// Exports
|
||||
// Functions
|
||||
|
||||
/**
|
||||
@ -78,22 +133,36 @@ 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?
|
||||
try{
|
||||
if (ConfigManager.getSelectedAccount() === 'microsoft') {
|
||||
const validate = await validateSelectedMicrosoft()
|
||||
return validate
|
||||
} else {
|
||||
const validate = await validateSelectedMojang()
|
||||
return validate
|
||||
}
|
||||
logger.log('Account access token is invalid.')
|
||||
return false
|
||||
} catch (error) {
|
||||
return Promise.reject(error)
|
||||
}
|
||||
loggerSuccess.log('Account access token validated.')
|
||||
return true
|
||||
} else {
|
||||
loggerSuccess.log('Account access token validated.')
|
||||
return true
|
||||
} else return true
|
||||
}
|
||||
|
||||
exports.addMSAccount = async authCode => {
|
||||
try {
|
||||
const accessToken = await Microsoft.getAccessToken(authCode)
|
||||
ConfigManager.setMicrosoftAuth(accessToken)
|
||||
const MCAccessToken = await Microsoft.authMinecraft(accessToken.access_token)
|
||||
const minecraftBuyed = await Microsoft.checkMCStore(MCAccessToken.access_token)
|
||||
if(!minecraftBuyed)
|
||||
return Promise.reject({
|
||||
message: 'You didn\'t buy Minecraft! Please use another Microsoft account or buy Minecraft.'
|
||||
})
|
||||
const MCProfile = await Microsoft.getMCProfile(MCAccessToken.access_token)
|
||||
const ret = ConfigManager.addAuthAccount(MCProfile.id, MCAccessToken.access_token, MCProfile.name, MCProfile.name, MCAccessToken.expires_at, 'microsoft')
|
||||
ConfigManager.save()
|
||||
|
||||
return ret
|
||||
} catch(error) {
|
||||
return Promise.reject(error)
|
||||
}
|
||||
}
|
@ -103,7 +103,8 @@ const DEFAULT_CONFIG = {
|
||||
selectedServer: null, // Resolved
|
||||
selectedAccount: null,
|
||||
authenticationDatabase: {},
|
||||
modConfigurations: []
|
||||
modConfigurations: [],
|
||||
microsoftAuth: {}
|
||||
}
|
||||
|
||||
let config = null
|
||||
@ -325,8 +326,9 @@ exports.getAuthAccount = function(uuid){
|
||||
*
|
||||
* @returns {Object} The authenticated account object created by this action.
|
||||
*/
|
||||
exports.updateAuthAccount = function(uuid, accessToken){
|
||||
exports.updateAuthAccount = function(uuid, accessToken, expiresAt = undefined){
|
||||
config.authenticationDatabase[uuid].accessToken = accessToken
|
||||
config.authenticationDatabase[uuid].expiresAt = expiresAt
|
||||
return config.authenticationDatabase[uuid]
|
||||
}
|
||||
|
||||
@ -340,13 +342,15 @@ exports.updateAuthAccount = function(uuid, accessToken){
|
||||
*
|
||||
* @returns {Object} The authenticated account object created by this action.
|
||||
*/
|
||||
exports.addAuthAccount = function(uuid, accessToken, username, displayName){
|
||||
exports.addAuthAccount = function(uuid, accessToken, username, displayName, expiresAt = null, type = 'mojang'){
|
||||
config.selectedAccount = uuid
|
||||
config.authenticationDatabase[uuid] = {
|
||||
accessToken,
|
||||
username: username.trim(),
|
||||
uuid: uuid.trim(),
|
||||
displayName: displayName.trim()
|
||||
displayName: displayName.trim(),
|
||||
expiresAt: expiresAt,
|
||||
type: type
|
||||
}
|
||||
return config.authenticationDatabase[uuid]
|
||||
}
|
||||
@ -685,4 +689,19 @@ exports.getAllowPrerelease = function(def = false){
|
||||
*/
|
||||
exports.setAllowPrerelease = function(allowPrerelease){
|
||||
config.settings.launcher.allowPrerelease = allowPrerelease
|
||||
}
|
||||
}
|
||||
|
||||
exports.setMicrosoftAuth = microsoftAuth => {
|
||||
config.microsoftAuth = microsoftAuth
|
||||
}
|
||||
|
||||
exports.getMicrosoftAuth = () => {
|
||||
return config.microsoftAuth
|
||||
}
|
||||
|
||||
exports.updateMicrosoftAuth = (accessToken, expiresAt) => {
|
||||
config.microsoftAuth.access_token = accessToken
|
||||
config.microsoftAuth.expires_at = expiresAt
|
||||
|
||||
return config.microsoftAuth
|
||||
}
|
225
app/assets/js/microsoft.js
Normal file
225
app/assets/js/microsoft.js
Normal file
@ -0,0 +1,225 @@
|
||||
// Requirements
|
||||
const request = require('request')
|
||||
|
||||
// Constants
|
||||
const clientId = 'client id here'
|
||||
|
||||
const tokenUri = 'https://login.microsoftonline.com/consumers/oauth2/v2.0/token'
|
||||
const authXBLUri = 'https://user.auth.xboxlive.com/user/authenticate'
|
||||
const authXSTSUri = 'https://xsts.auth.xboxlive.com/xsts/authorize'
|
||||
const authMCUri = 'https://api.minecraftservices.com/authentication/login_with_xbox'
|
||||
const profileURI = 'https://api.minecraftservices.com/minecraft/profile'
|
||||
|
||||
// Functions
|
||||
function requestPromise(uri, options) {
|
||||
return new Promise((resolve, reject) => {
|
||||
request(uri, options, (error, response, body) => {
|
||||
if (error) {
|
||||
reject(error)
|
||||
} else if (response.statusCode !== 200) {
|
||||
reject([response.statusCode, response.statusMessage, response])
|
||||
} else {
|
||||
resolve(response)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
function getXBLToken(accessToken) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const data = new Object()
|
||||
|
||||
const options = {
|
||||
method: 'post',
|
||||
json: {
|
||||
Properties: {
|
||||
AuthMethod: 'RPS',
|
||||
SiteName: 'user.auth.xboxlive.com',
|
||||
RpsTicket: `d=${accessToken}`
|
||||
},
|
||||
RelyingParty: 'http://auth.xboxlive.com',
|
||||
TokenType: 'JWT'
|
||||
}
|
||||
}
|
||||
requestPromise(authXBLUri, options).then(response => {
|
||||
const body = response.body
|
||||
|
||||
data.token = body.Token
|
||||
data.uhs = body.DisplayClaims.xui[0].uhs
|
||||
|
||||
resolve(data)
|
||||
}).catch(error => {
|
||||
reject(error)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
function getXSTSToken(XBLToken) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const options = {
|
||||
method: 'post',
|
||||
json: {
|
||||
Properties: {
|
||||
SandboxId: 'RETAIL',
|
||||
UserTokens: [XBLToken]
|
||||
},
|
||||
RelyingParty: 'rp://api.minecraftservices.com/',
|
||||
TokenType: 'JWT'
|
||||
}
|
||||
}
|
||||
requestPromise(authXSTSUri, options).then(response => {
|
||||
if (response.body.XErr) {
|
||||
switch (response.body.XErr) {
|
||||
case 2148916233:
|
||||
reject({
|
||||
message: 'Your Microsoft account is not connected to an Xbox account. Please create one.<br>'
|
||||
})
|
||||
return
|
||||
|
||||
case 2148916238:
|
||||
reject({
|
||||
message: 'Since you are not yet 18 years old, an adult must add you to a family in order for you to use NexusLauncher!'
|
||||
})
|
||||
return
|
||||
|
||||
}
|
||||
reject(response.body)
|
||||
}
|
||||
resolve(response.body.Token)
|
||||
}).catch(error => {
|
||||
reject(error)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
function getMCAccessToken(UHS, XSTSToken) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const data = new Object()
|
||||
const expiresAt = new Date()
|
||||
|
||||
const options = {
|
||||
method: 'post',
|
||||
json: {
|
||||
identityToken: `XBL3.0 x=${UHS};${XSTSToken}`
|
||||
}
|
||||
}
|
||||
requestPromise(authMCUri, options).then(response => {
|
||||
const body = response.body
|
||||
|
||||
expiresAt.setSeconds(expiresAt.getSeconds() + body.expires_in)
|
||||
data.access_token = body.access_token
|
||||
data.expires_at = expiresAt
|
||||
|
||||
resolve(data)
|
||||
}).catch(error => {
|
||||
reject(error)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// Exports
|
||||
exports.getAccessToken = authCode => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const expiresAt = new Date()
|
||||
const data = new Object()
|
||||
|
||||
const options = {
|
||||
method: 'post',
|
||||
formData: {
|
||||
client_id: clientId,
|
||||
code: authCode,
|
||||
scope: 'XboxLive.signin',
|
||||
redirect_uri: 'https://login.microsoftonline.com/common/oauth2/nativeclient',
|
||||
grant_type: 'authorization_code'
|
||||
}
|
||||
}
|
||||
requestPromise(tokenUri, options).then(response => {
|
||||
const body = JSON.parse(response.body)
|
||||
expiresAt.setSeconds(expiresAt.getSeconds() + body.expires_in)
|
||||
data.expires_at = expiresAt
|
||||
data.access_token = body.access_token
|
||||
data.refresh_token = body.refresh_token
|
||||
|
||||
resolve(data)
|
||||
}).catch(error => {
|
||||
reject(error)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
exports.refreshAccessToken = refreshToken => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const expiresAt = new Date()
|
||||
const data = new Object()
|
||||
|
||||
const options = {
|
||||
method: 'post',
|
||||
formData: {
|
||||
client_id: clientId,
|
||||
refresh_token: refreshToken,
|
||||
scope: 'XboxLive.signin',
|
||||
redirect_uri: 'https://login.microsoftonline.com/common/oauth2/nativeclient',
|
||||
grant_type: 'refresh_token'
|
||||
}
|
||||
}
|
||||
requestPromise(tokenUri, options).then(response => {
|
||||
const body = JSON.parse(response.body)
|
||||
expiresAt.setSeconds(expiresAt.getSeconds() + body.expires_in)
|
||||
data.expires_at = expiresAt
|
||||
data.access_token = body.access_token
|
||||
|
||||
resolve(data)
|
||||
}).catch(error => {
|
||||
reject(error)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
exports.authMinecraft = async accessToken => {
|
||||
try {
|
||||
const XBLToken = await getXBLToken(accessToken)
|
||||
const XSTSToken = await getXSTSToken(XBLToken.token)
|
||||
const MCToken = await getMCAccessToken(XBLToken.uhs, XSTSToken)
|
||||
|
||||
return MCToken
|
||||
} catch (error) {
|
||||
Promise.reject(error)
|
||||
}
|
||||
}
|
||||
|
||||
exports.checkMCStore = async function(access_token){
|
||||
return new Promise((resolve, reject) => {
|
||||
request.get({
|
||||
url: 'https://api.minecraftservices.com/entitlements/mcstore',
|
||||
json: true,
|
||||
headers: {
|
||||
Authorization: 'Bearer ' + access_token
|
||||
}
|
||||
}, (err, res, body) => {
|
||||
if (err) {
|
||||
resolve(false)
|
||||
return
|
||||
}
|
||||
if(body.items && body.items.length > 0) resolve(true)
|
||||
else resolve(false)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
exports.getMCProfile = MCAccessToken => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const options = {
|
||||
method: 'get',
|
||||
headers: {
|
||||
Authorization: `Bearer ${MCAccessToken}`
|
||||
}
|
||||
}
|
||||
requestPromise(profileURI, options).then(response => {
|
||||
const body = JSON.parse(response.body)
|
||||
|
||||
resolve(body)
|
||||
}).catch(error => {
|
||||
reject(error)
|
||||
})
|
||||
})
|
||||
}
|
@ -17,6 +17,7 @@ const checkmarkContainer = document.getElementById('checkmarkContainer')
|
||||
const loginRememberOption = document.getElementById('loginRememberOption')
|
||||
const loginButton = document.getElementById('loginButton')
|
||||
const loginForm = document.getElementById('loginForm')
|
||||
const loginMSButton = document.getElementById('loginMSButton')
|
||||
|
||||
// Control variables.
|
||||
let lu = false, lp = false
|
||||
@ -297,4 +298,80 @@ loginButton.addEventListener('click', () => {
|
||||
loggerLogin.log('Error while logging in.', err)
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
loginMSButton.addEventListener('click', (event) => {
|
||||
loginMSButton.disabled = true
|
||||
ipcRenderer.send('openMSALoginWindow', 'open')
|
||||
})
|
||||
|
||||
ipcRenderer.on('MSALoginWindowReply', (event, ...args) => {
|
||||
if (args[0] === 'error') {
|
||||
setOverlayContent('ERROR', 'There is already a login window open!', 'OK')
|
||||
setOverlayHandler(() => {
|
||||
toggleOverlay(false)
|
||||
})
|
||||
toggleOverlay(true)
|
||||
return
|
||||
}
|
||||
|
||||
const queryMap = args[0]
|
||||
if (queryMap.has('error')) {
|
||||
let error = queryMap.get('error')
|
||||
let errorDesc = queryMap.get('error_description')
|
||||
if(error === 'access_denied'){
|
||||
error = 'ERRPR'
|
||||
errorDesc = 'To use the NexusLauncher, you must agree to the required permissions! Otherwise you can\'t use this launcher with Microsoft accounts.<br><br>Despite agreeing to the permissions you don\'t give us the possibility to do anything with your account, because all data will always be sent back to you (the launcher) IMMEDIATELY and WITHOUT WAY.'
|
||||
}
|
||||
setOverlayContent(error, errorDesc, 'OK')
|
||||
setOverlayHandler(() => {
|
||||
loginMSButton.disabled = false
|
||||
toggleOverlay(false)
|
||||
})
|
||||
toggleOverlay(true)
|
||||
return
|
||||
}
|
||||
|
||||
// Disable form.
|
||||
formDisabled(true)
|
||||
|
||||
// Show loading stuff.
|
||||
loginLoading(true)
|
||||
|
||||
const authCode = queryMap.get('code')
|
||||
AuthManager.addMSAccount(authCode).then(account => {
|
||||
updateSelectedAccount(account)
|
||||
loginButton.innerHTML = loginButton.innerHTML.replace(Lang.queryJS('login.loggingIn'), Lang.queryJS('login.success'))
|
||||
$('.circle-loader').toggleClass('load-complete')
|
||||
$('.checkmark').toggle()
|
||||
setTimeout(() => {
|
||||
switchView(VIEWS.login, loginViewOnSuccess, 500, 500, () => {
|
||||
// Temporary workaround
|
||||
if (loginViewOnSuccess === VIEWS.settings) {
|
||||
prepareSettings()
|
||||
}
|
||||
loginViewOnSuccess = VIEWS.landing // Reset this for good measure.
|
||||
loginCancelEnabled(false) // Reset this for good measure.
|
||||
loginViewCancelHandler = null // Reset this for good measure.
|
||||
loginUsername.value = ''
|
||||
loginPassword.value = ''
|
||||
$('.circle-loader').toggleClass('load-complete')
|
||||
$('.checkmark').toggle()
|
||||
loginLoading(false)
|
||||
loginButton.innerHTML = loginButton.innerHTML.replace(Lang.queryJS('login.success'), Lang.queryJS('login.login'))
|
||||
formDisabled(false)
|
||||
})
|
||||
}, 1000)
|
||||
}).catch(error => {
|
||||
loginMSButton.disabled = false
|
||||
loginLoading(false)
|
||||
setOverlayContent('ERROR', error.message ? error.message : 'An error occurred while logging in with Microsoft! For more detailed information please check the log. You can open it with CTRL + SHIFT + I.', Lang.queryJS('login.tryAgain'))
|
||||
setOverlayHandler(() => {
|
||||
formDisabled(false)
|
||||
toggleOverlay(false)
|
||||
})
|
||||
toggleOverlay(true)
|
||||
loggerLogin.error(error)
|
||||
})
|
||||
|
||||
})
|
164
app/login.ejs
164
app/login.ejs
@ -1,65 +1,99 @@
|
||||
<div id="loginContainer" style="display: none;">
|
||||
<div id="loginCancelContainer" style="display: none;">
|
||||
<button id="loginCancelButton">
|
||||
<div id="loginCancelIcon">X</div>
|
||||
<span id="loginCancelText">Cancel</span>
|
||||
</button>
|
||||
</div>
|
||||
<div id="loginContent">
|
||||
<form id="loginForm">
|
||||
<img id="loginImageSeal" src="assets/images/SealCircle.png"/>
|
||||
<span id="loginSubheader">MINECRAFT LOGIN</span>
|
||||
<div class="loginFieldContainer">
|
||||
<svg id="profileSVG" class="loginSVG" viewBox="40 37 65.36 61.43">
|
||||
<g>
|
||||
<path d="M86.77,58.12A13.79,13.79,0,1,0,73,71.91,13.79,13.79,0,0,0,86.77,58.12M97,103.67a3.41,3.41,0,0,0,3.39-3.84,27.57,27.57,0,0,0-54.61,0,3.41,3.41,0,0,0,3.39,3.84Z"/>
|
||||
</g>
|
||||
</svg>
|
||||
<span class="loginErrorSpan" id="loginEmailError">* Invalid Value</span>
|
||||
<input id="loginUsername" class="loginField" type="text" placeholder="EMAIL OR USERNAME"/>
|
||||
</div>
|
||||
<div class="loginFieldContainer">
|
||||
<svg id="lockSVG" class="loginSVG" viewBox="40 32 60.36 70.43">
|
||||
<g>
|
||||
<path d="M86.16,54a16.38,16.38,0,1,0-32,0H44V102.7H96V54Zm-25.9-3.39a9.89,9.89,0,1,1,19.77,0A9.78,9.78,0,0,1,79.39,54H60.89A9.78,9.78,0,0,1,60.26,50.59ZM70,96.2a6.5,6.5,0,0,1-6.5-6.5,6.39,6.39,0,0,1,3.1-5.4V67h6.5V84.11a6.42,6.42,0,0,1,3.39,5.6A6.5,6.5,0,0,1,70,96.2Z"/>
|
||||
</g>
|
||||
</svg>
|
||||
<span class="loginErrorSpan" id="loginPasswordError">* Required</span>
|
||||
<input id="loginPassword" class="loginField" type="password" placeholder="PASSWORD"/>
|
||||
</div>
|
||||
<div id="loginOptions">
|
||||
<span class="loginSpanDim">
|
||||
<a href="https://my.minecraft.net/en-us/password/forgot/">forgot password?</a>
|
||||
</span>
|
||||
<label id="checkmarkContainer">
|
||||
<input id="loginRememberOption" type="checkbox" checked>
|
||||
<span id="loginRememberText" class="loginSpanDim">remember me?</span>
|
||||
<span class="loginCheckmark"></span>
|
||||
</label>
|
||||
</div>
|
||||
<button id="loginButton" disabled>
|
||||
<div id="loginButtonContent">
|
||||
LOGIN
|
||||
<svg id="loginSVG" viewBox="0 0 24.87 13.97">
|
||||
<defs>
|
||||
<style>.arrowLine{fill:none;stroke:#FFF;stroke-width:2px;transition: 0.25s ease;}</style>
|
||||
</defs>
|
||||
<polyline class="arrowLine" points="0.71 13.26 12.56 1.41 24.16 13.02"/>
|
||||
</svg>
|
||||
<div class="circle-loader">
|
||||
<div class="checkmark draw"></div>
|
||||
</div>
|
||||
<!--<div class="spinningCircle" id="loginSpinner"></div>-->
|
||||
</div>
|
||||
</button>
|
||||
<div id="loginDisclaimer">
|
||||
<span class="loginSpanDim" id="loginRegisterSpan">
|
||||
<a href="https://minecraft.net/en-us/store/minecraft/">Need an Account?</a>
|
||||
</span>
|
||||
<p class="loginDisclaimerText">Your password is sent directly to mojang and never stored.</p>
|
||||
<p class="loginDisclaimerText">Helios Launcher is not affiliated with Mojang AB.</p>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<script src="./assets/js/scripts/login.js"></script>
|
||||
</div>
|
||||
<div id="loginContainer" style="display: none">
|
||||
<div id="loginCancelContainer" style="display: none">
|
||||
<button id="loginCancelButton">
|
||||
<div id="loginCancelIcon">X</div>
|
||||
<span id="loginCancelText">Cancel</span>
|
||||
</button>
|
||||
</div>
|
||||
<div id="loginContent">
|
||||
<form id="loginForm">
|
||||
<img id="loginImageSeal" src="assets/images/SealCircle.png" />
|
||||
<span id="loginSubheader">MINECRAFT LOGIN</span>
|
||||
<div class="loginFieldContainer">
|
||||
<svg id="profileSVG" class="loginSVG" viewBox="40 37 65.36 61.43">
|
||||
<g>
|
||||
<path
|
||||
d="M86.77,58.12A13.79,13.79,0,1,0,73,71.91,13.79,13.79,0,0,0,86.77,58.12M97,103.67a3.41,3.41,0,0,0,3.39-3.84,27.57,27.57,0,0,0-54.61,0,3.41,3.41,0,0,0,3.39,3.84Z"
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
<span class="loginErrorSpan" id="loginEmailError">* Invalid Value</span>
|
||||
<input
|
||||
id="loginUsername"
|
||||
class="loginField"
|
||||
type="text"
|
||||
placeholder="EMAIL OR USERNAME"
|
||||
/>
|
||||
</div>
|
||||
<div class="loginFieldContainer">
|
||||
<svg id="lockSVG" class="loginSVG" viewBox="40 32 60.36 70.43">
|
||||
<g>
|
||||
<path
|
||||
d="M86.16,54a16.38,16.38,0,1,0-32,0H44V102.7H96V54Zm-25.9-3.39a9.89,9.89,0,1,1,19.77,0A9.78,9.78,0,0,1,79.39,54H60.89A9.78,9.78,0,0,1,60.26,50.59ZM70,96.2a6.5,6.5,0,0,1-6.5-6.5,6.39,6.39,0,0,1,3.1-5.4V67h6.5V84.11a6.42,6.42,0,0,1,3.39,5.6A6.5,6.5,0,0,1,70,96.2Z"
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
<span class="loginErrorSpan" id="loginPasswordError">* Required</span>
|
||||
<input
|
||||
id="loginPassword"
|
||||
class="loginField"
|
||||
type="password"
|
||||
placeholder="PASSWORD"
|
||||
/>
|
||||
</div>
|
||||
<div id="loginOptions">
|
||||
<span class="loginSpanDim">
|
||||
<a href="https://my.minecraft.net/en-us/password/forgot/"
|
||||
>forgot password?</a
|
||||
>
|
||||
</span>
|
||||
<label id="checkmarkContainer">
|
||||
<input id="loginRememberOption" type="checkbox" checked />
|
||||
<span id="loginRememberText" class="loginSpanDim">remember me?</span>
|
||||
<span class="loginCheckmark"></span>
|
||||
</label>
|
||||
</div>
|
||||
<button id="loginButton" disabled>
|
||||
<div id="loginButtonContent">
|
||||
LOGIN
|
||||
<svg id="loginSVG" viewBox="0 0 24.87 13.97">
|
||||
<defs>
|
||||
<style>
|
||||
.arrowLine {
|
||||
fill: none;
|
||||
stroke: #fff;
|
||||
stroke-width: 2px;
|
||||
transition: 0.25s ease;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<polyline
|
||||
class="arrowLine"
|
||||
points="0.71 13.26 12.56 1.41 24.16 13.02"
|
||||
/>
|
||||
</svg>
|
||||
<div class="circle-loader">
|
||||
<div class="checkmark draw"></div>
|
||||
</div>
|
||||
<!--<div class="spinningCircle" id="loginSpinner"></div>-->
|
||||
</div>
|
||||
</button>
|
||||
oder <br />
|
||||
<button id="loginMSButton">Microsoft Login</button>
|
||||
<div id="loginDisclaimer">
|
||||
<span class="loginSpanDim" id="loginRegisterSpan">
|
||||
<a href="https://www.minecraft.net/en-us/store/minecraft-java-edition"
|
||||
>Need an Account?</a
|
||||
>
|
||||
</span>
|
||||
<p class="loginDisclaimerText">
|
||||
Your password is sent directly to mojang and never stored.
|
||||
</p>
|
||||
<p class="loginDisclaimerText">
|
||||
Helios Launcher is not affiliated with Mojang AB.
|
||||
</p>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<script src="./assets/js/scripts/login.js"></script>
|
||||
</div>
|
||||
|
46
index.js
46
index.js
@ -8,6 +8,9 @@ const path = require('path')
|
||||
const semver = require('semver')
|
||||
const url = require('url')
|
||||
|
||||
const redirectUriPrefix = 'https://login.microsoftonline.com/common/oauth2/nativeclient?'
|
||||
const clientID = 'client id here'
|
||||
|
||||
// Setup auto updater.
|
||||
function initAutoUpdater(event, data) {
|
||||
|
||||
@ -85,6 +88,49 @@ ipcMain.on('distributionIndexDone', (event, res) => {
|
||||
// https://electronjs.org/docs/tutorial/offscreen-rendering
|
||||
app.disableHardwareAcceleration()
|
||||
|
||||
let MSALoginWindow = null
|
||||
|
||||
// Open the Microsoft Account Login window
|
||||
ipcMain.on('openMSALoginWindow', (ipcEvent, args) => {
|
||||
if(MSALoginWindow != null){
|
||||
ipcEvent.sender.send('MSALoginWindowNotification', 'error', 'AlreadyOpenException')
|
||||
return
|
||||
}
|
||||
MSALoginWindow = new BrowserWindow({
|
||||
title: 'Microsoft-Login',
|
||||
backgroundColor: '#222222',
|
||||
width: 520,
|
||||
height: 600,
|
||||
frame: false,
|
||||
icon: getPlatformIcon('SealCircle')
|
||||
})
|
||||
|
||||
MSALoginWindow.on('closed', () => {
|
||||
MSALoginWindow = null
|
||||
})
|
||||
|
||||
MSALoginWindow.webContents.on('did-navigate', (event, uri, responseCode, statusText) => {
|
||||
if(uri.startsWith(redirectUriPrefix)) {
|
||||
let querys = uri.substring(redirectUriPrefix.length).split('#', 1).toString().split('&')
|
||||
let queryMap = new Map()
|
||||
|
||||
querys.forEach(query => {
|
||||
let arr = query.split('=')
|
||||
queryMap.set(arr[0], decodeURI(arr[1]))
|
||||
})
|
||||
|
||||
ipcEvent.reply('MSALoginWindowReply', queryMap)
|
||||
|
||||
MSALoginWindow.close()
|
||||
MSALoginWindow = null
|
||||
}
|
||||
})
|
||||
|
||||
MSALoginWindow.removeMenu()
|
||||
MSALoginWindow.loadURL('https://login.microsoftonline.com/consumers/oauth2/v2.0/authorize?prompt=consent&client_id=' + clientID + '&response_type=code&scope=XboxLive.signin%20offline_access&redirect_uri=https://login.microsoftonline.com/common/oauth2/nativeclient')
|
||||
})
|
||||
|
||||
|
||||
// https://github.com/electron/electron/issues/18397
|
||||
app.allowRendererProcessReuse = true
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user