From 4c0913d40a09a5f37600b43ba40027198d382496 Mon Sep 17 00:00:00 2001 From: NanamiNakano Date: Fri, 12 Jan 2024 20:34:06 +0800 Subject: [PATCH] full impl for MicrosoftAuthenticator --- .gitignore | 1 + lib/build.gradle.kts | 2 + .../models/minecraft/MinecraftAuthenticate.kt | 4 +- .../models/minecraft/ProfileResponse.kt | 39 +++++++++++++++ ...TSAuthorize.kt => XSTSAuthorizeRequest.kt} | 2 +- ...enticate.kt => XboxAuthenticateRequest.kt} | 2 +- .../utils/MicrosoftAuthenticator.kt | 20 +++++--- .../kotlin/mikataneko/TestMicrosoftAuth.kt | 48 +++++++++++++------ 8 files changed, 93 insertions(+), 25 deletions(-) create mode 100644 lib/src/main/kotlin/mikataneko/models/minecraft/ProfileResponse.kt rename lib/src/main/kotlin/mikataneko/models/xbox/{XSTSAuthorize.kt => XSTSAuthorizeRequest.kt} (94%) rename lib/src/main/kotlin/mikataneko/models/xbox/{XboxAuthenticate.kt => XboxAuthenticateRequest.kt} (94%) diff --git a/.gitignore b/.gitignore index 28c4a99..cc4d31c 100644 --- a/.gitignore +++ b/.gitignore @@ -6,5 +6,6 @@ build # test directory .minecraft +.mikataneko .idea diff --git a/lib/build.gradle.kts b/lib/build.gradle.kts index 8dbdd61..eb16f10 100644 --- a/lib/build.gradle.kts +++ b/lib/build.gradle.kts @@ -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 { diff --git a/lib/src/main/kotlin/mikataneko/models/minecraft/MinecraftAuthenticate.kt b/lib/src/main/kotlin/mikataneko/models/minecraft/MinecraftAuthenticate.kt index be01860..a31cad1 100644 --- a/lib/src/main/kotlin/mikataneko/models/minecraft/MinecraftAuthenticate.kt +++ b/lib/src/main/kotlin/mikataneko/models/minecraft/MinecraftAuthenticate.kt @@ -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 @@ -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, diff --git a/lib/src/main/kotlin/mikataneko/models/minecraft/ProfileResponse.kt b/lib/src/main/kotlin/mikataneko/models/minecraft/ProfileResponse.kt new file mode 100644 index 0000000..76f07c3 --- /dev/null +++ b/lib/src/main/kotlin/mikataneko/models/minecraft/ProfileResponse.kt @@ -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, + val capes: List, + 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() diff --git a/lib/src/main/kotlin/mikataneko/models/xbox/XSTSAuthorize.kt b/lib/src/main/kotlin/mikataneko/models/xbox/XSTSAuthorizeRequest.kt similarity index 94% rename from lib/src/main/kotlin/mikataneko/models/xbox/XSTSAuthorize.kt rename to lib/src/main/kotlin/mikataneko/models/xbox/XSTSAuthorizeRequest.kt index ad36640..2ff58c1 100644 --- a/lib/src/main/kotlin/mikataneko/models/xbox/XSTSAuthorize.kt +++ b/lib/src/main/kotlin/mikataneko/models/xbox/XSTSAuthorizeRequest.kt @@ -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") diff --git a/lib/src/main/kotlin/mikataneko/models/xbox/XboxAuthenticate.kt b/lib/src/main/kotlin/mikataneko/models/xbox/XboxAuthenticateRequest.kt similarity index 94% rename from lib/src/main/kotlin/mikataneko/models/xbox/XboxAuthenticate.kt rename to lib/src/main/kotlin/mikataneko/models/xbox/XboxAuthenticateRequest.kt index f7c387b..a54eb80 100644 --- a/lib/src/main/kotlin/mikataneko/models/xbox/XboxAuthenticate.kt +++ b/lib/src/main/kotlin/mikataneko/models/xbox/XboxAuthenticateRequest.kt @@ -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") diff --git a/lib/src/main/kotlin/mikataneko/utils/MicrosoftAuthenticator.kt b/lib/src/main/kotlin/mikataneko/utils/MicrosoftAuthenticator.kt index f53e2b2..f00d063 100644 --- a/lib/src/main/kotlin/mikataneko/utils/MicrosoftAuthenticator.kt +++ b/lib/src/main/kotlin/mikataneko/utils/MicrosoftAuthenticator.kt @@ -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 @@ -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()) @@ -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) } @@ -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(response.bodyAsText()) + } + + return Json.decodeFromString(response.bodyAsText()) + } } diff --git a/lib/src/test/kotlin/mikataneko/TestMicrosoftAuth.kt b/lib/src/test/kotlin/mikataneko/TestMicrosoftAuth.kt index c090984..819c478 100644 --- a/lib/src/test/kotlin/mikataneko/TestMicrosoftAuth.kt +++ b/lib/src/test/kotlin/mikataneko/TestMicrosoftAuth.kt @@ -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 @@ -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() } }