Skip to content

Commit

Permalink
Merge pull request #23 from stslex/dev
Browse files Browse the repository at this point in the history
init core auth
  • Loading branch information
stslex authored Dec 17, 2023
2 parents 79bfee9 + 775545c commit 74bcf54
Show file tree
Hide file tree
Showing 35 changed files with 529 additions and 65 deletions.
1 change: 1 addition & 0 deletions composeApp/src/androidMain/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

<application
android:allowBackup="true"
android:allowClearUserData="true"
android:enableOnBackInvokedCallback="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
Expand Down
27 changes: 25 additions & 2 deletions composeApp/src/commonMain/kotlin/InitialApp.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,14 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import cafe.adriel.voyager.navigator.Navigator
import cafe.adriel.voyager.transitions.SlideTransition
import com.stslex.core.database.store.UserStore
import com.stslex.core.network.utils.token.AuthController
import com.stslex.core.ui.navigation.AppNavigator
import com.stslex.core.ui.navigation.AppScreen
import com.stslex.feature.auth.ui.AuthScreen
import main_screen.MainScreen
import org.koin.compose.getKoin
Expand All @@ -17,7 +21,26 @@ import org.koin.compose.getKoin
fun InitialApp(
modifier: Modifier = Modifier
) {
val userStore = getKoin().get<UserStore>()
val koin = getKoin()
val userStore = remember {
koin.get<AuthController>()
}
val navigator = remember {
koin.get<AppNavigator>()
}

LaunchedEffect(Unit) {
userStore.isAuthFlow.collect { isAuth ->
val currentScreen = navigator.currentScreen
if (
isAuth.not() &&
currentScreen != null &&
currentScreen != AppScreen.Auth
) {
navigator.navigate(AppScreen.Auth)
}
}
}

Scaffold(
modifier = modifier
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,15 @@ class AppNavigatorImpl : AppNavigator {
_navigator = navigator
}

override val currentScreen: AppScreen?
get() = when (val item = _navigator?.lastItemOrNull) {
AuthScreen -> AppScreen.Auth
MainScreen -> AppScreen.Main
MatchFeedScreen -> AppScreen.MatchFeed
is FilmScreen -> AppScreen.Film(item.id)
else -> null
}

override fun navigate(
screen: AppScreen
) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@ package com.stslex.core.database.store

interface UserStore {

var userToken: String
var accessToken: String
var refreshToken: String
var username: String
var uuid: String
val isAuth: Boolean

fun clear()
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ class UserStoreImpl(
private val userSettings: UserSettings
) : UserStore {

override var userToken: String
override var accessToken: String
get() = userSettings.getString(KEY_TOKEN, EMPTY_VALUE)
set(value) {
userSettings[KEY_TOKEN] = value
Expand All @@ -30,9 +30,6 @@ class UserStoreImpl(
userSettings[KEY_UUID] = value
}

override val isAuth: Boolean
get() = userToken.isBlank().not()

override fun clear() {
userSettings.clear()
}
Expand Down
14 changes: 14 additions & 0 deletions core/network/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ kotlin {
sourceSets {
commonMain.dependencies {
implementation(project(":core:core"))
implementation(project(":core:database"))
implementation(libs.bundles.ktor)
implementation(libs.kotlinx.serialization.json)
implementation(libs.slf4j.simple)
Expand Down Expand Up @@ -66,5 +67,18 @@ fun BuildConfigExtension.setLocalProperty(dir: Project) {
val key = gradleLocalProperties(dir.projectDir)["KINOPOISK_API_KEY"]
?.toString()
?: throw IllegalStateException("KINOPOISK_API_KEY should be initialised")
val serverHost = gradleLocalProperties(dir.projectDir)["SERVER_HOST"]
?.toString()
?: throw IllegalStateException("SERVER_HOST should be initialised")
val serverApiVersion = gradleLocalProperties(dir.projectDir)["SERVER_API_VERSION"]
?.toString()
?: throw IllegalStateException("SERVER_API_VERSION should be initialised")
val serverPort = gradleLocalProperties(dir.projectDir)["SERVER_PORT"]
?.toString()
?: throw IllegalStateException("SERVER_PORT should be initialised")

buildConfigField("String", "KINOPOISK_API_KEY", key)
buildConfigField("String", "SERVER_HOST", serverHost)
buildConfigField("String", "SERVER_API_VERSION", serverApiVersion)
buildConfigField("String", "SERVER_PORT", serverPort)
}
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
package com.stslex.core.network.api.base

import com.stslex.core.core.AppDispatcher
import com.stslex.core.network.api.base.model.DefaultRequest
import com.stslex.core.network.api.base.NetworkClientBuilder.setupDefaultRequest
import com.stslex.core.network.api.base.NetworkClientBuilder.setupLogging
import com.stslex.core.network.api.base.NetworkClientBuilder.setupNegotiation
import com.stslex.core.network.api.base.model.DefaultRequest
import io.ktor.client.HttpClient
import io.ktor.client.engine.cio.CIO
import io.ktor.client.plugins.cache.HttpCache
import kotlinx.coroutines.withContext

abstract class BaseNetworkClient(
open class BaseNetworkClient(
private val appDispatcher: AppDispatcher,
defaultRequest: DefaultRequest = DefaultRequest.EMPTY
) : NetworkClient {
Expand Down

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
package com.stslex.core.network.api.server

import Wizard.core.network.BuildConfig
import com.stslex.core.core.AppDispatcher
import com.stslex.core.network.api.base.NetworkClient
import com.stslex.core.network.api.base.NetworkClientBuilder.setupLogging
import com.stslex.core.network.api.base.NetworkClientBuilder.setupNegotiation
import com.stslex.core.network.utils.token.AuthController
import com.stslex.core.network.utils.token.toModel
import io.ktor.client.HttpClient
import io.ktor.client.call.body
import io.ktor.client.engine.cio.CIO
import io.ktor.client.plugins.HttpResponseValidator
import io.ktor.client.plugins.ResponseException
import io.ktor.client.plugins.auth.Auth
import io.ktor.client.plugins.auth.providers.BearerTokens
import io.ktor.client.plugins.auth.providers.bearer
import io.ktor.client.plugins.defaultRequest
import io.ktor.client.request.HttpRequest
import io.ktor.client.request.get
import io.ktor.client.request.headers
import io.ktor.http.ContentType
import io.ktor.http.HttpStatusCode
import io.ktor.http.URLProtocol
import io.ktor.http.contentType
import kotlinx.coroutines.withContext

interface ServerApiClient : NetworkClient

class ServerApiClientImpl(
private val tokenProvider: AuthController,
private val appDispatcher: AppDispatcher
) : ServerApiClient {

private val client = HttpClient(CIO) {
setupNegotiation()

setupLogging()
expectSuccess = true

install(Auth) {
bearer {
loadTokens {
BearerTokens(
accessToken = tokenProvider.accessToken,
refreshToken = tokenProvider.refreshToken
)
}
}
}
HttpResponseValidator {
handleResponseExceptionWithRequest(errorParser)
}
defaultRequest {
url(
scheme = URLProtocol.HTTP.name,
host = BuildConfig.SERVER_HOST,
port = BuildConfig.SERVER_PORT.toInt(),
path = BuildConfig.SERVER_API_VERSION,
block = {
contentType(ContentType.Application.Json)
}
)
headers {
append(API_KEY_NAME, "API_KEY") // TODO move to buildConfig
}
}
}

override suspend fun <T> request(
request: suspend HttpClient.() -> T
): T = withContext(appDispatcher.io) {
request(client)
}

private val errorParser: suspend (Throwable, HttpRequest) -> Unit
get() = { exception, _ ->
val clientException = exception as? ResponseException ?: throw exception
if (HttpStatusCode.Unauthorized.value == clientException.response.status.value) {
refreshToken()
} else {
throw clientException
}
}

private suspend fun refreshToken() {
val tokenResponse = client
.get("passport/refresh")
.body<TokenResponseModel>()
tokenProvider.update(tokenResponse.toModel())
}

companion object {
private const val API_KEY_NAME = "x-api-key"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.stslex.core.network.api.server

import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable

@Serializable
data class TokenResponseModel(
@SerialName("uuid")
val uuid: String,
@SerialName("username")
val username: String,
@SerialName("access_token")
val accessToken: String,
@SerialName("refresh_token")
val refreshToken: String
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.stslex.core.network.clients.auth.client

import com.stslex.core.network.clients.auth.response.LoginOkResponse

interface AuthClient {

suspend fun authUser(login: String, password: String): LoginOkResponse

suspend fun registerUser(login: String, username: String, password: String): LoginOkResponse
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package com.stslex.core.network.clients.auth.client

import com.stslex.core.network.api.server.ServerApiClient
import com.stslex.core.network.clients.auth.request.LoginRequest
import com.stslex.core.network.clients.auth.request.RegisterRequest
import com.stslex.core.network.clients.auth.response.LoginOkResponse
import com.stslex.core.network.utils.token.AuthController
import com.stslex.core.network.utils.token.toModel
import io.ktor.client.call.body
import io.ktor.client.request.post
import io.ktor.client.request.setBody

class AuthClientImpl(
private val networkClient: ServerApiClient,
private val tokenController: AuthController
) : AuthClient {

override suspend fun authUser(
login: String,
password: String
): LoginOkResponse = networkClient.request {
post("$AUTH_URL/login") {
setBody(
LoginRequest(
login = login,
password = password
)
)
}.body<LoginOkResponse>().also {
tokenController.update(it.toModel())
}
}

override suspend fun registerUser(
login: String,
username: String,
password: String
): LoginOkResponse = networkClient.request {
post("$AUTH_URL/register") {
setBody(
RegisterRequest(
login = login,
password = password,
username = username
)
)
}.body<LoginOkResponse>().also {
tokenController.update(it.toModel())
}
}

companion object {
private const val AUTH_URL = "passport"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.stslex.core.network.clients.auth.request

import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable

@Serializable
data class LoginRequest(
@SerialName("login")
val login: String,
@SerialName("password")
val password: String
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.stslex.core.network.clients.auth.request

import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable

@Serializable
data class RegisterRequest(
@SerialName("login")
val login: String,
@SerialName("username")
val username: String,
@SerialName("password")
val password: String
)
Loading

0 comments on commit 74bcf54

Please sign in to comment.