Skip to content

Commit

Permalink
init core auth
Browse files Browse the repository at this point in the history
  • Loading branch information
stslex committed Dec 17, 2023
1 parent 19c03c2 commit a3b8a15
Show file tree
Hide file tree
Showing 34 changed files with 509 additions and 65 deletions.
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
1 change: 1 addition & 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
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,94 @@
package com.stslex.core.network.api.server

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 io.ktor.http.encodedPath
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(API_HOST) {
host = API_HOST
encodedPath = "api/v1"
protocol = URLProtocol.HTTP
contentType(ContentType.Application.Json)
}
headers {
append(API_KEY_NAME, "API_KEY") // TODO
}
}
}

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 = "API_KEY"
private const val API_HOST = "api_host"
}
}
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,53 @@
package com.stslex.core.network.clients.auth.client

import com.stslex.core.network.api.server.ServerApiClient
import com.stslex.core.network.clients.auth.request.AuthRequest
import com.stslex.core.network.clients.auth.response.LoginOkResponse
import com.stslex.core.network.clients.auth.response.RegisterRequest
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.util.InternalAPI

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

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

@OptIn(InternalAPI::class)
override suspend fun registerUser(
login: String,
username: String,
password: String
): LoginOkResponse = networkClient.request {
post("$AUTH_URL/register") {
body = 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 AuthRequest(
@SerialName("login")
val login: String,
@SerialName("password")
val password: String
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.stslex.core.network.clients.auth.response

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

@Serializable
data class LoginOkResponse(
@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,14 @@
package com.stslex.core.network.clients.auth.response

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 a3b8a15

Please sign in to comment.