Skip to content
This repository has been archived by the owner on Jul 9, 2022. It is now read-only.

Commit

Permalink
add direct microsoft account loginability
Browse files Browse the repository at this point in the history
  • Loading branch information
hax0r31337 committed Dec 5, 2021
1 parent 8569a44 commit 2ae6d86
Show file tree
Hide file tree
Showing 6 changed files with 213 additions and 8 deletions.
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ Username: `username`, Password: Empty String
### Mojang Account
Username: `[email protected]`, Password: `password`

### Microsoft Account
Username: `ms@[email protected]`, Password: `password`
Username: `[email protected]_BAY.token`, Password: Empty String

## Json Form
We provide a json form to make data easier to read and write.
~~~kotlin
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/me/liuli/elixir/account/CrackedAccount.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import me.liuli.elixir.compat.Session
import java.util.*

class CrackedAccount : MinecraftAccount("Cracked") {
override var name: String = "Player"
override var name = "Player"

override val session: Session
get() = Session(name, UUID.randomUUID().toString(), "-", "legacy")
Expand Down
136 changes: 136 additions & 0 deletions src/main/java/me/liuli/elixir/account/MicrosoftAccount.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
package me.liuli.elixir.account

import com.beust.klaxon.JsonObject
import com.beust.klaxon.Klaxon
import me.liuli.elixir.compat.Session
import me.liuli.elixir.exception.LoginException
import me.liuli.elixir.utils.HttpUtils

class MicrosoftAccount : MinecraftAccount("Microsoft") {
override var name = "UNKNOWN"
private var uuid = ""
private var accessToken = ""
private var refreshToken = ""

override val session: Session
get() {
if(uuid.isEmpty() || accessToken.isEmpty()) {
update()
}

return Session(name, uuid, accessToken, "mojang")
}

/**
* get minecraft account info from Microsoft Refresh Token
* @credit https://wiki.vg/Microsoft_Authentication_Scheme
*/
override fun update() {
val jsonPostHeader = mapOf("Content-Type" to "application/json", "Accept" to "application/json")

// get the microsoft access token
val msRefreshJson = Klaxon().parseJsonObject(HttpUtils.make(XBOX_AUTH_URL, "POST", XBOX_REFRESH_DATA + refreshToken,
mapOf("Content-Type" to "application/x-www-form-urlencoded")).inputStream.reader(Charsets.UTF_8))
val msAccessToken = msRefreshJson.string("access_token") ?: throw LoginException("Microsoft access token is null")
// refresh token is changed after refresh
refreshToken = msRefreshJson.string("refresh_token") ?: throw LoginException("Microsoft new refresh token is null")

// authenticate with XBL
val xblJson = Klaxon().parseJsonObject(HttpUtils.make(XBOX_XBL_URL, "POST", XBOX_XBL_DATA.replace("<access_token>", msAccessToken), jsonPostHeader).inputStream.reader(Charsets.UTF_8))
val xblToken = xblJson.string("Token") ?: throw LoginException("Microsoft XBL token is null")
val userhash = xblJson.obj("DisplayClaims")?.array<JsonObject>("xui")?.get(0)?.string("uhs") ?: throw LoginException("Microsoft XBL userhash is null")

// authenticate with XSTS
val xstsJson = Klaxon().parseJsonObject(HttpUtils.make(XBOX_XSTS_URL, "POST", XBOX_XSTS_DATA.replace("<xbl_token>", xblToken), jsonPostHeader).inputStream.reader(Charsets.UTF_8))
val xstsToken = xstsJson.string("Token") ?: throw LoginException("Microsoft XSTS token is null")

// get the minecraft access token
val mcJson = Klaxon().parseJsonObject(HttpUtils.make(MC_AUTH_URL, "POST", MC_AUTH_DATA.replace("<userhash>", userhash).replace("<xsts_token>", xstsToken), jsonPostHeader).inputStream.reader(Charsets.UTF_8))
accessToken = mcJson.string("access_token") ?: throw LoginException("Minecraft access token is null")

// get the minecraft account profile
val mcProfileJson = Klaxon().parseJsonObject(HttpUtils.make(MC_PROFILE_URL, "GET", "", mapOf("Authorization" to "Bearer $accessToken")).inputStream.reader(Charsets.UTF_8))
name = mcProfileJson.string("name") ?: throw LoginException("Minecraft account name is null")
uuid = mcProfileJson.string("id") ?: throw LoginException("Minecraft account uuid is null")
}

override fun toRawJson(json: JsonObject) {
json["name"] = name
json["refreshToken"] = refreshToken
}

override fun fromRawJson(json: JsonObject) {
name = json["name"] as String
refreshToken = json["refreshToken"] as String
}

companion object {
const val XBOX_PRE_AUTH_URL = "https://login.live.com/oauth20_authorize.srf?client_id=00000000441cc96b&redirect_uri=https://login.live.com/oauth20_desktop.srf&response_type=code&display=touch&scope=service::user.auth.xboxlive.com::MBI_SSL&locale=en"
const val XBOX_AUTH_URL = "https://login.live.com/oauth20_token.srf"
const val XBOX_XBL_URL = "https://user.auth.xboxlive.com/user/authenticate"
const val XBOX_XSTS_URL = "https://xsts.auth.xboxlive.com/xsts/authorize"
const val MC_AUTH_URL = "https://api.minecraftservices.com/authentication/login_with_xbox"
const val MC_PROFILE_URL = "https://api.minecraftservices.com/minecraft/profile"
const val XBOX_AUTH_DATA = "client_id=00000000441cc96b&redirect_uri=https://login.live.com/oauth20_desktop.srf&grant_type=authorization_code&code="
const val XBOX_REFRESH_DATA = "client_id=00000000441cc96b&scope=service::user.auth.xboxlive.com::MBI_SSL&grant_type=refresh_token&redirect_uri=https://login.live.com/oauth20_desktop.srf&refresh_token="
const val XBOX_XBL_DATA = """{"Properties":{"AuthMethod":"RPS","SiteName":"user.auth.xboxlive.com","RpsTicket":"<access_token>"},"RelyingParty":"http://auth.xboxlive.com","TokenType":"JWT"}"""
const val XBOX_XSTS_DATA = """{"Properties":{"SandboxId":"RETAIL","UserTokens":["<xbl_token>"]},"RelyingParty":"rp://api.minecraftservices.com/","TokenType":"JWT"}"""
const val MC_AUTH_DATA = """{"identityToken":"XBL3.0 x=<userhash>;<xsts_token>"}"""

/**
* Create a new [MicrosoftAccount] from a microsoft account authenticate [code]
*/
fun buildFromAuthCode(code: String): MicrosoftAccount {
val data = Klaxon().parseJsonObject(HttpUtils.make(XBOX_AUTH_URL, "POST", XBOX_AUTH_DATA + code, mapOf("Content-Type" to "application/x-www-form-urlencoded")).inputStream.reader(Charsets.UTF_8))
return if(data.containsKey("refresh_token")) {
MicrosoftAccount().also { it.refreshToken = data["refresh_token"] as String ; it.update() }
} else {
throw LoginException("Failed to get refresh token")
}
}

/**
* Create a new [MicrosoftAccount] from [username] and [password]
*
* @credit https://github.com/XboxReplay/xboxlive-auth
*/
fun buildFromPassword(username: String, password: String): MicrosoftAccount {
fun findArgs(resp: String, arg: String): String {
return if (resp.contains(arg)) {
resp.substring(resp.indexOf("$arg:'") + arg.length + 2).let {
it.substring(0, it.indexOf("',"))
}
} else {
throw LoginException("Failed to find argument in response $arg")
}
}

// first, get the pre-auth url
val preAuthConnection = HttpUtils.make(XBOX_PRE_AUTH_URL, "GET")
val html = preAuthConnection.inputStream.reader().readText()
val cookies = (preAuthConnection.headerFields["Set-Cookie"] ?: emptyList()).joinToString(";")
val urlPost = findArgs(html, "urlPost")
val ppft = findArgs(html, "sFTTag").let {
it.substring(it.indexOf("value=\"") + 7, it.length - 3)
}
preAuthConnection.disconnect()

// then, post the login form
val authConnection = HttpUtils.make(urlPost, "POST",
"login=${username}&loginfmt=${username}&passwd=${password}&PPFT=$ppft",
mapOf("Cookie" to cookies, "Content-Type" to "application/x-www-form-urlencoded"))
authConnection.inputStream.reader().readText()
val code = authConnection.url.toString().let {
if(!it.contains("code=")) {
throw LoginException("Failed to get auth code from response")
}
val pre = it.substring(it.indexOf("code=") + 5)
pre.substring(0, pre.indexOf("&"))
}
authConnection.disconnect()

// pass the code to [buildFromAuthCode]
return buildFromAuthCode(code)
}
}
}
16 changes: 11 additions & 5 deletions src/main/java/me/liuli/elixir/manage/AccountSerializer.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package me.liuli.elixir.manage

import com.beust.klaxon.JsonObject
import me.liuli.elixir.account.CrackedAccount
import me.liuli.elixir.account.MicrosoftAccount
import me.liuli.elixir.account.MinecraftAccount
import me.liuli.elixir.account.MojangAccount

Expand All @@ -12,7 +13,7 @@ object AccountSerializer {
fun toJson(account: MinecraftAccount): JsonObject {
val json = JsonObject()
account.toRawJson(json)
json["type"] = this.javaClass.canonicalName
json["type"] = account.javaClass.canonicalName
return json
}

Expand All @@ -29,11 +30,16 @@ object AccountSerializer {
* get an instance of [MinecraftAccount] from [name] and [password]
*/
fun accountInstance(name: String, password: String): MinecraftAccount {
return if(password.isEmpty()) {
return if (name.startsWith("ms@")) {
val realName = name.substring(3)
if(password.isEmpty()) {
MicrosoftAccount.buildFromAuthCode(realName)
} else {
MicrosoftAccount.buildFromPassword(realName, password)
}
} else if(password.isEmpty()) {
CrackedAccount().also { it.name = name }
} /*else if (name.startsWith("ms@")) {
MojangAccount().also { it.name = name; it.password = password } // TODO: microsoft account
} */else {
} else {
MojangAccount().also { it.name = name; it.password = password }
}
}
Expand Down
43 changes: 43 additions & 0 deletions src/main/java/me/liuli/elixir/utils/HttpUtils.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package me.liuli.elixir.utils

import java.io.DataOutputStream
import java.net.HttpURLConnection
import java.net.URL

object HttpUtils {
private const val DEFAULT_AGENT = "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36"

fun make(url: String, method: String, data: String = "", header: Map<String, String> = emptyMap(), agent: String = DEFAULT_AGENT): HttpURLConnection {
val httpConnection = URL(url).openConnection() as HttpURLConnection

httpConnection.requestMethod = method
httpConnection.connectTimeout = 2000
httpConnection.readTimeout = 10000

httpConnection.setRequestProperty("User-Agent", agent)
header.forEach { (key, value) -> httpConnection.setRequestProperty(key, value) }

httpConnection.instanceFollowRedirects = true
httpConnection.doOutput = true

if (data.isNotEmpty()) {
val dataOutputStream = DataOutputStream(httpConnection.outputStream)
dataOutputStream.writeBytes(data)
dataOutputStream.flush()
}

httpConnection.connect()

return httpConnection
}

fun request(url: String, method: String, data: String = "", header: Map<String, String> = emptyMap(), agent: String = DEFAULT_AGENT): String {
val connection = make(url, method, data, header, agent)

return connection.inputStream.reader().readText()
}

fun get(url: String, header: Map<String, String> = emptyMap()) = request(url, "GET", header = header)

fun post(url: String, data: String, header: Map<String, String> = emptyMap()) = request(url, "POST", data, header)
}
20 changes: 18 additions & 2 deletions src/test/java/me/liuli/elixir/test/ElixirTest.kt
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
package me.liuli.elixir.test

import com.beust.klaxon.JsonObject
import me.liuli.elixir.account.CrackedAccount
import me.liuli.elixir.manage.AccountSerializer

fun main(args: Array<String>) {
testMojang()
}

private fun testCracked() {
Expand Down Expand Up @@ -46,4 +44,22 @@ private fun testMojang() {
mojangAccount.update()
println(mojangAccount.session)
println(AccountSerializer.toJson(mojangAccount).toJsonString())
}

fun testMicrosoftDirect() {
val mail = ""
val password = ""

println("--- Microsoft Account Dynamic ---")
var microsoftAccount = AccountSerializer.accountInstance("ms@$mail", password)
// it will update when login
//microsoftAccount.update()
println(microsoftAccount.session)
println(AccountSerializer.toJson(microsoftAccount).toJsonString())

println("--- Microsoft Account Static ---")
microsoftAccount = AccountSerializer.fromJson(AccountSerializer.toJson(microsoftAccount))
microsoftAccount.update()
println(microsoftAccount.session)
println(AccountSerializer.toJson(microsoftAccount).toJsonString())
}

0 comments on commit 2ae6d86

Please sign in to comment.