Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

MLS #135

Closed
wants to merge 4 commits into from
Closed

MLS #135

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion app/assets/css/launcher.css
Original file line number Diff line number Diff line change
Expand Up @@ -858,6 +858,16 @@ body, button {
border-width: 0 2px 2px 0;
transform: rotate(45deg);
}
#loginMSButton {
border-color: transparent;
background-color: transparent;
cursor: pointer;
font-family: 'Avenir Medium';
font-size: 12px;
font-weight: bold;
margin-bottom: 3px;
color: rgba(255, 255, 255, 0.75);
}

/*
#login_filter {
Expand Down Expand Up @@ -3774,4 +3784,4 @@ input:checked + .toggleSwitchSlider:before {
/* Class which is applied when the spinner image is spinning. */
.rotating {
animation: rotating 10s linear infinite;
}
}
96 changes: 80 additions & 16 deletions app/assets/js/authmanager.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
} else 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)
}
} else return true

// Exports
// Functions

/**
Expand Down Expand Up @@ -78,22 +133,31 @@ 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
}
}
}

exports.addMSAccount = async authCode => {
try {
const accessToken = await Microsoft.getAccessToken(authCode)
ConfigManager.setMicrosoftAuth(accessToken)
const MCAccessToken = await Microsoft.authMinecraft(accessToken.access_token)
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)
}
}
28 changes: 23 additions & 5 deletions app/assets/js/configmanager.js
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,8 @@ const DEFAULT_CONFIG = {
selectedServer: null, // Resolved
selectedAccount: null,
authenticationDatabase: {},
modConfigurations: []
modConfigurations: [],
microsoftAuth: {}
}

let config = null
Expand Down Expand Up @@ -327,6 +328,7 @@ exports.getAuthAccount = function(uuid){
*/
exports.updateAuthAccount = function(uuid, accessToken){
config.authenticationDatabase[uuid].accessToken = accessToken
config.authenticationDatabase[uuid].expiresAt = expiresAt
return config.authenticationDatabase[uuid]
}

Expand All @@ -340,17 +342,18 @@ 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]
}

/**
* Remove an authenticated account from the database. If the account
* was also the selected account, a new one will be selected. If there
Expand Down Expand Up @@ -685,4 +688,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
}
191 changes: 191 additions & 0 deletions app/assets/js/microsoft.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
// Requirements
const request = require('request')
// const logger = require('./loggerutil')('%c[Microsoft]', 'color: #01a6f0; font-weight: bold')

// Constants
const clientId = 'Client ID(Azure)'
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 => {
const body = response.body

resolve(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.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)
})
})
}
Loading