Skip to content
This repository has been archived by the owner on Aug 14, 2024. It is now read-only.

Commit

Permalink
full impl for MicrosoftAuthenticator
Browse files Browse the repository at this point in the history
  • Loading branch information
NanamiNakano committed Jan 12, 2024
1 parent 86f313e commit 4c0913d
Show file tree
Hide file tree
Showing 8 changed files with 93 additions and 25 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,6 @@ build

# test directory
.minecraft
.mikataneko

.idea
2 changes: 2 additions & 0 deletions lib/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ dependencies {
implementation(kotlin("reflect"))
implementation("com.microsoft.graph:microsoft-graph:5.77.0")
implementation("com.microsoft.azure:msal4j:1.14.2")


}

java {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package mikataneko.models.minecraft

import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.JsonElement
import kotlinx.serialization.json.JsonArray
import kotlinx.serialization.json.JsonObject


Expand All @@ -16,7 +16,7 @@ data class XboxMinecraftAuthenticate(
@Serializable
data class MinecraftAuthenticateResponse(
val username: String,
val roles: JsonElement,
val roles: JsonArray,
val metadata: JsonObject,
@SerialName("access_token")
val accessToken: String,
Expand Down
39 changes: 39 additions & 0 deletions lib/src/main/kotlin/mikataneko/models/minecraft/ProfileResponse.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package mikataneko.models.minecraft

import kotlinx.serialization.Serializable
import kotlinx.serialization.json.JsonObject

sealed class ProfileResponse

@Serializable
data class FoundProfile(
val id: String,
val name: String,
val skins: List<Skin>,
val capes: List<Cape>,
val profileActions: JsonObject,
) : ProfileResponse()

@Serializable
data class Skin(
val id: String,
val state: String,
val url: String,
val textureKey: String,
val variant: String,
)

@Serializable
data class Cape(
val id: String,
val state: String,
val url: String,
val alias: String,
)

@Serializable
data class NotFoundProfile(
val path: String,
val error: String,
val errorMessage: String,
) : ProfileResponse()
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable

@Serializable
data class XSTSAuthorize(
data class XSTSAuthorizeRequest(
@SerialName("Properties")
val xstsProperties: XSTSProperties,
@SerialName("RelyingParty")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable

@Serializable
data class XboxAuthenticate(
data class XboxAuthenticateRequest(
@SerialName("Properties")
val properties: XboxProperties,
@SerialName("RelyingParty")
Expand Down
20 changes: 14 additions & 6 deletions lib/src/main/kotlin/mikataneko/utils/MicrosoftAuthenticator.kt
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,7 @@ import io.ktor.http.*
import kotlinx.serialization.json.Json
import mikataneko.globalClient
import mikataneko.models.MicrosoftAuthenticatorException
import mikataneko.models.minecraft.GameItems
import mikataneko.models.minecraft.MinecraftAuthenticateResponse
import mikataneko.models.minecraft.XboxMinecraftAuthenticate
import mikataneko.models.minecraft.*
import mikataneko.models.xbox.*
import java.util.function.Consumer

Expand Down Expand Up @@ -68,7 +66,7 @@ class MicrosoftAuthenticator(
suspend fun authenticateWithXboxLive(accessToken: String): XboxAuthenticateResponse {
val response = client.post("https://user.auth.xboxlive.com/user/authenticate") {
contentType(ContentType.Application.Json)
setBody(XboxAuthenticate(XboxProperties("d=$accessToken")))
setBody(XboxAuthenticateRequest(XboxProperties("d=$accessToken")))
accept(ContentType.Application.Json)
}
return Json.decodeFromString(response.bodyAsText())
Expand All @@ -77,7 +75,7 @@ class MicrosoftAuthenticator(
suspend fun authorizeWithXSTS(xblToken: String): XSTSAuthorizeResponse {
val response = client.post("https://xsts.auth.xboxlive.com/xsts/authorize") {
contentType(ContentType.Application.Json)
setBody(XSTSAuthorize(XSTSProperties(listOf(xblToken))))
setBody(XSTSAuthorizeRequest(XSTSProperties(listOf(xblToken))))
accept(ContentType.Application.Json)
}

Expand Down Expand Up @@ -106,5 +104,15 @@ class MicrosoftAuthenticator(
return Json.decodeFromString(response.bodyAsText())
}


suspend fun getProfile(minecraftAccessToken: String): ProfileResponse {
val response = client.get("https://api.minecraftservices.com/minecraft/profile") {
header(HttpHeaders.Authorization, "Bearer $minecraftAccessToken")
}

if (response.status == HttpStatusCode.NotFound) {
return Json.decodeFromString<NotFoundProfile>(response.bodyAsText())
}

return Json.decodeFromString<FoundProfile>(response.bodyAsText())
}
}
48 changes: 33 additions & 15 deletions lib/src/test/kotlin/mikataneko/TestMicrosoftAuth.kt
Original file line number Diff line number Diff line change
@@ -1,28 +1,42 @@
package mikataneko

import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import mikataneko.models.xbox.DeviceCodeFlow
import mikataneko.utils.MicrosoftAuthenticator
import mikataneko.interfaces.TokenCacheAspect
import mikataneko.models.xbox.DeviceCodeFlow
import mikataneko.models.xbox.XSTSError
import mikataneko.models.xbox.XSTSSuccess
import kotlin.io.path.readText
import kotlin.io.path.writeText

private val json = Json {
prettyPrint = true
}
import mikataneko.utils.MicrosoftAuthenticator
import kotlin.io.path.*

suspend fun main() {
val deviceCodeFlow = DeviceCodeFlow(System.getenv("CLIENT_ID"))
val tokenCacheAspect = TokenCacheAspectImpl()

val authenticator = MicrosoftAuthenticator(tokenCacheAspect, deviceCodeFlow)
val result = authenticator.authenticateWithDeviceCode { deviceCode ->
println(deviceCode.message())
val accounts = authenticator.getCachedAccounts()
println(accounts)

val account = if (accounts.isEmpty()) {
null
} else {
accounts.first()
}
println(account)

val result = if (account != null) {
authenticator.authenticateWithDeviceCode(account) { deviceCode ->
println(deviceCode.message())
}
} else {
authenticator.authenticateWithDeviceCode { deviceCode ->
println(deviceCode.message())
}
}

// val result = authenticator.authenticateWithDeviceCode { deviceCode ->
// println(deviceCode.message())
// }

val accessToken = result.accessToken()
val xblResponse = authenticator.authenticateWithXboxLive(accessToken)
val xblToken = xblResponse.token
Expand All @@ -32,18 +46,22 @@ suspend fun main() {
is XSTSSuccess -> xstsResponse.token
}
val minecraftToken = authenticator.authenticateWithMinecraft(userHash, xstsToken).accessToken
val productList = authenticator.getGameItems(minecraftToken)
println(json.encodeToString(productList))
val profile = authenticator.getProfile(minecraftToken)
println(profile)
}

class TokenCacheAspectImpl : TokenCacheAspect() {
private val file = kotlin.io.path.createTempFile()
private val file = Path("./.mikataneko/tokenCacheAspectPersistence").apply {
if (!this.exists()) {
this.createParentDirectories().createFile()
}
}

override fun loadCacheData(): String {
return file.readText()
}

override fun saveCacheData(data: String) {
file.writeText(data)
file.toFile().deleteOnExit()
}
}

0 comments on commit 4c0913d

Please sign in to comment.