diff --git a/api/rest/src/commonMain/kotlin/moe/lava/neon/api/ApiClient.kt b/api/rest/src/commonMain/kotlin/moe/lava/neon/api/ApiClient.kt index 7122519..371b589 100644 --- a/api/rest/src/commonMain/kotlin/moe/lava/neon/api/ApiClient.kt +++ b/api/rest/src/commonMain/kotlin/moe/lava/neon/api/ApiClient.kt @@ -11,6 +11,8 @@ import io.ktor.client.plugins.plugin import io.ktor.client.plugins.websocket.WebSockets import io.ktor.client.request.header import io.ktor.client.statement.bodyAsText +import io.ktor.http.ContentType +import io.ktor.http.contentType import io.ktor.http.userAgent import io.ktor.serialization.kotlinx.json.json import io.ktor.util.appendAll @@ -28,7 +30,7 @@ class ApiClient { } @OptIn(ExperimentalSerializationApi::class) - val client = HttpClient { + internal val client = HttpClient { expectSuccess = true install(ContentNegotiation) { json(ApiConstants.json) @@ -37,6 +39,7 @@ class ApiClient { install(HttpCookies) defaultRequest { url("https://discord.com/api/v9/") + contentType(ContentType.Application.Json) userAgent(ApiConstants.userAgent) headers.appendAll(ApiConstants.baseHeaders) } diff --git a/api/rest/src/commonMain/kotlin/moe/lava/neon/api/endpoints/Auth.kt b/api/rest/src/commonMain/kotlin/moe/lava/neon/api/endpoints/Auth.kt new file mode 100644 index 0000000..d6a613e --- /dev/null +++ b/api/rest/src/commonMain/kotlin/moe/lava/neon/api/endpoints/Auth.kt @@ -0,0 +1,46 @@ +package moe.lava.neon.api.endpoints + +import io.ktor.client.call.body +import io.ktor.client.request.get +import io.ktor.client.request.header +import io.ktor.client.request.parameter +import io.ktor.client.request.post +import io.ktor.client.request.setBody +import kotlinx.serialization.Serializable +import moe.lava.neon.api.ApiClient + +@Serializable +data class ExperimentResponse( + val fingerprint: String, +) + +@Serializable +private data class LoginRequest( + val login: String, + val password: String, + val undelete: Boolean = false, + val loginSource: String? = null, + val giftCodeSkuId: String? = null, +) + +@Serializable +data class LoginResponse( + val userId: String, + val token: String, + val userSettings: UserSettings, +) { + @Serializable + data class UserSettings(val locale: String, val theme: String) +} + +suspend fun ApiClient.getExperiments() = client.get("experiments") { + parameter("with_guild_experiments", "true") +}.body() + +suspend fun ApiClient.login(email: String, password: String, fingerprint: String) = client.post("auth/login") { + header("X-Fingerprint", fingerprint) + setBody(LoginRequest( + login = email, + password = password, + )) +}.body() diff --git a/core/build.gradle.kts b/core/build.gradle.kts index 00d01b0..8075553 100644 --- a/core/build.gradle.kts +++ b/core/build.gradle.kts @@ -21,8 +21,6 @@ kotlin { implementation(project(":api:gateway")) implementation(project(":api:rest")) implementation(project(":common")) - implementation(libs.ktor.client.core) - implementation(libs.ktor.serialization.kotlinx.json) implementation(project.dependencies.platform(libs.koin.bom)) implementation(libs.koin.core) diff --git a/core/src/commonMain/kotlin/moe/lava/neon/core/repository/AuthRepository.kt b/core/src/commonMain/kotlin/moe/lava/neon/core/repository/AuthRepository.kt index e2ecb90..eeb458d 100644 --- a/core/src/commonMain/kotlin/moe/lava/neon/core/repository/AuthRepository.kt +++ b/core/src/commonMain/kotlin/moe/lava/neon/core/repository/AuthRepository.kt @@ -1,43 +1,14 @@ package moe.lava.neon.core.repository import co.touchlab.kermit.Logger -import io.ktor.client.call.body -import io.ktor.client.request.get -import io.ktor.client.request.header -import io.ktor.client.request.parameter -import io.ktor.client.request.post -import io.ktor.client.request.setBody -import io.ktor.http.ContentType -import io.ktor.http.contentType -import kotlinx.serialization.Serializable import moe.lava.neon.api.ApiClient +import moe.lava.neon.api.endpoints.getExperiments +import moe.lava.neon.api.endpoints.login import moe.lava.neon.core.AppSettings -@Serializable -private data class ExperimentResponse( - val fingerprint: String, -) - -@Serializable -private data class LoginRequest( - val login: String, - val password: String, - val undelete: Boolean = false, - val loginSource: String? = null, - val giftCodeSkuId: String? = null, -) - -@Serializable -private data class LoginResponse( - val userId: String, - val token: String, - val userSettings: UserSettings, -) { - @Serializable - data class UserSettings(val locale: String, val theme: String) -} - sealed class AuthResponse { + // TODO: Specify all possible error types here + data class Failed(val error: Throwable) : AuthResponse() data class Success(val token: String) : AuthResponse() // TODO // data class MFARequested() : AuthResponse() @@ -57,24 +28,23 @@ class AuthRepository internal constructor( email: String, password: String, ): AuthResponse { - if (fingerprint == null) { - fingerprint = api.client.get("experiments") { - parameter("with_guild_experiments", "true") - }.body().fingerprint - } + try { + if (fingerprint == null) { + fingerprint = api.getExperiments().fingerprint + } - val res = api.client.post("auth/login") { - header("X-Fingerprint", fingerprint) - contentType(ContentType.Application.Json) - setBody(LoginRequest( - login = email, + val login = api.login( + email = email, password = password, - )) + fingerprint = fingerprint!!, + ) + logger.i { "Login success $login" } + this.token = login.token + + return AuthResponse.Success(login.token) + } catch (e: Throwable) { + return AuthResponse.Failed(e) } - val body = res.body() - logger.i { "Login success $body" } - this.token = body.token - return AuthResponse.Success(body.token) } fun login(token: String): String { diff --git a/ui/src/commonMain/kotlin/moe/lava/neon/ui/screens/Login.kt b/ui/src/commonMain/kotlin/moe/lava/neon/ui/screens/Login.kt index b04a8fe..5e0078d 100644 --- a/ui/src/commonMain/kotlin/moe/lava/neon/ui/screens/Login.kt +++ b/ui/src/commonMain/kotlin/moe/lava/neon/ui/screens/Login.kt @@ -122,13 +122,13 @@ class LoginViewModel( } suspend fun login(email: String, password: String): LoginResult { - return try { - when (val res = auth.login(email, password)) { - is AuthResponse.Success -> LoginResult.Success + return when (val res = auth.login(email, password)) { + is AuthResponse.Success -> LoginResult.Success + is AuthResponse.Failed -> { + val e = res.error + logger.e(e) { "Login failed" } + LoginResult.Failed(e.toString()) } - } catch(e: Throwable) { - logger.e(e) { "Login failed" } - LoginResult.Failed(e.toString()) } } }